@lobehub/chat 1.133.2 → 1.133.4
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/.github/workflows/claude-translator.yml +2 -3
- package/.github/workflows/issue-auto-comments.yml +4 -9
- package/.github/workflows/issue-close-require.yml +3 -6
- package/CHANGELOG.md +66 -0
- package/changelog/v1.json +24 -0
- package/locales/ar/image.json +7 -0
- package/locales/ar/models.json +1 -1
- package/locales/bg-BG/image.json +7 -0
- package/locales/de-DE/image.json +7 -0
- package/locales/en-US/image.json +7 -0
- package/locales/es-ES/image.json +7 -0
- package/locales/es-ES/tool.json +1 -1
- package/locales/fa-IR/image.json +7 -0
- package/locales/fa-IR/models.json +1 -1
- package/locales/fr-FR/image.json +7 -0
- package/locales/fr-FR/models.json +1 -1
- package/locales/it-IT/image.json +7 -0
- package/locales/ja-JP/image.json +7 -0
- package/locales/ko-KR/image.json +7 -0
- package/locales/nl-NL/image.json +7 -0
- package/locales/pl-PL/image.json +7 -0
- package/locales/pt-BR/image.json +7 -0
- package/locales/ru-RU/image.json +7 -0
- package/locales/ru-RU/tool.json +1 -1
- package/locales/tr-TR/image.json +7 -0
- package/locales/tr-TR/models.json +1 -1
- package/locales/vi-VN/image.json +7 -0
- package/locales/zh-CN/image.json +7 -0
- package/locales/zh-TW/image.json +7 -0
- package/package.json +4 -5
- package/packages/const/package.json +4 -0
- package/packages/const/src/currency.ts +2 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/model-bank/package.json +2 -1
- package/packages/model-bank/src/aiModels/aihubmix.ts +34 -1
- package/packages/model-bank/src/aiModels/anthropic.ts +3 -64
- package/packages/model-bank/src/aiModels/google.ts +6 -0
- package/packages/model-bank/src/aiModels/novita.ts +2 -2
- package/packages/model-bank/src/aiModels/openai.ts +6 -22
- package/packages/model-bank/src/aiModels/qwen.ts +21 -0
- package/packages/model-bank/src/aiModels/zhipu.ts +255 -62
- package/packages/model-bank/src/standard-parameters/index.ts +56 -46
- package/packages/model-runtime/package.json +1 -0
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +4 -2
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +12 -2
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +16 -5
- package/packages/model-runtime/src/core/streams/anthropic.ts +25 -36
- package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +1 -1
- package/packages/model-runtime/src/core/streams/google/index.ts +18 -42
- package/packages/model-runtime/src/core/streams/openai/openai.test.ts +7 -10
- package/packages/model-runtime/src/core/streams/openai/openai.ts +14 -11
- package/packages/model-runtime/src/core/streams/openai/responsesStream.ts +11 -5
- package/packages/model-runtime/src/core/streams/protocol.ts +25 -6
- package/packages/model-runtime/src/core/streams/qwen.ts +2 -2
- package/packages/model-runtime/src/core/streams/spark.ts +3 -3
- package/packages/model-runtime/src/core/streams/vertex-ai.test.ts +2 -2
- package/packages/model-runtime/src/core/streams/vertex-ai.ts +14 -23
- package/packages/model-runtime/src/core/usageConverters/anthropic.test.ts +99 -0
- package/packages/model-runtime/src/core/usageConverters/anthropic.ts +73 -0
- package/packages/model-runtime/src/core/usageConverters/google-ai.test.ts +88 -0
- package/packages/model-runtime/src/core/usageConverters/google-ai.ts +55 -0
- package/packages/model-runtime/src/core/usageConverters/index.ts +4 -0
- package/packages/model-runtime/src/core/usageConverters/openai.test.ts +429 -0
- package/packages/model-runtime/src/core/usageConverters/openai.ts +152 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +455 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.ts +293 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeImageCost.test.ts +47 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeImageCost.ts +121 -0
- package/packages/model-runtime/src/core/usageConverters/utils/index.ts +11 -0
- package/packages/model-runtime/src/core/usageConverters/utils/withUsageCost.ts +19 -0
- package/packages/model-runtime/src/index.ts +2 -0
- package/packages/model-runtime/src/providers/anthropic/index.test.ts +0 -12
- package/packages/model-runtime/src/providers/anthropic/index.ts +48 -1
- package/packages/model-runtime/src/providers/google/createImage.ts +11 -2
- package/packages/model-runtime/src/providers/google/index.ts +8 -1
- package/packages/model-runtime/src/providers/novita/index.ts +2 -1
- package/packages/model-runtime/src/providers/novita/type.ts +4 -0
- package/packages/model-runtime/src/providers/ollamacloud/index.ts +1 -1
- package/packages/model-runtime/src/providers/openai/__snapshots__/index.test.ts.snap +7 -0
- package/packages/model-runtime/src/providers/openrouter/index.ts +11 -4
- package/packages/model-runtime/src/providers/zhipu/index.ts +3 -1
- package/packages/model-runtime/src/types/chat.ts +5 -3
- package/packages/model-runtime/src/types/image.ts +20 -9
- package/packages/model-runtime/src/utils/getModelPricing.ts +36 -0
- package/packages/obervability-otel/package.json +2 -2
- package/packages/ssrf-safe-fetch/index.test.ts +343 -0
- package/packages/ssrf-safe-fetch/index.ts +37 -0
- package/packages/ssrf-safe-fetch/package.json +17 -0
- package/packages/ssrf-safe-fetch/vitest.config.mts +10 -0
- package/packages/types/src/message/base.ts +43 -17
- package/packages/utils/package.json +0 -1
- package/packages/utils/src/client/apiKeyManager.test.ts +70 -0
- package/packages/utils/src/client/apiKeyManager.ts +41 -0
- package/packages/utils/src/client/index.ts +2 -0
- package/packages/utils/src/fetch/fetchSSE.ts +4 -4
- package/packages/utils/src/index.ts +1 -0
- package/packages/utils/src/toolManifest.ts +2 -1
- package/src/app/(backend)/webapi/proxy/route.ts +2 -13
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatMinimap/index.tsx +51 -23
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/QualitySelect.tsx +23 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +9 -0
- package/src/config/modelProviders/anthropic.ts +0 -30
- package/src/config/modelProviders/ollamacloud.ts +1 -0
- package/src/config/modelProviders/zhipu.ts +4 -21
- package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.test.ts +13 -13
- package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +1 -1
- package/src/features/Conversation/components/WideScreenContainer/index.tsx +3 -0
- package/src/locales/default/image.ts +7 -0
- package/src/server/modules/EdgeConfig/index.ts +1 -1
- package/src/server/routers/async/image.ts +9 -1
- package/src/services/_auth.ts +12 -12
- package/src/services/chat/contextEngineering.ts +2 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
1
2
|
import type { Simplify } from 'type-fest';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
|
|
@@ -53,36 +54,14 @@ export const CHAT_MODEL_IMAGE_GENERATION_PARAMS: ModelParamsSchema = {
|
|
|
53
54
|
|
|
54
55
|
// 定义顶层的元规范 - 平铺结构
|
|
55
56
|
export const ModelParamsMetaSchema = z.object({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
cfg: z
|
|
66
|
-
.object({
|
|
67
|
-
default: z.number(),
|
|
68
|
-
description: z.string().optional(),
|
|
69
|
-
max: z.number(),
|
|
70
|
-
min: z.number(),
|
|
71
|
-
step: z.number(),
|
|
72
|
-
type: z.literal('number').optional(),
|
|
73
|
-
})
|
|
74
|
-
.optional(),
|
|
75
|
-
|
|
76
|
-
height: z
|
|
77
|
-
.object({
|
|
78
|
-
default: z.number(),
|
|
79
|
-
description: z.string().optional(),
|
|
80
|
-
max: z.number(),
|
|
81
|
-
min: z.number(),
|
|
82
|
-
step: z.number().optional().default(1),
|
|
83
|
-
type: z.literal('number').optional(),
|
|
84
|
-
})
|
|
85
|
-
.optional(),
|
|
57
|
+
/**
|
|
58
|
+
* Prompt 是唯一一个每个模型都有的参数
|
|
59
|
+
*/
|
|
60
|
+
prompt: z.object({
|
|
61
|
+
default: z.string().optional().default(''),
|
|
62
|
+
description: z.string().optional(),
|
|
63
|
+
type: z.literal('string').optional(),
|
|
64
|
+
}),
|
|
86
65
|
|
|
87
66
|
imageUrl: z
|
|
88
67
|
.object({
|
|
@@ -106,22 +85,25 @@ export const ModelParamsMetaSchema = z.object({
|
|
|
106
85
|
})
|
|
107
86
|
.optional(),
|
|
108
87
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
88
|
+
width: z
|
|
89
|
+
.object({
|
|
90
|
+
default: z.number(),
|
|
91
|
+
description: z.string().optional(),
|
|
92
|
+
max: z.number(),
|
|
93
|
+
min: z.number(),
|
|
94
|
+
step: z.number().optional().default(1),
|
|
95
|
+
type: z.literal('number').optional(),
|
|
96
|
+
})
|
|
97
|
+
.optional(),
|
|
117
98
|
|
|
118
|
-
|
|
99
|
+
height: z
|
|
119
100
|
.object({
|
|
120
|
-
default: z.number()
|
|
101
|
+
default: z.number(),
|
|
121
102
|
description: z.string().optional(),
|
|
122
|
-
max: z.number()
|
|
123
|
-
min: z.number()
|
|
124
|
-
|
|
103
|
+
max: z.number(),
|
|
104
|
+
min: z.number(),
|
|
105
|
+
step: z.number().optional().default(1),
|
|
106
|
+
type: z.literal('number').optional(),
|
|
125
107
|
})
|
|
126
108
|
.optional(),
|
|
127
109
|
|
|
@@ -134,18 +116,27 @@ export const ModelParamsMetaSchema = z.object({
|
|
|
134
116
|
})
|
|
135
117
|
.optional(),
|
|
136
118
|
|
|
137
|
-
|
|
119
|
+
aspectRatio: z
|
|
120
|
+
.object({
|
|
121
|
+
default: z.string(),
|
|
122
|
+
description: z.string().optional(),
|
|
123
|
+
enum: z.array(z.string()),
|
|
124
|
+
type: z.literal('string').optional(),
|
|
125
|
+
})
|
|
126
|
+
.optional(),
|
|
127
|
+
|
|
128
|
+
cfg: z
|
|
138
129
|
.object({
|
|
139
130
|
default: z.number(),
|
|
140
131
|
description: z.string().optional(),
|
|
141
132
|
max: z.number(),
|
|
142
133
|
min: z.number(),
|
|
143
|
-
step: z.number()
|
|
134
|
+
step: z.number(),
|
|
144
135
|
type: z.literal('number').optional(),
|
|
145
136
|
})
|
|
146
137
|
.optional(),
|
|
147
138
|
|
|
148
|
-
|
|
139
|
+
steps: z
|
|
149
140
|
.object({
|
|
150
141
|
default: z.number(),
|
|
151
142
|
description: z.string().optional(),
|
|
@@ -155,6 +146,25 @@ export const ModelParamsMetaSchema = z.object({
|
|
|
155
146
|
type: z.literal('number').optional(),
|
|
156
147
|
})
|
|
157
148
|
.optional(),
|
|
149
|
+
|
|
150
|
+
quality: z
|
|
151
|
+
.object({
|
|
152
|
+
default: z.string(),
|
|
153
|
+
description: z.string().optional(),
|
|
154
|
+
enum: z.array(z.string()),
|
|
155
|
+
type: z.literal('string').optional(),
|
|
156
|
+
})
|
|
157
|
+
.optional(),
|
|
158
|
+
|
|
159
|
+
seed: z
|
|
160
|
+
.object({
|
|
161
|
+
default: z.number().nullable().default(null),
|
|
162
|
+
description: z.string().optional(),
|
|
163
|
+
max: z.number().optional().default(MAX_SEED),
|
|
164
|
+
min: z.number().optional().default(0),
|
|
165
|
+
type: z.tuple([z.literal('number'), z.literal('null')]).optional(),
|
|
166
|
+
})
|
|
167
|
+
.optional(),
|
|
158
168
|
});
|
|
159
169
|
// 导出推断出的类型,供定义对象使用
|
|
160
170
|
export type ModelParamsSchema = z.input<typeof ModelParamsMetaSchema>;
|
|
@@ -129,7 +129,7 @@ export const createRouterRuntime = ({
|
|
|
129
129
|
...params
|
|
130
130
|
}: CreateRouterRuntimeOptions) => {
|
|
131
131
|
return class UniformRuntime implements LobeRuntimeAI {
|
|
132
|
-
|
|
132
|
+
public _options: ClientOptions & Record<string, any>;
|
|
133
133
|
private _routers: Routers;
|
|
134
134
|
private _params: any;
|
|
135
135
|
private _id: string;
|
|
@@ -148,7 +148,7 @@ export const createRouterRuntime = ({
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
* TODO:
|
|
151
|
+
* TODO: 考虑添加缓存机制,避免重复创建相同配置的 runtimes
|
|
152
152
|
*/
|
|
153
153
|
private async createRuntimesByRouters(model?: string): Promise<RuntimeItem[]> {
|
|
154
154
|
// 动态获取 routers,支持传入 model
|
|
@@ -181,9 +181,11 @@ export const createRouterRuntime = ({
|
|
|
181
181
|
for (const runtimeItem of runtimes) {
|
|
182
182
|
const models = runtimeItem.models || [];
|
|
183
183
|
if (models.includes(model)) {
|
|
184
|
+
console.log(`get runtime ${runtimeItem.id} ${model}`);
|
|
184
185
|
return runtimeItem.runtime;
|
|
185
186
|
}
|
|
186
187
|
}
|
|
188
|
+
|
|
187
189
|
return runtimes.at(-1)!.runtime;
|
|
188
190
|
}
|
|
189
191
|
|
|
@@ -3,9 +3,11 @@ import { RuntimeImageGenParamsValue } from 'model-bank';
|
|
|
3
3
|
import OpenAI from 'openai';
|
|
4
4
|
|
|
5
5
|
import { CreateImagePayload, CreateImageResponse } from '../../types/image';
|
|
6
|
+
import { getModelPricing } from '../../utils/getModelPricing';
|
|
6
7
|
import { imageUrlToBase64 } from '../../utils/imageToBase64';
|
|
7
8
|
import { convertImageUrlToFile } from '../../utils/openaiHelpers';
|
|
8
9
|
import { parseDataUri } from '../../utils/uriParser';
|
|
10
|
+
import { convertOpenAIImageUsage } from '../usageConverters/openai';
|
|
9
11
|
|
|
10
12
|
const log = createDebug('lobe-image:openai-compatible');
|
|
11
13
|
|
|
@@ -15,6 +17,7 @@ const log = createDebug('lobe-image:openai-compatible');
|
|
|
15
17
|
async function generateByImageMode(
|
|
16
18
|
client: OpenAI,
|
|
17
19
|
payload: CreateImagePayload,
|
|
20
|
+
provider: string,
|
|
18
21
|
): Promise<CreateImageResponse> {
|
|
19
22
|
const { model, params } = payload;
|
|
20
23
|
|
|
@@ -112,8 +115,15 @@ async function generateByImageMode(
|
|
|
112
115
|
throw new Error('Invalid image response: missing both b64_json and url fields');
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
log('provider: %s', provider);
|
|
119
|
+
|
|
115
120
|
return {
|
|
116
121
|
imageUrl,
|
|
122
|
+
...(img.usage
|
|
123
|
+
? {
|
|
124
|
+
modelUsage: convertOpenAIImageUsage(img.usage, await getModelPricing(model, provider)),
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
117
127
|
};
|
|
118
128
|
}
|
|
119
129
|
|
|
@@ -218,7 +228,7 @@ async function generateByChatModel(
|
|
|
218
228
|
export async function createOpenAICompatibleImage(
|
|
219
229
|
client: OpenAI,
|
|
220
230
|
payload: CreateImagePayload,
|
|
221
|
-
|
|
231
|
+
provider: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
222
232
|
): Promise<CreateImageResponse> {
|
|
223
233
|
try {
|
|
224
234
|
const { model } = payload;
|
|
@@ -229,7 +239,7 @@ export async function createOpenAICompatibleImage(
|
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
// Default to traditional images API
|
|
232
|
-
return await generateByImageMode(client, payload);
|
|
242
|
+
return await generateByImageMode(client, payload, provider);
|
|
233
243
|
} catch (error) {
|
|
234
244
|
const err = error as Error;
|
|
235
245
|
log('Error in createImage: %O', err);
|
|
@@ -27,6 +27,7 @@ import { AgentRuntimeError } from '../../utils/createError';
|
|
|
27
27
|
import { debugResponse, debugStream } from '../../utils/debugStream';
|
|
28
28
|
import { desensitizeUrl } from '../../utils/desensitizeUrl';
|
|
29
29
|
import { getModelPropertyWithFallback } from '../../utils/getFallbackModelProperty';
|
|
30
|
+
import { getModelPricing } from '../../utils/getModelPricing';
|
|
30
31
|
import { handleOpenAIError } from '../../utils/handleOpenAIError';
|
|
31
32
|
import { convertOpenAIMessages, convertOpenAIResponseInputs } from '../../utils/openaiHelpers';
|
|
32
33
|
import { postProcessModelList } from '../../utils/postProcessModelList';
|
|
@@ -228,7 +229,11 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
228
229
|
const streamOptions: OpenAIStreamOptions = {
|
|
229
230
|
bizErrorTypeTransformer: chatCompletion?.handleStreamBizErrorType,
|
|
230
231
|
callbacks: options?.callback,
|
|
231
|
-
|
|
232
|
+
payload: {
|
|
233
|
+
model: payload.model,
|
|
234
|
+
pricing: await getModelPricing(payload.model, this.id),
|
|
235
|
+
provider: this.id,
|
|
236
|
+
},
|
|
232
237
|
};
|
|
233
238
|
|
|
234
239
|
if (customClient?.createChatCompletionStream) {
|
|
@@ -276,7 +281,10 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
276
281
|
callbacks: streamOptions.callbacks,
|
|
277
282
|
inputStartAt,
|
|
278
283
|
})
|
|
279
|
-
: OpenAIStream(prod, {
|
|
284
|
+
: OpenAIStream(prod, {
|
|
285
|
+
...streamOptions,
|
|
286
|
+
inputStartAt,
|
|
287
|
+
}),
|
|
280
288
|
{
|
|
281
289
|
headers: options?.headers,
|
|
282
290
|
},
|
|
@@ -320,7 +328,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
320
328
|
}
|
|
321
329
|
|
|
322
330
|
// Use the new createOpenAICompatibleImage function
|
|
323
|
-
return createOpenAICompatibleImage(this.client, payload,
|
|
331
|
+
return createOpenAICompatibleImage(this.client, payload, this.id);
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
async models() {
|
|
@@ -453,7 +461,6 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
453
461
|
headers: options?.headers,
|
|
454
462
|
signal: options?.signal,
|
|
455
463
|
});
|
|
456
|
-
|
|
457
464
|
return mp3.arrayBuffer();
|
|
458
465
|
} catch (error) {
|
|
459
466
|
throw this.handleError(error);
|
|
@@ -585,7 +592,11 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
585
592
|
const streamOptions: OpenAIStreamOptions = {
|
|
586
593
|
bizErrorTypeTransformer: chatCompletion?.handleStreamBizErrorType,
|
|
587
594
|
callbacks: options?.callback,
|
|
588
|
-
|
|
595
|
+
payload: {
|
|
596
|
+
model: payload.model,
|
|
597
|
+
pricing: await getModelPricing(payload.model, this.id),
|
|
598
|
+
provider: this.id,
|
|
599
|
+
},
|
|
589
600
|
};
|
|
590
601
|
|
|
591
602
|
if (isStreaming) {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import type { Stream } from '@anthropic-ai/sdk/streaming';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { ChatCitationItem } from '@/types/message';
|
|
4
5
|
|
|
5
6
|
import { ChatStreamCallbacks } from '../../types';
|
|
7
|
+
import { convertAnthropicUsage } from '../usageConverters';
|
|
6
8
|
import {
|
|
9
|
+
ChatPayloadForTransformStream,
|
|
7
10
|
StreamContext,
|
|
8
11
|
StreamProtocolChunk,
|
|
9
12
|
StreamProtocolToolCallChunk,
|
|
@@ -17,31 +20,20 @@ import {
|
|
|
17
20
|
export const transformAnthropicStream = (
|
|
18
21
|
chunk: Anthropic.MessageStreamEvent,
|
|
19
22
|
context: StreamContext,
|
|
23
|
+
payload?: ChatPayloadForTransformStream,
|
|
20
24
|
): StreamProtocolChunk | StreamProtocolChunk[] => {
|
|
21
25
|
// maybe need another structure to add support for multiple choices
|
|
22
26
|
switch (chunk.type) {
|
|
23
27
|
case 'message_start': {
|
|
24
28
|
context.id = chunk.message.id;
|
|
25
29
|
context.returnedCitationArray = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
chunk.message.usage?.cache_creation_input_tokens ||
|
|
30
|
-
chunk.message.usage?.cache_read_input_tokens
|
|
31
|
-
) {
|
|
32
|
-
totalInputTokens =
|
|
33
|
-
chunk.message.usage?.input_tokens +
|
|
34
|
-
(chunk.message.usage.cache_creation_input_tokens || 0) +
|
|
35
|
-
(chunk.message.usage.cache_read_input_tokens || 0);
|
|
36
|
-
}
|
|
30
|
+
const usage = convertAnthropicUsage(chunk, undefined, payload);
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
totalOutputTokens: chunk.message.usage?.output_tokens,
|
|
44
|
-
};
|
|
32
|
+
if (usage) {
|
|
33
|
+
context.usage = usage;
|
|
34
|
+
} else {
|
|
35
|
+
delete context.usage;
|
|
36
|
+
}
|
|
45
37
|
|
|
46
38
|
return { data: chunk.message, id: chunk.message.id, type: 'data' };
|
|
47
39
|
}
|
|
@@ -193,26 +185,19 @@ export const transformAnthropicStream = (
|
|
|
193
185
|
}
|
|
194
186
|
|
|
195
187
|
case 'message_delta': {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
188
|
+
const aggregatedUsage = convertAnthropicUsage(chunk, context.usage, payload);
|
|
189
|
+
|
|
190
|
+
if (aggregatedUsage) {
|
|
191
|
+
context.usage = aggregatedUsage;
|
|
192
|
+
}
|
|
200
193
|
|
|
201
|
-
if (totalTokens > 0) {
|
|
194
|
+
if (aggregatedUsage && (aggregatedUsage.totalTokens ?? 0) > 0) {
|
|
202
195
|
return [
|
|
203
196
|
{ data: chunk.delta.stop_reason, id: context.id, type: 'stop' },
|
|
204
|
-
{
|
|
205
|
-
data: {
|
|
206
|
-
...context.usage,
|
|
207
|
-
totalInputTokens,
|
|
208
|
-
totalOutputTokens,
|
|
209
|
-
totalTokens,
|
|
210
|
-
} as ModelTokensUsage,
|
|
211
|
-
id: context.id,
|
|
212
|
-
type: 'usage',
|
|
213
|
-
},
|
|
197
|
+
{ data: aggregatedUsage, id: context.id, type: 'usage' },
|
|
214
198
|
];
|
|
215
199
|
}
|
|
200
|
+
|
|
216
201
|
return { data: chunk.delta.stop_reason, id: context.id, type: 'stop' };
|
|
217
202
|
}
|
|
218
203
|
|
|
@@ -241,20 +226,24 @@ export interface AnthropicStreamOptions {
|
|
|
241
226
|
callbacks?: ChatStreamCallbacks;
|
|
242
227
|
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
243
228
|
inputStartAt?: number;
|
|
229
|
+
payload?: ChatPayloadForTransformStream;
|
|
244
230
|
}
|
|
245
231
|
|
|
246
232
|
export const AnthropicStream = (
|
|
247
233
|
stream: Stream<Anthropic.MessageStreamEvent> | ReadableStream,
|
|
248
|
-
{ callbacks, inputStartAt, enableStreaming = true }: AnthropicStreamOptions = {},
|
|
234
|
+
{ callbacks, inputStartAt, enableStreaming = true, payload }: AnthropicStreamOptions = {},
|
|
249
235
|
) => {
|
|
250
236
|
const streamStack: StreamContext = { id: '' };
|
|
251
237
|
|
|
252
238
|
const readableStream =
|
|
253
239
|
stream instanceof ReadableStream ? stream : convertIterableToStream(stream);
|
|
254
240
|
|
|
241
|
+
const transformWithPayload: typeof transformAnthropicStream = (chunk, ctx) =>
|
|
242
|
+
transformAnthropicStream(chunk, ctx, payload);
|
|
243
|
+
|
|
255
244
|
return readableStream
|
|
256
245
|
.pipeThrough(
|
|
257
|
-
createTokenSpeedCalculator(
|
|
246
|
+
createTokenSpeedCalculator(transformWithPayload, {
|
|
258
247
|
enableStreaming: enableStreaming,
|
|
259
248
|
inputStartAt,
|
|
260
249
|
streamStack,
|
|
@@ -281,7 +281,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
281
281
|
// usage
|
|
282
282
|
'id: chat_1\n',
|
|
283
283
|
'event: usage\n',
|
|
284
|
-
`data: {"inputCachedTokens":14286,"inputTextTokens":15725,"outputImageTokens":0,"outputTextTokens":1053,"totalInputTokens":15725,"totalOutputTokens":1053,"totalTokens":16778}\n\n`,
|
|
284
|
+
`data: {"inputCacheMissTokens":1439,"inputCachedTokens":14286,"inputTextTokens":15725,"outputImageTokens":0,"outputTextTokens":1053,"totalInputTokens":15725,"totalOutputTokens":1053,"totalTokens":16778}\n\n`,
|
|
285
285
|
]);
|
|
286
286
|
});
|
|
287
287
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { GenerateContentResponse } from '@google/genai';
|
|
2
|
-
import { GroundingSearch
|
|
2
|
+
import { GroundingSearch } from '@lobechat/types';
|
|
3
3
|
|
|
4
4
|
import { ChatStreamCallbacks } from '../../../types';
|
|
5
5
|
import { nanoid } from '../../../utils/uuid';
|
|
6
|
+
import { convertGoogleAIUsage } from '../../usageConverters/google-ai';
|
|
6
7
|
import {
|
|
8
|
+
ChatPayloadForTransformStream,
|
|
7
9
|
StreamContext,
|
|
8
10
|
StreamProtocolChunk,
|
|
9
11
|
StreamToolCallChunkData,
|
|
@@ -28,6 +30,7 @@ const getBlockReasonMessage = (blockReason: string): string => {
|
|
|
28
30
|
const transformGoogleGenerativeAIStream = (
|
|
29
31
|
chunk: GenerateContentResponse,
|
|
30
32
|
context: StreamContext,
|
|
33
|
+
payload?: ChatPayloadForTransformStream,
|
|
31
34
|
): StreamProtocolChunk | StreamProtocolChunk[] => {
|
|
32
35
|
// Handle injected internal error marker to pass through detailed error info
|
|
33
36
|
if ((chunk as any)?.[LOBE_ERROR_KEY]) {
|
|
@@ -60,46 +63,15 @@ const transformGoogleGenerativeAIStream = (
|
|
|
60
63
|
|
|
61
64
|
// maybe need another structure to add support for multiple choices
|
|
62
65
|
const candidate = chunk.candidates?.[0];
|
|
63
|
-
const
|
|
66
|
+
const { usageMetadata } = chunk;
|
|
64
67
|
const usageChunks: StreamProtocolChunk[] = [];
|
|
65
|
-
if (candidate?.finishReason &&
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
candidatesDetails?.reduce((s: number, i: any) => s + (i?.tokenCount ?? 0), 0) ??
|
|
73
|
-
0;
|
|
74
|
-
|
|
75
|
-
const outputImageTokens =
|
|
76
|
-
candidatesDetails?.find((i: any) => i.modality === 'IMAGE')?.tokenCount ?? 0;
|
|
77
|
-
const outputTextTokens =
|
|
78
|
-
candidatesDetails?.find((i: any) => i.modality === 'TEXT')?.tokenCount ??
|
|
79
|
-
Math.max(0, candidatesTotal - outputImageTokens);
|
|
80
|
-
|
|
81
|
-
const totalOutputTokens = candidatesTotal + (reasoningTokens ?? 0);
|
|
82
|
-
|
|
83
|
-
usageChunks.push(
|
|
84
|
-
{ data: candidate.finishReason, id: context?.id, type: 'stop' },
|
|
85
|
-
{
|
|
86
|
-
data: {
|
|
87
|
-
inputCachedTokens: usage.cachedContentTokenCount,
|
|
88
|
-
inputImageTokens: usage.promptTokensDetails?.find((i) => i.modality === 'IMAGE')
|
|
89
|
-
?.tokenCount,
|
|
90
|
-
inputTextTokens: usage.promptTokensDetails?.find((i) => i.modality === 'TEXT')
|
|
91
|
-
?.tokenCount,
|
|
92
|
-
outputImageTokens,
|
|
93
|
-
outputReasoningTokens: reasoningTokens,
|
|
94
|
-
outputTextTokens,
|
|
95
|
-
totalInputTokens: usage.promptTokenCount,
|
|
96
|
-
totalOutputTokens,
|
|
97
|
-
totalTokens: usage.totalTokenCount,
|
|
98
|
-
} as ModelTokensUsage,
|
|
99
|
-
id: context?.id,
|
|
100
|
-
type: 'usage',
|
|
101
|
-
},
|
|
102
|
-
);
|
|
68
|
+
if (candidate?.finishReason && usageMetadata) {
|
|
69
|
+
usageChunks.push({ data: candidate.finishReason, id: context?.id, type: 'stop' });
|
|
70
|
+
|
|
71
|
+
const convertedUsage = convertGoogleAIUsage(usageMetadata, payload?.pricing);
|
|
72
|
+
if (convertedUsage) {
|
|
73
|
+
usageChunks.push({ data: convertedUsage, id: context?.id, type: 'usage' });
|
|
74
|
+
}
|
|
103
75
|
}
|
|
104
76
|
|
|
105
77
|
const functionCalls = chunk.functionCalls;
|
|
@@ -213,17 +185,21 @@ export interface GoogleAIStreamOptions {
|
|
|
213
185
|
callbacks?: ChatStreamCallbacks;
|
|
214
186
|
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
215
187
|
inputStartAt?: number;
|
|
188
|
+
payload?: ChatPayloadForTransformStream;
|
|
216
189
|
}
|
|
217
190
|
|
|
218
191
|
export const GoogleGenerativeAIStream = (
|
|
219
192
|
rawStream: ReadableStream<GenerateContentResponse>,
|
|
220
|
-
{ callbacks, inputStartAt, enableStreaming = true }: GoogleAIStreamOptions = {},
|
|
193
|
+
{ callbacks, inputStartAt, enableStreaming = true, payload }: GoogleAIStreamOptions = {},
|
|
221
194
|
) => {
|
|
222
195
|
const streamStack: StreamContext = { id: 'chat_' + nanoid() };
|
|
223
196
|
|
|
197
|
+
const transformWithPayload: typeof transformGoogleGenerativeAIStream = (chunk, ctx) =>
|
|
198
|
+
transformGoogleGenerativeAIStream(chunk, ctx, payload);
|
|
199
|
+
|
|
224
200
|
return rawStream
|
|
225
201
|
.pipeThrough(
|
|
226
|
-
createTokenSpeedCalculator(
|
|
202
|
+
createTokenSpeedCalculator(transformWithPayload, {
|
|
227
203
|
enableStreaming: enableStreaming,
|
|
228
204
|
inputStartAt,
|
|
229
205
|
streamStack,
|
|
@@ -360,14 +360,9 @@ describe('OpenAIStream', () => {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
expect(chunks).toEqual(
|
|
363
|
-
[
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
`data: "Hello"\n`,
|
|
367
|
-
'id: 1',
|
|
368
|
-
'event: data',
|
|
369
|
-
`data: {"id":"1"}\n`,
|
|
370
|
-
].map((i) => `${i}\n`),
|
|
363
|
+
['id: 1', 'event: text', `data: "Hello"\n`, 'id: 1', 'event: data', `data: {"id":"1"}\n`].map(
|
|
364
|
+
(i) => `${i}\n`,
|
|
365
|
+
),
|
|
371
366
|
);
|
|
372
367
|
});
|
|
373
368
|
|
|
@@ -413,7 +408,9 @@ describe('OpenAIStream', () => {
|
|
|
413
408
|
|
|
414
409
|
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
|
415
410
|
bizErrorTypeTransformer: () => AgentRuntimeErrorType.PermissionDenied,
|
|
416
|
-
|
|
411
|
+
payload: {
|
|
412
|
+
provider: 'grok',
|
|
413
|
+
},
|
|
417
414
|
});
|
|
418
415
|
|
|
419
416
|
const decoder = new TextDecoder();
|
|
@@ -2481,4 +2478,4 @@ describe('OpenAIStream', () => {
|
|
|
2481
2478
|
`data: "${base64_2}"\n\n`,
|
|
2482
2479
|
]);
|
|
2483
2480
|
});
|
|
2484
|
-
});
|
|
2481
|
+
});
|
|
@@ -4,8 +4,9 @@ import type { Stream } from 'openai/streaming';
|
|
|
4
4
|
|
|
5
5
|
import { ChatStreamCallbacks } from '../../../types';
|
|
6
6
|
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../../types/error';
|
|
7
|
-
import {
|
|
7
|
+
import { convertOpenAIUsage } from '../../usageConverters';
|
|
8
8
|
import {
|
|
9
|
+
ChatPayloadForTransformStream,
|
|
9
10
|
FIRST_CHUNK_ERROR_KEY,
|
|
10
11
|
StreamContext,
|
|
11
12
|
StreamProtocolChunk,
|
|
@@ -44,7 +45,7 @@ const processMarkdownBase64Images = (text: string): { cleanedText: string; urls:
|
|
|
44
45
|
const transformOpenAIStream = (
|
|
45
46
|
chunk: OpenAI.ChatCompletionChunk,
|
|
46
47
|
streamContext: StreamContext,
|
|
47
|
-
|
|
48
|
+
payload?: ChatPayloadForTransformStream,
|
|
48
49
|
): StreamProtocolChunk | StreamProtocolChunk[] => {
|
|
49
50
|
// handle the first chunk error
|
|
50
51
|
if (FIRST_CHUNK_ERROR_KEY in chunk) {
|
|
@@ -75,7 +76,7 @@ const transformOpenAIStream = (
|
|
|
75
76
|
if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {
|
|
76
77
|
if (chunk.usage) {
|
|
77
78
|
const usage = chunk.usage;
|
|
78
|
-
return { data:
|
|
79
|
+
return { data: convertOpenAIUsage(usage, payload), id: chunk.id, type: 'usage' };
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
return { data: chunk, id: chunk.id, type: 'data' };
|
|
@@ -232,7 +233,7 @@ const transformOpenAIStream = (
|
|
|
232
233
|
|
|
233
234
|
if (chunk.usage) {
|
|
234
235
|
const usage = chunk.usage;
|
|
235
|
-
return { data:
|
|
236
|
+
return { data: convertOpenAIUsage(usage, payload), id: chunk.id, type: 'usage' };
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
// xAI Live Search 功能返回引用源
|
|
@@ -312,7 +313,7 @@ const transformOpenAIStream = (
|
|
|
312
313
|
// 如果 content 是空字符串但 chunk 带有 usage,则优先返回 usage(例如 Gemini image-preview 最终会在单独的 chunk 中返回 usage)
|
|
313
314
|
if (content === '' && chunk.usage) {
|
|
314
315
|
const usage = chunk.usage;
|
|
315
|
-
return { data:
|
|
316
|
+
return { data: convertOpenAIUsage(usage, payload), id: chunk.id, type: 'usage' };
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
// 判断是否有 citations 内容,更新 returnedCitation 状态
|
|
@@ -387,7 +388,7 @@ const transformOpenAIStream = (
|
|
|
387
388
|
// litellm 的返回结果中,存在 delta 为空,但是有 usage 的情况
|
|
388
389
|
if (chunk.usage) {
|
|
389
390
|
const usage = chunk.usage;
|
|
390
|
-
return { data:
|
|
391
|
+
return { data: convertOpenAIUsage(usage, payload), id: chunk.id, type: 'usage' };
|
|
391
392
|
}
|
|
392
393
|
|
|
393
394
|
// 其余情况下,返回 delta 和 index
|
|
@@ -426,23 +427,25 @@ export interface OpenAIStreamOptions {
|
|
|
426
427
|
callbacks?: ChatStreamCallbacks;
|
|
427
428
|
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
428
429
|
inputStartAt?: number;
|
|
429
|
-
|
|
430
|
+
payload?: ChatPayloadForTransformStream;
|
|
430
431
|
}
|
|
431
432
|
|
|
432
433
|
export const OpenAIStream = (
|
|
433
434
|
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
|
|
434
435
|
{
|
|
435
436
|
callbacks,
|
|
436
|
-
provider,
|
|
437
437
|
bizErrorTypeTransformer,
|
|
438
|
+
payload,
|
|
438
439
|
inputStartAt,
|
|
439
440
|
enableStreaming = true,
|
|
440
441
|
}: OpenAIStreamOptions = {},
|
|
441
442
|
) => {
|
|
442
|
-
const streamStack: StreamContext = {
|
|
443
|
+
const streamStack: StreamContext = {
|
|
444
|
+
id: '',
|
|
445
|
+
};
|
|
443
446
|
|
|
444
447
|
const transformWithProvider = (chunk: OpenAI.ChatCompletionChunk, streamContext: StreamContext) =>
|
|
445
|
-
transformOpenAIStream(chunk, streamContext,
|
|
448
|
+
transformOpenAIStream(chunk, streamContext, payload);
|
|
446
449
|
|
|
447
450
|
const readableStream =
|
|
448
451
|
stream instanceof ReadableStream ? stream : convertIterableToStream(stream);
|
|
@@ -452,7 +455,7 @@ export const OpenAIStream = (
|
|
|
452
455
|
// 1. handle the first error if exist
|
|
453
456
|
// provider like huggingface or minimax will return error in the stream,
|
|
454
457
|
// so in the first Transformer, we need to handle the error
|
|
455
|
-
.pipeThrough(createFirstErrorHandleTransformer(bizErrorTypeTransformer, provider))
|
|
458
|
+
.pipeThrough(createFirstErrorHandleTransformer(bizErrorTypeTransformer, payload?.provider))
|
|
456
459
|
.pipeThrough(
|
|
457
460
|
createTokenSpeedCalculator(transformWithProvider, {
|
|
458
461
|
enableStreaming: enableStreaming,
|