@keo-ai/axiom 0.1.9 → 0.2.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/README.md CHANGED
@@ -411,6 +411,42 @@ console.log(result.harness);
411
411
 
412
412
  **并行隔离原则**:本轮并行执行的多个 tool,各自收到的 Harness 是"本轮并行开始前"的快照,互相看不到同轮其他正在执行的 tool。
413
413
 
414
+ ### Tool 结果分流(ToolResult)
415
+
416
+ 当 tool 返回大量结构化数据时,直接 `JSON.stringify` 给 LLM 会导致模型重复复述。此时可以用 `ToolResult` 把结果分成两份:
417
+
418
+ ```ts
419
+ const recommendTool = {
420
+ name: 'recommend_conferences',
421
+ description: 'Recommend academic conferences',
422
+ parameters: { /* ... */ },
423
+ execute: async (args) => {
424
+ const raw = await recommendConferences(args);
425
+ return {
426
+ forLLM: `找到 ${raw.results.length} 个相关会议:${raw.results.map(r => r.name).join('、')}`,
427
+ forFrontend: raw,
428
+ };
429
+ },
430
+ };
431
+ ```
432
+
433
+ | 字段 | 去向 | 说明 |
434
+ |---|---|---|
435
+ | `forLLM` | `messages[]` → LLM 上下文 | 精简摘要,建议用自然语言 |
436
+ | `forFrontend` | `tool_result` SSE 事件 | 完整原始数据,前端渲染用 |
437
+
438
+ 流式消费时前端可以拿到完整数据:
439
+
440
+ ```ts
441
+ for await (const chunk of stream) {
442
+ if (chunk.type === 'tool_result' && chunk.frontendData) {
443
+ renderCard(chunk.frontendData); // 前端拿到完整 JSON 渲染卡片
444
+ }
445
+ }
446
+ ```
447
+
448
+ **向后兼容**:返回普通值(非 `ToolResult`)时行为完全不变,仍按原有逻辑 `JSON.stringify` 后透给 LLM。
449
+
414
450
  ### Tool 审批拦截
415
451
 
416
452
  对于敏感操作(转账、删除数据等),可以给 tool 配置 `approval`,Loop 会在执行前暂停并返回 `pendingApproval`:
@@ -21,6 +21,7 @@ export declare function compressMessages(config: LoopConfig, messages: Message[]
21
21
  export type ExecutionResult = {
22
22
  toolCallId: string;
23
23
  content: string;
24
+ frontendData?: unknown;
24
25
  record: HarnessRecord;
25
26
  events: LoopEvent[];
26
27
  pendingApproval?: {
@@ -263,6 +263,12 @@ function compressMessages(config, messages, _turn) {
263
263
  }
264
264
  });
265
265
  }
266
+ function isToolResult(value) {
267
+ return (typeof value === 'object' &&
268
+ value !== null &&
269
+ 'forLLM' in value &&
270
+ typeof value.forLLM === 'string');
271
+ }
266
272
  /**
267
273
  * 并行执行 tool。
268
274
  */
@@ -367,14 +373,27 @@ function executeToolsInParallel(config, harness, toolCalls, visibleToolNames, ha
367
373
  error: finalRecord.error,
368
374
  turn,
369
375
  };
370
- const content = status === 'success'
371
- ? typeof result === 'object' && result !== null
372
- ? JSON.stringify(result)
373
- : String(result !== null && result !== void 0 ? result : '')
374
- : error !== null && error !== void 0 ? error : 'Execution failed';
376
+ let content;
377
+ let frontendData;
378
+ if (status === 'success') {
379
+ if (isToolResult(result)) {
380
+ content = result.forLLM;
381
+ frontendData = result.forFrontend;
382
+ }
383
+ else {
384
+ content =
385
+ typeof result === 'object' && result !== null
386
+ ? JSON.stringify(result)
387
+ : String(result !== null && result !== void 0 ? result : '');
388
+ }
389
+ }
390
+ else {
391
+ content = error !== null && error !== void 0 ? error : 'Execution failed';
392
+ }
375
393
  return {
376
394
  toolCallId: callId,
377
395
  content,
396
+ frontendData,
378
397
  record: finalRecord,
379
398
  events: [endEvent],
380
399
  pendingApproval: typeof result === 'object' &&
@@ -191,6 +191,7 @@ function runLoopStream(config) {
191
191
  callId: result.toolCallId,
192
192
  toolName: result.record.toolName,
193
193
  content: result.content,
194
+ frontendData: result.frontendData,
194
195
  status: result.record.status,
195
196
  });
196
197
  }
@@ -107,6 +107,15 @@ export interface ApprovalConfig {
107
107
  approved?: boolean;
108
108
  }>;
109
109
  }
110
+ /**
111
+ * Tool 执行结果的分流包装。
112
+ * 当 execute 返回此类型时,forLLM 进入 messages[] 给 LLM 上下文,
113
+ * forFrontend 通过 SSE tool_result 事件透给前端渲染。
114
+ */
115
+ export interface ToolResult {
116
+ readonly forLLM: string;
117
+ readonly forFrontend?: unknown;
118
+ }
110
119
  /**
111
120
  * 注册到 Loop 中的 Tool。
112
121
  */
@@ -115,7 +124,7 @@ export interface Tool {
115
124
  readonly description: string;
116
125
  readonly parameters: unknown;
117
126
  readonly discover?: (harness: ReadonlyArray<HarnessRecord>, metadata: unknown) => ToolDiscoverResult | Promise<ToolDiscoverResult>;
118
- readonly execute: (args: unknown, context: ToolExecuteContext) => unknown | Promise<unknown>;
127
+ readonly execute: (args: unknown, context: ToolExecuteContext) => unknown | ToolResult | Promise<unknown | ToolResult>;
119
128
  readonly approval?: ApprovalConfig;
120
129
  }
121
130
  /**
@@ -245,6 +254,7 @@ export type LoopStreamChunk = {
245
254
  readonly callId: string;
246
255
  readonly toolName: string;
247
256
  readonly content: string;
257
+ readonly frontendData?: unknown;
248
258
  readonly status: HarnessRecordStatus;
249
259
  } | {
250
260
  readonly type: 'turn_end';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keo-ai/axiom",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "基于 LLM 的预测与推理库,支持多 Provider 切换",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",