ruby-laya 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +44 -0
- data/LICENSE +176 -0
- data/NOTICE +7 -0
- data/README.md +399 -0
- data/lib/laya/agent.rb +324 -0
- data/lib/laya/ask.rb +73 -0
- data/lib/laya/checkpoints.rb +77 -0
- data/lib/laya/common.rb +175 -0
- data/lib/laya/configuration.rb +72 -0
- data/lib/laya/decision.rb +120 -0
- data/lib/laya/email.rb +187 -0
- data/lib/laya/errors.rb +16 -0
- data/lib/laya/hub.rb +206 -0
- data/lib/laya/lang.rb +301 -0
- data/lib/laya/presets.rb +197 -0
- data/lib/laya/py_json.rb +144 -0
- data/lib/laya/question.rb +171 -0
- data/lib/laya/questions.rb +43 -0
- data/lib/laya/result.rb +263 -0
- data/lib/laya/router.rb +353 -0
- data/lib/laya/runtime.rb +79 -0
- data/lib/laya/shortlist.rb +197 -0
- data/lib/laya/tokenizer.rb +101 -0
- data/lib/laya/training.rb +88 -0
- data/lib/laya/util.rb +57 -0
- data/lib/laya/version.rb +8 -0
- data/lib/laya.rb +129 -0
- metadata +110 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "tokenizers"
|
|
5
|
+
|
|
6
|
+
module Laya
|
|
7
|
+
# Thin wrapper over the Hugging Face `tokenizers` gem exposing what sequence construction
|
|
8
|
+
# needs: the special tokens and ids of a checkpoint, `encode_ids` (no special tokens) and a
|
|
9
|
+
# padded batch encoder for the embedding helper.
|
|
10
|
+
class Tokenizer
|
|
11
|
+
SPECIAL = %w[mask cls sep pad].freeze
|
|
12
|
+
CANDIDATES = {
|
|
13
|
+
"mask" => ["[MASK]", "<mask>"],
|
|
14
|
+
"cls" => ["[CLS]", "<s>", "<cls>", "<bos>"],
|
|
15
|
+
"sep" => ["[SEP]", "</s>", "<sep>", "<eos>"],
|
|
16
|
+
"pad" => ["[PAD]", "<pad>"]
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :inner, :mask_token, :cls_token, :sep_token, :pad_token,
|
|
20
|
+
:mask_token_id, :cls_token_id, :sep_token_id, :pad_token_id
|
|
21
|
+
|
|
22
|
+
# Load `tokenizer.json` plus the special-token names from `tokenizer_config.json` /
|
|
23
|
+
# `special_tokens_map.json` in `dir`.
|
|
24
|
+
def self.from_dir(dir)
|
|
25
|
+
json = File.join(dir, "tokenizer.json")
|
|
26
|
+
raise ModelNotFoundError, "tokenizer.json not found in #{dir}" unless File.exist?(json)
|
|
27
|
+
|
|
28
|
+
specials = {}
|
|
29
|
+
["special_tokens_map.json", "tokenizer_config.json"].each do |name|
|
|
30
|
+
path = File.join(dir, name)
|
|
31
|
+
next unless File.exist?(path)
|
|
32
|
+
|
|
33
|
+
cfg = JSON.parse(File.read(path))
|
|
34
|
+
SPECIAL.each do |kind|
|
|
35
|
+
value = cfg["#{kind}_token"]
|
|
36
|
+
value = value["content"] if value.is_a?(Hash)
|
|
37
|
+
specials[kind] = value if value.is_a?(String) && !value.empty?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
new(Tokenizers.from_file(json), **specials.transform_keys { |k| :"#{k}_token" })
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(inner, mask_token: nil, cls_token: nil, sep_token: nil, pad_token: nil)
|
|
44
|
+
@inner = inner
|
|
45
|
+
@lock = Mutex.new
|
|
46
|
+
@mask_token = resolve("mask", mask_token)
|
|
47
|
+
@cls_token = resolve("cls", cls_token)
|
|
48
|
+
@sep_token = resolve("sep", sep_token)
|
|
49
|
+
@pad_token = resolve("pad", pad_token)
|
|
50
|
+
@mask_token_id = @inner.token_to_id(@mask_token)
|
|
51
|
+
@cls_token_id = @inner.token_to_id(@cls_token)
|
|
52
|
+
@sep_token_id = @inner.token_to_id(@sep_token)
|
|
53
|
+
@pad_token_id = @inner.token_to_id(@pad_token)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Token ids for `text`, without special tokens (what `tok(text, add_special_tokens=False)` gives).
|
|
57
|
+
def encode_ids(text)
|
|
58
|
+
@inner.encode(text, add_special_tokens: false).ids
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Tokens for `text`, without special tokens.
|
|
62
|
+
def tokenize(text)
|
|
63
|
+
@inner.encode(text, add_special_tokens: false).tokens
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def decode(ids, skip_special_tokens: true)
|
|
67
|
+
@inner.decode(ids, skip_special_tokens: skip_special_tokens)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def vocab_size
|
|
71
|
+
@inner.vocab_size
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Encode several texts with special tokens, truncated to `max_length` and padded to the
|
|
75
|
+
# longest. Returns {"input_ids" => [[...]], "attention_mask" => [[...]]}.
|
|
76
|
+
def encode_batch(texts, max_length: 512)
|
|
77
|
+
@lock.synchronize do
|
|
78
|
+
@inner.enable_truncation(max_length)
|
|
79
|
+
@inner.enable_padding(pad_id: @pad_token_id, pad_token: @pad_token)
|
|
80
|
+
begin
|
|
81
|
+
encodings = @inner.encode_batch(texts, add_special_tokens: true)
|
|
82
|
+
ensure
|
|
83
|
+
@inner.no_truncation
|
|
84
|
+
@inner.no_padding
|
|
85
|
+
end
|
|
86
|
+
{ "input_ids" => encodings.map(&:ids), "attention_mask" => encodings.map(&:attention_mask) }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def resolve(kind, explicit)
|
|
93
|
+
candidates = explicit ? [explicit] + CANDIDATES[kind] : CANDIDATES[kind]
|
|
94
|
+
found = candidates.find { |c| !@inner.token_to_id(c).nil? }
|
|
95
|
+
return found if found
|
|
96
|
+
|
|
97
|
+
raise IncompatibleModelError,
|
|
98
|
+
"tokenizer has no #{kind} token (tried #{candidates.inspect}); check tokenizer_config.json"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laya
|
|
4
|
+
# The reward and target arithmetic behind Laya's training, ported so a Ruby process can score
|
|
5
|
+
# or audit a checkpoint's calibration. Training itself lives upstream; these are the pure
|
|
6
|
+
# functions, over plain Arrays.
|
|
7
|
+
module Training
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# The strictly proper scoring rule Laya is trained against: log score plus spherical score,
|
|
11
|
+
# minus a ranked probability score for ordinal questions.
|
|
12
|
+
#
|
|
13
|
+
# `reported` and `targets` are Arrays of distributions, `qtypes` the question type per row
|
|
14
|
+
# (a name or its index), and `mask` marks the valid options of each row.
|
|
15
|
+
def proper_reward(reported, targets, qtypes, mask = nil, spherical_weight: 0.5,
|
|
16
|
+
rps_weight: 1.0, log_floor: -9.21)
|
|
17
|
+
reported.each_with_index.map do |row, i|
|
|
18
|
+
valid = if mask
|
|
19
|
+
mask[i].map { |flag| flag ? 1.0 : 0.0 }
|
|
20
|
+
else
|
|
21
|
+
Array.new(row.length, 1.0)
|
|
22
|
+
end
|
|
23
|
+
masked = row.each_with_index.map { |value, j| value * valid[j] }
|
|
24
|
+
target = targets[i]
|
|
25
|
+
reward = log_score(masked, target, log_floor) +
|
|
26
|
+
(spherical_weight * spherical_score(masked, target))
|
|
27
|
+
next reward unless score_question?(qtypes[i])
|
|
28
|
+
|
|
29
|
+
reward - (rps_weight * ranked_probability_score(masked, target, valid))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def log_score(reported, target, log_floor)
|
|
34
|
+
reported.each_with_index.sum do |value, i|
|
|
35
|
+
target[i] * [Math.log([value, 1e-12].max), log_floor].max
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def spherical_score(reported, target)
|
|
40
|
+
norm = Math.sqrt(reported.sum { |value| value * value })
|
|
41
|
+
reported.each_with_index.sum { |value, i| target[i] * value } / [norm, 1e-9].max
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ranked_probability_score(reported, target, valid)
|
|
45
|
+
options = [valid.sum, 2.0].max
|
|
46
|
+
reported_cdf = cumulative(reported)
|
|
47
|
+
target_cdf = cumulative(target)
|
|
48
|
+
squared = reported_cdf.each_with_index.sum do |value, i|
|
|
49
|
+
((value - target_cdf[i])**2) * valid[i]
|
|
50
|
+
end
|
|
51
|
+
squared / (options - 1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cumulative(values)
|
|
55
|
+
total = 0.0
|
|
56
|
+
values.map { |value| total += value }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def score_question?(qtype)
|
|
60
|
+
index = qtype.is_a?(Integer) ? qtype : QTYPES[qtype.to_s]
|
|
61
|
+
index == QTYPES["score"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# TD(lambda) targets for multi-turn conversation trajectories.
|
|
65
|
+
#
|
|
66
|
+
# `batch` holds "target" (a `[false, true]` pair per row) and, for trajectories, "ep_group"
|
|
67
|
+
# and "ep_step". Without groups the targets are returned unchanged.
|
|
68
|
+
def td_lambda_targets(p_true, batch, lam: 1.0)
|
|
69
|
+
targets = Util.get(batch, "target").map(&:dup)
|
|
70
|
+
groups = Util.get(batch, "ep_group")
|
|
71
|
+
return targets if groups.nil?
|
|
72
|
+
|
|
73
|
+
steps = Util.get(batch, "ep_step")
|
|
74
|
+
groups.uniq.select { |group| group >= 0 }.sort.each do |group|
|
|
75
|
+
rows = groups.each_index.select { |i| groups[i] == group }.sort_by { |i| steps[i] }
|
|
76
|
+
discounted = targets[rows.last][1]
|
|
77
|
+
rows.each_index.reverse_each do |position|
|
|
78
|
+
row = rows[position]
|
|
79
|
+
unless position == rows.length - 1
|
|
80
|
+
discounted = ((1 - lam) * p_true[rows[position + 1]]) + (lam * discounted)
|
|
81
|
+
end
|
|
82
|
+
targets[row] = [1 - discounted, discounted]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
targets
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/laya/util.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laya
|
|
4
|
+
# Small helpers shared across modules. Question and state hashes may use string or symbol
|
|
5
|
+
# keys (Ruby callers naturally write `type: :choice`, JSON payloads arrive as strings), so
|
|
6
|
+
# every lookup goes through {get} which accepts both.
|
|
7
|
+
module Util
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Fetch `key` (a String) from `hash`, also trying its Symbol form.
|
|
11
|
+
def get(hash, key)
|
|
12
|
+
return nil unless hash.is_a?(Hash)
|
|
13
|
+
return hash[key] if hash.key?(key)
|
|
14
|
+
|
|
15
|
+
sym = key.to_sym
|
|
16
|
+
hash.key?(sym) ? hash[sym] : nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# True when `hash` holds `key` as a String or a Symbol.
|
|
20
|
+
def key?(hash, key)
|
|
21
|
+
hash.is_a?(Hash) && (hash.key?(key) || hash.key?(key.to_sym))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Assign `value` under `key`, keeping whichever key form (String/Symbol) the hash already uses.
|
|
25
|
+
def put(hash, key, value)
|
|
26
|
+
if hash.key?(key.to_sym) && !hash.key?(key)
|
|
27
|
+
hash[key.to_sym] = value
|
|
28
|
+
else
|
|
29
|
+
hash[key] = value
|
|
30
|
+
end
|
|
31
|
+
hash
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Deep-convert Symbol keys to Strings (values are left alone, apart from nested containers).
|
|
35
|
+
def stringify_keys(obj)
|
|
36
|
+
case obj
|
|
37
|
+
when Hash then obj.to_h { |k, v| [k.is_a?(Symbol) ? k.to_s : k, stringify_keys(v)] }
|
|
38
|
+
when Array then obj.map { |v| stringify_keys(v) }
|
|
39
|
+
else obj
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A callable: a Proc/Method or anything responding to #call.
|
|
44
|
+
def callable?(obj)
|
|
45
|
+
obj.respond_to?(:call)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Convert a strictly positive integer argument, rejecting booleans, floats and strings.
|
|
49
|
+
def positive_int!(value, name)
|
|
50
|
+
unless value.is_a?(Integer) && value >= 1
|
|
51
|
+
raise ArgumentError, "#{name} must be a positive integer, got #{value.inspect}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
value
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/laya/version.rb
ADDED
data/lib/laya.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "laya/version"
|
|
6
|
+
require_relative "laya/errors"
|
|
7
|
+
require_relative "laya/util"
|
|
8
|
+
require_relative "laya/py_json"
|
|
9
|
+
require_relative "laya/common"
|
|
10
|
+
require_relative "laya/checkpoints"
|
|
11
|
+
require_relative "laya/lang"
|
|
12
|
+
require_relative "laya/email"
|
|
13
|
+
require_relative "laya/presets"
|
|
14
|
+
require_relative "laya/shortlist"
|
|
15
|
+
require_relative "laya/training"
|
|
16
|
+
require_relative "laya/hub"
|
|
17
|
+
require_relative "laya/router"
|
|
18
|
+
require_relative "laya/configuration"
|
|
19
|
+
require_relative "laya/questions"
|
|
20
|
+
|
|
21
|
+
# Laya: a fast, non-autoregressive System 1 decision engine with calibrated probabilities.
|
|
22
|
+
#
|
|
23
|
+
# agent = Laya.load("convaiinnovations/laya")
|
|
24
|
+
# result = agent.predict({ "message" => "I was charged twice" }, Laya.triage_questions)
|
|
25
|
+
# result[:intent].choice # => "refund"
|
|
26
|
+
# result[:churn_risk].probability # => 0.89
|
|
27
|
+
#
|
|
28
|
+
# Routing, language detection, email cleaning, the presets and the shortlist are pure Ruby and
|
|
29
|
+
# load with the gem. The runtime, which needs ONNX Runtime, is loaded on first use, so a process
|
|
30
|
+
# that only routes never opens a model.
|
|
31
|
+
module Laya
|
|
32
|
+
# Absolute paths, so the runtime still autoloads when the gem was reached by require_relative
|
|
33
|
+
# rather than through the load path.
|
|
34
|
+
{ Agent: "agent", RLAgent: "agent", Answer: "result", Ask: "ask", Decision: "decision",
|
|
35
|
+
Question: "question", Result: "result", Runtime: "runtime", Tokenizer: "tokenizer" }.each do |constant, file|
|
|
36
|
+
autoload constant, File.expand_path("laya/#{file}", __dir__)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The shipped question sets, as decision classes:
|
|
40
|
+
#
|
|
41
|
+
# Laya::Guard.decide(prompt).jailbreak?
|
|
42
|
+
# Laya::Triage.decide(message).churn_risk.probability
|
|
43
|
+
#
|
|
44
|
+
# Each is a {Decision}, so it subclasses like any other and its questions are readable with
|
|
45
|
+
# `.questions`. The plain hashes are still there as `Laya.triage_questions` and friends.
|
|
46
|
+
def self.preset(questions)
|
|
47
|
+
Decision.define(questions)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Load a checkpoint, by upstream model id or from a directory holding an ONNX export.
|
|
52
|
+
#
|
|
53
|
+
# Laya.load("convaiinnovations/laya") # English
|
|
54
|
+
# Laya.load("convaiinnovations/laya", subfolder: "multilingual") # 100+ languages
|
|
55
|
+
# Laya.load("./my-export", device: "coreml")
|
|
56
|
+
#
|
|
57
|
+
# Given a block, the agent is closed when the block returns.
|
|
58
|
+
def load(model_id_or_path = Checkpoints::BUNDLE_REPO, **, &)
|
|
59
|
+
Agent.load(model_id_or_path, **, &)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Ask questions about `state` without declaring a class for them.
|
|
63
|
+
#
|
|
64
|
+
# Laya.ask(email)
|
|
65
|
+
# .choice(:department, "Which team?", billing: "invoices", technical: "outages")
|
|
66
|
+
# .noul(:refund, "Do they want money back?")
|
|
67
|
+
# .decide
|
|
68
|
+
#
|
|
69
|
+
# Runs on the shared client unless given `client:`; see {Laya.configure}.
|
|
70
|
+
def ask(state, client: nil, **)
|
|
71
|
+
Ask.new(state, client: client, **)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A {Router} that picks a checkpoint per request. Given a block, it is closed afterwards.
|
|
75
|
+
def router(**, &)
|
|
76
|
+
Router.open(**, &)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# --- language and script detection
|
|
80
|
+
def detect_language(state) = Lang.analyse(state)
|
|
81
|
+
def detect_script(text) = Lang.detect_script(text)
|
|
82
|
+
def english?(state) = Lang.english?(state)
|
|
83
|
+
def is_english(state) = Lang.english?(state)
|
|
84
|
+
|
|
85
|
+
# --- email
|
|
86
|
+
def clean_email_body(body, max_chars: 3000) = Email.clean_email_body(body, max_chars: max_chars)
|
|
87
|
+
|
|
88
|
+
def email_state(subject, body, sender: nil, clean: true, **extra)
|
|
89
|
+
Email.email_state(subject, body, sender: sender, clean: clean, **extra)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# --- question presets
|
|
93
|
+
def email_questions(categories = nil) = Presets.email_questions(categories)
|
|
94
|
+
def guard_questions = Presets.guard_questions
|
|
95
|
+
def moderation_questions = Presets.moderation_questions
|
|
96
|
+
def router_questions = Presets.router_questions
|
|
97
|
+
def triage_questions = Presets.triage_questions
|
|
98
|
+
|
|
99
|
+
# --- shortlist
|
|
100
|
+
def shortlist_choice(state, criteria, embed_fn, k: Shortlist::DEFAULT_SHORTLIST_K, instructions: nil)
|
|
101
|
+
Shortlist.shortlist_choice(state, criteria, embed_fn, k: k, instructions: instructions)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def predict_shortlist(agent, state, questions, embed_fn, k: Shortlist::DEFAULT_SHORTLIST_K, **)
|
|
105
|
+
Shortlist.predict_shortlist(agent, state, questions, embed_fn, k: k, **)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def embed_fn_from_agent(agent, max_length: nil, batch_size: 32)
|
|
109
|
+
Shortlist.embed_fn_from_agent(agent, max_length: max_length, batch_size: batch_size)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# --- rendering and calibration
|
|
113
|
+
def render_options(question) = Common.render_options(question)
|
|
114
|
+
def confidence_from_probs(probabilities, k) = Common.confidence_from_probs(probabilities, k)
|
|
115
|
+
def ece_score(confidences, correct, bins: 15) = Common.ece_score(confidences, correct, bins: bins)
|
|
116
|
+
|
|
117
|
+
# --- training arithmetic
|
|
118
|
+
def proper_reward(...) = Training.proper_reward(...)
|
|
119
|
+
def td_lambda_targets(...) = Training.td_lambda_targets(...)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The shipped question sets, ready to ask. Declared last so {Decision} and {Presets} are both
|
|
123
|
+
# in place; each is a plain subclass, so `Laya::Guard.questions` and subclassing both work.
|
|
124
|
+
Triage = preset(Presets.triage_questions)
|
|
125
|
+
EmailTriage = preset(Presets.email_questions)
|
|
126
|
+
Guard = preset(Presets.guard_questions)
|
|
127
|
+
Moderation = preset(Presets.moderation_questions)
|
|
128
|
+
RequestRouting = preset(Presets.router_questions)
|
|
129
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby-laya
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Valentino Stoll
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: onnxruntime
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.9'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.9'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: tokenizers
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.5'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.5'
|
|
41
|
+
description: |
|
|
42
|
+
Ruby port of Laya: typed decisions (choice, score, noul) over any state in a single
|
|
43
|
+
forward pass, with calibrated probabilities and a router that picks the right checkpoint
|
|
44
|
+
per request (English, multilingual, typed-decisions). Inference runs on ONNX Runtime
|
|
45
|
+
against exports of the published checkpoints, so installing needs no Python, no LibTorch
|
|
46
|
+
and no compiler. Language and script detection, email cleaning, workflow presets and the
|
|
47
|
+
embedding shortlist are pure Ruby.
|
|
48
|
+
email:
|
|
49
|
+
- v@codenamev.com
|
|
50
|
+
executables: []
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- CHANGELOG.md
|
|
55
|
+
- LICENSE
|
|
56
|
+
- NOTICE
|
|
57
|
+
- README.md
|
|
58
|
+
- lib/laya.rb
|
|
59
|
+
- lib/laya/agent.rb
|
|
60
|
+
- lib/laya/ask.rb
|
|
61
|
+
- lib/laya/checkpoints.rb
|
|
62
|
+
- lib/laya/common.rb
|
|
63
|
+
- lib/laya/configuration.rb
|
|
64
|
+
- lib/laya/decision.rb
|
|
65
|
+
- lib/laya/email.rb
|
|
66
|
+
- lib/laya/errors.rb
|
|
67
|
+
- lib/laya/hub.rb
|
|
68
|
+
- lib/laya/lang.rb
|
|
69
|
+
- lib/laya/presets.rb
|
|
70
|
+
- lib/laya/py_json.rb
|
|
71
|
+
- lib/laya/question.rb
|
|
72
|
+
- lib/laya/questions.rb
|
|
73
|
+
- lib/laya/result.rb
|
|
74
|
+
- lib/laya/router.rb
|
|
75
|
+
- lib/laya/runtime.rb
|
|
76
|
+
- lib/laya/shortlist.rb
|
|
77
|
+
- lib/laya/tokenizer.rb
|
|
78
|
+
- lib/laya/training.rb
|
|
79
|
+
- lib/laya/util.rb
|
|
80
|
+
- lib/laya/version.rb
|
|
81
|
+
homepage: https://github.com/codenamev/ruby-laya
|
|
82
|
+
licenses:
|
|
83
|
+
- Apache-2.0
|
|
84
|
+
metadata:
|
|
85
|
+
homepage_uri: https://codenamev.github.io/ruby-laya
|
|
86
|
+
source_code_uri: https://github.com/codenamev/ruby-laya
|
|
87
|
+
changelog_uri: https://github.com/codenamev/ruby-laya/blob/main/CHANGELOG.md
|
|
88
|
+
documentation_uri: https://codenamev.github.io/ruby-laya
|
|
89
|
+
bug_tracker_uri: https://github.com/codenamev/ruby-laya/issues
|
|
90
|
+
rubygems_mfa_required: 'true'
|
|
91
|
+
post_install_message:
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '3.3'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.5.22
|
|
107
|
+
signing_key:
|
|
108
|
+
specification_version: 4
|
|
109
|
+
summary: Fast, non-autoregressive System 1 decision engine with calibrated probabilities
|
|
110
|
+
test_files: []
|