@keo-ai/axiom 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -197,6 +197,362 @@ try {
197
197
 
198
198
  ---
199
199
 
200
+ ## Function Call Loop 模块
201
+
202
+ Function Call Loop 是 Axiom 的底层 Function Call 引擎。它负责把 **LLM → Tool → Result → LLM** 的循环跑稳,同时向上层暴露完整的生命周期事件和执行历史(Harness)。
203
+
204
+ ### 核心设计
205
+
206
+ | 设计点 | 说明 |
207
+ |---|---|
208
+ | **Harness 与 Messages 分离** | Harness 是只增不减的完整执行档案;Messages 是给 LLM 看的对话历史,可被压缩 |
209
+ | **Prompt 完全外部化** | 引擎不内置任何系统提示词、告警文案或终止文案 |
210
+ | **默认安全** | 默认不压缩、不隐藏 tool、不拦截执行 |
211
+ | **Plan and Execute** | 单轮 LLM 可返回多个 tool call,这些 tool 在本轮内并行执行 |
212
+ | **硬兜底与软策略并存** | Turn Policy 是每轮必走的软策略;`maxTurns` 是引擎底层的硬兜底 |
213
+
214
+ ### 快速开始
215
+
216
+ #### 方式一:直接传模型参数(推荐)
217
+
218
+ 像 predict 模块一样,设置环境变量后一行调用:
219
+
220
+ ```bash
221
+ export BAILIAN_API_KEY="your-api-key"
222
+ ```
223
+
224
+ ```ts
225
+ import { FunctionCallLoop } from '@keo-ai/axiom';
226
+
227
+ const result = await FunctionCallLoop.runLoopWithModel({
228
+ messages: [
229
+ { role: 'system', content: 'You are a helpful assistant.' },
230
+ { role: 'user', content: 'What is the weather in Beijing?' },
231
+ ],
232
+ model: 'qwen-max',
233
+ temperature: 0.7,
234
+ maxTokens: 2048,
235
+ tools: [
236
+ {
237
+ name: 'get_weather',
238
+ description: 'Get weather for a city',
239
+ parameters: {
240
+ type: 'object',
241
+ properties: {
242
+ city: { type: 'string', description: 'City name' },
243
+ },
244
+ required: ['city'],
245
+ },
246
+ execute: async (args) => {
247
+ return { temperature: 25, condition: 'Sunny' };
248
+ },
249
+ },
250
+ ],
251
+ maxTurns: 5,
252
+ onEvent: (event) => {
253
+ if (event.type === 'execution:end') {
254
+ console.log(`${event.toolName}: ${event.status} (${event.durationMs}ms)`);
255
+ }
256
+ },
257
+ });
258
+
259
+ console.log(result.finalContent);
260
+ console.log('Turns:', result.turns);
261
+ console.log('Harness:', result.harness);
262
+ ```
263
+
264
+ ### 生命周期事件
265
+
266
+ Loop 每处理一个 tool call,按顺序抛出两个事件:
267
+
268
+ ```ts
269
+ const result = await FunctionCallLoop.runLoopWithModel({
270
+ // ...
271
+ onEvent: (event) => {
272
+ if (event.type === 'execution:start') {
273
+ console.log(`[${event.turn}] ${event.toolName} started`);
274
+ }
275
+ if (event.type === 'execution:end') {
276
+ console.log(`[${event.turn}] ${event.toolName} done in ${event.durationMs}ms`);
277
+ console.log(`[${event.turn}] ${event.toolName} result:`, event.result ?? event.error);
278
+ }
279
+ },
280
+ });
281
+ ```
282
+
283
+ | 事件 | 触发时机 | 包含字段 |
284
+ |---|---|---|
285
+ | `execution:start` | execute 函数被调用之前 | `callId`, `toolName`, `args`, `turn` |
286
+ | `execution:end` | execute 函数返回之后 | `callId`, `toolName`, `status`, `durationMs`, `result` / `error`, `turn` |
287
+
288
+ `status` 类型为 `HarnessRecordStatus`,取值如下:
289
+
290
+ | 值 | 含义 |
291
+ |---|---|
292
+ | `'success'` | execute 函数正常返回 |
293
+ | `'error'` | execute 函数抛异常 |
294
+ | `'timeout'` | 执行超时 |
295
+ | `'cancelled'` | 被 AbortSignal 取消 |
296
+
297
+ ### Harness(执行历史)
298
+
299
+ Harness 是跨轮次累积的只读执行记录,tool 之间可以互相看见:
300
+
301
+ ```ts
302
+ console.log(result.harness);
303
+ // [
304
+ // { id: 'call-1', turn: 1, toolName: 'get_weather', status: 'success', ... },
305
+ // { id: 'call-2', turn: 2, toolName: 'get_time', status: 'success', ... },
306
+ // ]
307
+ ```
308
+
309
+ 每条记录包含:唯一 ID、所属轮次、全局序号、tool 名称、参数、执行状态、返回值/错误、时间戳和耗时。
310
+
311
+ **并行隔离原则**:本轮并行执行的多个 tool,各自收到的 Harness 是"本轮并行开始前"的快照,互相看不到同轮其他正在执行的 tool。
312
+
313
+ ### Tool 审批拦截
314
+
315
+ 对于敏感操作(转账、删除数据等),可以给 tool 配置 `approval`,Loop 会在执行前暂停并返回 `pendingApproval`:
316
+
317
+ ```ts
318
+ const result = await FunctionCallLoop.runLoopWithModel({
319
+ messages: [
320
+ { role: 'system', content: 'You are a helpful assistant.' },
321
+ { role: 'user', content: 'Transfer 1000 to Alice' },
322
+ ],
323
+ metadata: { userId: 'u-123', orgId: 'o-456' },
324
+ tools: [
325
+ {
326
+ name: 'transfer_money',
327
+ description: 'Transfer money',
328
+ parameters: { /* ... */ },
329
+ approval: {
330
+ createTicket: async (args, context) => {
331
+ const { userId } = context.metadata as { userId: string };
332
+ const ticket = await createApprovalTicket({ ...args, applicant: userId });
333
+ return { ticketId: ticket.id };
334
+ },
335
+ },
336
+ execute: async (args) => {
337
+ // 审批通过后才会执行到这里
338
+ return await doTransfer(args);
339
+ },
340
+ },
341
+ ],
342
+ });
343
+
344
+ if (result.pendingApproval) {
345
+ // 保存 checkpoint,等待 webhook 回调或人工审批
346
+ await db.saveCheckpoint({
347
+ messages: result.messages,
348
+ ticketId: result.pendingApproval.ticketId,
349
+ toolName: result.pendingApproval.toolName,
350
+ });
351
+ return { status: 'waiting_approval' };
352
+ }
353
+ ```
354
+
355
+ 审批通过后继续执行(tool 配置保持不变):
356
+
357
+ ```ts
358
+ const record = await db.findByTicketId(ticketId);
359
+
360
+ const result = await FunctionCallLoop.runLoopWithModel({
361
+ messages: record.messages,
362
+ metadata: { userId: 'u-123', orgId: 'o-456' },
363
+ tools: [
364
+ {
365
+ name: 'transfer_money',
366
+ description: 'Transfer money',
367
+ parameters: { /* ... */ },
368
+ approval: {
369
+ createTicket: async (args, context) => {
370
+ // 查询该 ticket 是否已审批
371
+ const ticket = await db.findTicket(record.ticketId);
372
+ if (ticket?.status === 'approved') {
373
+ return { ticketId: ticket.id, approved: true };
374
+ }
375
+ return { ticketId: ticket.id };
376
+ },
377
+ },
378
+ execute: async (args) => await doTransfer(args),
379
+ },
380
+ ],
381
+ });
382
+ ```
383
+
384
+ **关键点**:
385
+ - `approval.createTicket` 被调用时,`execute` **不会**执行;返回 `{ approved: true }` 时直接执行
386
+ - `createTicket` 可通过 `context.metadata` 访问 `LoopConfig.metadata`,用于携带业务上下文
387
+ - `result.messages` 中已包含完整的对话历史(assistant 的 tool_calls + 占位 tool result),可直接用于续跑
388
+ - 恢复执行时无需去掉 `approval` 配置,通过 `createTicket` 内部判断审批状态即可
389
+
390
+ ### Tool 发现(动态可见性)
391
+
392
+ 每次调用 LLM 之前,Loop 会对所有已注册 tool 执行 `discover` 函数,动态决定本轮暴露哪些 tool:
393
+
394
+ ```ts
395
+ const tool = {
396
+ name: 'admin_only',
397
+ description: 'Admin operation',
398
+ parameters: {},
399
+ discover: async (harness, metadata) => {
400
+ // 根据 Harness 或元数据决定是否暴露
401
+ const isAdmin = metadata?.role === 'admin';
402
+ return {
403
+ name: 'admin_only',
404
+ description: 'Admin operation',
405
+ parameters: {},
406
+ visible: isAdmin,
407
+ };
408
+ },
409
+ execute: async (args) => { /* ... */ },
410
+ };
411
+ ```
412
+
413
+ | 边界情况 | 行为 |
414
+ |---|---|
415
+ | `discover` 抛异常 | 视为 `visible: false`,该 tool 本轮隐藏 |
416
+ | 所有 tool 都不可见 | LLM 接收空 tool 列表 |
417
+ | LLM 调用了不可见的 tool | 按错误处理,记录 `error` 状态 |
418
+ | 未配置 `discover` | 使用静态注册配置,`visible: true` |
419
+
420
+ ### Turn Policy(全局轮次策略)
421
+
422
+ 每轮开始时,Loop 先执行 Turn Policy 决定是继续还是终止:
423
+
424
+ ```ts
425
+ const result = await FunctionCallLoop.runLoopWithModel({
426
+ // ...
427
+ turnPolicy: async (harness, turn, metadata) => {
428
+ // 连续失败降级
429
+ const failCount = harness.filter((r) => r.status === 'error').length;
430
+ if (failCount >= 3) {
431
+ return {
432
+ message: 'Too many failures, please try again later.',
433
+ };
434
+ }
435
+ return {};
436
+ },
437
+ });
438
+ ```
439
+
440
+ **默认策略**(未自定义时自动生效):
441
+
442
+ | 条件 | 行为 |
443
+ |---|---|
444
+ | `turn < maxTurns - 1` | 继续执行 |
445
+ | `turn === maxTurns - 1` | 注入 `warningMessage`,继续执行 |
446
+ | `turn >= maxTurns` | 注入 `terminateMessage`,终止 Loop |
447
+
448
+ | 边界情况 | 行为 |
449
+ |---|---|
450
+ | `turnPolicy` 抛异常 | 视为 `continue`,记录异常 |
451
+ | 返回 `terminate` | 向 Messages 塞入 `fallbackMessage`,结束 Loop,不再调 LLM |
452
+
453
+ ### 上下文压缩
454
+
455
+ 控制给 LLM 的历史消息长度,只动 Messages,不动 Harness:
456
+
457
+ ```ts
458
+ const result = await FunctionCallLoop.runLoopWithModel({
459
+ // ...
460
+ compression: {
461
+ keepRounds: 3, // 最近 3 轮保持完整
462
+ compress: async (oldRounds) => {
463
+ // oldRounds: 需要压缩的轮次数组,每轮是一个 Message 数组
464
+ // 返回压缩后的 Message 数组
465
+ return [
466
+ {
467
+ role: 'system',
468
+ content: `Previous ${oldRounds.length} rounds summarized...`,
469
+ },
470
+ ];
471
+ },
472
+ },
473
+ });
474
+ ```
475
+
476
+ | 规则 | 说明 |
477
+ |---|---|
478
+ | 压缩只影响 Messages | Harness 始终完整保留 |
479
+ | 系统消息和初始输入不参与压缩 | 始终保留 |
480
+ | `compress` 抛异常 | 视为不压缩,使用原始 Messages 继续执行 |
481
+
482
+ ### 取消信号
483
+
484
+ 支持通过 `AbortSignal` 终止 Loop:
485
+
486
+ ```ts
487
+ const controller = new AbortController();
488
+
489
+ const promise = FunctionCallLoop.runLoopWithModel({
490
+ // ...
491
+ signal: controller.signal,
492
+ });
493
+
494
+ // 5 秒后取消
495
+ setTimeout(() => controller.abort(), 5000);
496
+
497
+ const result = await promise;
498
+ ```
499
+
500
+ ### 配置项
501
+
502
+ #### `runLoopWithModel` 配置
503
+
504
+ **对话入口**
505
+
506
+ | 配置项 | 类型 | 必填 | 说明 |
507
+ |---|---|---|---|
508
+ | `messages` | `Message[]` | ✓ | 初始消息列表,直接作为对话起点 |
509
+
510
+ **Tool 与执行控制**
511
+
512
+ | 配置项 | 类型 | 必填 | 说明 |
513
+ |---|---|---|---|
514
+ | `tools` | `Tool[]` | ✅ | 注册的 tool 列表 |
515
+ | `maxTurns` | `number` | — | 最大轮次,默认无限制 |
516
+ | `turnPolicy` | `TurnPolicy` | — | 自定义轮次策略 |
517
+ | `compression` | `CompressionConfig` | — | 上下文压缩配置 |
518
+ | `metadata` | `unknown` | — | 传递给 Tool 和 Turn Policy 的元数据 |
519
+ | `warningMessage` | `string` | — | 默认策略在 `maxTurns - 1` 时注入的告警文本 |
520
+ | `terminateMessage` | `string` | — | 默认策略在 `maxTurns` 时注入的终止文本 |
521
+ | `onEvent` | `(event) => void` | — | 生命周期事件监听器 |
522
+ | `signal` | `AbortSignal` | — | 取消信号 |
523
+
524
+ **LLM 调用参数**
525
+
526
+ | 配置项 | 类型 | 必填 | 说明 |
527
+ |---|---|---|---|
528
+ | `model` | `string` | — | 模型名称。默认从 `BAILIAN_DEFAULT_MODEL` 环境变量读取,否则 `qwen-max` |
529
+ | `maxTokens` | `number` | — | 单次 LLM 调用的最大输出 token 数 |
530
+ | `temperature` | `number` | — | 采样温度,范围 0~2 |
531
+ | `topP` | `number` | — | 核采样概率阈值,范围 0~1 |
532
+ | `reasoningEffort` | `'low' \| 'medium' \| 'high'` | — | 推理深度。仅部分模型支持(如 o1、o3) |
533
+
534
+ ### 返回结果
535
+
536
+ ```ts
537
+ interface LoopResult {
538
+ messages: Message[]; // 完整的对话历史
539
+ harness: HarnessRecord[]; // 执行历史
540
+ finalContent: string | null; // 最终回复内容
541
+ turns: number; // 实际执行轮数
542
+ }
543
+ ```
544
+
545
+ ### 错误处理
546
+
547
+ 同其他模块:**直接抛异常**。常见场景:
548
+
549
+ - `execute` 抛异常 → 记录 `error` 状态,发出事件,不影响同轮其他并行 tool
550
+ - `discover` 抛异常 → 该 tool 本轮隐藏
551
+ - `compress` 抛异常 → 视为不压缩
552
+ - `turnPolicy` 抛异常 → 视为 `continue`
553
+
554
+ ---
555
+
200
556
  ## EmbeddingSearch 模块(向量检索)
201
557
 
202
558
  EmbeddingSearch 是 Axiom 的 RAG 底座。它封装了 `query → embedding → pgvector 检索 → [可选 rerank]` 的完整链路,只需一行代码即可实现语义检索。
@@ -235,6 +591,14 @@ CREATE TABLE documents (
235
591
  CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
236
592
  ```
237
593
 
594
+ ### 列设计建议
595
+
596
+ EmbeddingSearch 不限制你的 schema,但建议遵循这个分层原则:
597
+
598
+ - **频繁过滤或需要索引的字段** → 放独立列(如 `category`、`price`),配合 `filter` 做数据库层过滤
599
+ - **不参与索引的业务杂项** → 可以放入 JSONB 列(如 `metadata`),通过 `select` 整列取出后在应用层消费。JSONB 不适合 `filter`,普通等值比较走不了索引,容易触发全表扫描
600
+ - **rerank 的文本内容** → 必须是独立的文本列(`TEXT`/`VARCHAR`),`rerankKey` 不支持指向 JSONB 列
601
+
238
602
  ### 快速开始
239
603
 
240
604
  不传 `select` 时默认返回所有列(`SELECT *`):
@@ -301,10 +665,11 @@ interface SearchResult {
301
665
  | `embeddingThreshold` | `number` | `0.8` | embedding 相似度阈值过滤(cosine similarity) |
302
666
  | `rerankThreshold` | `number` | `0.1` | rerank 分数阈值过滤 |
303
667
  | `embeddingColumn` | `string` | `'embedding'` | 向量列名 |
304
- | `rerankKey` | `string` | `'query'` | 启用 rerank 时,用于重排序的文本列名 |
668
+ | `rerankKey` | `string` | `'query'` | 启用 rerank 时,用于重排序的文本列名(必须是文本列,不支持 JSONB) |
305
669
  | `dimensions` | `number` | `1024` | embedding 输出维度(1 ~ 1024) |
306
670
  | `select` | `string[]` | `undefined` | 指定返回哪些列,不传则 `SELECT *` |
307
- | `filter` | `Record<string, unknown>` | — | 对表独立列做等值/范围过滤 |
671
+ | `filter` | `Record<string, unknown>` | — | 对表独立列做等值/范围过滤(不支持 JSONB 内部字段) |
672
+ | `parseEnum` | `boolean` | `true` | 是否将枚举字段的数字值自动解析为可读文本 |
308
673
 
309
674
  ### 过滤
310
675
 
@@ -333,6 +698,43 @@ const multi = await EmbeddingSearch.query('query', {
333
698
  }, pool);
334
699
  ```
335
700
 
701
+ > `filter` 只支持对独立列做过滤,不支持 JSONB 内部字段(如 `metadata->>'field'`)。
702
+
703
+ ### 枚举值解析
704
+
705
+ 默认开启 `parseEnum`,返回结果中的枚举字段会自动从数字值解析为可读文本。当前支持以下字段:
706
+
707
+ | 字段 | 说明 | 示例(原值 → 解析后) |
708
+ |---|---|---|
709
+ | `type` | 期刊类型 | `11` → `"SCI/SSCI/AHCI"` |
710
+ | `subject1` | 学科大类 | `17` → `"计算机"` |
711
+ | `db` | 数据库 | `2` → `"SCI(SCIE)"` |
712
+ | `attribute` | 刊物属性 | `3` → `"快审刊"` |
713
+ | `oa` | 发表模式 | `1` → `"开源模式(OA)"` |
714
+
715
+ ```ts
716
+ const results = await EmbeddingSearch.query('query', {
717
+ tableName: 'journals',
718
+ }, pool);
719
+
720
+ // 默认 parseEnum: true,枚举字段自动解析为文本
721
+ console.log(results[0].type); // "SCI/SSCI/AHCI"
722
+ console.log(results[0].subject1); // "计算机"
723
+ ```
724
+
725
+ 如需关闭解析(保留原始数字值):
726
+
727
+ ```ts
728
+ const results = await EmbeddingSearch.query('query', {
729
+ tableName: 'journals',
730
+ parseEnum: false,
731
+ }, pool);
732
+
733
+ console.log(results[0].type); // 11
734
+ ```
735
+
736
+ 未知枚举值、非数字类型、非枚举字段均保持原样不变。
737
+
336
738
  ### 生成 Embedding
337
739
 
338
740
  如果你只需要把文本转成向量,直接用 `embed`:
@@ -0,0 +1,6 @@
1
+ export declare const JournalTypeMap: Record<number, string>;
2
+ export declare const SubjectMap: Record<number, string>;
3
+ export declare const DatabaseMap: Record<number, string>;
4
+ export declare const AttributeMap: Record<number, string>;
5
+ export declare const OAMap: Record<number, string>;
6
+ export declare function parseEnumValues(results: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAMap = exports.AttributeMap = exports.DatabaseMap = exports.SubjectMap = exports.JournalTypeMap = void 0;
4
+ exports.parseEnumValues = parseEnumValues;
5
+ exports.JournalTypeMap = {
6
+ 1: '正刊',
7
+ 2: '专刊',
8
+ 3: '专刊(SI)',
9
+ 4: '增刊',
10
+ 5: '摘要集',
11
+ 6: 'EI',
12
+ 7: 'ESCI/Scopus',
13
+ 8: '中英文普刊',
14
+ 9: 'Scopus',
15
+ 10: '中文核心',
16
+ 11: 'SCI/SSCI/AHCI',
17
+ 12: 'SCI&SSCI',
18
+ 13: 'SCI',
19
+ 14: '科技核心',
20
+ 15: '英文普刊',
21
+ 16: '中文普刊',
22
+ };
23
+ exports.SubjectMap = {
24
+ 11: '医学',
25
+ 12: '生物',
26
+ 13: '农林科学',
27
+ 14: '环境科学与生态学',
28
+ 15: '化学',
29
+ 16: '工程技术',
30
+ 17: '计算机',
31
+ 18: '数学',
32
+ 19: '物理与天体物理',
33
+ 20: '地球科学',
34
+ 21: '人文社科',
35
+ 22: '经济学与管理学',
36
+ 23: '多学科',
37
+ 24: '材料科学',
38
+ 25: '心理学',
39
+ };
40
+ exports.DatabaseMap = {
41
+ 1: 'SSCI',
42
+ 2: 'SCI(SCIE)',
43
+ 3: 'EI',
44
+ 5: '知网(CNKI)',
45
+ 6: 'A&HCI',
46
+ 7: 'Scopus',
47
+ 9: 'IEEE Xplore',
48
+ 12: 'Google Scholar',
49
+ 13: 'ESCI',
50
+ 14: 'CPCI-S',
51
+ 15: 'CPCI-SSH',
52
+ 16: '维普(VIP)',
53
+ 17: '万方(Wanfang)',
54
+ 18: 'CNKI Scholar(知网外文库)',
55
+ 19: 'Medline',
56
+ 20: 'PubMed',
57
+ 21: '万方应用',
58
+ 22: '超星(Superstar)',
59
+ 23: '龙源',
60
+ 24: '中文社会科学引文索引(CSSCI/南大核心)',
61
+ 25: '中文核心期刊要目(北大核心)',
62
+ 26: '中国科技论文与引文数据库(CSTPCD/科技核心)',
63
+ 27: 'CSSCI',
64
+ 28: '中国科学引文数据库(CSCD)',
65
+ 29: '中国科学引文数据库的扩展版本(CSCD扩展版)',
66
+ 30: 'AMI核心',
67
+ 31: 'AMI扩展',
68
+ 32: '武大核心(RCCSE)',
69
+ 33: 'Mycite',
70
+ };
71
+ exports.AttributeMap = {
72
+ 1: '调研刊',
73
+ 2: '合作刊',
74
+ 3: '快审刊',
75
+ 4: '速发刊',
76
+ };
77
+ exports.OAMap = {
78
+ 1: '开源模式(OA)',
79
+ 2: '订阅模式(非OA)',
80
+ 3: '混合模式(OA/非OA)',
81
+ };
82
+ const EnumFieldMaps = {
83
+ type: exports.JournalTypeMap,
84
+ subject1: exports.SubjectMap,
85
+ db: exports.DatabaseMap,
86
+ attribute: exports.AttributeMap,
87
+ oa: exports.OAMap,
88
+ };
89
+ function parseEnumValues(results) {
90
+ return results.map((row) => {
91
+ const parsed = Object.assign({}, row);
92
+ for (const [field, map] of Object.entries(EnumFieldMaps)) {
93
+ if (field in parsed) {
94
+ const val = parsed[field];
95
+ if (typeof val === 'number' || typeof val === 'string') {
96
+ const numVal = typeof val === 'string' ? Number(val) : val;
97
+ if (!Number.isNaN(numVal) && numVal in map) {
98
+ parsed[field] = map[numVal];
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return parsed;
104
+ });
105
+ }
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.embed = exports.EmbeddingSearch = void 0;
24
24
  const pgvector_1 = require("./pgvector");
25
25
  const embed_1 = require("./embed");
26
+ const enums_1 = require("./enums");
26
27
  const RERANK_URL = 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank';
27
28
  const RERANK_MODEL = 'qwen3-rerank';
28
29
  function rerank(query, documents, topN, apiKey) {
@@ -89,7 +90,7 @@ function rerank(query, documents, topN, apiKey) {
89
90
  class EmbeddingSearch {
90
91
  static query(query, config, pool) {
91
92
  return __awaiter(this, void 0, void 0, function* () {
92
- const { enableRerank = true, embeddingTopK = 10, finalTopN = 5, embeddingThreshold = 0.8, rerankThreshold = 0.1, tableName, embeddingColumn, rerankKey = 'query', dimensions, filter, select, } = config;
93
+ const { enableRerank = true, embeddingTopK = 10, finalTopN = 5, embeddingThreshold = 0.8, rerankThreshold = 0.1, tableName, embeddingColumn, rerankKey = 'query', dimensions, filter, select, parseEnum = true, } = config;
93
94
  const idColumn = 'id';
94
95
  const actualTopK = enableRerank
95
96
  ? Math.max(embeddingTopK, finalTopN)
@@ -123,9 +124,15 @@ class EmbeddingSearch {
123
124
  throw new Error(`Rerank requires column "${rerankKey}" but it was not found. ` +
124
125
  `Either create this column in your table, or set rerankKey to the correct column name.`);
125
126
  }
127
+ const nonStringRerank = searchResults.find((r) => typeof r[rerankKey] !== 'string');
128
+ if (nonStringRerank) {
129
+ throw new Error(`Rerank requires column "${rerankKey}" to be a text column, but got ${typeof nonStringRerank[rerankKey]}. ` +
130
+ `JSONB columns are not supported for rerank.`);
131
+ }
126
132
  }
127
133
  if (!enableRerank) {
128
- return searchResults.slice(0, finalTopN);
134
+ const results = searchResults.slice(0, finalTopN);
135
+ return parseEnum ? (0, enums_1.parseEnumValues)(results) : results;
129
136
  }
130
137
  const rerankResults = yield rerank(query, searchResults.map((r) => ({
131
138
  id: r[idColumn],
@@ -148,7 +155,7 @@ class EmbeddingSearch {
148
155
  return rest;
149
156
  });
150
157
  }
151
- return reranked;
158
+ return parseEnum ? (0, enums_1.parseEnumValues)(reranked) : reranked;
152
159
  });
153
160
  }
154
161
  static vectorSearch(options) {
@@ -18,6 +18,7 @@ export interface EmbeddingSearchConfig {
18
18
  readonly dimensions?: number;
19
19
  readonly filter?: Record<string, FilterValue>;
20
20
  readonly select?: string[];
21
+ readonly parseEnum?: boolean;
21
22
  }
22
23
  export interface SearchResult {
23
24
  readonly [key: string]: unknown;
@@ -0,0 +1,34 @@
1
+ import type { HarnessRecord, HarnessRecordStatus } from './types';
2
+ /**
3
+ * 执行历史(Harness)。
4
+ * 按时间顺序累积执行记录,只追加不修改,跨轮次持续累积。
5
+ */
6
+ export declare class Harness {
7
+ private records;
8
+ /** 当前 Harness 中的记录数量 */
9
+ get length(): number;
10
+ /** 获取完整记录数组的只读视图 */
11
+ getAll(): ReadonlyArray<HarnessRecord>;
12
+ /** 获取当前记录的副本(快照) */
13
+ snapshot(): ReadonlyArray<HarnessRecord>;
14
+ /**
15
+ * 追加一条记录。
16
+ * @param record - 要追加的记录
17
+ */
18
+ append(record: HarnessRecord): void;
19
+ }
20
+ /**
21
+ * 创建一条 pending 状态的 HarnessRecord。
22
+ */
23
+ export declare function createPendingRecord(params: {
24
+ id: string;
25
+ turn: number;
26
+ globalIndex: number;
27
+ toolName: string;
28
+ args: unknown;
29
+ rawArgs: string;
30
+ }): HarnessRecord;
31
+ /**
32
+ * 将 pending 记录完善为最终状态。
33
+ */
34
+ export declare function finalizeRecord(record: HarnessRecord, status: Exclude<HarnessRecordStatus, 'pending'>, result?: unknown, error?: string): HarnessRecord;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Harness = void 0;
4
+ exports.createPendingRecord = createPendingRecord;
5
+ exports.finalizeRecord = finalizeRecord;
6
+ /**
7
+ * 执行历史(Harness)。
8
+ * 按时间顺序累积执行记录,只追加不修改,跨轮次持续累积。
9
+ */
10
+ class Harness {
11
+ constructor() {
12
+ this.records = [];
13
+ }
14
+ /** 当前 Harness 中的记录数量 */
15
+ get length() {
16
+ return this.records.length;
17
+ }
18
+ /** 获取完整记录数组的只读视图 */
19
+ getAll() {
20
+ return this.records;
21
+ }
22
+ /** 获取当前记录的副本(快照) */
23
+ snapshot() {
24
+ return [...this.records];
25
+ }
26
+ /**
27
+ * 追加一条记录。
28
+ * @param record - 要追加的记录
29
+ */
30
+ append(record) {
31
+ this.records.push(record);
32
+ }
33
+ }
34
+ exports.Harness = Harness;
35
+ /**
36
+ * 创建一条 pending 状态的 HarnessRecord。
37
+ */
38
+ function createPendingRecord(params) {
39
+ return Object.assign(Object.assign({}, params), { status: 'pending', startedAt: Date.now(), durationMs: 0 });
40
+ }
41
+ /**
42
+ * 将 pending 记录完善为最终状态。
43
+ */
44
+ function finalizeRecord(record, status, result, error) {
45
+ return Object.assign(Object.assign({}, record), { status,
46
+ result,
47
+ error, durationMs: Date.now() - record.startedAt });
48
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Function Call Loop — Function Call 引擎。
3
+ *
4
+ * 核心能力:
5
+ * - 每次 tool 执行向上层回传执行前、执行后两个事件
6
+ * - 维护跨轮次的执行历史(Harness),tool 之间能互相看见
7
+ * - 每次调 LLM 之前动态决定暴露哪些 tool
8
+ * - 支持自定义上下文压缩
9
+ * - 每轮全局策略判断,决定继续执行还是终止降级
10
+ * - 单轮支持多个 tool 并行执行(Plan and Execute)
11
+ * - 系统 Prompt 完全由外部注入
12
+ */
13
+ export { runLoopWithModel } from './loop';
14
+ export type { Message, ToolCall, ToolDefinition, HarnessRecord, HarnessRecordStatus, ToolExecutionStartEvent, ToolExecutionEndEvent, LoopEvent, ToolDiscoverResult, ToolExecuteContext, Tool, ApprovalConfig, TurnPolicyResult, TurnPolicy, CompressionConfig, LLMCaller, LLMCallOptions, LoopConfig, LoopResult, PendingApprovalInfo, } from './types';