@keo-ai/axiom 0.1.7 → 0.1.9
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 +129 -12
- package/dist/function_call_loop/index.d.ts +2 -1
- package/dist/function_call_loop/index.js +3 -1
- package/dist/function_call_loop/loop.d.ts +34 -1
- package/dist/function_call_loop/loop.js +8 -1
- package/dist/function_call_loop/provider.d.ts +8 -1
- package/dist/function_call_loop/provider.js +182 -1
- package/dist/function_call_loop/stream.d.ts +24 -0
- package/dist/function_call_loop/stream.js +226 -0
- package/dist/function_call_loop/types.d.ts +50 -4
- package/dist/llm_provider/bailian.d.ts +2 -0
- package/dist/llm_provider/bailian.js +48 -2
- package/dist/llm_provider/index.d.ts +5 -6
- package/dist/llm_provider/index.js +3 -22
- package/dist/llm_provider/llm.d.ts +8 -0
- package/dist/llm_provider/models.d.ts +4 -6
- package/dist/llm_provider/models.js +4 -21
- package/dist/llm_provider/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,16 +172,20 @@ for await (const chunk of LLM.streamPredict({ model: 'qwen3.7-max', prompt: '讲
|
|
|
172
172
|
|
|
173
173
|
当前支持的模型(通过 `Model` 类型枚举):
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
| 模型 | 推理深度 | 说明 |
|
|
176
|
+
|------|----------|------|
|
|
177
|
+
| `deepseek-v4-pro` | ✅ 原生支持 | `low`/`medium`/`high` 直接传递 |
|
|
178
|
+
| `deepseek-v4-flash` | ✅ 原生支持 | `low`/`medium`/`high` 直接传递 |
|
|
179
|
+
| `kimi-k2.6` | ✅ 原生支持 | `low`/`medium`/`high` 直接传递 |
|
|
180
|
+
| `qwen-plus` | ✅ 映射支持 | `low` 关闭推理,`medium`/`high` 映射为 `enable_thinking` |
|
|
181
|
+
| `qwen-turbo` | ✅ 映射支持 | 同上 |
|
|
182
|
+
| `qwen3.7-max` | ✅ 映射支持 | 同上 |
|
|
183
|
+
| `qwq-plus` | ❌ 不支持 | 固定推理行为,不可调节 |
|
|
184
|
+
| `glm-5.1` | ❌ 不支持 | 不支持推理参数 |
|
|
185
|
+
| `qwen-vl-plus` | ❌ 不支持 | 视觉模型,不支持推理参数 |
|
|
186
|
+
|
|
187
|
+
> 推理深度的支持方式由 Provider 内部维护。百炼 Provider 中,Qwen 系列通过 `extra_body.enable_thinking` 映射实现,DeepSeek / Kimi 则原生透传 `reasoning_effort`。
|
|
188
|
+
>
|
|
185
189
|
> 目前所有模型均路由到百炼 Provider。后续接入其他厂商时,通过 `MODEL_REGISTRY` 扩展映射即可。
|
|
186
190
|
|
|
187
191
|
### 错误处理
|
|
@@ -260,6 +264,104 @@ console.log('Turns:', result.turns);
|
|
|
260
264
|
console.log('Harness:', result.harness);
|
|
261
265
|
```
|
|
262
266
|
|
|
267
|
+
> 💡 需要**流式输出**(实时展示 LLM 生成内容)?使用下方的 [`runLoopStream`](#流式调用runloopstream)。
|
|
268
|
+
|
|
269
|
+
### 流式调用(runLoopStream)
|
|
270
|
+
|
|
271
|
+
当需要实时展示 LLM 的思考过程或输出内容时,使用 `runLoopStream`:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { FunctionCallLoop } from '@keo-ai/axiom';
|
|
275
|
+
|
|
276
|
+
const stream = FunctionCallLoop.runLoopStream({
|
|
277
|
+
messages: [
|
|
278
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
279
|
+
{ role: 'user', content: 'What is the weather in Beijing?' },
|
|
280
|
+
],
|
|
281
|
+
tools: [
|
|
282
|
+
{
|
|
283
|
+
name: 'get_weather',
|
|
284
|
+
description: 'Get weather for a city',
|
|
285
|
+
parameters: { /* ... */ },
|
|
286
|
+
execute: async (args) => {
|
|
287
|
+
return { temperature: 25, condition: 'Sunny' };
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
maxTurns: 5,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// 逐块消费流事件
|
|
295
|
+
for await (const chunk of stream) {
|
|
296
|
+
switch (chunk.type) {
|
|
297
|
+
case 'turn_start':
|
|
298
|
+
console.log(`\n--- Turn ${chunk.turn} ---`);
|
|
299
|
+
break;
|
|
300
|
+
case 'reasoning':
|
|
301
|
+
// 实时流式输出模型的 reasoning content(思考过程)
|
|
302
|
+
process.stdout.write(chunk.delta);
|
|
303
|
+
break;
|
|
304
|
+
case 'content':
|
|
305
|
+
// 实时流式输出 content(正式回复内容)
|
|
306
|
+
process.stdout.write(chunk.delta);
|
|
307
|
+
break;
|
|
308
|
+
case 'tool_call':
|
|
309
|
+
console.log('\n[Tool Call]', chunk.toolCalls.map(t => t.function.name));
|
|
310
|
+
break;
|
|
311
|
+
case 'tool_result':
|
|
312
|
+
console.log(`[Result] ${chunk.toolName}: ${chunk.content} (${chunk.status})`);
|
|
313
|
+
break;
|
|
314
|
+
case 'turn_end':
|
|
315
|
+
console.log(`\n--- Turn ${chunk.turn} End ---`);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**获取最终返回值**:流结束后需要通过手动驱动迭代器获取 `LoopResult`:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
const stream = FunctionCallLoop.runLoopStream(config);
|
|
325
|
+
|
|
326
|
+
const chunks: FunctionCallLoop.LoopStreamChunk[] = [];
|
|
327
|
+
let result: FunctionCallLoop.LoopResult | undefined;
|
|
328
|
+
|
|
329
|
+
while (true) {
|
|
330
|
+
const { value, done } = await stream.next();
|
|
331
|
+
if (done) {
|
|
332
|
+
result = value as FunctionCallLoop.LoopResult;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
chunks.push(value as FunctionCallLoop.LoopStreamChunk);
|
|
336
|
+
// 实时消费...
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.log('Final:', result!.finalContent);
|
|
340
|
+
console.log('Turns:', result!.turns);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### 流式事件类型
|
|
344
|
+
|
|
345
|
+
| 事件 | 触发时机 | 包含字段 |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `turn_start` | 新一轮开始 | `turn` |
|
|
348
|
+
| `reasoning` | LLM 输出 reasoning content(推理过程)增量 | `delta`, `turn` |
|
|
349
|
+
| `content` | LLM 输出 content(正式回复)增量 | `delta`, `turn` |
|
|
350
|
+
| `tool_call` | LLM 决定调用 tool(流结束、完整 tool_calls 解析完成) | `toolCalls`, `turn` |
|
|
351
|
+
| `tool_result` | tool 执行完成 | `callId`, `toolName`, `content`, `status`, `turn` |
|
|
352
|
+
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn` |
|
|
353
|
+
|
|
354
|
+
#### 流式 vs 非流式的选择
|
|
355
|
+
|
|
356
|
+
| 场景 | 推荐方式 |
|
|
357
|
+
|---|---|
|
|
358
|
+
| 需要实时展示 LLM 输出(打字机效果) | `runLoopStream` |
|
|
359
|
+
| 需要实时展示模型 reasoning(思考)过程 | `runLoopStream`(通过 `reasoning` 事件独立透出) |
|
|
360
|
+
| 后台静默执行,只关心最终结果 | `runLoop` |
|
|
361
|
+
| 低延迟、简单场景 | `runLoop` |
|
|
362
|
+
|
|
363
|
+
> **注意**:`runLoopStream` 需要 `LLMCaller` 支持 `stream` 方法。使用默认的 `createLLMCaller` 时自动支持。如果注入自定义 `llmCaller`,请确保实现了 `stream` 接口。
|
|
364
|
+
|
|
263
365
|
### 生命周期事件
|
|
264
366
|
|
|
265
367
|
Loop 每处理一个 tool call,按顺序抛出两个事件:
|
|
@@ -497,7 +599,9 @@ const result = await promise;
|
|
|
497
599
|
|
|
498
600
|
### 配置项
|
|
499
601
|
|
|
500
|
-
#### `runLoop` 配置
|
|
602
|
+
#### `runLoop` / `runLoopStream` 配置
|
|
603
|
+
|
|
604
|
+
两个函数共享同一套配置(`LoopConfig`),`runLoopStream` 额外要求 `llmCaller` 支持 `stream` 方法:
|
|
501
605
|
|
|
502
606
|
**对话入口**
|
|
503
607
|
|
|
@@ -528,7 +632,7 @@ const result = await promise;
|
|
|
528
632
|
| `maxTokens` | `number` | — | 单次 LLM 调用的最大输出 token 数 |
|
|
529
633
|
| `temperature` | `number` | — | 采样温度,范围 0~2 |
|
|
530
634
|
| `topP` | `number` | — | 核采样概率阈值,范围 0~1 |
|
|
531
|
-
| `reasoningEffort` | `'low' \| 'medium' \| 'high'` | — |
|
|
635
|
+
| `reasoningEffort` | `'low' \| 'medium' \| 'high'` | — | 推理深度。`low` 关闭推理,`medium`/`high` 开启推理。具体支持情况见「模型列表」 |
|
|
532
636
|
|
|
533
637
|
### 返回结果
|
|
534
638
|
|
|
@@ -541,6 +645,19 @@ interface LoopResult {
|
|
|
541
645
|
}
|
|
542
646
|
```
|
|
543
647
|
|
|
648
|
+
**Message 中的 reasoningContent**:当使用支持推理的模型(如 DeepSeek-R1、QwQ)时,`messages` 中 `role: 'assistant'` 的条目会包含 `reasoningContent` 字段,记录该轮模型的完整推理过程。该字段会自动保留在对话上下文中,供多轮对话回传。
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
const result = await FunctionCallLoop.runLoop(config);
|
|
652
|
+
|
|
653
|
+
for (const msg of result.messages) {
|
|
654
|
+
if (msg.role === 'assistant' && msg.reasoningContent) {
|
|
655
|
+
console.log('[Reasoning]', msg.reasoningContent);
|
|
656
|
+
console.log('[Content]', msg.content);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
544
661
|
### 错误处理
|
|
545
662
|
|
|
546
663
|
同其他模块:**直接抛异常**。常见场景:
|
|
@@ -11,4 +11,5 @@
|
|
|
11
11
|
* - 系统 Prompt 完全由外部注入
|
|
12
12
|
*/
|
|
13
13
|
export { runLoop } from './loop';
|
|
14
|
-
export
|
|
14
|
+
export { runLoopStream } from './stream';
|
|
15
|
+
export type { Message, ToolCall, ToolDefinition, HarnessRecord, HarnessRecordStatus, ToolExecutionStartEvent, ToolExecutionEndEvent, LoopEvent, ToolDiscoverResult, ToolExecuteContext, Tool, ApprovalConfig, TurnPolicyResult, TurnPolicy, CompactConfig, LLMCaller, LLMCallOptions, LLMStreamChunk, LoopConfig, LoopResult, LoopStreamChunk, PendingApprovalInfo, } from './types';
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* - 系统 Prompt 完全由外部注入
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.runLoop = void 0;
|
|
15
|
+
exports.runLoopStream = exports.runLoop = void 0;
|
|
16
16
|
var loop_1 = require("./loop");
|
|
17
17
|
Object.defineProperty(exports, "runLoop", { enumerable: true, get: function () { return loop_1.runLoop; } });
|
|
18
|
+
var stream_1 = require("./stream");
|
|
19
|
+
Object.defineProperty(exports, "runLoopStream", { enumerable: true, get: function () { return stream_1.runLoopStream; } });
|
|
@@ -1,2 +1,35 @@
|
|
|
1
|
-
import type { LoopConfig, LoopResult } from './types';
|
|
1
|
+
import type { LoopConfig, LoopResult, Message, ToolCall, ToolDiscoverResult, TurnPolicyResult, LoopEvent, HarnessRecord } from './types';
|
|
2
|
+
import { Harness } from './harness';
|
|
2
3
|
export declare function runLoop(config: LoopConfig): Promise<LoopResult>;
|
|
4
|
+
/**
|
|
5
|
+
* 执行 Turn Policy。
|
|
6
|
+
*/
|
|
7
|
+
export declare function runTurnPolicy(config: LoopConfig, harness: Harness, turn: number): Promise<TurnPolicyResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Tool 发现。
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverTools(config: LoopConfig, harness: Harness): Promise<ToolDiscoverResult[]>;
|
|
12
|
+
/**
|
|
13
|
+
* 将 Messages 按轮次分组(不含初始 system/user 消息)。
|
|
14
|
+
* 一轮 = 一个 assistant message + 其后的所有 tool messages。
|
|
15
|
+
*/
|
|
16
|
+
export declare function groupMessagesByRound(messages: ReadonlyArray<Message>): Message[][];
|
|
17
|
+
/**
|
|
18
|
+
* 上下文压缩。
|
|
19
|
+
*/
|
|
20
|
+
export declare function compressMessages(config: LoopConfig, messages: Message[], _turn: number): Promise<Message[]>;
|
|
21
|
+
export type ExecutionResult = {
|
|
22
|
+
toolCallId: string;
|
|
23
|
+
content: string;
|
|
24
|
+
record: HarnessRecord;
|
|
25
|
+
events: LoopEvent[];
|
|
26
|
+
pendingApproval?: {
|
|
27
|
+
ticketId: string;
|
|
28
|
+
toolName: string;
|
|
29
|
+
args: unknown;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* 并行执行 tool。
|
|
34
|
+
*/
|
|
35
|
+
export declare function executeToolsInParallel(config: LoopConfig, harness: Harness, toolCalls: ReadonlyArray<ToolCall>, visibleToolNames: Set<string>, harnessSnapshot: ReadonlyArray<HarnessRecord>, turn: number): Promise<ExecutionResult[]>;
|
|
@@ -10,6 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.runLoop = runLoop;
|
|
13
|
+
exports.runTurnPolicy = runTurnPolicy;
|
|
14
|
+
exports.discoverTools = discoverTools;
|
|
15
|
+
exports.groupMessagesByRound = groupMessagesByRound;
|
|
16
|
+
exports.compressMessages = compressMessages;
|
|
17
|
+
exports.executeToolsInParallel = executeToolsInParallel;
|
|
13
18
|
const harness_1 = require("./harness");
|
|
14
19
|
const provider_1 = require("./provider");
|
|
15
20
|
/**
|
|
@@ -87,6 +92,7 @@ function runLoop(config) {
|
|
|
87
92
|
messages.push({
|
|
88
93
|
role: 'assistant',
|
|
89
94
|
content: (_f = llmResponse.content) !== null && _f !== void 0 ? _f : '',
|
|
95
|
+
reasoningContent: llmResponse.reasoningContent,
|
|
90
96
|
});
|
|
91
97
|
return {
|
|
92
98
|
messages,
|
|
@@ -99,6 +105,7 @@ function runLoop(config) {
|
|
|
99
105
|
messages.push({
|
|
100
106
|
role: 'assistant',
|
|
101
107
|
content: (_g = llmResponse.content) !== null && _g !== void 0 ? _g : '',
|
|
108
|
+
reasoningContent: llmResponse.reasoningContent,
|
|
102
109
|
tool_calls: llmResponse.tool_calls,
|
|
103
110
|
});
|
|
104
111
|
// 第七步:并行执行 tool
|
|
@@ -159,7 +166,7 @@ function runTurnPolicy(config, harness, turn) {
|
|
|
159
166
|
if (turn < maxTurns - 1) {
|
|
160
167
|
return {};
|
|
161
168
|
}
|
|
162
|
-
if (turn === maxTurns - 1) {
|
|
169
|
+
if (turn === maxTurns - 1 && config.warningMessage) {
|
|
163
170
|
return {
|
|
164
171
|
injectMessage: config.warningMessage,
|
|
165
172
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Function Call Loop 的 LLM Provider 适配器。
|
|
3
3
|
* 基于 predict 模块的 `callChatCompletions` 构建,共用底层 OpenAI 协议 HTTP 层。
|
|
4
4
|
*/
|
|
5
|
-
import type { Message, ToolCall, ToolDefinition, LLMCallOptions } from './types';
|
|
5
|
+
import type { Message, ToolCall, ToolDefinition, LLMCallOptions, LLMStreamChunk } from './types';
|
|
6
6
|
export interface ProviderOptions {
|
|
7
7
|
readonly apiKey: string;
|
|
8
8
|
readonly baseUrl: string;
|
|
@@ -14,6 +14,13 @@ export interface ProviderOptions {
|
|
|
14
14
|
export declare function createLLMCaller(options: ProviderOptions): {
|
|
15
15
|
call(messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, callOptions?: LLMCallOptions): Promise<{
|
|
16
16
|
readonly content: string | null;
|
|
17
|
+
readonly reasoningContent?: string;
|
|
17
18
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
18
19
|
}>;
|
|
20
|
+
/**
|
|
21
|
+
* 流式调用 LLM,支持 content 和 tool_calls 的增量输出。
|
|
22
|
+
*
|
|
23
|
+
* @yields 内容片段(`content`)或结束标记(`finish`,携带完整 content 和 tool_calls)
|
|
24
|
+
*/
|
|
25
|
+
stream(messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, callOptions?: LLMCallOptions): AsyncGenerator<LLMStreamChunk, void, unknown>;
|
|
19
26
|
};
|
|
@@ -8,15 +8,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
12
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
13
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
14
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
15
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
16
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
17
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
18
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
19
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
20
|
+
function fulfill(value) { resume("next", value); }
|
|
21
|
+
function reject(value) { resume("throw", value); }
|
|
22
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
23
|
+
};
|
|
11
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
25
|
exports.createLLMCaller = createLLMCaller;
|
|
13
26
|
const llm_provider_1 = require("../llm_provider");
|
|
27
|
+
const bailian_1 = require("../llm_provider/bailian");
|
|
14
28
|
function toOpenAIMessages(messages) {
|
|
15
29
|
return messages.map((m) => {
|
|
16
30
|
var _a;
|
|
17
31
|
return ({
|
|
18
32
|
role: m.role,
|
|
19
33
|
content: m.content,
|
|
34
|
+
reasoning_content: m.reasoningContent,
|
|
20
35
|
tool_calls: (_a = m.tool_calls) === null || _a === void 0 ? void 0 : _a.map((tc) => ({
|
|
21
36
|
id: tc.id,
|
|
22
37
|
type: tc.type,
|
|
@@ -43,11 +58,17 @@ function toOpenAITools(tools) {
|
|
|
43
58
|
* 基于 OpenAI 协议的 LLM 调用器。
|
|
44
59
|
*/
|
|
45
60
|
function createLLMCaller(options) {
|
|
61
|
+
const provider = new bailian_1.BailianProvider({
|
|
62
|
+
name: 'bailian',
|
|
63
|
+
apiKey: options.apiKey,
|
|
64
|
+
baseUrl: options.baseUrl,
|
|
65
|
+
defaultModel: options.defaultModel,
|
|
66
|
+
});
|
|
46
67
|
return {
|
|
47
68
|
call(messages, tools, callOptions) {
|
|
48
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
49
70
|
var _a, _b;
|
|
50
|
-
const body =
|
|
71
|
+
const body = provider.adaptRequest({
|
|
51
72
|
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
52
73
|
messages: toOpenAIMessages(messages),
|
|
53
74
|
temperature: callOptions === null || callOptions === void 0 ? void 0 : callOptions.temperature,
|
|
@@ -60,6 +81,7 @@ function createLLMCaller(options) {
|
|
|
60
81
|
const choice = data.choices[0];
|
|
61
82
|
return {
|
|
62
83
|
content: choice.message.content,
|
|
84
|
+
reasoningContent: choice.message.reasoning_content,
|
|
63
85
|
tool_calls: (_b = choice.message.tool_calls) === null || _b === void 0 ? void 0 : _b.map((tc) => ({
|
|
64
86
|
id: tc.id,
|
|
65
87
|
type: tc.type,
|
|
@@ -71,5 +93,164 @@ function createLLMCaller(options) {
|
|
|
71
93
|
};
|
|
72
94
|
});
|
|
73
95
|
},
|
|
96
|
+
/**
|
|
97
|
+
* 流式调用 LLM,支持 content 和 tool_calls 的增量输出。
|
|
98
|
+
*
|
|
99
|
+
* @yields 内容片段(`content`)或结束标记(`finish`,携带完整 content 和 tool_calls)
|
|
100
|
+
*/
|
|
101
|
+
stream(messages, tools, callOptions) {
|
|
102
|
+
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
103
|
+
var _a, _b, _c, _d, _e;
|
|
104
|
+
const body = provider.adaptRequest({
|
|
105
|
+
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
106
|
+
messages: toOpenAIMessages(messages),
|
|
107
|
+
temperature: callOptions === null || callOptions === void 0 ? void 0 : callOptions.temperature,
|
|
108
|
+
max_tokens: callOptions === null || callOptions === void 0 ? void 0 : callOptions.maxTokens,
|
|
109
|
+
top_p: callOptions === null || callOptions === void 0 ? void 0 : callOptions.topP,
|
|
110
|
+
tools: tools.length > 0 ? toOpenAITools(tools) : undefined,
|
|
111
|
+
reasoning_effort: callOptions === null || callOptions === void 0 ? void 0 : callOptions.reasoningEffort,
|
|
112
|
+
stream: true,
|
|
113
|
+
});
|
|
114
|
+
const url = `${options.baseUrl}/chat/completions`;
|
|
115
|
+
let response;
|
|
116
|
+
try {
|
|
117
|
+
response = yield __await(fetch(url, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
catch (cause) {
|
|
127
|
+
throw new Error(`[llm] ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
128
|
+
}
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const text = yield __await(response.text());
|
|
131
|
+
throw new Error(`[llm] HTTP ${response.status}: ${text}`);
|
|
132
|
+
}
|
|
133
|
+
if (!response.body) {
|
|
134
|
+
throw new Error(`[llm] Response body is null`);
|
|
135
|
+
}
|
|
136
|
+
const reader = response.body.getReader();
|
|
137
|
+
const decoder = new TextDecoder();
|
|
138
|
+
let buffer = '';
|
|
139
|
+
// 用于增量收集 tool_calls
|
|
140
|
+
const toolCallAccumulators = [];
|
|
141
|
+
let fullContent = '';
|
|
142
|
+
let fullReasoningContent = '';
|
|
143
|
+
let hasYieldedFinish = false;
|
|
144
|
+
try {
|
|
145
|
+
while (true) {
|
|
146
|
+
const { done, value } = yield __await(reader.read());
|
|
147
|
+
if (done)
|
|
148
|
+
break;
|
|
149
|
+
buffer += decoder.decode(value, { stream: true });
|
|
150
|
+
const lines = buffer.split('\n');
|
|
151
|
+
buffer = (_b = lines.pop()) !== null && _b !== void 0 ? _b : '';
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
const trimmed = line.trim();
|
|
154
|
+
if (!trimmed || !trimmed.startsWith('data: '))
|
|
155
|
+
continue;
|
|
156
|
+
const data = trimmed.slice(6);
|
|
157
|
+
if (data === '[DONE]')
|
|
158
|
+
continue;
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(data);
|
|
162
|
+
}
|
|
163
|
+
catch (_f) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const choice = (_c = parsed.choices) === null || _c === void 0 ? void 0 : _c[0];
|
|
167
|
+
if (!choice)
|
|
168
|
+
continue;
|
|
169
|
+
const delta = choice.delta;
|
|
170
|
+
// 收集 reasoning_content 增量
|
|
171
|
+
if (delta.reasoning_content) {
|
|
172
|
+
fullReasoningContent += delta.reasoning_content;
|
|
173
|
+
yield yield __await({ type: 'reasoning', delta: delta.reasoning_content });
|
|
174
|
+
}
|
|
175
|
+
// 收集 content 增量
|
|
176
|
+
if (delta.content) {
|
|
177
|
+
fullContent += delta.content;
|
|
178
|
+
yield yield __await({ type: 'content', delta: delta.content });
|
|
179
|
+
}
|
|
180
|
+
// 收集 tool_calls 增量
|
|
181
|
+
if (delta.tool_calls) {
|
|
182
|
+
for (const tcDelta of delta.tool_calls) {
|
|
183
|
+
const index = tcDelta.index;
|
|
184
|
+
if (!toolCallAccumulators[index]) {
|
|
185
|
+
toolCallAccumulators[index] = {
|
|
186
|
+
function: { arguments: '' },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const acc = toolCallAccumulators[index];
|
|
190
|
+
if (tcDelta.id)
|
|
191
|
+
acc.id = tcDelta.id;
|
|
192
|
+
if (tcDelta.type)
|
|
193
|
+
acc.type = tcDelta.type;
|
|
194
|
+
if ((_d = tcDelta.function) === null || _d === void 0 ? void 0 : _d.name) {
|
|
195
|
+
acc.function.name = tcDelta.function.name;
|
|
196
|
+
}
|
|
197
|
+
if ((_e = tcDelta.function) === null || _e === void 0 ? void 0 : _e.arguments) {
|
|
198
|
+
acc.function.arguments += tcDelta.function.arguments;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// 流结束
|
|
203
|
+
if (choice.finish_reason) {
|
|
204
|
+
const toolCalls = [];
|
|
205
|
+
for (const acc of toolCallAccumulators) {
|
|
206
|
+
if (acc.id && acc.type && acc.function.name) {
|
|
207
|
+
toolCalls.push({
|
|
208
|
+
id: acc.id,
|
|
209
|
+
type: acc.type,
|
|
210
|
+
function: {
|
|
211
|
+
name: acc.function.name,
|
|
212
|
+
arguments: acc.function.arguments,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
yield yield __await({
|
|
218
|
+
type: 'finish',
|
|
219
|
+
content: fullContent || null,
|
|
220
|
+
reasoningContent: fullReasoningContent || undefined,
|
|
221
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
222
|
+
});
|
|
223
|
+
hasYieldedFinish = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 兜底:如果流正常结束但没有收到 finish_reason,也 yield 一个 finish
|
|
228
|
+
if (!hasYieldedFinish) {
|
|
229
|
+
const toolCalls = [];
|
|
230
|
+
for (const acc of toolCallAccumulators) {
|
|
231
|
+
if (acc.id && acc.type && acc.function.name) {
|
|
232
|
+
toolCalls.push({
|
|
233
|
+
id: acc.id,
|
|
234
|
+
type: acc.type,
|
|
235
|
+
function: {
|
|
236
|
+
name: acc.function.name,
|
|
237
|
+
arguments: acc.function.arguments,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
yield yield __await({
|
|
243
|
+
type: 'finish',
|
|
244
|
+
content: fullContent || null,
|
|
245
|
+
reasoningContent: fullReasoningContent || undefined,
|
|
246
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
reader.releaseLock();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
},
|
|
74
255
|
};
|
|
75
256
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LoopConfig, LoopResult, LoopStreamChunk } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 运行 Function Call Loop 的流式版本。
|
|
4
|
+
*
|
|
5
|
+
* 与非流式 `runLoop` 的区别:
|
|
6
|
+
* - 每轮 LLM 调用使用流式接口,content 增量实时通过 yield 透出
|
|
7
|
+
* - 支持 tool_calls 的增量收集;完整 tool_calls 在 finish chunk 中返回
|
|
8
|
+
* - 每轮开始/结束、tool 执行结果都会生成对应事件
|
|
9
|
+
*
|
|
10
|
+
* @param config - Loop 配置
|
|
11
|
+
* @yields 流式事件(content / tool_call / tool_result / turn_start / turn_end)
|
|
12
|
+
* @returns Loop 执行结果
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const stream = runLoopStream(config);
|
|
17
|
+
* for await (const chunk of stream) {
|
|
18
|
+
* if (chunk.type === 'content') process.stdout.write(chunk.delta);
|
|
19
|
+
* if (chunk.type === 'tool_call') console.log('调用工具:', chunk.toolCalls);
|
|
20
|
+
* }
|
|
21
|
+
* const result = await stream.return?.(); // 或使用 for await 之后的返回值
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function runLoopStream(config: LoopConfig): AsyncGenerator<LoopStreamChunk, LoopResult, unknown>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
3
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
4
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
5
|
+
var m = o[Symbol.asyncIterator], i;
|
|
6
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
7
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
8
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
9
|
+
};
|
|
10
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
11
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
12
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
13
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
14
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
15
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
16
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
17
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
18
|
+
function fulfill(value) { resume("next", value); }
|
|
19
|
+
function reject(value) { resume("throw", value); }
|
|
20
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.runLoopStream = runLoopStream;
|
|
24
|
+
const harness_1 = require("./harness");
|
|
25
|
+
const provider_1 = require("./provider");
|
|
26
|
+
const loop_1 = require("./loop");
|
|
27
|
+
const DEFAULT_BAILIAN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
|
28
|
+
const DEFAULT_BAILIAN_MODEL = 'qwen-max';
|
|
29
|
+
function buildInitialMessages(config) {
|
|
30
|
+
return [...config.messages];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 运行 Function Call Loop 的流式版本。
|
|
34
|
+
*
|
|
35
|
+
* 与非流式 `runLoop` 的区别:
|
|
36
|
+
* - 每轮 LLM 调用使用流式接口,content 增量实时通过 yield 透出
|
|
37
|
+
* - 支持 tool_calls 的增量收集;完整 tool_calls 在 finish chunk 中返回
|
|
38
|
+
* - 每轮开始/结束、tool 执行结果都会生成对应事件
|
|
39
|
+
*
|
|
40
|
+
* @param config - Loop 配置
|
|
41
|
+
* @yields 流式事件(content / tool_call / tool_result / turn_start / turn_end)
|
|
42
|
+
* @returns Loop 执行结果
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const stream = runLoopStream(config);
|
|
47
|
+
* for await (const chunk of stream) {
|
|
48
|
+
* if (chunk.type === 'content') process.stdout.write(chunk.delta);
|
|
49
|
+
* if (chunk.type === 'tool_call') console.log('调用工具:', chunk.toolCalls);
|
|
50
|
+
* }
|
|
51
|
+
* const result = await stream.return?.(); // 或使用 for await 之后的返回值
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function runLoopStream(config) {
|
|
55
|
+
return __asyncGenerator(this, arguments, function* runLoopStream_1() {
|
|
56
|
+
var _a, e_1, _b, _c;
|
|
57
|
+
var _d, _e, _f, _g, _h, _j, _k;
|
|
58
|
+
const llmCaller = (_d = config.llmCaller) !== null && _d !== void 0 ? _d : (0, provider_1.createLLMCaller)({
|
|
59
|
+
apiKey: (_e = process.env.BAILIAN_API_KEY) !== null && _e !== void 0 ? _e : '',
|
|
60
|
+
baseUrl: (_f = process.env.BAILIAN_BASE_URL) !== null && _f !== void 0 ? _f : DEFAULT_BAILIAN_BASE_URL,
|
|
61
|
+
defaultModel: (_g = process.env.BAILIAN_DEFAULT_MODEL) !== null && _g !== void 0 ? _g : DEFAULT_BAILIAN_MODEL,
|
|
62
|
+
});
|
|
63
|
+
if (!config.llmCaller && !process.env.BAILIAN_API_KEY) {
|
|
64
|
+
throw new Error('BAILIAN_API_KEY environment variable is not set.');
|
|
65
|
+
}
|
|
66
|
+
if (!llmCaller.stream) {
|
|
67
|
+
throw new Error('LLMCaller does not support streaming. Please provide an LLMCaller with a stream method.');
|
|
68
|
+
}
|
|
69
|
+
const harness = new harness_1.Harness();
|
|
70
|
+
const messages = buildInitialMessages(config);
|
|
71
|
+
let turn = 0;
|
|
72
|
+
const signal = config.signal;
|
|
73
|
+
while (true) {
|
|
74
|
+
// 检查取消信号
|
|
75
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
// 第一步:轮次加一
|
|
79
|
+
turn++;
|
|
80
|
+
yield yield __await({ type: 'turn_start', turn });
|
|
81
|
+
// 第二步:全局轮次策略
|
|
82
|
+
const decision = yield __await((0, loop_1.runTurnPolicy)(config, harness, turn));
|
|
83
|
+
if (decision.injectMessage) {
|
|
84
|
+
messages.push({ role: 'system', content: decision.injectMessage });
|
|
85
|
+
}
|
|
86
|
+
// 硬兜底检查
|
|
87
|
+
if (config.maxTurns !== undefined && turn >= config.maxTurns) {
|
|
88
|
+
messages.push({
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: (_h = config.terminateMessage) !== null && _h !== void 0 ? _h : 'Max turns reached.',
|
|
91
|
+
});
|
|
92
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
// 第三步:Tool 发现
|
|
96
|
+
const discoverResult = yield __await((0, loop_1.discoverTools)(config, harness));
|
|
97
|
+
const visibleTools = discoverResult.filter((t) => t.visible);
|
|
98
|
+
const toolDefinitions = visibleTools.map((t) => ({
|
|
99
|
+
type: 'function',
|
|
100
|
+
function: {
|
|
101
|
+
name: t.name,
|
|
102
|
+
description: t.description,
|
|
103
|
+
parameters: t.parameters,
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
const visibleToolNames = new Set(visibleTools.map((t) => t.name));
|
|
107
|
+
// 第四步:上下文压缩
|
|
108
|
+
const messagesForLLM = yield __await((0, loop_1.compressMessages)(config, messages, turn));
|
|
109
|
+
// 第五步:流式调用 LLM
|
|
110
|
+
let fullContent = '';
|
|
111
|
+
let fullReasoningContent = '';
|
|
112
|
+
let finishChunk = null;
|
|
113
|
+
const stream = llmCaller.stream(messagesForLLM, toolDefinitions, {
|
|
114
|
+
model: config.model,
|
|
115
|
+
temperature: config.temperature,
|
|
116
|
+
maxTokens: config.maxTokens,
|
|
117
|
+
topP: config.topP,
|
|
118
|
+
reasoningEffort: config.reasoningEffort,
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
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) {
|
|
122
|
+
_c = stream_1_1.value;
|
|
123
|
+
_l = false;
|
|
124
|
+
const chunk = _c;
|
|
125
|
+
if (chunk.type === 'content') {
|
|
126
|
+
fullContent += chunk.delta;
|
|
127
|
+
yield yield __await({ type: 'content', delta: chunk.delta, turn });
|
|
128
|
+
}
|
|
129
|
+
else if (chunk.type === 'reasoning') {
|
|
130
|
+
fullReasoningContent += chunk.delta;
|
|
131
|
+
yield yield __await({ type: 'reasoning', delta: chunk.delta, turn });
|
|
132
|
+
}
|
|
133
|
+
else if (chunk.type === 'finish') {
|
|
134
|
+
finishChunk = chunk;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
139
|
+
finally {
|
|
140
|
+
try {
|
|
141
|
+
if (!_l && !_a && (_b = stream_1.return)) yield __await(_b.call(stream_1));
|
|
142
|
+
}
|
|
143
|
+
finally { if (e_1) throw e_1.error; }
|
|
144
|
+
}
|
|
145
|
+
if (!finishChunk) {
|
|
146
|
+
throw new Error('LLM stream ended without a finish chunk');
|
|
147
|
+
}
|
|
148
|
+
// 使用 finish chunk 中的 content(优先级更高,可能包含完整格式化内容)
|
|
149
|
+
const assistantContent = (_j = finishChunk.content) !== null && _j !== void 0 ? _j : fullContent;
|
|
150
|
+
const assistantReasoningContent = (_k = finishChunk.reasoningContent) !== null && _k !== void 0 ? _k : (fullReasoningContent || undefined);
|
|
151
|
+
// 第六步:检查 LLM 回复
|
|
152
|
+
if (!finishChunk.tool_calls || finishChunk.tool_calls.length === 0) {
|
|
153
|
+
messages.push({
|
|
154
|
+
role: 'assistant',
|
|
155
|
+
content: assistantContent,
|
|
156
|
+
reasoningContent: assistantReasoningContent,
|
|
157
|
+
});
|
|
158
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
159
|
+
return yield __await({
|
|
160
|
+
messages,
|
|
161
|
+
harness: harness.getAll(),
|
|
162
|
+
finalContent: assistantContent,
|
|
163
|
+
turns: turn,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// 有 tool_calls,将 assistant message 加入 Messages
|
|
167
|
+
yield yield __await({
|
|
168
|
+
type: 'tool_call',
|
|
169
|
+
turn,
|
|
170
|
+
toolCalls: finishChunk.tool_calls,
|
|
171
|
+
});
|
|
172
|
+
messages.push({
|
|
173
|
+
role: 'assistant',
|
|
174
|
+
content: assistantContent,
|
|
175
|
+
reasoningContent: assistantReasoningContent,
|
|
176
|
+
tool_calls: finishChunk.tool_calls,
|
|
177
|
+
});
|
|
178
|
+
// 第七步:并行执行 tool
|
|
179
|
+
const harnessSnapshot = harness.snapshot();
|
|
180
|
+
const parallelResults = yield __await((0, loop_1.executeToolsInParallel)(config, harness, finishChunk.tool_calls, visibleToolNames, harnessSnapshot, turn));
|
|
181
|
+
// 将 tool result messages 加入 Messages,并 yield 事件
|
|
182
|
+
for (const result of parallelResults) {
|
|
183
|
+
messages.push({
|
|
184
|
+
role: 'tool',
|
|
185
|
+
content: result.content,
|
|
186
|
+
tool_call_id: result.toolCallId,
|
|
187
|
+
});
|
|
188
|
+
yield yield __await({
|
|
189
|
+
type: 'tool_result',
|
|
190
|
+
turn,
|
|
191
|
+
callId: result.toolCallId,
|
|
192
|
+
toolName: result.record.toolName,
|
|
193
|
+
content: result.content,
|
|
194
|
+
status: result.record.status,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// 检查是否有待审批的 tool
|
|
198
|
+
const pending = parallelResults.find((r) => r.pendingApproval);
|
|
199
|
+
if (pending === null || pending === void 0 ? void 0 : pending.pendingApproval) {
|
|
200
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
201
|
+
return yield __await({
|
|
202
|
+
messages,
|
|
203
|
+
harness: harness.getAll(),
|
|
204
|
+
finalContent: null,
|
|
205
|
+
turns: turn,
|
|
206
|
+
pendingApproval: {
|
|
207
|
+
ticketId: pending.pendingApproval.ticketId,
|
|
208
|
+
toolName: pending.pendingApproval.toolName,
|
|
209
|
+
args: pending.pendingApproval.args,
|
|
210
|
+
callId: pending.toolCallId,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
215
|
+
// 第八步:回到第一步继续下一轮
|
|
216
|
+
}
|
|
217
|
+
// Loop 被终止(Turn Policy 或硬限制)
|
|
218
|
+
const lastMessage = messages[messages.length - 1];
|
|
219
|
+
return yield __await({
|
|
220
|
+
messages,
|
|
221
|
+
harness: harness.getAll(),
|
|
222
|
+
finalContent: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant' ? lastMessage.content : null,
|
|
223
|
+
turns: turn,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export interface Message {
|
|
6
6
|
readonly role: 'system' | 'user' | 'assistant' | 'tool';
|
|
7
7
|
readonly content: string;
|
|
8
|
+
readonly reasoningContent?: string;
|
|
8
9
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
9
10
|
readonly tool_call_id?: string;
|
|
10
11
|
}
|
|
@@ -120,10 +121,9 @@ export interface Tool {
|
|
|
120
121
|
/**
|
|
121
122
|
* Turn Policy 决策结果。
|
|
122
123
|
*/
|
|
123
|
-
export
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
124
|
+
export type TurnPolicyResult = {
|
|
125
|
+
readonly injectMessage: string;
|
|
126
|
+
} | Record<string, never>;
|
|
127
127
|
/**
|
|
128
128
|
* 全局轮次策略。
|
|
129
129
|
*/
|
|
@@ -145,14 +145,31 @@ export interface LLMCallOptions {
|
|
|
145
145
|
readonly topP?: number;
|
|
146
146
|
readonly reasoningEffort?: 'low' | 'medium' | 'high';
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* LLM 流式输出片段。
|
|
150
|
+
*/
|
|
151
|
+
export type LLMStreamChunk = {
|
|
152
|
+
readonly type: 'content';
|
|
153
|
+
readonly delta: string;
|
|
154
|
+
} | {
|
|
155
|
+
readonly type: 'reasoning';
|
|
156
|
+
readonly delta: string;
|
|
157
|
+
} | {
|
|
158
|
+
readonly type: 'finish';
|
|
159
|
+
readonly content: string | null;
|
|
160
|
+
readonly reasoningContent?: string;
|
|
161
|
+
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
162
|
+
};
|
|
148
163
|
/**
|
|
149
164
|
* LLM 调用接口。由外部注入,Loop 内部不绑定具体 Provider。
|
|
150
165
|
*/
|
|
151
166
|
export interface LLMCaller {
|
|
152
167
|
readonly call: (messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, options?: LLMCallOptions) => Promise<{
|
|
153
168
|
readonly content: string | null;
|
|
169
|
+
readonly reasoningContent?: string;
|
|
154
170
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
155
171
|
}>;
|
|
172
|
+
readonly stream?: (messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, options?: LLMCallOptions) => AsyncGenerator<LLMStreamChunk, void, unknown>;
|
|
156
173
|
}
|
|
157
174
|
/**
|
|
158
175
|
* Loop 配置。
|
|
@@ -204,3 +221,32 @@ export interface LoopResult {
|
|
|
204
221
|
readonly turns: number;
|
|
205
222
|
readonly pendingApproval?: PendingApprovalInfo;
|
|
206
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* runLoopStream 的流式事件类型。
|
|
226
|
+
*/
|
|
227
|
+
export type LoopStreamChunk = {
|
|
228
|
+
readonly type: 'turn_start';
|
|
229
|
+
readonly turn: number;
|
|
230
|
+
} | {
|
|
231
|
+
readonly type: 'content';
|
|
232
|
+
readonly delta: string;
|
|
233
|
+
readonly turn: number;
|
|
234
|
+
} | {
|
|
235
|
+
readonly type: 'reasoning';
|
|
236
|
+
readonly delta: string;
|
|
237
|
+
readonly turn: number;
|
|
238
|
+
} | {
|
|
239
|
+
readonly type: 'tool_call';
|
|
240
|
+
readonly turn: number;
|
|
241
|
+
readonly toolCalls: ReadonlyArray<ToolCall>;
|
|
242
|
+
} | {
|
|
243
|
+
readonly type: 'tool_result';
|
|
244
|
+
readonly turn: number;
|
|
245
|
+
readonly callId: string;
|
|
246
|
+
readonly toolName: string;
|
|
247
|
+
readonly content: string;
|
|
248
|
+
readonly status: HarnessRecordStatus;
|
|
249
|
+
} | {
|
|
250
|
+
readonly type: 'turn_end';
|
|
251
|
+
readonly turn: number;
|
|
252
|
+
};
|
|
@@ -8,8 +8,10 @@ export declare class BailianProvider implements LLMProvider {
|
|
|
8
8
|
readonly config: ProviderConfig;
|
|
9
9
|
readonly name = "bailian";
|
|
10
10
|
private static readonly SUPPORTED_MODELS;
|
|
11
|
+
private static readonly MODEL_CAPABILITIES;
|
|
11
12
|
constructor(config: ProviderConfig);
|
|
12
13
|
supports(model: string): boolean;
|
|
14
|
+
adaptRequest(body: import('./index').OpenAIChatRequest): import('./index').OpenAIChatRequest;
|
|
13
15
|
/**
|
|
14
16
|
* 发送非流式 chat completion 请求到百炼服务。
|
|
15
17
|
* @param request - 标准化 LLM 请求
|
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
12
23
|
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
13
24
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
@@ -36,6 +47,25 @@ class BailianProvider {
|
|
|
36
47
|
supports(model) {
|
|
37
48
|
return BailianProvider.SUPPORTED_MODELS.has(model);
|
|
38
49
|
}
|
|
50
|
+
adaptRequest(body) {
|
|
51
|
+
var _a, _b, _c;
|
|
52
|
+
const caps = (_a = BailianProvider.MODEL_CAPABILITIES[body.model]) !== null && _a !== void 0 ? _a : { jsonMode: false, reasoningEffort: false };
|
|
53
|
+
if (body.reasoning_effort !== undefined && !caps.reasoningEffort) {
|
|
54
|
+
throw new Error(`[adapt] Model "${body.model}" does not support reasoning_effort`);
|
|
55
|
+
}
|
|
56
|
+
if (((_b = body.response_format) === null || _b === void 0 ? void 0 : _b.type) === 'json_object' && !caps.jsonMode) {
|
|
57
|
+
throw new Error(`[adapt] Model "${body.model}" does not support response_format json_object`);
|
|
58
|
+
}
|
|
59
|
+
// 百炼平台:Qwen 系列通过 enable_thinking 实现 reasoning_effort
|
|
60
|
+
if (body.reasoning_effort !== undefined) {
|
|
61
|
+
if (['qwen-plus', 'qwen-turbo', 'qwen3.7-max'].includes(body.model)) {
|
|
62
|
+
const { reasoning_effort } = body, rest = __rest(body, ["reasoning_effort"]);
|
|
63
|
+
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
|
+
// deepseek-v4-pro / deepseek-v4-flash / kimi-k2.6 原生支持,直接传递
|
|
66
|
+
}
|
|
67
|
+
return body;
|
|
68
|
+
}
|
|
39
69
|
/**
|
|
40
70
|
* 发送非流式 chat completion 请求到百炼服务。
|
|
41
71
|
* @param request - 标准化 LLM 请求
|
|
@@ -45,11 +75,12 @@ class BailianProvider {
|
|
|
45
75
|
generate(request) {
|
|
46
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
77
|
var _a;
|
|
48
|
-
const body =
|
|
78
|
+
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: false }));
|
|
49
79
|
const data = yield (0, index_1.callChatCompletions)(this.config.baseUrl, this.config.apiKey, body, this.name);
|
|
50
80
|
const choice = data.choices[0];
|
|
51
81
|
return {
|
|
52
82
|
content: choice.message.content,
|
|
83
|
+
reasoningContent: choice.message.reasoning_content,
|
|
53
84
|
usage: data.usage
|
|
54
85
|
? {
|
|
55
86
|
promptTokens: data.usage.prompt_tokens,
|
|
@@ -71,7 +102,7 @@ class BailianProvider {
|
|
|
71
102
|
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
72
103
|
var _a, _b;
|
|
73
104
|
const url = `${this.config.baseUrl}/chat/completions`;
|
|
74
|
-
const body =
|
|
105
|
+
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true }));
|
|
75
106
|
let response;
|
|
76
107
|
try {
|
|
77
108
|
response = yield __await(fetch(url, {
|
|
@@ -122,6 +153,9 @@ class BailianProvider {
|
|
|
122
153
|
if (!choice)
|
|
123
154
|
continue;
|
|
124
155
|
const delta = choice.delta;
|
|
156
|
+
if (delta.reasoning_content) {
|
|
157
|
+
yield yield __await({ type: 'reasoning', delta: delta.reasoning_content });
|
|
158
|
+
}
|
|
125
159
|
if (delta.content) {
|
|
126
160
|
yield yield __await({ type: 'content', delta: delta.content });
|
|
127
161
|
}
|
|
@@ -173,3 +207,15 @@ BailianProvider.SUPPORTED_MODELS = new Set([
|
|
|
173
207
|
'glm-5.1',
|
|
174
208
|
'qwen-vl-plus',
|
|
175
209
|
]);
|
|
210
|
+
BailianProvider.MODEL_CAPABILITIES = {
|
|
211
|
+
'qwen-max': { jsonMode: true, reasoningEffort: false },
|
|
212
|
+
'qwen3.7-max': { jsonMode: true, reasoningEffort: true },
|
|
213
|
+
'qwen-plus': { jsonMode: true, reasoningEffort: true },
|
|
214
|
+
'qwen-turbo': { jsonMode: true, reasoningEffort: true },
|
|
215
|
+
'qwq-plus': { jsonMode: true, reasoningEffort: false },
|
|
216
|
+
'deepseek-v4-pro': { jsonMode: true, reasoningEffort: true },
|
|
217
|
+
'deepseek-v4-flash': { jsonMode: true, reasoningEffort: true },
|
|
218
|
+
'kimi-k2.6': { jsonMode: true, reasoningEffort: true },
|
|
219
|
+
'glm-5.1': { jsonMode: false, reasoningEffort: false },
|
|
220
|
+
'qwen-vl-plus': { jsonMode: true, reasoningEffort: false },
|
|
221
|
+
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export interface OpenAIChatMessage {
|
|
6
6
|
role: string;
|
|
7
7
|
content: string | null;
|
|
8
|
+
reasoning_content?: string;
|
|
8
9
|
tool_calls?: Array<{
|
|
9
10
|
id: string;
|
|
10
11
|
type: 'function';
|
|
@@ -35,11 +36,14 @@ export interface OpenAIChatRequest {
|
|
|
35
36
|
};
|
|
36
37
|
stream?: boolean;
|
|
37
38
|
reasoning_effort?: 'low' | 'medium' | 'high';
|
|
39
|
+
/** 百炼等平台的扩展参数 */
|
|
40
|
+
extra_body?: Record<string, unknown>;
|
|
38
41
|
}
|
|
39
42
|
export interface OpenAIChatResponse {
|
|
40
43
|
choices: Array<{
|
|
41
44
|
message: {
|
|
42
45
|
content: string | null;
|
|
46
|
+
reasoning_content?: string;
|
|
43
47
|
tool_calls?: Array<{
|
|
44
48
|
id: string;
|
|
45
49
|
type: 'function';
|
|
@@ -64,14 +68,9 @@ export interface OpenAIChatResponse {
|
|
|
64
68
|
* 业务层(请求体组装、结果转换)由调用方负责。
|
|
65
69
|
*/
|
|
66
70
|
export declare function callChatCompletions(baseUrl: string, apiKey: string, body: OpenAIChatRequest, errorPrefix?: string): Promise<OpenAIChatResponse>;
|
|
67
|
-
/**
|
|
68
|
-
* 根据模型能力校验并适配 OpenAI 请求体。
|
|
69
|
-
* 若调用方明确要求了模型不支持的能力,直接抛异常。
|
|
70
|
-
*/
|
|
71
|
-
export declare function adaptRequestForModel(body: OpenAIChatRequest): OpenAIChatRequest;
|
|
72
71
|
/** 模型枚举与注册表 */
|
|
73
72
|
export type { Model, ModelConfig, ModelCapabilities } from './models';
|
|
74
|
-
export { MODEL_REGISTRY
|
|
73
|
+
export { MODEL_REGISTRY } from './models';
|
|
75
74
|
/** 标准化类型 */
|
|
76
75
|
export type { Message, LLMRequest, LLMResponse, ProviderConfig, StreamChunk } from './types';
|
|
77
76
|
/** Provider 接口与实现 */
|
|
@@ -13,9 +13,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
13
13
|
});
|
|
14
14
|
};
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.BailianProvider = exports.
|
|
16
|
+
exports.BailianProvider = exports.MODEL_REGISTRY = void 0;
|
|
17
17
|
exports.callChatCompletions = callChatCompletions;
|
|
18
|
-
exports.adaptRequestForModel = adaptRequestForModel;
|
|
19
18
|
/**
|
|
20
19
|
* 发送 OpenAI 兼容的 chat completions 请求。
|
|
21
20
|
* 只做 HTTP 层:构造请求、fetch、错误处理、基础解析。
|
|
@@ -57,25 +56,7 @@ function callChatCompletions(baseUrl_1, apiKey_1, body_1) {
|
|
|
57
56
|
return data;
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* 根据模型能力校验并适配 OpenAI 请求体。
|
|
63
|
-
* 若调用方明确要求了模型不支持的能力,直接抛异常。
|
|
64
|
-
*/
|
|
65
|
-
function adaptRequestForModel(body) {
|
|
66
|
-
var _a;
|
|
67
|
-
const caps = (0, models_1.getModelCapabilities)(body.model);
|
|
68
|
-
if (body.reasoning_effort !== undefined && !caps.reasoningEffort) {
|
|
69
|
-
throw new Error(`[adapt] Model "${body.model}" does not support reasoning_effort`);
|
|
70
|
-
}
|
|
71
|
-
if (((_a = body.response_format) === null || _a === void 0 ? void 0 : _a.type) === 'json_object' && !caps.jsonMode) {
|
|
72
|
-
throw new Error(`[adapt] Model "${body.model}" does not support response_format json_object`);
|
|
73
|
-
}
|
|
74
|
-
return body;
|
|
75
|
-
}
|
|
76
|
-
var models_2 = require("./models");
|
|
77
|
-
Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: function () { return models_2.MODEL_REGISTRY; } });
|
|
78
|
-
Object.defineProperty(exports, "MODEL_CAPABILITIES", { enumerable: true, get: function () { return models_2.MODEL_CAPABILITIES; } });
|
|
79
|
-
Object.defineProperty(exports, "getModelCapabilities", { enumerable: true, get: function () { return models_2.getModelCapabilities; } });
|
|
59
|
+
var models_1 = require("./models");
|
|
60
|
+
Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: function () { return models_1.MODEL_REGISTRY; } });
|
|
80
61
|
var bailian_1 = require("./bailian");
|
|
81
62
|
Object.defineProperty(exports, "BailianProvider", { enumerable: true, get: function () { return bailian_1.BailianProvider; } });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LLMRequest, LLMResponse, ProviderConfig, StreamChunk } from './types';
|
|
2
|
+
import type { OpenAIChatRequest } from './index';
|
|
2
3
|
/**
|
|
3
4
|
* LLM Provider 抽象接口。每个 Provider 实现负责对接具体的模型服务
|
|
4
5
|
*(如百炼、OpenAI、Anthropic 等),处理 HTTP 请求、流式解析和错误转换。
|
|
@@ -15,6 +16,13 @@ export interface LLMProvider {
|
|
|
15
16
|
* @returns true 表示支持,false 表示不支持(将自动轮询下一个候选)
|
|
16
17
|
*/
|
|
17
18
|
supports(model: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* 根据当前 Provider 的模型能力校验并适配 OpenAI 请求体。
|
|
21
|
+
* @param body - OpenAI 兼容请求体
|
|
22
|
+
* @returns 适配后的请求体
|
|
23
|
+
* @throws 模型不支持请求中指定的能力时抛异常
|
|
24
|
+
*/
|
|
25
|
+
adaptRequest(body: OpenAIChatRequest): OpenAIChatRequest;
|
|
18
26
|
/**
|
|
19
27
|
* 发送非流式请求,返回完整的模型响应。
|
|
20
28
|
* @param request - LLM 请求参数
|
|
@@ -16,13 +16,11 @@ export declare const MODEL_REGISTRY: Readonly<Record<Model, ReadonlyArray<ModelC
|
|
|
16
16
|
export interface ModelCapabilities {
|
|
17
17
|
/** 是否原生支持 response_format: { type: 'json_object' } */
|
|
18
18
|
readonly jsonMode: boolean;
|
|
19
|
-
/**
|
|
19
|
+
/** 是否支持 reasoning_effort 参数 */
|
|
20
20
|
readonly reasoningEffort: boolean;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* 模型能力矩阵。
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** 获取指定模型的能力配置 */
|
|
28
|
-
export declare function getModelCapabilities(model: string): ModelCapabilities;
|
|
24
|
+
* 由各 Provider 自行维护,不再全局定义。
|
|
25
|
+
* 同一模型在不同 Provider 下可能有不同的能力表现。
|
|
26
|
+
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.getModelCapabilities = getModelCapabilities;
|
|
3
|
+
exports.MODEL_REGISTRY = void 0;
|
|
5
4
|
/**
|
|
6
5
|
* 模型注册表。每个模型对应一个或多个 Provider 候选,按优先级排序。
|
|
7
6
|
* 当首选 Provider 失败时,Predictor 按此表顺序尝试下一个。
|
|
@@ -19,22 +18,6 @@ exports.MODEL_REGISTRY = {
|
|
|
19
18
|
};
|
|
20
19
|
/**
|
|
21
20
|
* 模型能力矩阵。
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'qwen-max': { jsonMode: true, reasoningEffort: false },
|
|
26
|
-
'qwen3.7-max': { jsonMode: true, reasoningEffort: false },
|
|
27
|
-
'qwen-plus': { jsonMode: true, reasoningEffort: false },
|
|
28
|
-
'qwen-turbo': { jsonMode: true, reasoningEffort: false },
|
|
29
|
-
'qwq-plus': { jsonMode: true, reasoningEffort: false },
|
|
30
|
-
'deepseek-v4-pro': { jsonMode: true, reasoningEffort: false },
|
|
31
|
-
'deepseek-v4-flash': { jsonMode: true, reasoningEffort: false },
|
|
32
|
-
'kimi-k2.6': { jsonMode: true, reasoningEffort: false },
|
|
33
|
-
'glm-5.1': { jsonMode: false, reasoningEffort: false },
|
|
34
|
-
'qwen-vl-plus': { jsonMode: true, reasoningEffort: false },
|
|
35
|
-
};
|
|
36
|
-
/** 获取指定模型的能力配置 */
|
|
37
|
-
function getModelCapabilities(model) {
|
|
38
|
-
var _a;
|
|
39
|
-
return (_a = exports.MODEL_CAPABILITIES[model]) !== null && _a !== void 0 ? _a : { jsonMode: false, reasoningEffort: false };
|
|
40
|
-
}
|
|
21
|
+
* 由各 Provider 自行维护,不再全局定义。
|
|
22
|
+
* 同一模型在不同 Provider 下可能有不同的能力表现。
|
|
23
|
+
*/
|
|
@@ -22,6 +22,7 @@ export interface LLMRequest {
|
|
|
22
22
|
*/
|
|
23
23
|
export interface LLMResponse {
|
|
24
24
|
readonly content: string | null;
|
|
25
|
+
readonly reasoningContent?: string;
|
|
25
26
|
readonly usage?: {
|
|
26
27
|
readonly promptTokens: number;
|
|
27
28
|
readonly completionTokens: number;
|
|
@@ -35,6 +36,9 @@ export interface LLMResponse {
|
|
|
35
36
|
export type StreamChunk = {
|
|
36
37
|
readonly type: 'content';
|
|
37
38
|
readonly delta: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: 'reasoning';
|
|
41
|
+
readonly delta: string;
|
|
38
42
|
} | {
|
|
39
43
|
readonly type: 'finish';
|
|
40
44
|
usage?: LLMResponse['usage'];
|