truffler 0.1.3 → 0.1.5
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 +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +77 -10
- data/bench/cassettes/1037e0b63ba790c02d14d0ddfc18b68151c11d03ce88d764658044b6bab2e55b.json +1 -0
- data/bench/cassettes/19bba849a846d30a69dc385a337d67704fbe5194506fd3c914d3270d86495fde.json +1 -0
- data/bench/cassettes/3768847dc57df8bf2328771e7763c0b8bd013a167244e7849967dac144420601.json +1 -0
- data/lib/generators/truffler/install/templates/migration.rb.tt +6 -3
- data/lib/generators/truffler/upgrade/templates/backfill_spends_tenant_key_migration.rb.tt +25 -0
- data/lib/generators/truffler/upgrade/templates/labels_search_covering_migration.rb.tt +32 -0
- data/lib/generators/truffler/upgrade/upgrade_generator.rb +49 -3
- data/lib/tasks/truffler.rake +38 -13
- data/lib/truffler/benchmark/runner.rb +1 -1
- data/lib/truffler/clients/evaluator.rb +44 -0
- data/lib/truffler/configuration.rb +12 -0
- data/lib/truffler/definition.rb +52 -0
- data/lib/truffler/embeddings/backfill.rb +40 -11
- data/lib/truffler/embeddings/label_vector.rb +1 -1
- data/lib/truffler/embeddings/neighbor_store.rb +33 -7
- data/lib/truffler/embeddings/vector_store.rb +9 -3
- data/lib/truffler/jobs/backfill_job.rb +14 -5
- data/lib/truffler/jobs/embed_job.rb +2 -0
- data/lib/truffler/jobs/label_flush_job.rb +4 -2
- data/lib/truffler/jobs/resume_job.rb +22 -9
- data/lib/truffler/label_definition.rb +55 -12
- data/lib/truffler/labeling/backfill.rb +100 -37
- data/lib/truffler/labeling/labeler.rb +20 -5
- data/lib/truffler/labeling/queue.rb +14 -9
- data/lib/truffler/labeling/supplied.rb +16 -5
- data/lib/truffler/lenses/backfill.rb +28 -10
- data/lib/truffler/lenses/lens_label.rb +7 -0
- data/lib/truffler/model.rb +5 -0
- data/lib/truffler/providers/backup.rb +1 -1
- data/lib/truffler/query_encoding/encoder.rb +56 -39
- data/lib/truffler/records/backfill_spend.rb +30 -6
- data/lib/truffler/redaction.rb +1 -1
- data/lib/truffler/search/encoding.rb +50 -12
- data/lib/truffler/search/encoding_cache.rb +4 -3
- data/lib/truffler/search/filler.rb +73 -0
- data/lib/truffler/search/keystroke.rb +29 -15
- data/lib/truffler/search/result.rb +11 -1
- data/lib/truffler/search/sql.rb +93 -15
- data/lib/truffler/smart_search/dispatcher.rb +1 -1
- data/lib/truffler/smart_search/starter.rb +1 -1
- data/lib/truffler/version.rb +1 -1
- data/lib/truffler/vocabulary.rb +13 -3
- metadata +8 -1
data/lib/truffler/search/sql.rb
CHANGED
|
@@ -3,14 +3,26 @@ module Truffler
|
|
|
3
3
|
# Builds the one keystroke query of KTD8, scored per KTD20:
|
|
4
4
|
#
|
|
5
5
|
# w_label * SUM(weight * value) over the intent's nonzero label keys
|
|
6
|
-
# + w_text * text similarity (inline SQL, or the store's top-K CASE)
|
|
6
|
+
# + w_text * text similarity (a top-K join, inline SQL, or the store's top-K CASE)
|
|
7
7
|
# + w_keyword * keyword hit + w_exact * exact-source hit
|
|
8
|
+
# + SOFT_KEYWORD * w_keyword * soft keyword hit
|
|
8
9
|
#
|
|
9
10
|
# A weighted dot product, not cosine: cosine would divide out magnitude
|
|
10
11
|
# and let a record high on unrelated labels outrank the one the query
|
|
11
12
|
# asked for. Hard filters are EXISTS subqueries that run before scoring.
|
|
13
|
+
#
|
|
14
|
+
# At scale: when every record past the filters is a candidate (label-only
|
|
15
|
+
# and filtered searches), label scores come from one grouped aggregate
|
|
16
|
+
# LEFT JOINed on record_id instead of a subquery per row. A store with
|
|
17
|
+
# `neighbors_sql` (NeighborStore on Postgres) is LEFT JOINed the same
|
|
18
|
+
# way, so text similarity is read from the tenant's top-K. Relation
|
|
19
|
+
# sources run once, as `id = ANY(ARRAY(subquery))` on Postgres.
|
|
12
20
|
class Sql
|
|
13
21
|
LABELS = "truffler_labels".freeze
|
|
22
|
+
LABEL_SCORES = "truffler_label_scores".freeze
|
|
23
|
+
NEIGHBORS = "truffler_neighbors".freeze
|
|
24
|
+
# Share of the keyword weight a soft keyword hit adds (see Encoding).
|
|
25
|
+
SOFT_KEYWORD = 0.25
|
|
14
26
|
|
|
15
27
|
attr_reader :model, :tenant_key, :query, :encoding, :vector, :weights
|
|
16
28
|
|
|
@@ -35,7 +47,8 @@ module Truffler
|
|
|
35
47
|
# filter the filter decides membership and text matches only rank.
|
|
36
48
|
def candidates(scope)
|
|
37
49
|
base = base(scope)
|
|
38
|
-
|
|
50
|
+
base = base.joins(Arel.sql(neighbors_join_sql)) if neighbors_sql
|
|
51
|
+
return base if every_base_record?
|
|
39
52
|
|
|
40
53
|
conditions = [ keyword_sql, exact_sql, (text_candidate_sql if text_score_sql) ].compact
|
|
41
54
|
base.where(Arel.sql(conditions.any? ? conditions.map { |condition| "(#{condition})" }.join(" OR ") : "1 = 0"))
|
|
@@ -43,19 +56,21 @@ module Truffler
|
|
|
43
56
|
|
|
44
57
|
def relation(scope, limit: nil)
|
|
45
58
|
relation = candidates(scope)
|
|
59
|
+
relation = relation.joins(Arel.sql(label_scores_join_sql)) if grouped_label_scores?
|
|
46
60
|
relation = relation.select(Arel.sql("#{table}.*")) if relation.select_values.empty?
|
|
47
61
|
relation = relation.select(*score_columns.map { |name, sql| Arel.sql("(#{sql}) AS #{name}") })
|
|
48
62
|
relation = relation.reorder(*ordering)
|
|
49
63
|
limit ? relation.limit(limit) : relation
|
|
50
64
|
end
|
|
51
65
|
|
|
66
|
+
# The label term: a per-candidate subquery when sources narrow the
|
|
67
|
+
# candidates, else the grouped join's score.
|
|
52
68
|
def label_score_sql
|
|
53
|
-
|
|
54
|
-
return if
|
|
69
|
+
return if encoding.intent_vector.empty?
|
|
70
|
+
return "COALESCE(#{connection.quote_table_name(LABEL_SCORES)}.score, 0.0)" if grouped_label_scores?
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"WHERE #{label_scope_sql} AND #{label_column('label_key')} IN (#{intent.keys.map { |key| quote(key) }.join(', ')})), 0.0)"
|
|
72
|
+
"COALESCE((SELECT SUM(#{label_case_sql}) FROM #{quoted_labels} " \
|
|
73
|
+
"WHERE #{label_scope_sql} AND #{label_keys_sql}), 0.0)"
|
|
59
74
|
end
|
|
60
75
|
|
|
61
76
|
def sources
|
|
@@ -64,7 +79,7 @@ module Truffler
|
|
|
64
79
|
end
|
|
65
80
|
|
|
66
81
|
def keywords
|
|
67
|
-
encoding.keywords(query)
|
|
82
|
+
@keywords ||= encoding.keywords(query, keep: -> { Filler.label_words(definition, tenant_key) })
|
|
68
83
|
end
|
|
69
84
|
|
|
70
85
|
private
|
|
@@ -112,6 +127,46 @@ module Truffler
|
|
|
112
127
|
range.to ? scope.where(arrived_at.lt(range.to)) : scope
|
|
113
128
|
end
|
|
114
129
|
|
|
130
|
+
def label_case_sql
|
|
131
|
+
cases = encoding.intent_vector.map { |key, weight| "WHEN #{quote(key)} THEN #{Float(weight)} * #{label_column('value')}" }
|
|
132
|
+
"CASE #{label_column('label_key')} #{cases.join(' ')} ELSE 0.0 END"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def label_keys_sql
|
|
136
|
+
"#{label_column('label_key')} IN (#{encoding.intent_vector.keys.map { |key| quote(key) }.join(', ')})"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Every record past the tenant, time, and filters is a candidate.
|
|
140
|
+
def every_base_record?
|
|
141
|
+
label_only? || encoding.filters.any?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def grouped_label_scores?
|
|
145
|
+
encoding.intent_vector.any? && every_base_record?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# One aggregate over the tenant's rows for the intent's keys, which
|
|
149
|
+
# `index_truffler_labels_for_search` (with INCLUDE (record_id)) serves
|
|
150
|
+
# as an index-only scan.
|
|
151
|
+
def label_scores_join_sql
|
|
152
|
+
tenant = definition.scoped? ? " AND #{label_column('tenant_key')} = #{quote(tenant_key)}" : ""
|
|
153
|
+
scores = connection.quote_table_name(LABEL_SCORES)
|
|
154
|
+
"LEFT JOIN (SELECT #{label_column('record_id')} AS record_id, SUM(#{label_case_sql}) AS score FROM #{quoted_labels} " \
|
|
155
|
+
"WHERE #{label_column('record_type')} = #{quote(model.polymorphic_name)}#{tenant} AND #{label_keys_sql} " \
|
|
156
|
+
"GROUP BY #{label_column('record_id')}) #{scores} ON #{scores}.record_id = #{primary_key}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def neighbors_sql
|
|
160
|
+
return @neighbors_sql if defined?(@neighbors_sql)
|
|
161
|
+
|
|
162
|
+
@neighbors_sql = (store.neighbors_sql(model, tenant_key: tenant_key, vector: vector) if vector && store.respond_to?(:neighbors_sql))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def neighbors_join_sql
|
|
166
|
+
neighbors = connection.quote_table_name(NEIGHBORS)
|
|
167
|
+
"LEFT JOIN (#{neighbors_sql}) #{neighbors} ON #{neighbors}.record_id = #{primary_key}"
|
|
168
|
+
end
|
|
169
|
+
|
|
115
170
|
def label_filter_sql(key, threshold)
|
|
116
171
|
"EXISTS (SELECT 1 FROM #{quoted_labels} WHERE #{label_scope_sql} AND #{label_column('label_key')} = #{quote(key)} " \
|
|
117
172
|
"AND #{label_column('value')} >= #{Float(threshold)})"
|
|
@@ -130,20 +185,28 @@ module Truffler
|
|
|
130
185
|
def keyword_sql
|
|
131
186
|
return @keyword_sql if defined?(@keyword_sql)
|
|
132
187
|
|
|
133
|
-
@keyword_sql = keywords.empty? ? nil : keyword_condition(definition.keyword)
|
|
188
|
+
@keyword_sql = keywords.empty? ? nil : keyword_condition(definition.keyword, keywords)
|
|
134
189
|
end
|
|
135
190
|
|
|
136
|
-
|
|
191
|
+
# Any soft keyword hit; soft keywords rank and never narrow.
|
|
192
|
+
def soft_keyword_sql
|
|
193
|
+
return @soft_keyword_sql if defined?(@soft_keyword_sql)
|
|
194
|
+
|
|
195
|
+
conditions = (encoding.soft_keyword_tokens - keywords).uniq.filter_map { |token| keyword_condition(definition.keyword, [ token ]) }
|
|
196
|
+
@soft_keyword_sql = conditions.empty? ? nil : conditions.map { |condition| "(#{condition})" }.join(" OR ")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def keyword_condition(source, tokens)
|
|
137
200
|
case source
|
|
138
201
|
when Array
|
|
139
202
|
return if source.empty?
|
|
140
203
|
|
|
141
|
-
|
|
204
|
+
tokens.map do |token|
|
|
142
205
|
pattern = quote("%#{model.sanitize_sql_like(token)}%")
|
|
143
206
|
"(#{source.map { |name| "LOWER(#{column(name)}) LIKE #{pattern} ESCAPE '\\'" }.join(' OR ')})"
|
|
144
207
|
end.join(" AND ")
|
|
145
208
|
when nil then nil
|
|
146
|
-
else membership_sql(source.call(tenant_scope,
|
|
209
|
+
else membership_sql(source.call(tenant_scope, tokens))
|
|
147
210
|
end
|
|
148
211
|
end
|
|
149
212
|
|
|
@@ -160,7 +223,10 @@ module Truffler
|
|
|
160
223
|
def text_score_sql
|
|
161
224
|
return @text_score_sql if defined?(@text_score_sql)
|
|
162
225
|
|
|
163
|
-
@text_score_sql =
|
|
226
|
+
@text_score_sql =
|
|
227
|
+
if neighbors_sql then "COALESCE(#{connection.quote_table_name(NEIGHBORS)}.similarity, 0.0)"
|
|
228
|
+
elsif vector && store then store.similarity_sql(model, tenant_key: tenant_key, vector: vector).to_s
|
|
229
|
+
end
|
|
164
230
|
end
|
|
165
231
|
|
|
166
232
|
def text_candidate_sql
|
|
@@ -175,9 +241,14 @@ module Truffler
|
|
|
175
241
|
definition.scoped? ? model.where(definition.tenant_column => tenant_key) : model.all
|
|
176
242
|
end
|
|
177
243
|
|
|
244
|
+
# An id list is the fast path. A relation runs once as an array on
|
|
245
|
+
# Postgres, where `IN (subquery)` becomes a hashed filter over every
|
|
246
|
+
# tenant row.
|
|
178
247
|
def membership_sql(result)
|
|
179
248
|
case result
|
|
180
|
-
when ActiveRecord::Relation
|
|
249
|
+
when ActiveRecord::Relation
|
|
250
|
+
subquery = result.reselect(result.klass.arel_table[result.klass.primary_key]).to_sql
|
|
251
|
+
connection.adapter_name.match?(/postg/i) ? "#{primary_key} = ANY(ARRAY(#{subquery}))" : "#{primary_key} IN (#{subquery})"
|
|
181
252
|
when nil then nil
|
|
182
253
|
else
|
|
183
254
|
ids = Array(result)
|
|
@@ -189,12 +260,19 @@ module Truffler
|
|
|
189
260
|
terms = {
|
|
190
261
|
truffler_label_score: (label_score_sql && "#{Float(weights[:label])} * #{label_score_sql}"),
|
|
191
262
|
truffler_text_score: (text_score_sql && "#{Float(weights[:text])} * #{text_score_sql}"),
|
|
192
|
-
truffler_keyword_score:
|
|
263
|
+
truffler_keyword_score: keyword_score_sql,
|
|
193
264
|
truffler_exact_score: (exact_sql && "#{Float(weights[:exact])} * (CASE WHEN #{exact_sql} THEN 1.0 ELSE 0.0 END)")
|
|
194
265
|
}.compact
|
|
195
266
|
{ truffler_score: terms.any? ? terms.values.map { |term| "(#{term})" }.join(" + ") : "0.0", **terms }
|
|
196
267
|
end
|
|
197
268
|
|
|
269
|
+
def keyword_score_sql
|
|
270
|
+
weight = Float(weights[:keyword])
|
|
271
|
+
terms = [ (keyword_sql && "#{weight} * (CASE WHEN #{keyword_sql} THEN 1.0 ELSE 0.0 END)"),
|
|
272
|
+
(soft_keyword_sql && "#{weight * SOFT_KEYWORD} * (CASE WHEN #{soft_keyword_sql} THEN 1.0 ELSE 0.0 END)") ].compact
|
|
273
|
+
terms.map { |term| "(#{term})" }.join(" + ") if terms.any?
|
|
274
|
+
end
|
|
275
|
+
|
|
198
276
|
def ordering
|
|
199
277
|
order_column, direction = definition.order
|
|
200
278
|
[ Arel.sql("truffler_score DESC"), (Arel.sql("#{column(order_column)} #{direction == :asc ? 'ASC' : 'DESC'}") if order_column),
|
|
@@ -64,7 +64,7 @@ module Truffler
|
|
|
64
64
|
if @encodings.encoded?(key) then @encodings.read_encoding(key, query)
|
|
65
65
|
elsif @encodings.in_flight?(key) then @encoder.await(key, deadline: @deadline, query: query)
|
|
66
66
|
end
|
|
67
|
-
encoding&.without(run.suppressed)
|
|
67
|
+
encoding&.without(run.suppressed, keep_words: -> { Search::Filler.label_words(model.truffler_definition, run.tenant_key) })
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# The snapshot narrowed to what the encoding allows, in the tenant, in
|
|
@@ -30,7 +30,7 @@ module Truffler
|
|
|
30
30
|
local_ids = result.ids
|
|
31
31
|
run = Run.create(@model, query: @query.raw.strip, tenant_key: keystroke.tenant_key, user_key: keystroke.user_key,
|
|
32
32
|
surface: keystroke.surface, suppressed: @suppressed, pool_ids: pool(result, local_ids), local_ids: local_ids,
|
|
33
|
-
local_weak: result.
|
|
33
|
+
local_weak: result.local_weak?, explicit_action: result.explicit_action, store: @store)
|
|
34
34
|
previous = @store.supersede(run.record_type, run.tenant_key, run.user_key, run.surface, run.id)
|
|
35
35
|
Run.load(previous, store: @store).cancel! if previous
|
|
36
36
|
Instrumentation.instrument(:smart_search, run_id: run.id, record_type: run.record_type, tenant_key: run.tenant_key,
|
data/lib/truffler/version.rb
CHANGED
data/lib/truffler/vocabulary.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Truffler
|
|
2
2
|
# A label's fingerprint is the SHA-256 of its canonical question plus the
|
|
3
3
|
# pinned Jev model; a host-supplied label's is its supplied fingerprint
|
|
4
|
-
# (type,
|
|
5
|
-
# vocabulary version digests every fingerprint. A stored
|
|
6
|
-
# when its fingerprint differs from the current one.
|
|
4
|
+
# (type, option keys, version), which no Jev model change or rewording
|
|
5
|
+
# touches. The vocabulary version digests every fingerprint. A stored
|
|
6
|
+
# label is stale when its fingerprint differs from the current one.
|
|
7
7
|
#
|
|
8
8
|
# A scope's vocabulary also holds its active lens labels (KTD21), so a lens
|
|
9
9
|
# changes the version only where it applies. user_key is the searcher key
|
|
@@ -39,6 +39,16 @@ module Truffler
|
|
|
39
39
|
Canonical.digest(fingerprints(tenant_key: tenant_key, user_key: user_key, all_users: all_users))
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# The version query encodings are cached under: the labeling version plus
|
|
43
|
+
# a digest of the wording only query encoding reads (label descriptions,
|
|
44
|
+
# option descriptions and search texts), which labeling never sees for
|
|
45
|
+
# supplied labels and never sees for search texts.
|
|
46
|
+
def encoding_version(tenant_key: nil, user_key: nil)
|
|
47
|
+
labels = labels_for(tenant_key: tenant_key, user_key: user_key)
|
|
48
|
+
Canonical.digest(version: Canonical.digest(labels.transform_values { |label| fingerprint_of(label, tenant_key) }),
|
|
49
|
+
wording: labels.transform_values { |label| label.encoding_wording(tenant_key) }.compact)
|
|
50
|
+
end
|
|
51
|
+
|
|
42
52
|
private
|
|
43
53
|
|
|
44
54
|
def fingerprint_of(label, tenant_key)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: truffler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kieran Klaassen
|
|
@@ -102,12 +102,14 @@ files:
|
|
|
102
102
|
- bench/cassettes/0858c5006c0bfac3e970ddaff3c139f6c110dbe3d8539fd95263706c637cdc8e.json
|
|
103
103
|
- bench/cassettes/0c0eb7c27d76bd0b9263690a63cd92d6b68c305826318555fd5b084a09970b78.json
|
|
104
104
|
- bench/cassettes/0fada0c0a103882e2895f7f455d73d2cbe2b4a81bf743bb19b2b4f6e0130126b.json
|
|
105
|
+
- bench/cassettes/1037e0b63ba790c02d14d0ddfc18b68151c11d03ce88d764658044b6bab2e55b.json
|
|
105
106
|
- bench/cassettes/1117d79ac7c43b21beaf75e510461f91cd4ecc3cdbbe461f1794644af4aaf8f3.json
|
|
106
107
|
- bench/cassettes/11b7444a074cca4d0ef1535db0cf0edefe4c34eac4be3c593c9c2f33d3b038b8.json
|
|
107
108
|
- bench/cassettes/11d00371d1b993675be00463159164536f1f8ed90a48652b0935eb9c36f8b684.json
|
|
108
109
|
- bench/cassettes/1456ebe12616ce961d68a14eada62778a7271ebd3d1b4cb6eefd6857651c7afc.json
|
|
109
110
|
- bench/cassettes/14c3693f64b0e9ea7e470277fccae6ab501d404185eec5bb9074ef4b75c572c0.json
|
|
110
111
|
- bench/cassettes/14fe9f6d8055d41f5d970dc32ab640c213ea8782d365c82d32b9884ba4f28c67.json
|
|
112
|
+
- bench/cassettes/19bba849a846d30a69dc385a337d67704fbe5194506fd3c914d3270d86495fde.json
|
|
111
113
|
- bench/cassettes/1a1d8557e4698426c2467aa573a240357715d3d9e3fa77bf066672a8b6bf9a86.json
|
|
112
114
|
- bench/cassettes/1b5a8aaaf98305560588246cd51f53c6c185a951f8018c26bf588954d9de1cea.json
|
|
113
115
|
- bench/cassettes/1fe596be747e33e9c7e4d7c50de32613957ac66bee43679b4bed96c551b1fd0a.json
|
|
@@ -116,6 +118,7 @@ files:
|
|
|
116
118
|
- bench/cassettes/2f6576325dfab696cec8d8b35fa89c29960bee15d10dab31c91cef507f4842be.json
|
|
117
119
|
- bench/cassettes/33ec27d22c0f63dbbe93008f50af07fa8667a5c806a9fbfc7897683915412b40.json
|
|
118
120
|
- bench/cassettes/351f745b0c47e0cf3272b0fea7fc4941b6aa733f013c55a9a19707b542543200.json
|
|
121
|
+
- bench/cassettes/3768847dc57df8bf2328771e7763c0b8bd013a167244e7849967dac144420601.json
|
|
119
122
|
- bench/cassettes/37d974fdc0c1876413111c244d363c8d2306e3229fec668c158b5c924ffdb983.json
|
|
120
123
|
- bench/cassettes/39524467463dd612cbf03e314b9cfd8cbe1c5f06969da2289359cf7cf83de5da.json
|
|
121
124
|
- bench/cassettes/3f2c758c111ab7f1163ba579f2329718afe15620507d356d3ebe2a053df237ea.json
|
|
@@ -199,6 +202,8 @@ files:
|
|
|
199
202
|
- lib/generators/truffler/install/templates/initializer.rb.tt
|
|
200
203
|
- lib/generators/truffler/install/templates/migration.rb.tt
|
|
201
204
|
- lib/generators/truffler/upgrade/templates/backfill_spends_migration.rb.tt
|
|
205
|
+
- lib/generators/truffler/upgrade/templates/backfill_spends_tenant_key_migration.rb.tt
|
|
206
|
+
- lib/generators/truffler/upgrade/templates/labels_search_covering_migration.rb.tt
|
|
202
207
|
- lib/generators/truffler/upgrade/upgrade_generator.rb
|
|
203
208
|
- lib/tasks/truffler.rake
|
|
204
209
|
- lib/tasks/truffler/bench.rake
|
|
@@ -221,6 +226,7 @@ files:
|
|
|
221
226
|
- lib/truffler/clients/base.rb
|
|
222
227
|
- lib/truffler/clients/callable.rb
|
|
223
228
|
- lib/truffler/clients/cassette.rb
|
|
229
|
+
- lib/truffler/clients/evaluator.rb
|
|
224
230
|
- lib/truffler/clients/fake.rb
|
|
225
231
|
- lib/truffler/clients/ruby_llm_typesafe.rb
|
|
226
232
|
- lib/truffler/configuration.rb
|
|
@@ -294,6 +300,7 @@ files:
|
|
|
294
300
|
- lib/truffler/registry.rb
|
|
295
301
|
- lib/truffler/search/encoding.rb
|
|
296
302
|
- lib/truffler/search/encoding_cache.rb
|
|
303
|
+
- lib/truffler/search/filler.rb
|
|
297
304
|
- lib/truffler/search/keystroke.rb
|
|
298
305
|
- lib/truffler/search/query.rb
|
|
299
306
|
- lib/truffler/search/result.rb
|