@keo-ai/axiom 0.2.2 → 0.2.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
@@ -473,6 +473,37 @@ for await (const chunk of stream) {
473
473
 
474
474
  **向后兼容**:返回普通值(非 `ToolResult`)时行为完全不变,仍按原有逻辑 `JSON.stringify` 后透给 LLM。
475
475
 
476
+ #### 用 ToolResult 终止 Loop(end tool)
477
+
478
+ 当某个 tool 的执行结果就是最终答案、不需要再调 LLM 时,可以设置 `endLoop: true`:
479
+
480
+ ```ts
481
+ const finalAnswerTool = {
482
+ name: 'get_answer',
483
+ description: 'Get final answer',
484
+ parameters: { /* ... */ },
485
+ execute: async (args) => {
486
+ const answer = await computeAnswer(args);
487
+ return {
488
+ forLLM: '已找到最终答案',
489
+ forFrontend: answer,
490
+ endLoop: true,
491
+ finalContent: answer.summary,
492
+ };
493
+ },
494
+ };
495
+ ```
496
+
497
+ | 字段 | 说明 |
498
+ |---|---|
499
+ | `endLoop` | 为 `true` 时,该 tool 执行完成后直接结束 Loop,不再进入下一轮 LLM 调用 |
500
+ | `finalContent` | 可选。设置后作为 `LoopResult.finalContent` 返回;不设置则使用 `forLLM` |
501
+
502
+ **行为规则**:
503
+ - `endLoop` 仅在 execute 成功返回 `ToolResult` 时生效
504
+ - 若一轮并行调用中有多个 tool 都设置 `endLoop`,取第一个完成的结果作为最终输出
505
+ - `pendingApproval` 的优先级高于 `endLoop`:未审批时先返回 `pendingApproval`
506
+
476
507
  ### Tool 审批拦截
477
508
 
478
509
  对于敏感操作(转账、删除数据等),可以给 tool 配置 `approval`,Loop 会在执行前暂停并返回 `pendingApproval`:
@@ -29,6 +29,8 @@ export type ExecutionResult = {
29
29
  toolName: string;
30
30
  args: unknown;
31
31
  };
32
+ endLoop?: boolean;
33
+ finalContent?: string;
32
34
  };
