@keo-ai/axiom 0.1.6 → 0.1.8
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 +112 -15
- 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 +7 -12
- package/dist/function_call_loop/provider.d.ts +7 -1
- package/dist/function_call_loop/provider.js +175 -5
- package/dist/function_call_loop/stream.d.ts +24 -0
- package/dist/function_call_loop/stream.js +218 -0
- package/dist/function_call_loop/types.d.ts +40 -9
- package/dist/llm_provider/bailian.d.ts +2 -0
- package/dist/llm_provider/bailian.js +46 -4
- package/dist/llm_provider/index.d.ts +4 -1
- package/dist/llm_provider/llm.d.ts +8 -0
- package/dist/llm_provider/models.d.ts +12 -0
- package/dist/llm_provider/models.js +5 -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,99 @@ 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 'content':
|
|
301
|
+
// 实时流式输出 content(支持 reasoning 内容)
|
|
302
|
+
process.stdout.write(chunk.delta);
|
|
303
|
+
break;
|
|
304
|
+
case 'tool_call':
|
|
305
|
+
console.log('\n[Tool Call]', chunk.toolCalls.map(t => t.function.name));
|
|
306
|
+
break;
|
|
307
|
+
case 'tool_result':
|
|
308
|
+
console.log(`[Result] ${chunk.toolName}: ${chunk.content} (${chunk.status})`);
|
|
309
|
+
break;
|
|
310
|
+
case 'turn_end':
|
|
311
|
+
console.log(`\n--- Turn ${chunk.turn} End ---`);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**获取最终返回值**:流结束后需要通过手动驱动迭代器获取 `LoopResult`:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const stream = FunctionCallLoop.runLoopStream(config);
|
|
321
|
+
|
|
322
|
+
const chunks: FunctionCallLoop.LoopStreamChunk[] = [];
|
|
323
|
+
let result: FunctionCallLoop.LoopResult | undefined;
|
|
324
|
+
|
|
325
|
+
while (true) {
|
|
326
|
+
const { value, done } = await stream.next();
|
|
327
|
+
if (done) {
|
|
328
|
+
result = value as FunctionCallLoop.LoopResult;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
chunks.push(value as FunctionCallLoop.LoopStreamChunk);
|
|
332
|
+
// 实时消费...
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log('Final:', result!.finalContent);
|
|
336
|
+
console.log('Turns:', result!.turns);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### 流式事件类型
|
|
340
|
+
|
|
341
|
+
| 事件 | 触发时机 | 包含字段 |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| `turn_start` | 新一轮开始 | `turn` |
|
|
344
|
+
| `content` | LLM 输出 content 增量 | `delta`, `turn` |
|
|
345
|
+
| `tool_call` | LLM 决定调用 tool(流结束、完整 tool_calls 解析完成) | `toolCalls`, `turn` |
|
|
346
|
+
| `tool_result` | tool 执行完成 | `callId`, `toolName`, `content`, `status`, `turn` |
|
|
347
|
+
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn` |
|
|
348
|
+
|
|
349
|
+
#### 流式 vs 非流式的选择
|
|
350
|
+
|
|
351
|
+
| 场景 | 推荐方式 |
|
|
352
|
+
|---|---|
|
|
353
|
+
| 需要实时展示 LLM 输出(打字机效果) | `runLoopStream` |
|
|
354
|
+
| 需要展示模型 reasoning 过程 | `runLoopStream`(content 增量中包含 reasoning) |
|
|
355
|
+
| 后台静默执行,只关心最终结果 | `runLoop` |
|
|
356
|
+
| 低延迟、简单场景 | `runLoop` |
|
|
357
|
+
|
|
358
|
+
> **注意**:`runLoopStream` 需要 `LLMCaller` 支持 `stream` 方法。使用默认的 `createLLMCaller` 时自动支持。如果注入自定义 `llmCaller`,请确保实现了 `stream` 接口。
|
|
359
|
+
|
|
263
360
|
### 生命周期事件
|
|
264
361
|
|
|
265
362
|
Loop 每处理一个 tool call,按顺序抛出两个事件:
|
|
@@ -497,7 +594,9 @@ const result = await promise;
|
|
|
497
594
|
|
|
498
595
|
### 配置项
|
|
499
596
|
|
|
500
|
-
#### `runLoop` 配置
|
|
597
|
+
#### `runLoop` / `runLoopStream` 配置
|
|
598
|
+
|
|
599
|
+
两个函数共享同一套配置(`LoopConfig`),`runLoopStream` 额外要求 `llmCaller` 支持 `stream` 方法:
|
|
501
600
|
|
|
502
601
|
**对话入口**
|
|
503
602
|
|
|
@@ -528,8 +627,7 @@ const result = await promise;
|
|
|
528
627
|
| `maxTokens` | `number` | — | 单次 LLM 调用的最大输出 token 数 |
|
|
529
628
|
| `temperature` | `number` | — | 采样温度,范围 0~2 |
|
|
530
629
|
| `topP` | `number` | — | 核采样概率阈值,范围 0~1 |
|
|
531
|
-
| `reasoningEffort` | `'low' \| 'medium' \| 'high'` | — |
|
|
532
|
-
| `responseFormat` | `'text' \| 'json'` | — | 响应格式。`json` 时模型输出会被内部 `JSON.parse`,解析结果存到 `parsedContent` |
|
|
630
|
+
| `reasoningEffort` | `'low' \| 'medium' \| 'high'` | — | 推理深度。`low` 关闭推理,`medium`/`high` 开启推理。具体支持情况见「模型列表」 |
|
|
533
631
|
|
|
534
632
|
### 返回结果
|
|
535
633
|
|
|
@@ -537,9 +635,8 @@ const result = await promise;
|
|
|
537
635
|
interface LoopResult {
|
|
538
636
|
messages: Message[]; // 完整的对话历史
|
|
539
637
|
harness: HarnessRecord[]; // 执行历史
|
|
540
|
-
finalContent: string | null; //
|
|
638
|
+
finalContent: string | null; // 最终回复内容
|
|
541
639
|
turns: number; // 实际执行轮数
|
|
542
|
-
parsedContent?: unknown; // 仅当 responseFormat === 'json' 时存在,JSON.parse 后的结果
|
|
543
640
|
}
|
|
544
641
|
```
|
|
545
642
|
|
|
@@ -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
|
/**
|
|
@@ -81,7 +86,6 @@ function runLoop(config) {
|
|
|
81
86
|
maxTokens: config.maxTokens,
|
|
82
87
|
topP: config.topP,
|
|
83
88
|
reasoningEffort: config.reasoningEffort,
|
|
84
|
-
responseFormat: config.responseFormat,
|
|
85
89
|
});
|
|
86
90
|
// 第六步:检查 LLM 回复
|
|
87
91
|
if (!llmResponse.tool_calls || llmResponse.tool_calls.length === 0) {
|
|
@@ -89,21 +93,12 @@ function runLoop(config) {
|
|
|
89
93
|
role: 'assistant',
|
|
90
94
|
content: (_f = llmResponse.content) !== null && _f !== void 0 ? _f : '',
|
|
91
95
|
});
|
|
92
|
-
|
|
96
|
+
return {
|
|
93
97
|
messages,
|
|
94
98
|
harness: harness.getAll(),
|
|
95
99
|
finalContent: llmResponse.content,
|
|
96
100
|
turns: turn,
|
|
97
101
|
};
|
|
98
|
-
if (config.responseFormat === 'json' && llmResponse.content) {
|
|
99
|
-
try {
|
|
100
|
-
Object.assign(result, { parsedContent: JSON.parse(llmResponse.content) });
|
|
101
|
-
}
|
|
102
|
-
catch (_h) {
|
|
103
|
-
// JSON 解析失败时保持 parsedContent 未定义,finalContent 仍为原始字符串
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
102
|
}
|
|
108
103
|
// 有 tool_calls,将 assistant message 加入 Messages
|
|
109
104
|
messages.push({
|
|
@@ -169,7 +164,7 @@ function runTurnPolicy(config, harness, turn) {
|
|
|
169
164
|
if (turn < maxTurns - 1) {
|
|
170
165
|
return {};
|
|
171
166
|
}
|
|
172
|
-
if (turn === maxTurns - 1) {
|
|
167
|
+
if (turn === maxTurns - 1 && config.warningMessage) {
|
|
173
168
|
return {
|
|
174
169
|
injectMessage: config.warningMessage,
|
|
175
170
|
};
|
|
@@ -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;
|
|
@@ -16,4 +16,10 @@ export declare function createLLMCaller(options: ProviderOptions): {
|
|
|
16
16
|
readonly content: string | null;
|
|
17
17
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
18
18
|
}>;
|
|
19
|
+
/**
|
|
20
|
+
* 流式调用 LLM,支持 content 和 tool_calls 的增量输出。
|
|
21
|
+
*
|
|
22
|
+
* @yields 内容片段(`content`)或结束标记(`finish`,携带完整 content 和 tool_calls)
|
|
23
|
+
*/
|
|
24
|
+
stream(messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, callOptions?: LLMCallOptions): AsyncGenerator<LLMStreamChunk, void, unknown>;
|
|
19
25
|
};
|
|
@@ -8,9 +8,23 @@ 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;
|
|
@@ -43,21 +57,26 @@ function toOpenAITools(tools) {
|
|
|
43
57
|
* 基于 OpenAI 协议的 LLM 调用器。
|
|
44
58
|
*/
|
|
45
59
|
function createLLMCaller(options) {
|
|
60
|
+
const provider = new bailian_1.BailianProvider({
|
|
61
|
+
name: 'bailian',
|
|
62
|
+
apiKey: options.apiKey,
|
|
63
|
+
baseUrl: options.baseUrl,
|
|
64
|
+
defaultModel: options.defaultModel,
|
|
65
|
+
});
|
|
46
66
|
return {
|
|
47
67
|
call(messages, tools, callOptions) {
|
|
48
68
|
return __awaiter(this, void 0, void 0, function* () {
|
|
49
69
|
var _a, _b;
|
|
50
|
-
const
|
|
70
|
+
const body = provider.adaptRequest({
|
|
51
71
|
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
52
72
|
messages: toOpenAIMessages(messages),
|
|
53
73
|
temperature: callOptions === null || callOptions === void 0 ? void 0 : callOptions.temperature,
|
|
54
74
|
max_tokens: callOptions === null || callOptions === void 0 ? void 0 : callOptions.maxTokens,
|
|
55
75
|
top_p: callOptions === null || callOptions === void 0 ? void 0 : callOptions.topP,
|
|
56
76
|
tools: tools.length > 0 ? toOpenAITools(tools) : undefined,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}, 'llm');
|
|
77
|
+
reasoning_effort: callOptions === null || callOptions === void 0 ? void 0 : callOptions.reasoningEffort,
|
|
78
|
+
});
|
|
79
|
+
const data = yield (0, llm_provider_1.callChatCompletions)(options.baseUrl, options.apiKey, body, 'llm');
|
|
61
80
|
const choice = data.choices[0];
|
|
62
81
|
return {
|
|
63
82
|
content: choice.message.content,
|
|
@@ -72,5 +91,156 @@ function createLLMCaller(options) {
|
|
|
72
91
|
};
|
|
73
92
|
});
|
|
74
93
|
},
|
|
94
|
+
/**
|
|
95
|
+
* 流式调用 LLM,支持 content 和 tool_calls 的增量输出。
|
|
96
|
+
*
|
|
97
|
+
* @yields 内容片段(`content`)或结束标记(`finish`,携带完整 content 和 tool_calls)
|
|
98
|
+
*/
|
|
99
|
+
stream(messages, tools, callOptions) {
|
|
100
|
+
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
101
|
+
var _a, _b, _c, _d, _e;
|
|
102
|
+
const body = provider.adaptRequest({
|
|
103
|
+
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
104
|
+
messages: toOpenAIMessages(messages),
|
|
105
|
+
temperature: callOptions === null || callOptions === void 0 ? void 0 : callOptions.temperature,
|
|
106
|
+
max_tokens: callOptions === null || callOptions === void 0 ? void 0 : callOptions.maxTokens,
|
|
107
|
+
top_p: callOptions === null || callOptions === void 0 ? void 0 : callOptions.topP,
|
|
108
|
+
tools: tools.length > 0 ? toOpenAITools(tools) : undefined,
|
|
109
|
+
reasoning_effort: callOptions === null || callOptions === void 0 ? void 0 : callOptions.reasoningEffort,
|
|
110
|
+
stream: true,
|
|
111
|
+
});
|
|
112
|
+
const url = `${options.baseUrl}/chat/completions`;
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
response = yield __await(fetch(url, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
throw new Error(`[llm] ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
126
|
+
}
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
const text = yield __await(response.text());
|
|
129
|
+
throw new Error(`[llm] HTTP ${response.status}: ${text}`);
|
|
130
|
+
}
|
|
131
|
+
if (!response.body) {
|
|
132
|
+
throw new Error(`[llm] Response body is null`);
|
|
133
|
+
}
|
|
134
|
+
const reader = response.body.getReader();
|
|
135
|
+
const decoder = new TextDecoder();
|
|
136
|
+
let buffer = '';
|
|
137
|
+
// 用于增量收集 tool_calls
|
|
138
|
+
const toolCallAccumulators = [];
|
|
139
|
+
let fullContent = '';
|
|
140
|
+
let hasYieldedFinish = false;
|
|
141
|
+
try {
|
|
142
|
+
while (true) {
|
|
143
|
+
const { done, value } = yield __await(reader.read());
|
|
144
|
+
if (done)
|
|
145
|
+
break;
|
|
146
|
+
buffer += decoder.decode(value, { stream: true });
|
|
147
|
+
const lines = buffer.split('\n');
|
|
148
|
+
buffer = (_b = lines.pop()) !== null && _b !== void 0 ? _b : '';
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const trimmed = line.trim();
|
|
151
|
+
if (!trimmed || !trimmed.startsWith('data: '))
|
|
152
|
+
continue;
|
|
153
|
+
const data = trimmed.slice(6);
|
|
154
|
+
if (data === '[DONE]')
|
|
155
|
+
continue;
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(data);
|
|
159
|
+
}
|
|
160
|
+
catch (_f) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const choice = (_c = parsed.choices) === null || _c === void 0 ? void 0 : _c[0];
|
|
164
|
+
if (!choice)
|
|
165
|
+
continue;
|
|
166
|
+
const delta = choice.delta;
|
|
167
|
+
// 收集 content 增量
|
|
168
|
+
if (delta.content) {
|
|
169
|
+
fullContent += delta.content;
|
|
170
|
+
yield yield __await({ type: 'content', delta: delta.content });
|
|
171
|
+
}
|
|
172
|
+
// 收集 tool_calls 增量
|
|
173
|
+
if (delta.tool_calls) {
|
|
174
|
+
for (const tcDelta of delta.tool_calls) {
|
|
175
|
+
const index = tcDelta.index;
|
|
176
|
+
if (!toolCallAccumulators[index]) {
|
|
177
|
+
toolCallAccumulators[index] = {
|
|
178
|
+
function: { arguments: '' },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const acc = toolCallAccumulators[index];
|
|
182
|
+
if (tcDelta.id)
|
|
183
|
+
acc.id = tcDelta.id;
|
|
184
|
+
if (tcDelta.type)
|
|
185
|
+
acc.type = tcDelta.type;
|
|
186
|
+
if ((_d = tcDelta.function) === null || _d === void 0 ? void 0 : _d.name) {
|
|
187
|
+
acc.function.name = tcDelta.function.name;
|
|
188
|
+
}
|
|
189
|
+
if ((_e = tcDelta.function) === null || _e === void 0 ? void 0 : _e.arguments) {
|
|
190
|
+
acc.function.arguments += tcDelta.function.arguments;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// 流结束
|
|
195
|
+
if (choice.finish_reason) {
|
|
196
|
+
const toolCalls = [];
|
|
197
|
+
for (const acc of toolCallAccumulators) {
|
|
198
|
+
if (acc.id && acc.type && acc.function.name) {
|
|
199
|
+
toolCalls.push({
|
|
200
|
+
id: acc.id,
|
|
201
|
+
type: acc.type,
|
|
202
|
+
function: {
|
|
203
|
+
name: acc.function.name,
|
|
204
|
+
arguments: acc.function.arguments,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
yield yield __await({
|
|
210
|
+
type: 'finish',
|
|
211
|
+
content: fullContent || null,
|
|
212
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
213
|
+
});
|
|
214
|
+
hasYieldedFinish = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// 兜底:如果流正常结束但没有收到 finish_reason,也 yield 一个 finish
|
|
219
|
+
if (!hasYieldedFinish) {
|
|
220
|
+
const toolCalls = [];
|
|
221
|
+
for (const acc of toolCallAccumulators) {
|
|
222
|
+
if (acc.id && acc.type && acc.function.name) {
|
|
223
|
+
toolCalls.push({
|
|
224
|
+
id: acc.id,
|
|
225
|
+
type: acc.type,
|
|
226
|
+
function: {
|
|
227
|
+
name: acc.function.name,
|
|
228
|
+
arguments: acc.function.arguments,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
yield yield __await({
|
|
234
|
+
type: 'finish',
|
|
235
|
+
content: fullContent || null,
|
|
236
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
reader.releaseLock();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
},
|
|
75
245
|
};
|
|
76
246
|
}
|
|
@@ -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,218 @@
|
|
|
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;
|
|
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 finishChunk = null;
|
|
112
|
+
const stream = llmCaller.stream(messagesForLLM, toolDefinitions, {
|
|
113
|
+
model: config.model,
|
|
114
|
+
temperature: config.temperature,
|
|
115
|
+
maxTokens: config.maxTokens,
|
|
116
|
+
topP: config.topP,
|
|
117
|
+
reasoningEffort: config.reasoningEffort,
|
|
118
|
+
});
|
|
119
|
+
try {
|
|
120
|
+
for (var _k = 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; _k = true) {
|
|
121
|
+
_c = stream_1_1.value;
|
|
122
|
+
_k = false;
|
|
123
|
+
const chunk = _c;
|
|
124
|
+
if (chunk.type === 'content') {
|
|
125
|
+
fullContent += chunk.delta;
|
|
126
|
+
yield yield __await({ type: 'content', delta: chunk.delta, turn });
|
|
127
|
+
}
|
|
128
|
+
else if (chunk.type === 'finish') {
|
|
129
|
+
finishChunk = chunk;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
134
|
+
finally {
|
|
135
|
+
try {
|
|
136
|
+
if (!_k && !_a && (_b = stream_1.return)) yield __await(_b.call(stream_1));
|
|
137
|
+
}
|
|
138
|
+
finally { if (e_1) throw e_1.error; }
|
|
139
|
+
}
|
|
140
|
+
if (!finishChunk) {
|
|
141
|
+
throw new Error('LLM stream ended without a finish chunk');
|
|
142
|
+
}
|
|
143
|
+
// 使用 finish chunk 中的 content(优先级更高,可能包含完整格式化内容)
|
|
144
|
+
const assistantContent = (_j = finishChunk.content) !== null && _j !== void 0 ? _j : fullContent;
|
|
145
|
+
// 第六步:检查 LLM 回复
|
|
146
|
+
if (!finishChunk.tool_calls || finishChunk.tool_calls.length === 0) {
|
|
147
|
+
messages.push({
|
|
148
|
+
role: 'assistant',
|
|
149
|
+
content: assistantContent,
|
|
150
|
+
});
|
|
151
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
152
|
+
return yield __await({
|
|
153
|
+
messages,
|
|
154
|
+
harness: harness.getAll(),
|
|
155
|
+
finalContent: assistantContent,
|
|
156
|
+
turns: turn,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// 有 tool_calls,将 assistant message 加入 Messages
|
|
160
|
+
yield yield __await({
|
|
161
|
+
type: 'tool_call',
|
|
162
|
+
turn,
|
|
163
|
+
toolCalls: finishChunk.tool_calls,
|
|
164
|
+
});
|
|
165
|
+
messages.push({
|
|
166
|
+
role: 'assistant',
|
|
167
|
+
content: assistantContent,
|
|
168
|
+
tool_calls: finishChunk.tool_calls,
|
|
169
|
+
});
|
|
170
|
+
// 第七步:并行执行 tool
|
|
171
|
+
const harnessSnapshot = harness.snapshot();
|
|
172
|
+
const parallelResults = yield __await((0, loop_1.executeToolsInParallel)(config, harness, finishChunk.tool_calls, visibleToolNames, harnessSnapshot, turn));
|
|
173
|
+
// 将 tool result messages 加入 Messages,并 yield 事件
|
|
174
|
+
for (const result of parallelResults) {
|
|
175
|
+
messages.push({
|
|
176
|
+
role: 'tool',
|
|
177
|
+
content: result.content,
|
|
178
|
+
tool_call_id: result.toolCallId,
|
|
179
|
+
});
|
|
180
|
+
yield yield __await({
|
|
181
|
+
type: 'tool_result',
|
|
182
|
+
turn,
|
|
183
|
+
callId: result.toolCallId,
|
|
184
|
+
toolName: result.record.toolName,
|
|
185
|
+
content: result.content,
|
|
186
|
+
status: result.record.status,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// 检查是否有待审批的 tool
|
|
190
|
+
const pending = parallelResults.find((r) => r.pendingApproval);
|
|
191
|
+
if (pending === null || pending === void 0 ? void 0 : pending.pendingApproval) {
|
|
192
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
193
|
+
return yield __await({
|
|
194
|
+
messages,
|
|
195
|
+
harness: harness.getAll(),
|
|
196
|
+
finalContent: null,
|
|
197
|
+
turns: turn,
|
|
198
|
+
pendingApproval: {
|
|
199
|
+
ticketId: pending.pendingApproval.ticketId,
|
|
200
|
+
toolName: pending.pendingApproval.toolName,
|
|
201
|
+
args: pending.pendingApproval.args,
|
|
202
|
+
callId: pending.toolCallId,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
yield yield __await({ type: 'turn_end', turn });
|
|
207
|
+
// 第八步:回到第一步继续下一轮
|
|
208
|
+
}
|
|
209
|
+
// Loop 被终止(Turn Policy 或硬限制)
|
|
210
|
+
const lastMessage = messages[messages.length - 1];
|
|
211
|
+
return yield __await({
|
|
212
|
+
messages,
|
|
213
|
+
harness: harness.getAll(),
|
|
214
|
+
finalContent: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant' ? lastMessage.content : null,
|
|
215
|
+
turns: turn,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
@@ -120,10 +120,9 @@ export interface Tool {
|
|
|
120
120
|
/**
|
|
121
121
|
* Turn Policy 决策结果。
|
|
122
122
|
*/
|
|
123
|
-
export
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
123
|
+
export type TurnPolicyResult = {
|
|
124
|
+
readonly injectMessage: string;
|
|
125
|
+
} | Record<string, never>;
|
|
127
126
|
/**
|
|
128
127
|
* 全局轮次策略。
|
|
129
128
|
*/
|
|
@@ -144,8 +143,18 @@ export interface LLMCallOptions {
|
|
|
144
143
|
readonly maxTokens?: number;
|
|
145
144
|
readonly topP?: number;
|
|
146
145
|
readonly reasoningEffort?: 'low' | 'medium' | 'high';
|
|
147
|
-
readonly responseFormat?: 'text' | 'json';
|
|
148
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* LLM 流式输出片段。
|
|
149
|
+
*/
|
|
150
|
+
export type LLMStreamChunk = {
|
|
151
|
+
readonly type: 'content';
|
|
152
|
+
readonly delta: string;
|
|
153
|
+
} | {
|
|
154
|
+
readonly type: 'finish';
|
|
155
|
+
readonly content: string | null;
|
|
156
|
+
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
157
|
+
};
|
|
149
158
|
/**
|
|
150
159
|
* LLM 调用接口。由外部注入,Loop 内部不绑定具体 Provider。
|
|
151
160
|
*/
|
|
@@ -154,6 +163,7 @@ export interface LLMCaller {
|
|
|
154
163
|
readonly content: string | null;
|
|
155
164
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
156
165
|
}>;
|
|
166
|
+
readonly stream?: (messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, options?: LLMCallOptions) => AsyncGenerator<LLMStreamChunk, void, unknown>;
|
|
157
167
|
}
|
|
158
168
|
/**
|
|
159
169
|
* Loop 配置。
|
|
@@ -185,8 +195,6 @@ export interface LoopConfig {
|
|
|
185
195
|
readonly topP?: number;
|
|
186
196
|
/** 推理深度。仅部分模型支持 */
|
|
187
197
|
readonly reasoningEffort?: 'low' | 'medium' | 'high';
|
|
188
|
-
/** 响应格式。默认 text */
|
|
189
|
-
readonly responseFormat?: 'text' | 'json';
|
|
190
198
|
}
|
|
191
199
|
/**
|
|
192
200
|
* 待审批信息。
|
|
@@ -206,6 +214,29 @@ export interface LoopResult {
|
|
|
206
214
|
readonly finalContent: string | null;
|
|
207
215
|
readonly turns: number;
|
|
208
216
|
readonly pendingApproval?: PendingApprovalInfo;
|
|
209
|
-
/** 当 responseFormat === 'json' 时,finalContent 经 JSON.parse 后的结果 */
|
|
210
|
-
readonly parsedContent?: unknown;
|
|
211
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* runLoopStream 的流式事件类型。
|
|
220
|
+
*/
|
|
221
|
+
export type LoopStreamChunk = {
|
|
222
|
+
readonly type: 'turn_start';
|
|
223
|
+
readonly turn: number;
|
|
224
|
+
} | {
|
|
225
|
+
readonly type: 'content';
|
|
226
|
+
readonly delta: string;
|
|
227
|
+
readonly turn: number;
|
|
228
|
+
} | {
|
|
229
|
+
readonly type: 'tool_call';
|
|
230
|
+
readonly turn: number;
|
|
231
|
+
readonly toolCalls: ReadonlyArray<ToolCall>;
|
|
232
|
+
} | {
|
|
233
|
+
readonly type: 'tool_result';
|
|
234
|
+
readonly turn: number;
|
|
235
|
+
readonly callId: string;
|
|
236
|
+
readonly toolName: string;
|
|
237
|
+
readonly content: string;
|
|
238
|
+
readonly status: HarnessRecordStatus;
|
|
239
|
+
} | {
|
|
240
|
+
readonly type: 'turn_end';
|
|
241
|
+
readonly turn: number;
|
|
242
|
+
};
|
|
@@ -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,8 +75,8 @@ class BailianProvider {
|
|
|
45
75
|
generate(request) {
|
|
46
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
77
|
var _a;
|
|
48
|
-
const body = this.buildRequestBody(request);
|
|
49
|
-
const data = yield (0, index_1.callChatCompletions)(this.config.baseUrl, this.config.apiKey,
|
|
78
|
+
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: false }));
|
|
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,
|
|
@@ -71,7 +101,7 @@ class BailianProvider {
|
|
|
71
101
|
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
72
102
|
var _a, _b;
|
|
73
103
|
const url = `${this.config.baseUrl}/chat/completions`;
|
|
74
|
-
const body = this.buildRequestBody(request);
|
|
104
|
+
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true }));
|
|
75
105
|
let response;
|
|
76
106
|
try {
|
|
77
107
|
response = yield __await(fetch(url, {
|
|
@@ -80,7 +110,7 @@ class BailianProvider {
|
|
|
80
110
|
'Content-Type': 'application/json',
|
|
81
111
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
82
112
|
},
|
|
83
|
-
body: JSON.stringify(
|
|
113
|
+
body: JSON.stringify(body),
|
|
84
114
|
}));
|
|
85
115
|
}
|
|
86
116
|
catch (cause) {
|
|
@@ -173,3 +203,15 @@ BailianProvider.SUPPORTED_MODELS = new Set([
|
|
|
173
203
|
'glm-5.1',
|
|
174
204
|
'qwen-vl-plus',
|
|
175
205
|
]);
|
|
206
|
+
BailianProvider.MODEL_CAPABILITIES = {
|
|
207
|
+
'qwen-max': { jsonMode: true, reasoningEffort: false },
|
|
208
|
+
'qwen3.7-max': { jsonMode: true, reasoningEffort: true },
|
|
209
|
+
'qwen-plus': { jsonMode: true, reasoningEffort: true },
|
|
210
|
+
'qwen-turbo': { jsonMode: true, reasoningEffort: true },
|
|
211
|
+
'qwq-plus': { jsonMode: true, reasoningEffort: false },
|
|
212
|
+
'deepseek-v4-pro': { jsonMode: true, reasoningEffort: true },
|
|
213
|
+
'deepseek-v4-flash': { jsonMode: true, reasoningEffort: true },
|
|
214
|
+
'kimi-k2.6': { jsonMode: true, reasoningEffort: true },
|
|
215
|
+
'glm-5.1': { jsonMode: false, reasoningEffort: false },
|
|
216
|
+
'qwen-vl-plus': { jsonMode: true, reasoningEffort: false },
|
|
217
|
+
};
|
|
@@ -34,6 +34,9 @@ export interface OpenAIChatRequest {
|
|
|
34
34
|
type: 'text' | 'json_object';
|
|
35
35
|
};
|
|
36
36
|
stream?: boolean;
|
|
37
|
+
reasoning_effort?: 'low' | 'medium' | 'high';
|
|
38
|
+
/** 百炼等平台的扩展参数 */
|
|
39
|
+
extra_body?: Record<string, unknown>;
|
|
37
40
|
}
|
|
38
41
|
export interface OpenAIChatResponse {
|
|
39
42
|
choices: Array<{
|
|
@@ -64,7 +67,7 @@ export interface OpenAIChatResponse {
|
|
|
64
67
|
*/
|
|
65
68
|
export declare function callChatCompletions(baseUrl: string, apiKey: string, body: OpenAIChatRequest, errorPrefix?: string): Promise<OpenAIChatResponse>;
|
|
66
69
|
/** 模型枚举与注册表 */
|
|
67
|
-
export type { Model, ModelConfig } from './models';
|
|
70
|
+
export type { Model, ModelConfig, ModelCapabilities } from './models';
|
|
68
71
|
export { MODEL_REGISTRY } from './models';
|
|
69
72
|
/** 标准化类型 */
|
|
70
73
|
export type { Message, LLMRequest, LLMResponse, ProviderConfig, StreamChunk } from './types';
|
|
@@ -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 请求参数
|
|
@@ -12,3 +12,15 @@ export interface ModelConfig {
|
|
|
12
12
|
* 当首选 Provider 失败时,Predictor 按此表顺序尝试下一个。
|
|
13
13
|
*/
|
|
14
14
|
export declare const MODEL_REGISTRY: Readonly<Record<Model, ReadonlyArray<ModelConfig>>>;
|
|
15
|
+
/** 模型能力配置 */
|
|
16
|
+
export interface ModelCapabilities {
|
|
17
|
+
/** 是否原生支持 response_format: { type: 'json_object' } */
|
|
18
|
+
readonly jsonMode: boolean;
|
|
19
|
+
/** 是否支持 reasoning_effort 参数 */
|
|
20
|
+
readonly reasoningEffort: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 模型能力矩阵。
|
|
24
|
+
* 由各 Provider 自行维护,不再全局定义。
|
|
25
|
+
* 同一模型在不同 Provider 下可能有不同的能力表现。
|
|
26
|
+
*/
|