@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`:
|
|
@@ -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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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' &&
|
|
@@ -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';
|