@keo-ai/axiom 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -177,14 +177,17 @@ for await (const chunk of LLM.streamPredict({ model: 'qwen-max', prompt: '讲个
177
177
  if (chunk.type === 'reasoning') {
178
178
  process.stdout.write(chunk.delta); // 推理过程
179
179
  }
180
- if (chunk.type === 'finish' && chunk.usage) {
180
+ if (chunk.type === 'finish') {
181
+ console.log('Finish:', chunk.finishReason, chunk.sawDone);
181
182
  console.log('Token usage:', chunk.usage);
182
- // { promptTokens, completionTokens, totalTokens, cachedPromptTokens? }
183
183
  }
184
184
  }
185
185
  ```
186
186
 
187
- > 💡 流式调用自动启用 `stream_options: { include_usage: true }`,`finish` 事件携带完整的 token 消耗统计。usage 可能与 `finish_reason` 在同一个 SSE chunk,也可能在独立的 chunk(`choices: []`)中返回,两种情况均已兼容。
187
+ > 💡 流式调用自动启用 `stream_options: { include_usage: true }`。`finish` 事件会返回 `finishReason`、
188
+ > `sawDone`、`sawFinishReason` 和 token 消耗统计。usage 可能与 `finish_reason` 在同一个 SSE chunk,也可能在独立的
189
+ > chunk(`choices: []`)中返回,两种情况均已兼容。若连接结束前既没有 `[DONE]` 也没有非空
190
+ > `finish_reason`,流会抛出异常,避免把提前 EOF 当作成功响应。
188
191
 
189
192
  ### 模型列表
190
193
 
@@ -60,7 +60,7 @@ class BailianProvider {
60
60
  // 通过 extra_body.enable_thinking 控制思考开关(low 关闭,medium/high 开启);
61
61
  // Kimi(Moonshot)则通过 extra_body.thinking.type 实现(默认开启)。
62
62
  if (body.reasoning_effort !== undefined) {
63
- if (['qwen-plus', 'qwen-turbo', 'qwen3.7-max', 'qwen3.7-flash', 'deepseek-v4-pro', 'deepseek-v4-flash'].includes(body.model)) {
63
+ if (['qwen-plus', 'qwen-turbo', 'qwen3.8-flash', 'qwen3.7-max', 'qwen3.7-flash', 'deepseek-v4-pro', 'deepseek-v4-flash'].includes(body.model)) {
64
64
  const { reasoning_effort } = body, rest = __rest(body, ["reasoning_effort"]);
65
65
  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' }) });
66
66
  }
@@ -109,7 +109,7 @@ class BailianProvider {
109
109
  */
110
110
  stream(request) {
111
111
  return __asyncGenerator(this, arguments, function* stream_1() {
112
- var _a, _b, _c;
112
+ var _a;
113
113
  const url = `${this.config.baseUrl}/chat/completions`;
114
114
  const body = this.adaptRequest(Object.assign(Object.assign({}, this.buildRequestBody(request)), { stream: true, stream_options: { include_usage: true } }));
115
115
  let response;
@@ -140,6 +140,53 @@ class BailianProvider {
140
140
  // usage 可能与 finish_reason 在同一个 chunk,也可能在独立的 chunk(choices: [])中返回。
141
141
  // 统一在此收集,流结束后一次性 yield finish 事件。
142
142
  let streamUsage;
143
+ let finishReason;
144
+ let sawDone = false;
145
+ let sawFinishReason = false;
146
+ function* processSseLine(line) {
147
+ var _a, _b;
148
+ const trimmed = line.trim();
149
+ if (!trimmed || !trimmed.startsWith('data:'))
150
+ return;
151
+ const data = trimmed.slice(5).trimStart();
152
+ if (!data)
153
+ return;
154
+ if (data === '[DONE]') {
155
+ sawDone = true;
156
+ return;
157
+ }
158
+ let parsed;
159
+ try {
160
+ parsed = JSON.parse(data);
161
+ }
162
+ catch (cause) {
163
+ const message = cause instanceof Error ? cause.message : String(cause);
164
+ throw new Error(`[bailian] Malformed SSE JSON: ${message}`);
165
+ }
166
+ // 从任意 chunk 中收集 usage(包括 choices 为空的独立 usage chunk)
167
+ if (parsed.usage) {
168
+ streamUsage = {
169
+ promptTokens: parsed.usage.prompt_tokens,
170
+ completionTokens: parsed.usage.completion_tokens,
171
+ totalTokens: parsed.usage.total_tokens,
172
+ cachedPromptTokens: (_a = parsed.usage.prompt_tokens_details) === null || _a === void 0 ? void 0 : _a.cached_tokens,
173
+ };
174
+ }
175
+ const choice = (_b = parsed.choices) === null || _b === void 0 ? void 0 : _b[0];
176
+ if (!choice)
177
+ return;
178
+ const delta = choice.delta;
179
+ if (delta.reasoning_content) {
180
+ yield { type: 'reasoning', delta: delta.reasoning_content };
181
+ }
182
+ if (delta.content) {
183
+ yield { type: 'content', delta: delta.content };
184
+ }
185
+ if (choice.finish_reason) {
186
+ finishReason = choice.finish_reason;
187
+ sawFinishReason = true;
188
+ }
189
+ }
143
190
  try {
144
191
  while (true) {
145
192
  const { done, value } = yield __await(reader.read());
@@ -149,41 +196,31 @@ class BailianProvider {
149
196
  const lines = buffer.split('\n');
150
197
  buffer = (_a = lines.pop()) !== null && _a !== void 0 ? _a : '';
151
198
  for (const line of lines) {
152
- const trimmed = line.trim();
153
- if (!trimmed || !trimmed.startsWith('data: '))
154
- continue;
155
- const data = trimmed.slice(6);
156
- if (data === '[DONE]')
157
- continue;
158
- let parsed;
159
- try {
160
- parsed = JSON.parse(data);
161
- }
162
- catch (_d) {
163
- continue;
164
- }
165
- // 从任意 chunk 中收集 usage(包括 choices 为空的独立 usage chunk)
166
- if (parsed.usage) {
167
- streamUsage = {
168
- promptTokens: parsed.usage.prompt_tokens,
169
- completionTokens: parsed.usage.completion_tokens,
170
- totalTokens: parsed.usage.total_tokens,
171
- cachedPromptTokens: (_b = parsed.usage.prompt_tokens_details) === null || _b === void 0 ? void 0 : _b.cached_tokens,
172
- };
173
- }
174
- const choice = (_c = parsed.choices) === null || _c === void 0 ? void 0 : _c[0];
175
- if (!choice)
176
- continue;
177
- const delta = choice.delta;
178
- if (delta.reasoning_content) {
179
- yield yield __await({ type: 'reasoning', delta: delta.reasoning_content });
199
+ for (const chunk of processSseLine(line)) {
200
+ yield yield __await(chunk);
180
201
  }
181
- if (delta.content) {
182
- yield yield __await({ type: 'content', delta: delta.content });
202
+ }
203
+ }
204
+ // TextDecoder 的流式模式可能仍持有不完整的多字节字符;EOF 时必须 flush。
205
+ buffer += decoder.decode();
206
+ if (buffer) {
207
+ const finalLines = buffer.split('\n');
208
+ for (const line of finalLines) {
209
+ for (const chunk of processSseLine(line)) {
210
+ yield yield __await(chunk);
183
211
  }
184
212
  }
185
213
  }
186
- yield yield __await({ type: 'finish', usage: streamUsage });
214
+ if (!sawDone && !sawFinishReason) {
215
+ throw new Error(`[${this.name}] SSE stream ended before [DONE] or finish_reason`);
216
+ }
217
+ yield yield __await({
218
+ type: 'finish',
219
+ finishReason,
220
+ sawDone,
221
+ sawFinishReason,
222
+ usage: streamUsage,
223
+ });
187
224
  }
188
225
  finally {
189
226
  reader.releaseLock();
@@ -212,6 +249,7 @@ class BailianProvider {
212
249
  }
213
250
  exports.BailianProvider = BailianProvider;
214
251
  BailianProvider.SUPPORTED_MODELS = new Set([
252
+ 'qwen3.8-flash',
215
253
  'qwen3.7-max',
216
254
  'qwen3.7-flash',
217
255
  'qwen-plus',
@@ -225,6 +263,7 @@ BailianProvider.SUPPORTED_MODELS = new Set([
225
263
  ]);
226
264
  BailianProvider.MODEL_CAPABILITIES = {
227
265
  'qwen-max': { jsonMode: true, reasoningEffort: false },
266
+ 'qwen3.8-flash': { jsonMode: true, reasoningEffort: true },
228
267
  'qwen3.7-max': { jsonMode: true, reasoningEffort: true },
229
268
  'qwen3.7-flash': { jsonMode: true, reasoningEffort: true },
230
269
  'qwen-plus': { jsonMode: true, reasoningEffort: true },
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 支持的模型枚举。项目层通过此枚举选择模型,Axiom 内部路由到对应 Provider。
3
3
  */
4
- export type Model = 'qwen3.7-max' | 'qwen3.7-flash' | 'qwen-plus' | 'qwen-turbo' | 'qwq-plus' | 'deepseek-v4-pro' | 'deepseek-v4-flash' | 'kimi-k2.6' | 'glm-5.1' | 'qwen-vl-plus';
4
+ export type Model = 'qwen3.8-flash' | 'qwen3.7-max' | 'qwen3.7-flash' | 'qwen-plus' | 'qwen-turbo' | 'qwq-plus' | 'deepseek-v4-pro' | 'deepseek-v4-flash' | 'kimi-k2.6' | 'glm-5.1' | 'qwen-vl-plus';
5
5
  /** 模型到 Provider 的映射配置 */
6
6
  export interface ModelConfig {
7
7
  readonly model: string;
@@ -6,6 +6,7 @@ exports.MODEL_REGISTRY = void 0;
6
6
  * 当首选 Provider 失败时,Predictor 按此表顺序尝试下一个。
7
7
  */
8
8
  exports.MODEL_REGISTRY = {
9
+ 'qwen3.8-flash': [{ model: 'qwen3.8-flash', provider: 'bailian' }],
9
10
  'qwen3.7-max': [{ model: 'qwen3.7-max', provider: 'bailian' }],
10
11
  'qwen3.7-flash': [{ model: 'qwen3.7-flash', provider: 'bailian' }],
11
12
  'qwen-plus': [{ model: 'qwen-plus', provider: 'bailian' }],
@@ -52,7 +52,13 @@ export type StreamChunk = {
52
52
  readonly delta: string;
53
53
  } | {
54
54
  readonly type: 'finish';
55
- usage?: LLMResponse['usage'];
55
+ /** 上游返回的生成结束原因,例如 `stop`、`length` 或 `content_filter`。 */
56
+ readonly finishReason?: string;
57
+ /** SSE 流是否收到显式的 `data: [DONE]` 终止标记;未提供表示 Provider 不支持该观测。 */
58
+ readonly sawDone?: boolean;
59
+ /** SSE 流是否收到非空的 `finish_reason`;未提供表示 Provider 不支持该观测。 */
60
+ readonly sawFinishReason?: boolean;
61
+ readonly usage?: LLMResponse['usage'];
56
62
  };
57
63
  /**
58
64
  * Provider 配置。每个 Provider 实例需要一组连接参数。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keo-ai/axiom",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "基于 LLM 的预测与推理库,支持多 Provider 切换",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",