@keo-ai/axiom 0.2.0 → 0.2.2
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 +82 -78
- package/dist/function_call_loop/loop.js +24 -0
- package/dist/function_call_loop/provider.d.ts +2 -1
- package/dist/function_call_loop/provider.js +56 -50
- package/dist/function_call_loop/stream.js +27 -3
- package/dist/function_call_loop/types.d.ts +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm_provider/bailian.js +23 -17
- package/dist/llm_provider/index.d.ts +7 -0
- package/dist/llm_provider/types.d.ts +14 -5
- package/dist/predict/predict.d.ts +25 -13
- package/dist/predict/predict.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
> **The execution foundation for LLM agents.**
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
**Axiom 不是编排框架。它是大模型调用工具的「底盘」**
|
|
9
8
|
|
|
10
|
-
|
|
11
9
|
</div>
|
|
12
10
|
|
|
13
11
|
---
|
|
@@ -27,17 +25,13 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
27
25
|
├────────────────────────────────────────────┤
|
|
28
26
|
│ ⚡ AXIOM │
|
|
29
27
|
│ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │
|
|
30
|
-
│ │ LLM调用 │ │
|
|
31
|
-
│ │
|
|
32
|
-
│ │
|
|
33
|
-
│ └─────────┘ │
|
|
34
|
-
│ │
|
|
35
|
-
│
|
|
36
|
-
│
|
|
37
|
-
│ │ Embedding │ │
|
|
38
|
-
│ │ pgvector │ │
|
|
39
|
-
│ │ rerank │ │
|
|
40
|
-
│ └─────────────┘ │
|
|
28
|
+
│ │ LLM调用 │ │ Function │ │ RAG基础 │ │
|
|
29
|
+
│ │ 故障转移 │ │ Call │ │ Embedding │ │
|
|
30
|
+
│ │ 流式输出 │ │ Loop │ │ pgvector │ │
|
|
31
|
+
│ └─────────┘ │ 状态机 │ │ rerank │ │
|
|
32
|
+
│ │ 副作用 │ └─────────────┘ │
|
|
33
|
+
│ │ 传播 │ │
|
|
34
|
+
│ └──────────┘ │
|
|
41
35
|
└────────────────────────────────────────────┘
|
|
42
36
|
```
|
|
43
37
|
|
|
@@ -46,9 +40,8 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
46
40
|
**Axiom 做:**
|
|
47
41
|
- ✅ LLM 统一调用 + Provider 故障转移
|
|
48
42
|
- ✅ Function Call Loop 的驱动、记录、校验、副作用传播
|
|
49
|
-
- ✅ 记忆系统的抽象与基础实现
|
|
50
43
|
- ✅ RAG 基础(Embedding / pgvector 向量检索 / rerank)
|
|
51
|
-
- ✅
|
|
44
|
+
- ✅ 环境变量配置
|
|
52
45
|
|
|
53
46
|
**Axiom 不做:**
|
|
54
47
|
- ❌ 意图识别与路由
|
|
@@ -57,6 +50,7 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
57
50
|
- ❌ 消息队列与削峰
|
|
58
51
|
- ❌ 具体业务 Tool 的实现逻辑
|
|
59
52
|
- ❌ Provider 排序策略(成本/能力/速度)
|
|
53
|
+
- ❌ 记忆系统(Faiss / 语义记忆 / 槽位)— 由上层业务实现
|
|
60
54
|
|
|
61
55
|
> 上层写业务,Axiom 跑底盘。
|
|
62
56
|
|
|
@@ -105,39 +99,54 @@ export BAILIAN_API_KEY="your-api-key"
|
|
|
105
99
|
```ts
|
|
106
100
|
import { LLM } from '@keo-ai/axiom';
|
|
107
101
|
|
|
108
|
-
const
|
|
109
|
-
console.log(
|
|
102
|
+
const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: '你好' });
|
|
103
|
+
console.log(content); // 模型回复
|
|
104
|
+
console.log(usage); // { promptTokens, completionTokens, totalTokens, cachedPromptTokens? }
|
|
110
105
|
```
|
|
111
106
|
|
|
112
107
|
### API 概览
|
|
113
108
|
|
|
114
109
|
| 方法 | 用途 | 返回类型 |
|
|
115
110
|
|---|---|---|
|
|
116
|
-
| `LLM.predict(config)` | 单轮快速调用,自动构建 user message |
|
|
111
|
+
| `LLM.predict(config)` | 单轮快速调用,自动构建 user message | `{ content, usage?: TokenUsage }`,依 `responseFormat` 而定(见下) |
|
|
117
112
|
| `LLM.predictWithMessages(messages, config)` | 传入完整消息列表,适用于多轮对话 | 同上 |
|
|
118
113
|
| `LLM.streamPredict(config)` | 流式调用,逐块返回内容 | `AsyncGenerator<StreamChunk>` |
|
|
119
114
|
| `LLM.streamPredictWithMessages(messages, config)` | 流式 + 自定义消息列表 | `AsyncGenerator<StreamChunk>` |
|
|
120
115
|
|
|
116
|
+
需要自定义 Provider 列表或故障转移策略时,直接使用 `Predictor` 类:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Predictor, BailianProvider } from '@keo-ai/axiom';
|
|
120
|
+
|
|
121
|
+
const predictor = new Predictor({
|
|
122
|
+
providers: [
|
|
123
|
+
new BailianProvider({ name: 'bailian', apiKey, baseUrl, defaultModel: 'qwen-max' }),
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
const response = await predictor.generateForModel('qwen-max', { messages: [...] });
|
|
127
|
+
```
|
|
128
|
+
|
|
121
129
|
### responseFormat 与返回类型
|
|
122
130
|
|
|
123
|
-
`predict` 和 `predictWithMessages`
|
|
131
|
+
`predict` 和 `predictWithMessages` 返回 `{ content, usage?: TokenUsage }`,其中 `content` 的类型由 `responseFormat` 决定:
|
|
124
132
|
|
|
125
|
-
| `responseFormat` |
|
|
133
|
+
| `responseFormat` | `content` 类型 | 说明 |
|
|
126
134
|
|---|---|---|
|
|
127
135
|
| 未设置 / `'text'` | `string` | 直接返回模型输出的文本 |
|
|
128
|
-
| `'json'` | `any` | 自动 `JSON.parse` |
|
|
136
|
+
| `'json'` | `any` | 自动 `JSON.parse`。注意:`glm-5.1` 不支持此模式 |
|
|
129
137
|
|
|
130
138
|
```ts
|
|
131
|
-
// text → string
|
|
132
|
-
const
|
|
139
|
+
// text → { content: string, usage?: TokenUsage }
|
|
140
|
+
const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: '讲个故事', responseFormat: 'text' });
|
|
133
141
|
|
|
134
|
-
// json →
|
|
135
|
-
const obj = await LLM.predict({
|
|
136
|
-
model: '
|
|
142
|
+
// json → { content: any, usage?: TokenUsage }
|
|
143
|
+
const { content: obj, usage } = await LLM.predict({
|
|
144
|
+
model: 'qwen-max',
|
|
137
145
|
prompt: '生成一个 JSON',
|
|
138
146
|
responseFormat: 'json',
|
|
139
147
|
});
|
|
140
148
|
console.log(obj.name);
|
|
149
|
+
console.log(usage); // { promptTokens, completionTokens, totalTokens, cachedPromptTokens? }
|
|
141
150
|
```
|
|
142
151
|
|
|
143
152
|
### Provider 配置
|
|
@@ -147,7 +156,7 @@ console.log(obj.name);
|
|
|
147
156
|
```bash
|
|
148
157
|
BAILIAN_API_KEY # 必填
|
|
149
158
|
BAILIAN_BASE_URL # 可选,默认 https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
150
|
-
BAILIAN_DEFAULT_MODEL # 可选,默认
|
|
159
|
+
BAILIAN_DEFAULT_MODEL # 可选,默认 qwen-max
|
|
151
160
|
```
|
|
152
161
|
|
|
153
162
|
### 故障转移
|
|
@@ -161,30 +170,40 @@ Predictor 内部按模型查询 `MODEL_REGISTRY`,获取候选 Provider 队列
|
|
|
161
170
|
流式版本返回 `AsyncGenerator<StreamChunk>`,迭代即可逐块消费:
|
|
162
171
|
|
|
163
172
|
```ts
|
|
164
|
-
for await (const chunk of LLM.streamPredict({ model: '
|
|
173
|
+
for await (const chunk of LLM.streamPredict({ model: 'qwen-max', prompt: '讲个故事' })) {
|
|
165
174
|
if (chunk.type === 'content') {
|
|
166
175
|
process.stdout.write(chunk.delta);
|
|
167
176
|
}
|
|
177
|
+
if (chunk.type === 'reasoning') {
|
|
178
|
+
process.stdout.write(chunk.delta); // 推理过程
|
|
179
|
+
}
|
|
180
|
+
if (chunk.type === 'finish' && chunk.usage) {
|
|
181
|
+
console.log('Token usage:', chunk.usage);
|
|
182
|
+
// { promptTokens, completionTokens, totalTokens, cachedPromptTokens? }
|
|
183
|
+
}
|
|
168
184
|
}
|
|
169
185
|
```
|
|
170
186
|
|
|
187
|
+
> 💡 流式调用自动启用 `stream_options: { include_usage: true }`,`finish` 事件携带完整的 token 消耗统计。usage 可能与 `finish_reason` 在同一个 SSE chunk,也可能在独立的 chunk(`choices: []`)中返回,两种情况均已兼容。
|
|
188
|
+
|
|
171
189
|
### 模型列表
|
|
172
190
|
|
|
173
191
|
当前支持的模型(通过 `Model` 类型枚举):
|
|
174
192
|
|
|
175
|
-
| 模型 | 推理深度 | 说明 |
|
|
176
|
-
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `qwen-
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
| 模型 | 推理深度 | json 模式 | 说明 |
|
|
194
|
+
|------|----------|-----------|------|
|
|
195
|
+
| `qwen-max` | ❌ 不支持 | ✅ | **默认模型** |
|
|
196
|
+
| `qwen3.7-max` | ✅ 映射支持 | ✅ | `low`/`medium`/`high` 映射为 `enable_thinking` |
|
|
197
|
+
| `qwen-plus` | ✅ 映射支持 | ✅ | 同上 |
|
|
198
|
+
| `qwen-turbo` | ✅ 映射支持 | ✅ | 同上 |
|
|
199
|
+
| `deepseek-v4-pro` | ✅ 原生支持 | ✅ | `low`/`medium`/`high` 直接传递 |
|
|
200
|
+
| `deepseek-v4-flash` | ✅ 原生支持 | ✅ | 同上 |
|
|
201
|
+
| `kimi-k2.6` | ✅ 原生支持 | ✅ | 同上 |
|
|
202
|
+
| `qwq-plus` | ❌ 不支持 | ✅ | 固定推理行为,不可调节 |
|
|
203
|
+
| `glm-5.1` | ❌ 不支持 | ❌ | 不支持推理参数,不支持 json 模式 |
|
|
204
|
+
| `qwen-vl-plus` | ❌ 不支持 | ✅ | 视觉模型,不支持推理参数 |
|
|
205
|
+
|
|
206
|
+
> 推理深度的支持方式由 Provider 内部维护。百炼 Provider 中,Qwen 系列通过 `extra_body.enable_thinking` 映射实现(`low` 关闭,`medium`/`high` 开启),DeepSeek / Kimi 则原生透传 `reasoning_effort`。
|
|
188
207
|
>
|
|
189
208
|
> 目前所有模型均路由到百炼 Provider。后续接入其他厂商时,通过 `MODEL_REGISTRY` 扩展映射即可。
|
|
190
209
|
|
|
@@ -194,7 +213,7 @@ Predict 模块遵循 Axiom 的统一错误策略:**直接抛异常,调用方
|
|
|
194
213
|
|
|
195
214
|
```ts
|
|
196
215
|
try {
|
|
197
|
-
const
|
|
216
|
+
const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: 'hi' });
|
|
198
217
|
} catch (e) {
|
|
199
218
|
// e.message 包含 Provider 汇总错误信息
|
|
200
219
|
}
|
|
@@ -232,7 +251,7 @@ const result = await FunctionCallLoop.runLoop({
|
|
|
232
251
|
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
233
252
|
{ role: 'user', content: 'What is the weather in Beijing?' },
|
|
234
253
|
],
|
|
235
|
-
model: '
|
|
254
|
+
model: 'qwen-max',
|
|
236
255
|
temperature: 0.7,
|
|
237
256
|
maxTokens: 2048,
|
|
238
257
|
tools: [
|
|
@@ -262,6 +281,8 @@ const result = await FunctionCallLoop.runLoop({
|
|
|
262
281
|
console.log(result.finalContent);
|
|
263
282
|
console.log('Turns:', result.turns);
|
|
264
283
|
console.log('Harness:', result.harness);
|
|
284
|
+
console.log('Token usage:', result.totalUsage); // 累计 token 消耗
|
|
285
|
+
console.log('Usage history:', result.usageHistory); // 每轮明细
|
|
265
286
|
```
|
|
266
287
|
|
|
267
288
|
> 💡 需要**流式输出**(实时展示 LLM 生成内容)?使用下方的 [`runLoopStream`](#流式调用runloopstream)。
|
|
@@ -313,6 +334,9 @@ for await (const chunk of stream) {
|
|
|
313
334
|
break;
|
|
314
335
|
case 'turn_end':
|
|
315
336
|
console.log(`\n--- Turn ${chunk.turn} End ---`);
|
|
337
|
+
if (chunk.usage) {
|
|
338
|
+
console.log(`Token usage: prompt=${chunk.usage.promptTokens}, completion=${chunk.usage.completionTokens}`);
|
|
339
|
+
}
|
|
316
340
|
break;
|
|
317
341
|
}
|
|
318
342
|
}
|
|
@@ -349,7 +373,9 @@ console.log('Turns:', result!.turns);
|
|
|
349
373
|
| `content` | LLM 输出 content(正式回复)增量 | `delta`, `turn` |
|
|
350
374
|
| `tool_call` | LLM 决定调用 tool(流结束、完整 tool_calls 解析完成) | `toolCalls`, `turn` |
|
|
351
375
|
| `tool_result` | tool 执行完成 | `callId`, `toolName`, `content`, `status`, `turn` |
|
|
352
|
-
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn` |
|
|
376
|
+
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn`, `usage?` |
|
|
377
|
+
|
|
378
|
+
> 💡 `turn_end` 的 `usage` 字段携带该轮 LLM 调用的 token 消耗统计(`promptTokens`、`completionTokens`、`totalTokens`、`cachedPromptTokens?`)。流式请求自动启用 `stream_options: { include_usage: true }`,确保 API 返回 usage 数据。
|
|
353
379
|
|
|
354
380
|
#### 流式 vs 非流式的选择
|
|
355
381
|
|
|
@@ -678,6 +704,15 @@ interface LoopResult {
|
|
|
678
704
|
harness: HarnessRecord[]; // 执行历史
|
|
679
705
|
finalContent: string | null; // 最终回复内容
|
|
680
706
|
turns: number; // 实际执行轮数
|
|
707
|
+
usageHistory: TokenUsage[]; // 每轮 LLM 调用的 token 消耗明细
|
|
708
|
+
totalUsage: TokenUsage; // 累计 token 消耗(含 cachedPromptTokens)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
interface TokenUsage {
|
|
712
|
+
promptTokens: number;
|
|
713
|
+
completionTokens: number;
|
|
714
|
+
totalTokens: number;
|
|
715
|
+
cachedPromptTokens?: number; // 命中缓存的 prompt token(Prompt Caching)
|
|
681
716
|
}
|
|
682
717
|
```
|
|
683
718
|
|
|
@@ -825,7 +860,7 @@ interface SearchResult {
|
|
|
825
860
|
|
|
826
861
|
### 过滤
|
|
827
862
|
|
|
828
|
-
|
|
863
|
+
支持对表的任意独立列做等值、范围和 IN 查询:
|
|
829
864
|
|
|
830
865
|
```ts
|
|
831
866
|
// 等值过滤
|
|
@@ -833,7 +868,7 @@ const results = await EmbeddingSearch.query('query', {
|
|
|
833
868
|
filter: { category: '衣服', shop: '旗舰店' },
|
|
834
869
|
}, pool);
|
|
835
870
|
|
|
836
|
-
//
|
|
871
|
+
// 数值范围
|
|
837
872
|
const ranked = await EmbeddingSearch.query('query', {
|
|
838
873
|
filter: {
|
|
839
874
|
category: '衣服',
|
|
@@ -854,36 +889,7 @@ const multi = await EmbeddingSearch.query('query', {
|
|
|
854
889
|
|
|
855
890
|
### 枚举值解析
|
|
856
891
|
|
|
857
|
-
默认开启 `parseEnum
|
|
858
|
-
|
|
859
|
-
| 字段 | 说明 | 示例(原值 → 解析后) |
|
|
860
|
-
|---|---|---|
|
|
861
|
-
| `type` | 期刊类型 | `11` → `"SCI/SSCI/AHCI"` |
|
|
862
|
-
| `subject1` | 学科大类 | `17` → `"计算机"` |
|
|
863
|
-
| `db` | 数据库 | `2` → `"SCI(SCIE)"` |
|
|
864
|
-
| `attribute` | 刊物属性 | `3` → `"快审刊"` |
|
|
865
|
-
| `oa` | 发表模式 | `1` → `"开源模式(OA)"` |
|
|
866
|
-
|
|
867
|
-
```ts
|
|
868
|
-
const results = await EmbeddingSearch.query('query', {
|
|
869
|
-
tableName: 'journals',
|
|
870
|
-
}, pool);
|
|
871
|
-
|
|
872
|
-
// 默认 parseEnum: true,枚举字段自动解析为文本
|
|
873
|
-
console.log(results[0].type); // "SCI/SSCI/AHCI"
|
|
874
|
-
console.log(results[0].subject1); // "计算机"
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
如需关闭解析(保留原始数字值):
|
|
878
|
-
|
|
879
|
-
```ts
|
|
880
|
-
const results = await EmbeddingSearch.query('query', {
|
|
881
|
-
tableName: 'journals',
|
|
882
|
-
parseEnum: false,
|
|
883
|
-
}, pool);
|
|
884
|
-
|
|
885
|
-
console.log(results[0].type); // 11
|
|
886
|
-
```
|
|
892
|
+
默认开启 `parseEnum`,返回结果中的特定枚举字段会自动从数字值解析为可读文本。如需关闭解析(保留原始数字值),设置 `parseEnum: false`。
|
|
887
893
|
|
|
888
894
|
未知枚举值、非数字类型、非枚举字段均保持原样不变。
|
|
889
895
|
|
|
@@ -970,5 +976,3 @@ EmbeddingSearch 模块内部调用百炼的 embedding 和 rerank 服务,无需
|
|
|
970
976
|
- **项目层填充内容** — 上层准备消息、工具、业务逻辑,Axiom 负责可靠执行
|
|
971
977
|
- **副作用即状态** — 工具调用改变外部世界,Harness 让这种改变在 Loop 间可见
|
|
972
978
|
- **单一事实来源** — Harness 的执行记录是 Function Call 的唯一权威来源
|
|
973
|
-
|
|
974
|
-
---
|
|
@@ -44,7 +44,24 @@ function runLoop(config) {
|
|
|
44
44
|
const harness = new harness_1.Harness();
|
|
45
45
|
const messages = buildInitialMessages(config);
|
|
46
46
|
let turn = 0;
|
|
47
|
+
const usageHistory = [];
|
|
47
48
|
const signal = config.signal;
|
|
49
|
+
function addUsage(usage) {
|
|
50
|
+
if (usage) {
|
|
51
|
+
usageHistory.push(usage);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function computeTotalUsage() {
|
|
55
|
+
return usageHistory.reduce((acc, u) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
return ({
|
|
58
|
+
promptTokens: acc.promptTokens + u.promptTokens,
|
|
59
|
+
completionTokens: acc.completionTokens + u.completionTokens,
|
|
60
|
+
totalTokens: acc.totalTokens + u.totalTokens,
|
|
61
|
+
cachedPromptTokens: ((_a = acc.cachedPromptTokens) !== null && _a !== void 0 ? _a : 0) + ((_b = u.cachedPromptTokens) !== null && _b !== void 0 ? _b : 0),
|
|
62
|
+
});
|
|
63
|
+
}, { promptTokens: 0, completionTokens: 0, totalTokens: 0 });
|
|
64
|
+
}
|
|
48
65
|
while (true) {
|
|
49
66
|
// 检查取消信号
|
|
50
67
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
@@ -87,6 +104,7 @@ function runLoop(config) {
|
|
|
87
104
|
topP: config.topP,
|
|
88
105
|
reasoningEffort: config.reasoningEffort,
|
|
89
106
|
});
|
|
107
|
+
addUsage(llmResponse.usage);
|
|
90
108
|
// 第六步:检查 LLM 回复
|
|
91
109
|
if (!llmResponse.tool_calls || llmResponse.tool_calls.length === 0) {
|
|
92
110
|
messages.push({
|
|
@@ -99,6 +117,8 @@ function runLoop(config) {
|
|
|
99
117
|
harness: harness.getAll(),
|
|
100
118
|
finalContent: llmResponse.content,
|
|
101
119
|
turns: turn,
|
|
120
|
+
usageHistory,
|
|
121
|
+
totalUsage: computeTotalUsage(),
|
|
102
122
|
};
|
|
103
123
|
}
|
|
104
124
|
// 有 tool_calls,将 assistant message 加入 Messages
|
|
@@ -133,6 +153,8 @@ function runLoop(config) {
|
|
|
133
153
|
args: pending.pendingApproval.args,
|
|
134
154
|
callId: pending.toolCallId,
|
|
135
155
|
},
|
|
156
|
+
usageHistory,
|
|
157
|
+
totalUsage: computeTotalUsage(),
|
|
136
158
|
};
|
|
137
159
|
}
|
|
138
160
|
// 第八步:回到第一步继续下一轮
|
|
@@ -144,6 +166,8 @@ function runLoop(config) {
|
|
|
144
166
|
harness: harness.getAll(),
|
|
145
167
|
finalContent: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant' ? lastMessage.content : null,
|
|
146
168
|
turns: turn,
|
|
169
|
+
usageHistory,
|
|
170
|
+
totalUsage: computeTotalUsage(),
|
|
147
171
|
};
|
|
148
172
|
});
|
|
149
173
|
}
|
|
@@ -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, LLMStreamChunk } from './types';
|
|
5
|
+
import type { Message, ToolCall, ToolDefinition, LLMCallOptions, LLMStreamChunk, TokenUsage } from './types';
|
|
6
6
|
export interface ProviderOptions {
|
|
7
7
|
readonly apiKey: string;
|
|
8
8
|
readonly baseUrl: string;
|
|
@@ -16,6 +16,7 @@ export declare function createLLMCaller(options: ProviderOptions): {
|
|
|
16
16
|
readonly content: string | null;
|
|
17
17
|
readonly reasoningContent?: string;
|
|
18
18
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
19
|
+
readonly usage?: TokenUsage;
|
|
19
20
|
}>;
|
|
20
21
|
/**
|
|
21
22
|
* 流式调用 LLM,支持 content 和 tool_calls 的增量输出。
|
|
@@ -67,7 +67,7 @@ function createLLMCaller(options) {
|
|
|
67
67
|
return {
|
|
68
68
|
call(messages, tools, callOptions) {
|
|
69
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
-
var _a, _b;
|
|
70
|
+
var _a, _b, _c;
|
|
71
71
|
const body = provider.adaptRequest({
|
|
72
72
|
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
73
73
|
messages: toOpenAIMessages(messages),
|
|
@@ -90,6 +90,14 @@ function createLLMCaller(options) {
|
|
|
90
90
|
arguments: tc.function.arguments,
|
|
91
91
|
},
|
|
92
92
|
})),
|
|
93
|
+
usage: data.usage
|
|
94
|
+
? {
|
|
95
|
+
promptTokens: data.usage.prompt_tokens,
|
|
96
|
+
completionTokens: data.usage.completion_tokens,
|
|
97
|
+
totalTokens: data.usage.total_tokens,
|
|
98
|
+
cachedPromptTokens: (_c = data.usage.prompt_tokens_details) === null || _c === void 0 ? void 0 : _c.cached_tokens,
|
|
99
|
+
}
|
|
100
|
+
: undefined,
|
|
93
101
|
};
|
|
94
102
|
});
|
|
95
103
|
},
|
|
@@ -100,7 +108,7 @@ function createLLMCaller(options) {
|
|
|
100
108
|
*/
|
|
101
109
|
stream(messages, tools, callOptions) {
|
|
102
110
|
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
103
|
-
var _a, _b, _c, _d, _e;
|
|
111
|
+
var _a, _b, _c, _d, _e, _f;
|
|
104
112
|
const body = provider.adaptRequest({
|
|
105
113
|
model: (_a = callOptions === null || callOptions === void 0 ? void 0 : callOptions.model) !== null && _a !== void 0 ? _a : options.defaultModel,
|
|
106
114
|
messages: toOpenAIMessages(messages),
|
|
@@ -110,6 +118,7 @@ function createLLMCaller(options) {
|
|
|
110
118
|
tools: tools.length > 0 ? toOpenAITools(tools) : undefined,
|
|
111
119
|
reasoning_effort: callOptions === null || callOptions === void 0 ? void 0 : callOptions.reasoningEffort,
|
|
112
120
|
stream: true,
|
|
121
|
+
stream_options: { include_usage: true },
|
|
113
122
|
});
|
|
114
123
|
const url = `${options.baseUrl}/chat/completions`;
|
|
115
124
|
let response;
|
|
@@ -140,7 +149,27 @@ function createLLMCaller(options) {
|
|
|
140
149
|
const toolCallAccumulators = [];
|
|
141
150
|
let fullContent = '';
|
|
142
151
|
let fullReasoningContent = '';
|
|
143
|
-
|
|
152
|
+
// 累积 usage:OpenAI 兼容 API 在 stream_options.include_usage 开启时,
|
|
153
|
+
// usage 可能与 finish_reason 在同一个 chunk,也可能在独立的 chunk(choices: [])中返回。
|
|
154
|
+
// 统一在此收集,流结束后合并到 finish 事件中 yield。
|
|
155
|
+
let streamUsage;
|
|
156
|
+
let hasFinishReason = false;
|
|
157
|
+
function buildToolCalls() {
|
|
158
|
+
const toolCalls = [];
|
|
159
|
+
for (const acc of toolCallAccumulators) {
|
|
160
|
+
if (acc.id && acc.type && acc.function.name) {
|
|
161
|
+
toolCalls.push({
|
|
162
|
+
id: acc.id,
|
|
163
|
+
type: acc.type,
|
|
164
|
+
function: {
|
|
165
|
+
name: acc.function.name,
|
|
166
|
+
arguments: acc.function.arguments,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return toolCalls;
|
|
172
|
+
}
|
|
144
173
|
try {
|
|
145
174
|
while (true) {
|
|
146
175
|
const { done, value } = yield __await(reader.read());
|
|
@@ -160,10 +189,19 @@ function createLLMCaller(options) {
|
|
|
160
189
|
try {
|
|
161
190
|
parsed = JSON.parse(data);
|
|
162
191
|
}
|
|
163
|
-
catch (
|
|
192
|
+
catch (_g) {
|
|
164
193
|
continue;
|
|
165
194
|
}
|
|
166
|
-
|
|
195
|
+
// 从任意 chunk 中收集 usage(包括 choices 为空的独立 usage chunk)
|
|
196
|
+
if (parsed.usage) {
|
|
197
|
+
streamUsage = {
|
|
198
|
+
promptTokens: parsed.usage.prompt_tokens,
|
|
199
|
+
completionTokens: parsed.usage.completion_tokens,
|
|
200
|
+
totalTokens: parsed.usage.total_tokens,
|
|
201
|
+
cachedPromptTokens: (_c = parsed.usage.prompt_tokens_details) === null || _c === void 0 ? void 0 : _c.cached_tokens,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const choice = (_d = parsed.choices) === null || _d === void 0 ? void 0 : _d[0];
|
|
167
205
|
if (!choice)
|
|
168
206
|
continue;
|
|
169
207
|
const delta = choice.delta;
|
|
@@ -191,61 +229,29 @@ function createLLMCaller(options) {
|
|
|
191
229
|
acc.id = tcDelta.id;
|
|
192
230
|
if (tcDelta.type)
|
|
193
231
|
acc.type = tcDelta.type;
|
|
194
|
-
if ((
|
|
232
|
+
if ((_e = tcDelta.function) === null || _e === void 0 ? void 0 : _e.name) {
|
|
195
233
|
acc.function.name = tcDelta.function.name;
|
|
196
234
|
}
|
|
197
|
-
if ((
|
|
235
|
+
if ((_f = tcDelta.function) === null || _f === void 0 ? void 0 : _f.arguments) {
|
|
198
236
|
acc.function.arguments += tcDelta.function.arguments;
|
|
199
237
|
}
|
|
200
238
|
}
|
|
201
239
|
}
|
|
202
|
-
//
|
|
240
|
+
// 标记流结束(不立即 yield finish,等 usage 收集完毕)
|
|
203
241
|
if (choice.finish_reason) {
|
|
204
|
-
|
|
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
|
-
});
|
|
242
|
+
hasFinishReason = true;
|
|
240
243
|
}
|
|
241
244
|
}
|
|
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
245
|
}
|
|
246
|
+
// 流结束,统一 yield finish 事件(确保包含累积的 usage)
|
|
247
|
+
const toolCalls = buildToolCalls();
|
|
248
|
+
yield yield __await({
|
|
249
|
+
type: 'finish',
|
|
250
|
+
content: fullContent || null,
|
|
251
|
+
reasoningContent: fullReasoningContent || undefined,
|
|
252
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
253
|
+
usage: streamUsage,
|
|
254
|
+
});
|
|
249
255
|
}
|
|
250
256
|
finally {
|
|
251
257
|
reader.releaseLock();
|
|
@@ -69,7 +69,24 @@ function runLoopStream(config) {
|
|
|
69
69
|
const harness = new harness_1.Harness();
|
|
70
70
|
const messages = buildInitialMessages(config);
|
|
71
71
|
let turn = 0;
|
|
72
|
+
const usageHistory = [];
|
|
72
73
|
const signal = config.signal;
|
|
74
|
+
function addUsage(usage) {
|
|
75
|
+
if (usage) {
|
|
76
|
+
usageHistory.push(usage);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function computeTotalUsage() {
|
|
80
|
+
return usageHistory.reduce((acc, u) => {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
return ({
|
|
83
|
+
promptTokens: acc.promptTokens + u.promptTokens,
|
|
84
|
+
completionTokens: acc.completionTokens + u.completionTokens,
|
|
85
|
+
totalTokens: acc.totalTokens + u.totalTokens,
|
|
86
|
+
cachedPromptTokens: ((_a = acc.cachedPromptTokens) !== null && _a !== void 0 ? _a : 0) + ((_b = u.cachedPromptTokens) !== null && _b !== void 0 ? _b : 0),
|
|
87
|
+
});
|
|
88
|
+
}, { promptTokens: 0, completionTokens: 0, totalTokens: 0 });
|
|
89
|
+
}
|
|
73
90
|
while (true) {
|
|
74
91
|
// 检查取消信号
|
|
75
92
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
@@ -145,6 +162,7 @@ function runLoopStream(config) {
|
|
|
145
162
|
if (!finishChunk) {
|
|
146
163
|
throw new Error('LLM stream ended without a finish chunk');
|
|
147
164
|
}
|
|
165
|
+
addUsage(finishChunk.usage);
|
|
148
166
|
// 使用 finish chunk 中的 content(优先级更高,可能包含完整格式化内容)
|
|
149
167
|
const assistantContent = (_j = finishChunk.content) !== null && _j !== void 0 ? _j : fullContent;
|
|
150
168
|
const assistantReasoningContent = (_k = finishChunk.reasoningContent) !== null && _k !== void 0 ? _k : (fullReasoningContent || undefined);
|
|
@@ -155,12 +173,14 @@ function runLoopStream(config) {
|
|
|
155
173
|
content: assistantContent,
|
|
156
174
|
reasoningContent: assistantReasoningContent,
|
|
157
175
|
});
|
|
158
|
-
yield yield __await({ type: 'turn_end', turn });
|
|
176
|
+
yield yield __await({ type: 'turn_end', turn, usage: finishChunk.usage });
|
|
159
177
|
return yield __await({
|
|
160
178
|
messages,
|
|
161
179
|
harness: harness.getAll(),
|
|
162
180
|
finalContent: assistantContent,
|
|
163
181
|
turns: turn,
|
|
182
|
+
usageHistory,
|
|
183
|
+
totalUsage: computeTotalUsage(),
|
|
164
184
|
});
|
|
165
185
|
}
|
|
166
186
|
// 有 tool_calls,将 assistant message 加入 Messages
|
|
@@ -198,7 +218,7 @@ function runLoopStream(config) {
|
|
|
198
218
|
// 检查是否有待审批的 tool
|
|
199
219
|
const pending = parallelResults.find((r) => r.pendingApproval);
|
|
200
220
|
if (pending === null || pending === void 0 ? void 0 : pending.pendingApproval) {
|
|
201
|
-
yield yield __await({ type: 'turn_end', turn });
|
|
221
|
+
yield yield __await({ type: 'turn_end', turn, usage: finishChunk.usage });
|
|
202
222
|
return yield __await({
|
|
203
223
|
messages,
|
|
204
224
|
harness: harness.getAll(),
|
|
@@ -210,9 +230,11 @@ function runLoopStream(config) {
|
|
|
210
230
|
args: pending.pendingApproval.args,
|
|
211
231
|
callId: pending.toolCallId,
|
|
212
232
|
},
|
|
233
|
+
usageHistory,
|
|
234
|
+
totalUsage: computeTotalUsage(),
|
|
213
235
|
});
|
|
214
236
|
}
|
|
215
|
-
yield yield __await({ type: 'turn_end', turn });
|
|
237
|
+
yield yield __await({ type: 'turn_end', turn, usage: finishChunk.usage });
|
|
216
238
|
// 第八步:回到第一步继续下一轮
|
|
217
239
|
}
|
|
218
240
|
// Loop 被终止(Turn Policy 或硬限制)
|
|
@@ -222,6 +244,8 @@ function runLoopStream(config) {
|
|
|
222
244
|
harness: harness.getAll(),
|
|
223
245
|
finalContent: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant' ? lastMessage.content : null,
|
|
224
246
|
turns: turn,
|
|
247
|
+
usageHistory,
|
|
248
|
+
totalUsage: computeTotalUsage(),
|
|
225
249
|
});
|
|
226
250
|
});
|
|
227
251
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TokenUsage } from '../llm_provider/types';
|
|
2
|
+
export type { TokenUsage };
|
|
1
3
|
/**
|
|
2
4
|
* LLM 对话消息。支持 system、user、assistant、tool 四种角色。
|
|
3
5
|
* assistant 角色可携带 tool_calls;tool 角色需携带 tool_call_id。
|
|
@@ -168,6 +170,7 @@ export type LLMStreamChunk = {
|
|
|
168
170
|
readonly content: string | null;
|
|
169
171
|
readonly reasoningContent?: string;
|
|
170
172
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
173
|
+
readonly usage?: TokenUsage;
|
|
171
174
|
};
|
|
172
175
|
/**
|
|
173
176
|
* LLM 调用接口。由外部注入,Loop 内部不绑定具体 Provider。
|
|
@@ -177,6 +180,7 @@ export interface LLMCaller {
|
|
|
177
180
|
readonly content: string | null;
|
|
178
181
|
readonly reasoningContent?: string;
|
|
179
182
|
readonly tool_calls?: ReadonlyArray<ToolCall>;
|
|
183
|
+
readonly usage?: TokenUsage;
|
|
180
184
|
}>;
|
|
181
185
|
readonly stream?: (messages: ReadonlyArray<Message>, tools: ReadonlyArray<ToolDefinition>, options?: LLMCallOptions) => AsyncGenerator<LLMStreamChunk, void, unknown>;
|
|
182
186
|
}
|
|
@@ -229,6 +233,10 @@ export interface LoopResult {
|
|
|
229
233
|
readonly finalContent: string | null;
|
|
230
234
|
readonly turns: number;
|
|
231
235
|
readonly pendingApproval?: PendingApprovalInfo;
|
|
236
|
+
/** 每轮 LLM 调用的 token 消耗明细 */
|
|
237
|
+
readonly usageHistory: TokenUsage[];
|
|
238
|
+
/** 累计 token 消耗 */
|
|
239
|
+
readonly totalUsage: TokenUsage;
|
|
232
240
|
}
|
|
233
241
|
/**
|
|
234
242
|
* runLoopStream 的流式事件类型。
|
|
@@ -259,4 +267,5 @@ export type LoopStreamChunk = {
|
|
|
259
267
|
} | {
|
|
260
268
|
readonly type: 'turn_end';
|
|
261
269
|
readonly turn: number;
|
|
270
|
+
readonly usage?: TokenUsage;
|
|
262
271
|
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* import 'dotenv/config';
|
|
8
8
|
* import { LLM } from '@keo-ai/axiom';
|
|
9
9
|
*
|
|
10
|
-
* const
|
|
10
|
+
* const { content, usage } = await LLM.predict({ model: 'qwen3.7-max', prompt: '你好' });
|
|
11
11
|
* ```
|
|
12
12
|
*
|
|
13
13
|
* 必需环境变量:
|
|
@@ -74,7 +74,7 @@ class BailianProvider {
|
|
|
74
74
|
*/
|
|
75
75
|
generate(request) {
|
|
76
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
-
var _a;
|
|
77
|
+
var _a, _b;
|
|
78
78
|
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: false }));
|
|
79
79
|
const data = yield (0, index_1.callChatCompletions)(this.config.baseUrl, this.config.apiKey, body, this.name);
|
|
80
80
|
const choice = data.choices[0];
|
|
@@ -86,9 +86,10 @@ class BailianProvider {
|
|
|
86
86
|
promptTokens: data.usage.prompt_tokens,
|
|
87
87
|
completionTokens: data.usage.completion_tokens,
|
|
88
88
|
totalTokens: data.usage.total_tokens,
|
|
89
|
+
cachedPromptTokens: (_a = data.usage.prompt_tokens_details) === null || _a === void 0 ? void 0 : _a.cached_tokens,
|
|
89
90
|
}
|
|
90
91
|
: undefined,
|
|
91
|
-
model: (
|
|
92
|
+
model: (_b = data.model) !== null && _b !== void 0 ? _b : body.model,
|
|
92
93
|
};
|
|
93
94
|
});
|
|
94
95
|
}
|
|
@@ -100,9 +101,9 @@ class BailianProvider {
|
|
|
100
101
|
*/
|
|
101
102
|
stream(request) {
|
|
102
103
|
return __asyncGenerator(this, arguments, function* stream_1() {
|
|
103
|
-
var _a, _b;
|
|
104
|
+
var _a, _b, _c;
|
|
104
105
|
const url = `${this.config.baseUrl}/chat/completions`;
|
|
105
|
-
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true }));
|
|
106
|
+
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true, stream_options: { include_usage: true } }));
|
|
106
107
|
let response;
|
|
107
108
|
try {
|
|
108
109
|
response = yield __await(fetch(url, {
|
|
@@ -127,6 +128,10 @@ class BailianProvider {
|
|
|
127
128
|
const reader = response.body.getReader();
|
|
128
129
|
const decoder = new TextDecoder();
|
|
129
130
|
let buffer = '';
|
|
131
|
+
// 累积 usage:OpenAI 兼容 API 在 stream_options.include_usage 开启时,
|
|
132
|
+
// usage 可能与 finish_reason 在同一个 chunk,也可能在独立的 chunk(choices: [])中返回。
|
|
133
|
+
// 统一在此收集,流结束后一次性 yield finish 事件。
|
|
134
|
+
let streamUsage;
|
|
130
135
|
try {
|
|
131
136
|
while (true) {
|
|
132
137
|
const { done, value } = yield __await(reader.read());
|
|
@@ -146,10 +151,19 @@ class BailianProvider {
|
|
|
146
151
|
try {
|
|
147
152
|
parsed = JSON.parse(data);
|
|
148
153
|
}
|
|
149
|
-
catch (
|
|
154
|
+
catch (_d) {
|
|
150
155
|
continue;
|
|
151
156
|
}
|
|
152
|
-
|
|
157
|
+
// 从任意 chunk 中收集 usage(包括 choices 为空的独立 usage chunk)
|
|
158
|
+
if (parsed.usage) {
|
|
159
|
+
streamUsage = {
|
|
160
|
+
promptTokens: parsed.usage.prompt_tokens,
|
|
161
|
+
completionTokens: parsed.usage.completion_tokens,
|
|
162
|
+
totalTokens: parsed.usage.total_tokens,
|
|
163
|
+
cachedPromptTokens: (_b = parsed.usage.prompt_tokens_details) === null || _b === void 0 ? void 0 : _b.cached_tokens,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const choice = (_c = parsed.choices) === null || _c === void 0 ? void 0 : _c[0];
|
|
153
167
|
if (!choice)
|
|
154
168
|
continue;
|
|
155
169
|
const delta = choice.delta;
|
|
@@ -159,19 +173,9 @@ class BailianProvider {
|
|
|
159
173
|
if (delta.content) {
|
|
160
174
|
yield yield __await({ type: 'content', delta: delta.content });
|
|
161
175
|
}
|
|
162
|
-
if (choice.finish_reason && parsed.usage) {
|
|
163
|
-
yield yield __await({
|
|
164
|
-
type: 'finish',
|
|
165
|
-
usage: {
|
|
166
|
-
promptTokens: parsed.usage.prompt_tokens,
|
|
167
|
-
completionTokens: parsed.usage.completion_tokens,
|
|
168
|
-
totalTokens: parsed.usage.total_tokens,
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
|
-
yield yield __await({ type: 'finish' });
|
|
178
|
+
yield yield __await({ type: 'finish', usage: streamUsage });
|
|
175
179
|
}
|
|
176
180
|
finally {
|
|
177
181
|
reader.releaseLock();
|
|
@@ -192,6 +196,8 @@ class BailianProvider {
|
|
|
192
196
|
response_format: request.responseFormat
|
|
193
197
|
? { type: request.responseFormat === 'json' ? 'json_object' : 'text' }
|
|
194
198
|
: undefined,
|
|
199
|
+
stream: request.stream,
|
|
200
|
+
stream_options: request.streamOptions,
|
|
195
201
|
};
|
|
196
202
|
}
|
|
197
203
|
}
|
|
@@ -35,6 +35,10 @@ export interface OpenAIChatRequest {
|
|
|
35
35
|
type: 'text' | 'json_object';
|
|
36
36
|
};
|
|
37
37
|
stream?: boolean;
|
|
38
|
+
/** 流式请求时是否在最后一个 chunk 中返回 usage 信息。OpenAI 兼容 API 默认不返回,需显式开启 */
|
|
39
|
+
stream_options?: {
|
|
40
|
+
include_usage: boolean;
|
|
41
|
+
};
|
|
38
42
|
reasoning_effort?: 'low' | 'medium' | 'high';
|
|
39
43
|
/** 百炼等平台的扩展参数 */
|
|
40
44
|
extra_body?: Record<string, unknown>;
|
|
@@ -59,6 +63,9 @@ export interface OpenAIChatResponse {
|
|
|
59
63
|
prompt_tokens: number;
|
|
60
64
|
completion_tokens: number;
|
|
61
65
|
total_tokens: number;
|
|
66
|
+
prompt_tokens_details?: {
|
|
67
|
+
cached_tokens: number;
|
|
68
|
+
};
|
|
62
69
|
};
|
|
63
70
|
model?: string;
|
|
64
71
|
}
|
|
@@ -14,20 +14,29 @@ export interface LLMRequest {
|
|
|
14
14
|
readonly maxTokens?: number;
|
|
15
15
|
readonly topP?: number;
|
|
16
16
|
readonly stream?: boolean;
|
|
17
|
+
readonly streamOptions?: {
|
|
18
|
+
include_usage: boolean;
|
|
19
|
+
};
|
|
17
20
|
readonly model?: string;
|
|
18
21
|
readonly responseFormat?: 'text' | 'json';
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* LLM 调用的 token 消耗统计。
|
|
25
|
+
*/
|
|
26
|
+
export interface TokenUsage {
|
|
27
|
+
readonly promptTokens: number;
|
|
28
|
+
readonly completionTokens: number;
|
|
29
|
+
readonly totalTokens: number;
|
|
30
|
+
/** 命中缓存的 prompt token 数(Prompt Caching) */
|
|
31
|
+
readonly cachedPromptTokens?: number;
|
|
32
|
+
}
|
|
20
33
|
/**
|
|
21
34
|
* 标准化 LLM 响应。各 Provider 将原始响应解析为此格式后返回。
|
|
22
35
|
*/
|
|
23
36
|
export interface LLMResponse {
|
|
24
37
|
readonly content: string | null;
|
|
25
38
|
readonly reasoningContent?: string;
|
|
26
|
-
readonly usage?:
|
|
27
|
-
readonly promptTokens: number;
|
|
28
|
-
readonly completionTokens: number;
|
|
29
|
-
readonly totalTokens: number;
|
|
30
|
-
};
|
|
39
|
+
readonly usage?: TokenUsage;
|
|
31
40
|
readonly model: string;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, StreamChunk } from '../llm_provider/types';
|
|
1
|
+
import type { Message, StreamChunk, TokenUsage } from '../llm_provider/types';
|
|
2
2
|
import type { PredictConfig, PredictWithMessagesConfig } from './config';
|
|
3
3
|
/**
|
|
4
4
|
* LLM 静态类。封装 Provider 连接、请求组装、故障转移和返回解析。
|
|
@@ -8,8 +8,8 @@ import type { PredictConfig, PredictWithMessagesConfig } from './config';
|
|
|
8
8
|
* ```ts
|
|
9
9
|
* import { LLM } from 'axiom';
|
|
10
10
|
*
|
|
11
|
-
* const
|
|
12
|
-
* console.log(
|
|
11
|
+
* const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: '你好' });
|
|
12
|
+
* console.log(content, usage);
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export declare class LLM {
|
|
@@ -20,33 +20,45 @@ export declare class LLM {
|
|
|
20
20
|
* 内部自动构造单条 user message,支持可选的 system prompt。
|
|
21
21
|
*
|
|
22
22
|
* 根据 `responseFormat` 返回不同类型:
|
|
23
|
-
* - 未设置 / `'text'` → `string`
|
|
24
|
-
* - `'json'` →
|
|
23
|
+
* - 未设置 / `'text'` → `{ content: string, usage?: TokenUsage }`
|
|
24
|
+
* - `'json'` → `{ content: any, usage?: TokenUsage }`
|
|
25
25
|
*
|
|
26
26
|
* @param config - 预测配置(模型、prompt、温度等)
|
|
27
|
-
* @returns
|
|
27
|
+
* @returns 模型生成的完整响应及 token 消耗统计
|
|
28
28
|
* @throws Error - Provider 未配置、模型不存在、或所有 Provider 均失败时抛出
|
|
29
29
|
*/
|
|
30
30
|
static predict(config: PredictConfig & {
|
|
31
31
|
responseFormat: 'json';
|
|
32
|
-
}): Promise<
|
|
33
|
-
|
|
32
|
+
}): Promise<{
|
|
33
|
+
content: any;
|
|
34
|
+
usage?: TokenUsage;
|
|
35
|
+
}>;
|
|
36
|
+
static predict(config: PredictConfig): Promise<{
|
|
37
|
+
content: string;
|
|
38
|
+
usage?: TokenUsage;
|
|
39
|
+
}>;
|
|
34
40
|
/**
|
|
35
41
|
* 使用自定义消息列表调用 LLM。适用于多轮对话等需要精细控制 message 结构的场景。
|
|
36
42
|
*
|
|
37
43
|
* 根据 `responseFormat` 返回不同类型:
|
|
38
|
-
* - 未设置 / `'text'` → `string`
|
|
39
|
-
* - `'json'` →
|
|
44
|
+
* - 未设置 / `'text'` → `{ content: string, usage?: TokenUsage }`
|
|
45
|
+
* - `'json'` → `{ content: any, usage?: TokenUsage }`
|
|
40
46
|
*
|
|
41
47
|
* @param messages - 消息列表(user/assistant 角色)
|
|
42
48
|
* @param config - 预测配置(不含 prompt,因为由 messages 提供)
|
|
43
|
-
* @returns
|
|
49
|
+
* @returns 模型生成的完整响应及 token 消耗统计
|
|
44
50
|
* @throws Error - Provider 未配置、模型不存在、或所有 Provider 均失败时抛出
|
|
45
51
|
*/
|
|
46
52
|
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig & {
|
|
47
53
|
responseFormat: 'json';
|
|
48
|
-
}): Promise<
|
|
49
|
-
|
|
54
|
+
}): Promise<{
|
|
55
|
+
content: any;
|
|
56
|
+
usage?: TokenUsage;
|
|
57
|
+
}>;
|
|
58
|
+
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig): Promise<{
|
|
59
|
+
content: string;
|
|
60
|
+
usage?: TokenUsage;
|
|
61
|
+
}>;
|
|
50
62
|
/**
|
|
51
63
|
* 流式调用 LLM,逐块返回模型输出。
|
|
52
64
|
*
|
package/dist/predict/predict.js
CHANGED
|
@@ -69,14 +69,15 @@ function toLLMRequest(config, messages) {
|
|
|
69
69
|
}
|
|
70
70
|
function unwrapResponse(response, responseFormat) {
|
|
71
71
|
var _a;
|
|
72
|
+
const usage = response.usage;
|
|
72
73
|
if (responseFormat === 'json') {
|
|
73
74
|
if (!response.content) {
|
|
74
75
|
throw new Error('Empty response content when responseFormat is json');
|
|
75
76
|
}
|
|
76
|
-
return JSON.parse(response.content);
|
|
77
|
+
return { content: JSON.parse(response.content), usage };
|
|
77
78
|
}
|
|
78
79
|
// 默认按 text 返回(包括未设置 responseFormat 的情况)
|
|
79
|
-
return (_a = response.content) !== null && _a !== void 0 ? _a : '';
|
|
80
|
+
return { content: (_a = response.content) !== null && _a !== void 0 ? _a : '', usage };
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* LLM 静态类。封装 Provider 连接、请求组装、故障转移和返回解析。
|
|
@@ -86,8 +87,8 @@ function unwrapResponse(response, responseFormat) {
|
|
|
86
87
|
* ```ts
|
|
87
88
|
* import { LLM } from 'axiom';
|
|
88
89
|
*
|
|
89
|
-
* const
|
|
90
|
-
* console.log(
|
|
90
|
+
* const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: '你好' });
|
|
91
|
+
* console.log(content, usage);
|
|
91
92
|
* ```
|
|
92
93
|
*/
|
|
93
94
|
class LLM {
|