@keo-ai/axiom 0.2.0 → 0.2.1
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 +71 -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 +19 -3
- 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 +6 -4
- package/dist/llm_provider/index.d.ts +3 -0
- package/dist/llm_provider/types.d.ts +11 -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,10 +170,13 @@ 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
|
+
}
|
|
168
180
|
}
|
|
169
181
|
```
|
|
170
182
|
|
|
@@ -172,19 +184,20 @@ for await (const chunk of LLM.streamPredict({ model: 'qwen3.7-max', prompt: '讲
|
|
|
172
184
|
|
|
173
185
|
当前支持的模型(通过 `Model` 类型枚举):
|
|
174
186
|
|
|
175
|
-
| 模型 | 推理深度 | 说明 |
|
|
176
|
-
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `qwen-
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
| 模型 | 推理深度 | json 模式 | 说明 |
|
|
188
|
+
|------|----------|-----------|------|
|
|
189
|
+
| `qwen-max` | ❌ 不支持 | ✅ | **默认模型** |
|
|
190
|
+
| `qwen3.7-max` | ✅ 映射支持 | ✅ | `low`/`medium`/`high` 映射为 `enable_thinking` |
|
|
191
|
+
| `qwen-plus` | ✅ 映射支持 | ✅ | 同上 |
|
|
192
|
+
| `qwen-turbo` | ✅ 映射支持 | ✅ | 同上 |
|
|
193
|
+
| `deepseek-v4-pro` | ✅ 原生支持 | ✅ | `low`/`medium`/`high` 直接传递 |
|
|
194
|
+
| `deepseek-v4-flash` | ✅ 原生支持 | ✅ | 同上 |
|
|
195
|
+
| `kimi-k2.6` | ✅ 原生支持 | ✅ | 同上 |
|
|
196
|
+
| `qwq-plus` | ❌ 不支持 | ✅ | 固定推理行为,不可调节 |
|
|
197
|
+
| `glm-5.1` | ❌ 不支持 | ❌ | 不支持推理参数,不支持 json 模式 |
|
|
198
|
+
| `qwen-vl-plus` | ❌ 不支持 | ✅ | 视觉模型,不支持推理参数 |
|
|
199
|
+
|
|
200
|
+
> 推理深度的支持方式由 Provider 内部维护。百炼 Provider 中,Qwen 系列通过 `extra_body.enable_thinking` 映射实现(`low` 关闭,`medium`/`high` 开启),DeepSeek / Kimi 则原生透传 `reasoning_effort`。
|
|
188
201
|
>
|
|
189
202
|
> 目前所有模型均路由到百炼 Provider。后续接入其他厂商时,通过 `MODEL_REGISTRY` 扩展映射即可。
|
|
190
203
|
|
|
@@ -194,7 +207,7 @@ Predict 模块遵循 Axiom 的统一错误策略:**直接抛异常,调用方
|
|
|
194
207
|
|
|
195
208
|
```ts
|
|
196
209
|
try {
|
|
197
|
-
const
|
|
210
|
+
const { content, usage } = await LLM.predict({ model: 'qwen-max', prompt: 'hi' });
|
|
198
211
|
} catch (e) {
|
|
199
212
|
// e.message 包含 Provider 汇总错误信息
|
|
200
213
|
}
|
|
@@ -232,7 +245,7 @@ const result = await FunctionCallLoop.runLoop({
|
|
|
232
245
|
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
233
246
|
{ role: 'user', content: 'What is the weather in Beijing?' },
|
|
234
247
|
],
|
|
235
|
-
model: '
|
|
248
|
+
model: 'qwen-max',
|
|
236
249
|
temperature: 0.7,
|
|
237
250
|
maxTokens: 2048,
|
|
238
251
|
tools: [
|
|
@@ -262,6 +275,8 @@ const result = await FunctionCallLoop.runLoop({
|
|
|
262
275
|
console.log(result.finalContent);
|
|
263
276
|
console.log('Turns:', result.turns);
|
|
264
277
|
console.log('Harness:', result.harness);
|
|
278
|
+
console.log('Token usage:', result.totalUsage); // 累计 token 消耗
|
|
279
|
+
console.log('Usage history:', result.usageHistory); // 每轮明细
|
|
265
280
|
```
|
|
266
281
|
|
|
267
282
|
> 💡 需要**流式输出**(实时展示 LLM 生成内容)?使用下方的 [`runLoopStream`](#流式调用runloopstream)。
|
|
@@ -349,7 +364,7 @@ console.log('Turns:', result!.turns);
|
|
|
349
364
|
| `content` | LLM 输出 content(正式回复)增量 | `delta`, `turn` |
|
|
350
365
|
| `tool_call` | LLM 决定调用 tool(流结束、完整 tool_calls 解析完成) | `toolCalls`, `turn` |
|
|
351
366
|
| `tool_result` | tool 执行完成 | `callId`, `toolName`, `content`, `status`, `turn` |
|
|
352
|
-
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn` |
|
|
367
|
+
| `turn_end` | 一轮结束(tool 全部执行完或 content 直接返回) | `turn`, `usage?` |
|
|
353
368
|
|
|
354
369
|
#### 流式 vs 非流式的选择
|
|
355
370
|
|
|
@@ -678,6 +693,15 @@ interface LoopResult {
|
|
|
678
693
|
harness: HarnessRecord[]; // 执行历史
|
|
679
694
|
finalContent: string | null; // 最终回复内容
|
|
680
695
|
turns: number; // 实际执行轮数
|
|
696
|
+
usageHistory: TokenUsage[]; // 每轮 LLM 调用的 token 消耗明细
|
|
697
|
+
totalUsage: TokenUsage; // 累计 token 消耗(含 cachedPromptTokens)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
interface TokenUsage {
|
|
701
|
+
promptTokens: number;
|
|
702
|
+
completionTokens: number;
|
|
703
|
+
totalTokens: number;
|
|
704
|
+
cachedPromptTokens?: number; // 命中缓存的 prompt token(Prompt Caching)
|
|
681
705
|
}
|
|
682
706
|
```
|
|
683
707
|
|
|
@@ -825,7 +849,7 @@ interface SearchResult {
|
|
|
825
849
|
|
|
826
850
|
### 过滤
|
|
827
851
|
|
|
828
|
-
|
|
852
|
+
支持对表的任意独立列做等值、范围和 IN 查询:
|
|
829
853
|
|
|
830
854
|
```ts
|
|
831
855
|
// 等值过滤
|
|
@@ -833,7 +857,7 @@ const results = await EmbeddingSearch.query('query', {
|
|
|
833
857
|
filter: { category: '衣服', shop: '旗舰店' },
|
|
834
858
|
}, pool);
|
|
835
859
|
|
|
836
|
-
//
|
|
860
|
+
// 数值范围
|
|
837
861
|
const ranked = await EmbeddingSearch.query('query', {
|
|
838
862
|
filter: {
|
|
839
863
|
category: '衣服',
|
|
@@ -854,36 +878,7 @@ const multi = await EmbeddingSearch.query('query', {
|
|
|
854
878
|
|
|
855
879
|
### 枚举值解析
|
|
856
880
|
|
|
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
|
-
```
|
|
881
|
+
默认开启 `parseEnum`,返回结果中的特定枚举字段会自动从数字值解析为可读文本。如需关闭解析(保留原始数字值),设置 `parseEnum: false`。
|
|
887
882
|
|
|
888
883
|
未知枚举值、非数字类型、非枚举字段均保持原样不变。
|
|
889
884
|
|
|
@@ -970,5 +965,3 @@ EmbeddingSearch 模块内部调用百炼的 embedding 和 rerank 服务,无需
|
|
|
970
965
|
- **项目层填充内容** — 上层准备消息、工具、业务逻辑,Axiom 负责可靠执行
|
|
971
966
|
- **副作用即状态** — 工具调用改变外部世界,Harness 让这种改变在 Loop 间可见
|
|
972
967
|
- **单一事实来源** — 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),
|
|
@@ -160,7 +168,7 @@ function createLLMCaller(options) {
|
|
|
160
168
|
try {
|
|
161
169
|
parsed = JSON.parse(data);
|
|
162
170
|
}
|
|
163
|
-
catch (
|
|
171
|
+
catch (_g) {
|
|
164
172
|
continue;
|
|
165
173
|
}
|
|
166
174
|
const choice = (_c = parsed.choices) === null || _c === void 0 ? void 0 : _c[0];
|
|
@@ -219,6 +227,14 @@ function createLLMCaller(options) {
|
|
|
219
227
|
content: fullContent || null,
|
|
220
228
|
reasoningContent: fullReasoningContent || undefined,
|
|
221
229
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
230
|
+
usage: parsed.usage
|
|
231
|
+
? {
|
|
232
|
+
promptTokens: parsed.usage.prompt_tokens,
|
|
233
|
+
completionTokens: parsed.usage.completion_tokens,
|
|
234
|
+
totalTokens: parsed.usage.total_tokens,
|
|
235
|
+
cachedPromptTokens: (_f = parsed.usage.prompt_tokens_details) === null || _f === void 0 ? void 0 : _f.cached_tokens,
|
|
236
|
+
}
|
|
237
|
+
: undefined,
|
|
222
238
|
});
|
|
223
239
|
hasYieldedFinish = true;
|
|
224
240
|
}
|
|
@@ -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,7 +101,7 @@ 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
106
|
const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true }));
|
|
106
107
|
let response;
|
|
@@ -146,7 +147,7 @@ class BailianProvider {
|
|
|
146
147
|
try {
|
|
147
148
|
parsed = JSON.parse(data);
|
|
148
149
|
}
|
|
149
|
-
catch (
|
|
150
|
+
catch (_d) {
|
|
150
151
|
continue;
|
|
151
152
|
}
|
|
152
153
|
const choice = (_b = parsed.choices) === null || _b === void 0 ? void 0 : _b[0];
|
|
@@ -166,6 +167,7 @@ class BailianProvider {
|
|
|
166
167
|
promptTokens: parsed.usage.prompt_tokens,
|
|
167
168
|
completionTokens: parsed.usage.completion_tokens,
|
|
168
169
|
totalTokens: parsed.usage.total_tokens,
|
|
170
|
+
cachedPromptTokens: (_c = parsed.usage.prompt_tokens_details) === null || _c === void 0 ? void 0 : _c.cached_tokens,
|
|
169
171
|
},
|
|
170
172
|
});
|
|
171
173
|
}
|
|
@@ -17,17 +17,23 @@ export interface LLMRequest {
|
|
|
17
17
|
readonly model?: string;
|
|
18
18
|
readonly responseFormat?: 'text' | 'json';
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* LLM 调用的 token 消耗统计。
|
|
22
|
+
*/
|
|
23
|
+
export interface TokenUsage {
|
|
24
|
+
readonly promptTokens: number;
|
|
25
|
+
readonly completionTokens: number;
|
|
26
|
+
readonly totalTokens: number;
|
|
27
|
+
/** 命中缓存的 prompt token 数(Prompt Caching) */
|
|
28
|
+
readonly cachedPromptTokens?: number;
|
|
29
|
+
}
|
|
20
30
|
/**
|
|
21
31
|
* 标准化 LLM 响应。各 Provider 将原始响应解析为此格式后返回。
|
|
22
32
|
*/
|
|
23
33
|
export interface LLMResponse {
|
|
24
34
|
readonly content: string | null;
|
|
25
35
|
readonly reasoningContent?: string;
|
|
26
|
-
readonly usage?:
|
|
27
|
-
readonly promptTokens: number;
|
|
28
|
-
readonly completionTokens: number;
|
|
29
|
-
readonly totalTokens: number;
|
|
30
|
-
};
|
|
36
|
+
readonly usage?: TokenUsage;
|
|
31
37
|
readonly model: string;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
@@ -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 {
|