33
35
  /**
34
36
  * 并行执行 tool。
@@ -32,7 +32,7 @@ const DEFAULT_BAILIAN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode
32
32
  const DEFAULT_BAILIAN_MODEL = 'qwen-max';
33
33
  function runLoop(config) {
34
34
  return __awaiter(this, void 0, void 0, function* () {
35
- var _a, _b, _c, _d, _e, _f, _g;
35
+ var _a, _b, _c, _d, _e, _f, _g, _h;
36
36
  const llmCaller = (_a = config.llmCaller) !== null && _a !== void 0 ? _a : (0, provider_1.createLLMCaller)({
37
37
  apiKey: (_b = process.env.BAILIAN_API_KEY) !== null && _b !== void 0 ? _b : '',
38
38
  baseUrl: (_c = process.env.BAILIAN_BASE_URL) !== null && _c !== void 0 ? _c : DEFAULT_BAILIAN_BASE_URL,
@@ -157,6 +157,18 @@ function runLoop(config) {
157
157
  totalUsage: computeTotalUsage(),
158
158
  };
159
159
  }
160
+ // 检查是否有 tool 要求结束 Loop
161
+ const endLoopResult = parallelResults.find((r) => r.endLoop);
162
+ if (endLoopResult === null || endLoopResult === void 0 ? void 0 : endLoopResult.endLoop) {
163
+ return {
164
+ messages,
165
+ harness: harness.getAll(),
166
+ finalContent: (_h = endLoopResult.finalContent) !== null && _h !== void 0 ? _h : endLoopResult.content,
167
+ turns: turn,
168
+ usageHistory,
169
+ totalUsage: computeTotalUsage(),
170
+ };
171
+ }
160
172
  // 第八步:回到第一步继续下一轮
161
173
  }
162
174
  // Loop 被终止(Turn Policy 或硬限制)
@@ -399,10 +411,14 @@ function executeToolsInParallel(config, harness, toolCalls, visibleToolNames, ha
399
411
  };
400
412
  let content;
401
413
  let frontendData;
414
+ let endLoop;
415
+ let finalContent;
402
416
  if (status === 'success') {
403
417
  if (isToolResult(result)) {
404
418
  content = result.forLLM;
405
419
  frontendData = result.forFrontend;
420
+ endLoop = result.endLoop;
421
+ finalContent = result.finalContent;
406
422
  }
407
423
  else {
408
424
  content =
@@ -418,6 +434,8 @@ function executeToolsInParallel(config, harness, toolCalls, visibleToolNames, ha
418
434
  toolCallId: callId,
419
435
  content,
420
436
  frontendData,
437
+ endLoop,
438
+ finalContent,
421
439
  record: finalRecord,
422
440
  events: [endEvent],
423
441
  pendingApproval: typeof result === 'object' &&
@@ -54,7 +54,7 @@ function buildInitialMessages(config) {
54
54
  function runLoopStream(config) {
55
55
  return __asyncGenerator(this, arguments, function* runLoopStream_1() {
56
56
  var _a, e_1, _b, _c;
57
- var _d, _e, _f, _g, _h, _j, _k;
57
+ var _d, _e, _f, _g, _h, _j, _k, _l;
58
58
  const llmCaller = (_d = config.llmCaller) !== null && _d !== void 0 ? _d : (0, provider_1.createLLMCaller)({
59
59
  apiKey: (_e = process.env.BAILIAN_API_KEY) !== null && _e !== void 0 ? _e : '',
60
60
  baseUrl: (_f = process.env.BAILIAN_BASE_URL) !== null && _f !== void 0 ? _f : DEFAULT_BAILIAN_BASE_URL,
@@ -135,9 +135,9 @@ function runLoopStream(config) {
135
135
  reasoningEffort: config.reasoningEffort,
136
136
  });
137
137
  try {
138
- for (var _l = true, stream_1 = (e_1 = void 0, __asyncValues(stream)), stream_1_1; stream_1_1 = yield __await(stream_1.next()), _a = stream_1_1.done, !_a; _l = true) {
138
+ for (var _m = true, stream_1 = (e_1 = void 0, __asyncValues(stream)), stream_1_1; stream_1_1 = yield __await(stream_1.next()), _a = stream_1_1.done, !_a; _m = true) {
139
139
  _c = stream_1_1.value;
140
- _l = false;
140
+ _m = false;
141
141
  const chunk = _c;
142
142
  if (chunk.type === 'content') {
143
143
  fullContent += chunk.delta;
@@ -155,7 +155,7 @@ function runLoopStream(config) {
155
155
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
156
156
  finally {
157
157
  try {
158
- if (!_l && !_a && (_b = stream_1.return)) yield __await(_b.call(stream_1));
158
+ if (!_m && !_a && (_b = stream_1.return)) yield __await(_b.call(stream_1));
159
159
  }
160
160
  finally { if (e_1) throw e_1.error; }
161
161
  }
@@ -234,6 +234,19 @@ function runLoopStream(config) {
234
234
  totalUsage: computeTotalUsage(),
235
235
  });
236
236
  }
237
+ // 检查是否有 tool 要求结束 Loop
238
+ const endLoopResult = parallelResults.find((r) => r.endLoop);
239
+ if (endLoopResult === null || endLoopResult === void 0 ? void 0 : endLoopResult.endLoop) {
240
+ yield yield __await({ type: 'turn_end', turn, usage: finishChunk.usage });
241
+ return yield __await({
242
+ messages,
243
+ harness: harness.getAll(),
244
+ finalContent: (_l = endLoopResult.finalContent) !== null && _l !== void 0 ? _l : endLoopResult.content,
245
+ turns: turn,
246
+ usageHistory,
247
+ totalUsage: computeTotalUsage(),
248
+ });
249
+ }
237
250
  yield yield __await({ type: 'turn_end', turn, usage: finishChunk.usage });
238
251
  // 第八步:回到第一步继续下一轮
239
252
  }
@@ -113,10 +113,15 @@ export interface ApprovalConfig {
113
113
  * Tool 执行结果的分流包装。
114
114
  * 当 execute 返回此类型时,forLLM 进入 messages[] 给 LLM 上下文,
115
115
  * forFrontend 通过 SSE tool_result 事件透给前端渲染。
116
+ * 设置 endLoop 为 true 可让 Loop 在执行完该 tool 后直接终止,不再调用 LLM。
116
117
  */
