@with-logic/intent 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.
- package/LICENSE +21 -0
- package/README.md +277 -0
- package/dist/batches.d.ts +55 -0
- package/dist/batches.js +91 -0
- package/dist/batches.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -0
- package/dist/extractors.d.ts +52 -0
- package/dist/extractors.js +88 -0
- package/dist/extractors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +402 -0
- package/dist/intent.js +540 -0
- package/dist/intent.js.map +1 -0
- package/dist/lib/config.d.ts +30 -0
- package/dist/lib/config.js +81 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/number.d.ts +5 -0
- package/dist/lib/number.js +15 -0
- package/dist/lib/number.js.map +1 -0
- package/dist/llm_client.d.ts +14 -0
- package/dist/llm_client.js +29 -0
- package/dist/llm_client.js.map +1 -0
- package/dist/messages.d.ts +41 -0
- package/dist/messages.js +136 -0
- package/dist/messages.js.map +1 -0
- package/dist/providers/groq.d.ts +84 -0
- package/dist/providers/groq.js +335 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/schema.d.ts +82 -0
- package/dist/schema.js +114 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
package/dist/intent.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { batchProcess } from "./batches";
|
|
2
|
+
import { CONFIG } from "./config";
|
|
3
|
+
import { DEFAULT_KEY_EXTRACTOR, DEFAULT_SUMMARY_EXTRACTOR } from "./extractors";
|
|
4
|
+
import { clamp } from "./lib/number";
|
|
5
|
+
import { selectLlmClient } from "./llm_client";
|
|
6
|
+
import { buildChoiceMessages, buildFilterMessages, buildMessages } from "./messages";
|
|
7
|
+
import { buildChoiceSchema, buildFilterSchema, buildRelevancySchema } from "./schema";
|
|
8
|
+
/**
|
|
9
|
+
* LLM-based reranker for arbitrary items.
|
|
10
|
+
*
|
|
11
|
+
* Uses a listwise LLM approach to score candidates within a configurable range based on relevance to a query,
|
|
12
|
+
* then filters by threshold and returns results sorted by score with stable ordering.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of items to rerank (defaults to any)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Simplest: uses defaults and GROQ_API_KEY from environment
|
|
19
|
+
* const intent = new Intent();
|
|
20
|
+
* const ranked = await intent.rank("find expense reports", items);
|
|
21
|
+
*
|
|
22
|
+
* // With custom extractors
|
|
23
|
+
* type Document = { id: string; title: string; content: string };
|
|
24
|
+
* const intent = new Intent<Document>({
|
|
25
|
+
* key: doc => doc.title,
|
|
26
|
+
* summary: doc => doc.content.slice(0, 200),
|
|
27
|
+
* relevancyThreshold: 5,
|
|
28
|
+
* batchSize: 20
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // With custom LLM client
|
|
32
|
+
* const intent = new Intent<Document>({
|
|
33
|
+
* llm: myClient,
|
|
34
|
+
* userId: "user-123",
|
|
35
|
+
* key: doc => doc.title,
|
|
36
|
+
* summary: doc => doc.content.slice(0, 200)
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class Intent {
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the model name to use for this Intent instance.
|
|
43
|
+
*
|
|
44
|
+
* Intent is provider-driven. Today only GROQ is supported; when using GROQ
|
|
45
|
+
* we always take the model from GROQ's config defaults.
|
|
46
|
+
*
|
|
47
|
+
* @returns Provider-specific model name
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
resolveModel() {
|
|
51
|
+
return this.env.GROQ.DEFAULT_MODEL;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds the context object from options.
|
|
55
|
+
*
|
|
56
|
+
* Constructs an IntentContext with only defined properties to satisfy
|
|
57
|
+
* TypeScript's exactOptionalPropertyTypes requirement.
|
|
58
|
+
*
|
|
59
|
+
* @param options - The options object containing llm, logger, and userId
|
|
60
|
+
* @returns IntentContext with only defined properties
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
buildContext(options) {
|
|
64
|
+
return {
|
|
65
|
+
...(options.llm !== undefined && { llm: options.llm }),
|
|
66
|
+
...(options.logger !== undefined && { logger: options.logger }),
|
|
67
|
+
...(options.userId !== undefined && { userId: options.userId }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds the extractors object from options.
|
|
72
|
+
*
|
|
73
|
+
* Uses provided extractors or falls back to generic defaults that work
|
|
74
|
+
* for any type T via JSON stringification and hashing.
|
|
75
|
+
*
|
|
76
|
+
* @param options - The options object containing key and summary extractors
|
|
77
|
+
* @returns Required extractors with defaults applied
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
buildExtractors(options) {
|
|
81
|
+
return {
|
|
82
|
+
key: options.key ?? (DEFAULT_KEY_EXTRACTOR),
|
|
83
|
+
summary: options.summary ?? (DEFAULT_SUMMARY_EXTRACTOR),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Builds the configuration object from options.
|
|
88
|
+
*
|
|
89
|
+
* Merges user-provided options with environment-based CONFIG defaults.
|
|
90
|
+
*
|
|
91
|
+
* @param options - The options object containing config overrides
|
|
92
|
+
* @returns Required config with all values populated
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
buildConfig(options) {
|
|
96
|
+
return {
|
|
97
|
+
provider: options.provider ?? this.env.INTENT.PROVIDER,
|
|
98
|
+
timeoutMs: options.timeoutMs ?? this.env.INTENT.TIMEOUT_MS,
|
|
99
|
+
relevancyThreshold: options.relevancyThreshold ?? this.env.INTENT.RELEVANCY_THRESHOLD,
|
|
100
|
+
batchSize: options.batchSize ?? this.env.INTENT.BATCH_SIZE,
|
|
101
|
+
tinyBatchFraction: options.tinyBatchFraction ?? this.env.INTENT.TINY_BATCH_FRACTION,
|
|
102
|
+
minScore: options.minScore ?? this.env.INTENT.MIN_SCORE,
|
|
103
|
+
maxScore: options.maxScore ?? this.env.INTENT.MAX_SCORE,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Validates the configuration values.
|
|
108
|
+
*
|
|
109
|
+
* Ensures the configured score range is valid and the relevancyThreshold is in range.
|
|
110
|
+
*
|
|
111
|
+
* @throws {Error} If maxScore is below minScore
|
|
112
|
+
* @throws {Error} If relevancyThreshold is not within [minScore, maxScore]
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
validateConfig() {
|
|
116
|
+
if (this.cfg.maxScore < this.cfg.minScore) {
|
|
117
|
+
throw new Error(`intent: maxScore must be >= minScore, got minScore=${this.cfg.minScore} maxScore=${this.cfg.maxScore}`);
|
|
118
|
+
}
|
|
119
|
+
if (this.cfg.relevancyThreshold < this.cfg.minScore ||
|
|
120
|
+
this.cfg.relevancyThreshold > this.cfg.maxScore) {
|
|
121
|
+
throw new Error(`intent: relevancyThreshold must be between ${this.cfg.minScore} and ${this.cfg.maxScore}, got ${this.cfg.relevancyThreshold}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Selects and validates the LLM client.
|
|
126
|
+
*
|
|
127
|
+
* Uses the provided client from context or attempts to create a default
|
|
128
|
+
* Groq client if GROQ_API_KEY is available.
|
|
129
|
+
*
|
|
130
|
+
* @returns The selected LLM client
|
|
131
|
+
* @throws {Error} If no LLM client is provided and GROQ_API_KEY is not set
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
selectAndValidateLlmClient() {
|
|
135
|
+
const selectedClient = selectLlmClient(this.ctx, this.env);
|
|
136
|
+
if (!selectedClient) {
|
|
137
|
+
throw new Error("intent: No LLM client provided and GROQ_API_KEY not set. Provide options.llm or set GROQ_API_KEY.");
|
|
138
|
+
}
|
|
139
|
+
return selectedClient;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Creates a new Intent instance.
|
|
143
|
+
*
|
|
144
|
+
* All options are optional with sensible defaults:
|
|
145
|
+
* - llm: Auto-detected from GROQ_API_KEY environment variable if available
|
|
146
|
+
* - key: Hash-based string from JSON representation of items
|
|
147
|
+
* - summary: Pretty-printed JSON of items (2-space indentation for LLM readability)
|
|
148
|
+
* - Config values: From INTENT_* environment variables or built-in defaults
|
|
149
|
+
*
|
|
150
|
+
* @param options - Optional configuration object
|
|
151
|
+
* @param options.llm - Optional LLM client. If omitted, uses Groq client when GROQ_API_KEY is set
|
|
152
|
+
* @param options.logger - Optional logger for warnings and errors
|
|
153
|
+
* @param options.userId - Optional user identifier for LLM provider abuse monitoring
|
|
154
|
+
* @param options.key - Optional function extracting a short human-readable key from items
|
|
155
|
+
* @param options.summary - Optional function extracting a short description for LLM reasoning
|
|
156
|
+
* @param options.provider - Optional provider override (default: INTENT_PROVIDER or "GROQ")
|
|
157
|
+
* @param options.timeoutMs - Optional timeout in milliseconds (default: INTENT_TIMEOUT_MS or 3000)
|
|
158
|
+
* @param options.relevancyThreshold - Optional minimum score to include results (default: INTENT_RELEVANCY_THRESHOLD)
|
|
159
|
+
* @param options.minScore - Optional minimum score value (default: INTENT_MIN_SCORE or 0)
|
|
160
|
+
* @param options.maxScore - Optional maximum score value (default: INTENT_MAX_SCORE or 10)
|
|
161
|
+
* @param options.batchSize - Optional number of candidates per LLM call (default: INTENT_BATCH_SIZE or 20)
|
|
162
|
+
* @param options.tinyBatchFraction - Optional threshold for merging small batches (default: INTENT_TINY_BATCH_FRACTION or 0.2)
|
|
163
|
+
* @throws {Error} If no LLM client is provided and GROQ_API_KEY is not set
|
|
164
|
+
* @throws {Error} If maxScore is below minScore
|
|
165
|
+
* @throws {Error} If relevancyThreshold is not within [minScore, maxScore]
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* // Minimal - uses all defaults
|
|
170
|
+
* const intent = new Intent();
|
|
171
|
+
*
|
|
172
|
+
* // With extractors
|
|
173
|
+
* const intent = new Intent<Doc>({
|
|
174
|
+
* key: doc => doc.title,
|
|
175
|
+
* summary: doc => doc.content
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* // Full configuration
|
|
179
|
+
* const intent = new Intent<Doc>({
|
|
180
|
+
* llm: myClient,
|
|
181
|
+
* userId: "org-123",
|
|
182
|
+
* key: doc => doc.title,
|
|
183
|
+
* summary: doc => doc.content,
|
|
184
|
+
* relevancyThreshold: 5,
|
|
185
|
+
* batchSize: 20
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
constructor(options = {}) {
|
|
190
|
+
this.env = options.config ?? CONFIG;
|
|
191
|
+
this.ctx = this.buildContext(options);
|
|
192
|
+
this.extractors = this.buildExtractors(options);
|
|
193
|
+
this.cfg = this.buildConfig(options);
|
|
194
|
+
this.validateConfig();
|
|
195
|
+
this.llm = this.selectAndValidateLlmClient();
|
|
196
|
+
}
|
|
197
|
+
async rank(query, candidates, options) {
|
|
198
|
+
try {
|
|
199
|
+
if (candidates.length === 0) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
if (candidates.length === 1) {
|
|
203
|
+
if (options?.explain) {
|
|
204
|
+
const [firstCandidate] = candidates;
|
|
205
|
+
return [{ item: firstCandidate, explanation: "" }];
|
|
206
|
+
}
|
|
207
|
+
return candidates;
|
|
208
|
+
}
|
|
209
|
+
const prepared = this.prepareCandidates(candidates);
|
|
210
|
+
const rankedWithExplanations = await batchProcess(prepared, this.cfg.batchSize, this.cfg.tinyBatchFraction, async (batch) => (await this.processBatch(query, batch, options?.userId !== undefined ? { userId: options.userId } : undefined)), this.ctx.logger, (batch) => batch.map(({ item }) => ({ item, explanation: "" })));
|
|
211
|
+
if (options?.explain) {
|
|
212
|
+
return rankedWithExplanations;
|
|
213
|
+
}
|
|
214
|
+
return rankedWithExplanations.map(({ item }) => item);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.ctx.logger?.warn?.("intent reranker failed, using fallback", {
|
|
218
|
+
error: error?.message,
|
|
219
|
+
});
|
|
220
|
+
if (options?.explain) {
|
|
221
|
+
return candidates.map((item) => ({ item, explanation: "" }));
|
|
222
|
+
}
|
|
223
|
+
return candidates;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async filter(query, candidates, options) {
|
|
227
|
+
if (candidates.length === 0) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
if (candidates.length === 1) {
|
|
231
|
+
if (options?.explain) {
|
|
232
|
+
const [firstCandidate] = candidates;
|
|
233
|
+
return [{ item: firstCandidate, explanation: "" }];
|
|
234
|
+
}
|
|
235
|
+
return candidates;
|
|
236
|
+
}
|
|
237
|
+
const prepared = this.prepareCandidates(candidates);
|
|
238
|
+
const filteredWithExplanations = await batchProcess(prepared, this.cfg.batchSize, this.cfg.tinyBatchFraction, async (batch) => (await this.processFilterBatch(query, batch, options?.userId !== undefined ? { userId: options.userId } : undefined)), this.ctx.logger, (batch) => batch.map(({ item }) => ({ item, explanation: "" })));
|
|
239
|
+
if (options?.explain) {
|
|
240
|
+
return filteredWithExplanations;
|
|
241
|
+
}
|
|
242
|
+
return filteredWithExplanations.map(({ item }) => item);
|
|
243
|
+
}
|
|
244
|
+
async choice(query, candidates, options) {
|
|
245
|
+
if (candidates.length === 0) {
|
|
246
|
+
throw new Error("intent: choice requires at least one candidate");
|
|
247
|
+
}
|
|
248
|
+
if (candidates.length === 1) {
|
|
249
|
+
const [firstCandidate] = candidates;
|
|
250
|
+
if (options?.explain) {
|
|
251
|
+
return { item: firstCandidate, explanation: "" };
|
|
252
|
+
}
|
|
253
|
+
return firstCandidate;
|
|
254
|
+
}
|
|
255
|
+
const prepared = this.prepareCandidates(candidates);
|
|
256
|
+
const keyed = this.ensureUniqueKeys(prepared);
|
|
257
|
+
const winners = await batchProcess(keyed, this.cfg.batchSize, this.cfg.tinyBatchFraction, async (batch) => [await this.processChoiceBatch(query, batch, options?.userId)], this.ctx.logger, (batch) => [{ item: batch[0].item, explanation: "" }]);
|
|
258
|
+
// batchProcess guarantees at least one winner via its per-batch fallback.
|
|
259
|
+
if (winners.length === 1) {
|
|
260
|
+
const [onlyWinner] = winners;
|
|
261
|
+
if (options?.explain) {
|
|
262
|
+
return onlyWinner;
|
|
263
|
+
}
|
|
264
|
+
return onlyWinner.item;
|
|
265
|
+
}
|
|
266
|
+
const preparedFinalists = this.prepareCandidates(winners.map((w) => w.item));
|
|
267
|
+
const keyedFinalists = this.ensureUniqueKeys(preparedFinalists);
|
|
268
|
+
const final = await this.processChoiceBatch(query, keyedFinalists, options?.userId);
|
|
269
|
+
if (options?.explain) {
|
|
270
|
+
return final;
|
|
271
|
+
}
|
|
272
|
+
return final.item;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Normalize incoming items into a consistent shape for downstream processing.
|
|
276
|
+
*
|
|
277
|
+
* Extracts the key and summary from each item using the configured extractors,
|
|
278
|
+
* and attaches the original input index for stable sorting later.
|
|
279
|
+
*
|
|
280
|
+
* @param candidates - Raw items to prepare
|
|
281
|
+
* @returns Array of prepared candidates with extracted metadata and original index
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
prepareCandidates(candidates) {
|
|
285
|
+
return candidates.map((item, idx) => ({
|
|
286
|
+
item,
|
|
287
|
+
idx,
|
|
288
|
+
baseKey: this.extractors.key(item),
|
|
289
|
+
summary: this.extractors.summary(item),
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Ensure keys are unique by suffixing duplicates with their input index.
|
|
294
|
+
*
|
|
295
|
+
* When multiple items share the same key, subsequent occurrences are renamed
|
|
296
|
+
* to "Key (idx)" where idx is the original input index. This prevents JSON
|
|
297
|
+
* schema validation errors and ensures the LLM can score each item independently.
|
|
298
|
+
*
|
|
299
|
+
* @param itemsBase - Prepared candidates with potentially duplicate keys
|
|
300
|
+
* @returns Candidates with guaranteed unique keys
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
ensureUniqueKeys(itemsBase) {
|
|
304
|
+
const counts = new Map();
|
|
305
|
+
return itemsBase.map(({ item, baseKey, summary, idx }) => {
|
|
306
|
+
const n = (counts.get(baseKey) ?? 0) + 1;
|
|
307
|
+
counts.set(baseKey, n);
|
|
308
|
+
const key = n === 1 ? baseKey : `${baseKey} (${idx})`;
|
|
309
|
+
return { item, idx, key, summary };
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Build the JSON schema and chat messages payload for the LLM.
|
|
314
|
+
*
|
|
315
|
+
* Creates a strict JSON schema requiring one integer property (minScore-maxScore) per candidate key,
|
|
316
|
+
* and constructs system + user messages instructing the LLM to score relevance.
|
|
317
|
+
*
|
|
318
|
+
* @param query - The search query to evaluate candidates against
|
|
319
|
+
* @param items - Candidates with unique keys and summaries
|
|
320
|
+
* @returns Object containing JSON schema and chat messages array
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
buildRequest(query, items) {
|
|
324
|
+
const keys = items.map((x) => x.key);
|
|
325
|
+
const schema = buildRelevancySchema(keys, this.cfg.minScore, this.cfg.maxScore);
|
|
326
|
+
const messages = buildMessages(query, items, {
|
|
327
|
+
minScore: this.cfg.minScore,
|
|
328
|
+
maxScore: this.cfg.maxScore,
|
|
329
|
+
});
|
|
330
|
+
return { schema, messages };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Build the JSON schema and chat messages payload for the LLM filter call.
|
|
334
|
+
*
|
|
335
|
+
* @param query - The search query to evaluate candidates against
|
|
336
|
+
* @param items - Candidates with unique keys and summaries
|
|
337
|
+
* @returns Object containing JSON schema and chat messages array
|
|
338
|
+
* @private
|
|
339
|
+
*/
|
|
340
|
+
buildFilterRequest(query, items) {
|
|
341
|
+
const keys = items.map((x) => x.key);
|
|
342
|
+
const schema = buildFilterSchema(keys);
|
|
343
|
+
const messages = buildFilterMessages(query, items);
|
|
344
|
+
return { schema, messages };
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Build the JSON schema and chat messages payload for the LLM choice call.
|
|
348
|
+
*
|
|
349
|
+
* @param query - The search query to choose against
|
|
350
|
+
* @param items - Candidates with unique keys and summaries
|
|
351
|
+
* @returns Object containing JSON schema and chat messages array
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
buildChoiceRequest(query, items) {
|
|
355
|
+
const keys = items.map((x) => x.key);
|
|
356
|
+
const schema = buildChoiceSchema(keys);
|
|
357
|
+
const messages = buildChoiceMessages(query, items);
|
|
358
|
+
return { schema, messages };
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Invoke the LLM and return the parsed map of candidate scores.
|
|
362
|
+
*
|
|
363
|
+
* Calls the configured LLM client with the messages, JSON schema, model config,
|
|
364
|
+
* and user ID. Returns null if the response is invalid or missing.
|
|
365
|
+
*
|
|
366
|
+
* @param messages - Chat messages (system + user) to send to LLM
|
|
367
|
+
* @param schema - Strict JSON schema defining expected response structure
|
|
368
|
+
* @param userId - Optional user identifier for provider abuse monitoring
|
|
369
|
+
* @returns Map of candidate keys to numeric scores, or null if response invalid
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
async fetchEvaluations(messages, schema, userId) {
|
|
373
|
+
const config = {
|
|
374
|
+
model: this.resolveModel(),
|
|
375
|
+
reasoningEffort: "medium",
|
|
376
|
+
timeoutMs: this.cfg.timeoutMs,
|
|
377
|
+
};
|
|
378
|
+
const { data } = await this.llm.call(messages, schema, config, userId ?? this.ctx.userId);
|
|
379
|
+
if (data == null || typeof data !== "object")
|
|
380
|
+
return null;
|
|
381
|
+
return data;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Invoke the LLM and return boolean relevancy decisions.
|
|
385
|
+
*
|
|
386
|
+
* @param messages - Chat messages to send
|
|
387
|
+
* @param schema - Strict JSON schema defining expected response structure
|
|
388
|
+
* @param userId - Optional user id
|
|
389
|
+
* @returns Map of candidate keys to filter decisions, or null if invalid
|
|
390
|
+
* @private
|
|
391
|
+
*/
|
|
392
|
+
async fetchFilterDecisions(messages, schema, userId) {
|
|
393
|
+
const config = {
|
|
394
|
+
model: this.resolveModel(),
|
|
395
|
+
reasoningEffort: "medium",
|
|
396
|
+
timeoutMs: this.cfg.timeoutMs,
|
|
397
|
+
};
|
|
398
|
+
const { data } = await this.llm.call(messages, schema, config, userId ?? this.ctx.userId);
|
|
399
|
+
if (data == null || typeof data !== "object")
|
|
400
|
+
return null;
|
|
401
|
+
return data;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Invoke the LLM and return a single selected key.
|
|
405
|
+
*
|
|
406
|
+
* @param messages - Chat messages to send
|
|
407
|
+
* @param schema - Strict JSON schema defining expected response structure
|
|
408
|
+
* @param userId - Optional user id
|
|
409
|
+
* @returns Choice result, or null if invalid
|
|
410
|
+
* @private
|
|
411
|
+
*/
|
|
412
|
+
async fetchChoice(messages, schema, userId) {
|
|
413
|
+
const config = {
|
|
414
|
+
model: this.resolveModel(),
|
|
415
|
+
reasoningEffort: "medium",
|
|
416
|
+
timeoutMs: this.cfg.timeoutMs,
|
|
417
|
+
};
|
|
418
|
+
const { data } = await this.llm.call(messages, schema, config, userId ?? this.ctx.userId);
|
|
419
|
+
if (data == null || typeof data !== "object")
|
|
420
|
+
return null;
|
|
421
|
+
const record = data;
|
|
422
|
+
const explanation = typeof record.explanation === "string" ? record.explanation : "";
|
|
423
|
+
const selectedKey = typeof record.selectedKey === "string" ? record.selectedKey : "";
|
|
424
|
+
if (selectedKey === "")
|
|
425
|
+
return null;
|
|
426
|
+
return { explanation, selectedKey };
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Apply boolean filter decisions while preserving input order.
|
|
430
|
+
*
|
|
431
|
+
* @param items - Candidates with unique keys
|
|
432
|
+
* @param decisions - Map of candidate keys to decisions
|
|
433
|
+
* @returns Filtered items with explanations
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
applyFilter(items, decisions) {
|
|
437
|
+
const kept = [];
|
|
438
|
+
for (const it of items) {
|
|
439
|
+
const decision = decisions[it.key];
|
|
440
|
+
if (decision?.isRelevant === true) {
|
|
441
|
+
kept.push({
|
|
442
|
+
item: it.item,
|
|
443
|
+
explanation: typeof decision.explanation === "string" ? decision.explanation : "",
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return kept;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Process a single batch of candidates through the LLM filter.
|
|
451
|
+
*
|
|
452
|
+
* @param query - Query to filter against
|
|
453
|
+
* @param batch - Prepared candidates
|
|
454
|
+
* @param options - Optional userId override
|
|
455
|
+
* @returns Filtered items (stable order), with explanations
|
|
456
|
+
* @private
|
|
457
|
+
*/
|
|
458
|
+
async processFilterBatch(query, batch, options) {
|
|
459
|
+
const keyed = this.ensureUniqueKeys(batch);
|
|
460
|
+
const { schema, messages } = this.buildFilterRequest(query, keyed);
|
|
461
|
+
const decisions = await this.fetchFilterDecisions(messages, schema, options?.userId);
|
|
462
|
+
if (decisions == null) {
|
|
463
|
+
return keyed.map(({ item }) => ({ item, explanation: "" }));
|
|
464
|
+
}
|
|
465
|
+
return this.applyFilter(keyed, decisions);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Process a batch of candidates and choose a single winner.
|
|
469
|
+
*
|
|
470
|
+
* @param query - Query to choose against
|
|
471
|
+
* @param batch - Candidates with unique keys
|
|
472
|
+
* @param userId - Optional userId override
|
|
473
|
+
* @returns Winner item with explanation
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
async processChoiceBatch(query, batch, userId) {
|
|
477
|
+
const { schema, messages } = this.buildChoiceRequest(query, batch);
|
|
478
|
+
const choice = await this.fetchChoice(messages, schema, userId);
|
|
479
|
+
if (choice == null) {
|
|
480
|
+
return { item: batch[0].item, explanation: "" };
|
|
481
|
+
}
|
|
482
|
+
const winner = batch.find((x) => x.key === choice.selectedKey);
|
|
483
|
+
if (!winner) {
|
|
484
|
+
return { item: batch[0].item, explanation: choice.explanation };
|
|
485
|
+
}
|
|
486
|
+
return { item: winner.item, explanation: choice.explanation };
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Apply relevancy threshold filtering and stable sorting.
|
|
490
|
+
*
|
|
491
|
+
* Scores are clamped to the configured score range, then filtered to keep only items with
|
|
492
|
+
* score > threshold. Results are sorted by score descending, with ties
|
|
493
|
+
* preserving original input order for deterministic results.
|
|
494
|
+
*
|
|
495
|
+
* @param items - Candidates with unique keys
|
|
496
|
+
* @param scores - Map of candidate keys to LLM-assigned scores
|
|
497
|
+
* @returns Filtered and sorted array of original items
|
|
498
|
+
* @private
|
|
499
|
+
*/
|
|
500
|
+
rankAndFilter(items, evaluations) {
|
|
501
|
+
const threshold = this.cfg.relevancyThreshold;
|
|
502
|
+
const scored = items.map(({ item, idx, key }) => ({
|
|
503
|
+
item,
|
|
504
|
+
idx,
|
|
505
|
+
explanation: typeof evaluations[key]?.explanation === "string" ? evaluations[key].explanation : "",
|
|
506
|
+
score: clamp(evaluations[key]?.score ?? this.cfg.minScore, this.cfg.minScore, this.cfg.maxScore),
|
|
507
|
+
}));
|
|
508
|
+
const filtered = scored.filter(({ score }) => score > threshold);
|
|
509
|
+
const sorted = filtered.sort((a, b) => {
|
|
510
|
+
if (b.score !== a.score)
|
|
511
|
+
return b.score - a.score;
|
|
512
|
+
return a.idx - b.idx;
|
|
513
|
+
});
|
|
514
|
+
return sorted;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Process a single batch of candidates through the LLM.
|
|
518
|
+
*
|
|
519
|
+
* Ensures unique keys, builds the request payload, fetches scores from the LLM,
|
|
520
|
+
* and returns filtered and sorted results. On any error or null response from
|
|
521
|
+
* the LLM, returns items in their original order as a fallback.
|
|
522
|
+
*
|
|
523
|
+
* @param query - The search query to evaluate candidates against
|
|
524
|
+
* @param batch - Batch of prepared candidates to process
|
|
525
|
+
* @param userId - Optional user identifier for provider abuse monitoring
|
|
526
|
+
* @returns Ranked and filtered items, or original order on error
|
|
527
|
+
* @private
|
|
528
|
+
*/
|
|
529
|
+
async processBatch(query, batch, options) {
|
|
530
|
+
const keyed = this.ensureUniqueKeys(batch);
|
|
531
|
+
const { schema, messages } = this.buildRequest(query, keyed);
|
|
532
|
+
const evaluations = await this.fetchEvaluations(messages, schema, options?.userId);
|
|
533
|
+
if (evaluations == null) {
|
|
534
|
+
return keyed.map(({ item }) => ({ item, explanation: "" }));
|
|
535
|
+
}
|
|
536
|
+
const ranked = this.rankAndFilter(keyed, evaluations);
|
|
537
|
+
return ranked.map(({ item, explanation }) => ({ item, explanation }));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
//# sourceMappingURL=intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight env var readers matching the API service pattern.
|
|
3
|
+
* - If the env var is set (non-empty), parse and return it.
|
|
4
|
+
* - Otherwise, if a default is provided, return the default.
|
|
5
|
+
* - Otherwise, throw a helpful error.
|
|
6
|
+
*
|
|
7
|
+
* This version exposes four self-contained readers with an options object
|
|
8
|
+
* supporting { default, min, max } where min/max apply to int/number only.
|
|
9
|
+
*/
|
|
10
|
+
export type BaseOptions<T> = {
|
|
11
|
+
default?: T;
|
|
12
|
+
};
|
|
13
|
+
export type RangeOptions = BaseOptions<number> & {
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
};
|
|
17
|
+
export type EnumOptions<T extends readonly string[]> = BaseOptions<T[number]> & {
|
|
18
|
+
values: T;
|
|
19
|
+
};
|
|
20
|
+
export declare function string(name: string, opts?: BaseOptions<string>): string;
|
|
21
|
+
/**
|
|
22
|
+
* Read an env var as a constrained enum string.
|
|
23
|
+
*
|
|
24
|
+
* When set, validates the value is included in opts.values.
|
|
25
|
+
* When unset, returns opts.default if provided, otherwise throws.
|
|
26
|
+
*/
|
|
27
|
+
export declare function enumeration<const T extends readonly string[]>(name: string, opts: EnumOptions<T>): T[number];
|
|
28
|
+
export declare function boolean(name: string, opts?: BaseOptions<boolean>): boolean;
|
|
29
|
+
export declare function int(name: string, opts?: RangeOptions): number;
|
|
30
|
+
export declare function number(name: string, opts?: RangeOptions): number;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight env var readers matching the API service pattern.
|
|
3
|
+
* - If the env var is set (non-empty), parse and return it.
|
|
4
|
+
* - Otherwise, if a default is provided, return the default.
|
|
5
|
+
* - Otherwise, throw a helpful error.
|
|
6
|
+
*
|
|
7
|
+
* This version exposes four self-contained readers with an options object
|
|
8
|
+
* supporting { default, min, max } where min/max apply to int/number only.
|
|
9
|
+
*/
|
|
10
|
+
function throwRequiredEnvVar(name) {
|
|
11
|
+
throw new Error(`${name} is required.`);
|
|
12
|
+
}
|
|
13
|
+
import { clamp } from "./number";
|
|
14
|
+
export function string(name, opts) {
|
|
15
|
+
const value = process.env[name];
|
|
16
|
+
const exists = value != null && value.length > 0;
|
|
17
|
+
if (exists) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (opts?.default !== undefined) {
|
|
21
|
+
return opts.default;
|
|
22
|
+
}
|
|
23
|
+
return throwRequiredEnvVar(name);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read an env var as a constrained enum string.
|
|
27
|
+
*
|
|
28
|
+
* When set, validates the value is included in opts.values.
|
|
29
|
+
* When unset, returns opts.default if provided, otherwise throws.
|
|
30
|
+
*/
|
|
31
|
+
export function enumeration(name, opts) {
|
|
32
|
+
const value = opts.default !== undefined ? string(name, { default: opts.default }) : string(name);
|
|
33
|
+
if (opts.values.includes(value)) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`${name} must be one of: ${opts.values.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
export function boolean(name, opts) {
|
|
39
|
+
const value = process.env[name];
|
|
40
|
+
const exists = value != null && value.length > 0;
|
|
41
|
+
if (exists) {
|
|
42
|
+
const v = value;
|
|
43
|
+
return ["0", "false"].includes(v) === false;
|
|
44
|
+
}
|
|
45
|
+
if (opts?.default !== undefined) {
|
|
46
|
+
return opts.default;
|
|
47
|
+
}
|
|
48
|
+
return throwRequiredEnvVar(name);
|
|
49
|
+
}
|
|
50
|
+
export function int(name, opts) {
|
|
51
|
+
const value = process.env[name];
|
|
52
|
+
const exists = value != null && value.length > 0;
|
|
53
|
+
if (exists) {
|
|
54
|
+
const parsed = Number.parseInt(value, 10);
|
|
55
|
+
if (Number.isNaN(parsed)) {
|
|
56
|
+
throw new Error(`${name} must be a valid integer`);
|
|
57
|
+
}
|
|
58
|
+
return clamp(parsed, opts?.min, opts?.max);
|
|
59
|
+
}
|
|
60
|
+
if (opts?.default !== undefined) {
|
|
61
|
+
const def = Math.trunc(opts.default);
|
|
62
|
+
return clamp(def, opts?.min, opts?.max);
|
|
63
|
+
}
|
|
64
|
+
return throwRequiredEnvVar(name);
|
|
65
|
+
}
|
|
66
|
+
export function number(name, opts) {
|
|
67
|
+
const value = process.env[name];
|
|
68
|
+
const exists = value != null && value.length > 0;
|
|
69
|
+
if (exists) {
|
|
70
|
+
const parsed = Number.parseFloat(value);
|
|
71
|
+
if (Number.isNaN(parsed)) {
|
|
72
|
+
throw new Error(`${name} must be a valid number`);
|
|
73
|
+
}
|
|
74
|
+
return clamp(parsed, opts?.min, opts?.max);
|
|
75
|
+
}
|
|
76
|
+
if (opts?.default !== undefined) {
|
|
77
|
+
return clamp(opts.default, opts?.min, opts?.max);
|
|
78
|
+
}
|
|
79
|
+
return throwRequiredEnvVar(name);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=config.js.map
|