117
118
  export interface ToolResult {
118
119
  readonly forLLM: string;
119
120
  readonly forFrontend?: unknown;
121
+ /** 为 true 时,tool 执行完成后直接结束 Loop,不再进入下一轮 LLM 调用 */
122
+ readonly endLoop?: boolean;
123
+ /** endLoop 为 true 时,作为 LoopResult.finalContent 返回;不设置则使用 forLLM */
124
+ readonly finalContent?: string;
120
125
  }
121
126
  /**
122
127
  * 注册到 Loop 中的 Tool。
@@ -48,7 +48,7 @@ class BailianProvider {
48
48
  return BailianProvider.SUPPORTED_MODELS.has(model);
49
49
  }
50
50
  adaptRequest(body) {
51
- var _a, _b, _c;
51
+ var _a, _b, _c, _d;
52
52
  const caps = (_a = BailianProvider.MODEL_CAPABILITIES[body.model]) !== null && _a !== void 0 ? _a : { jsonMode: false, reasoningEffort: false };
53
53
  if (body.reasoning_effort !== undefined && !caps.reasoningEffort) {
54
54
  throw new Error(`[adapt] Model "${body.model}" does not support reasoning_effort`);
@@ -57,12 +57,21 @@ class BailianProvider {
57
57
  throw new Error(`[adapt] Model "${body.model}" does not support response_format json_object`);
58
58
  }
59
59
  // 百炼平台:Qwen 系列通过 enable_thinking 实现 reasoning_effort
60
+ // Kimi(Moonshot)通过 extra_body.thinking.type 实现(默认开启)
60
61
  if (body.reasoning_effort !== undefined) {
61
62
  if (['qwen-plus', 'qwen-turbo', 'qwen3.7-max'].includes(body.model)) {
62
63
  const { reasoning_effort } = body, rest = __rest(body, ["reasoning_effort"]);
63
64
  return Object.assign(Object.assign({}, rest), { extra_body: Object.assign(Object.assign({}, ((_c = rest.extra_body) !== null && _c !== void 0 ? _c : {})), { enable_thinking: reasoning_effort !== 'low' }) });
64
65
  }
65
- // deepseek-v4-pro / deepseek-v4-flash / kimi-k2.6 原生支持,直接传递
66
+ if (body.model === 'kimi-k2.6') {
67
+ const { reasoning_effort } = body, rest = __rest(body, ["reasoning_effort"]);
68
+ // Kimi 默认开启思考;只有 low 显式禁用,medium/high 走默认(不传 thinking 字段)
69
+ if (reasoning_effort === 'low') {
70
+ return Object.assign(Object.assign({}, rest), { extra_body: Object.assign(Object.assign({}, ((_d = rest.extra_body) !== null && _d !== void 0 ? _d : {})), { thinking: { type: 'disabled' } }) });
71
+ }
72
+ return rest;
73
+ }
74
+ // deepseek-v4-pro / deepseek-v4-flash 原生支持 reasoning_effort,直接传递
66
75
  }
67
76
  return body;
68
77
  }
@@ -198,6 +207,7 @@ class BailianProvider {
198
207
  : undefined,
199
208
  stream: request.stream,
200
209
  stream_options: request.streamOptions,
210
+ reasoning_effort: request.reasoningEffort,
201
211
  };
202
212
  }
203
213
  }
@@ -19,6 +19,8 @@ export interface LLMRequest {
19
19
  };
20
20
  readonly model?: string;
21
21
  readonly responseFormat?: 'text' | 'json';
22
+ /** 推理深度。各 Provider 按自身能力映射到底层协议字段(如 enable_thinking / reasoning_effort) */
23
+ readonly reasoningEffort?: 'low' | 'medium' | 'high';
22
24
  }
23
25
  /**
24
26
  * LLM 调用的 token 消耗统计。
@@ -65,6 +65,7 @@ function toLLMRequest(config, messages) {
65
65
  maxTokens: config.maxTokens,
66
66
  topP: config.topP,
67
67
  responseFormat: config.responseFormat,
68
+ reasoningEffort: config.reasoningEffort,
68
69
  };
69
70
  }
70
71
  function unwrapResponse(response, responseFormat) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keo-ai/axiom",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "基于 LLM 的预测与推理库,支持多 Provider 切换",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",