@lobehub/chat 1.34.6 → 1.35.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/CHANGELOG.md +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/changelog/v1.json +18 -0
- package/docs/changelog/2024-07-19-gpt-4o-mini.mdx +32 -0
- package/docs/changelog/2024-07-19-gpt-4o-mini.zh-CN.mdx +5 -4
- package/docs/changelog/2024-08-02-lobe-chat-database-docker.mdx +36 -0
- package/docs/changelog/2024-08-02-lobe-chat-database-docker.zh-CN.mdx +0 -1
- package/docs/changelog/2024-08-21-file-upload-and-knowledge-base.mdx +30 -0
- package/docs/changelog/2024-08-21-file-upload-and-knowledge-base.zh-CN.mdx +0 -1
- package/docs/changelog/2024-09-13-openai-o1-models.mdx +31 -0
- package/docs/changelog/2024-09-20-artifacts.mdx +55 -0
- package/docs/changelog/2024-09-20-artifacts.zh-CN.mdx +3 -2
- package/docs/changelog/2024-10-27-pin-assistant.mdx +33 -0
- package/docs/changelog/2024-10-27-pin-assistant.zh-CN.mdx +0 -1
- package/docs/changelog/2024-11-06-share-text-json.mdx +24 -0
- package/docs/changelog/2024-11-06-share-text-json.zh-CN.mdx +3 -1
- package/docs/changelog/2024-11-25-november-providers.mdx +5 -5
- package/docs/changelog/2024-11-25-november-providers.zh-CN.mdx +5 -5
- package/docs/changelog/2024-11-27-forkable-chat.mdx +26 -0
- package/docs/changelog/2024-11-27-forkable-chat.zh-CN.mdx +16 -9
- package/docs/changelog/index.json +1 -1
- package/docs/self-hosting/environment-variables/analytics.mdx +31 -2
- package/locales/ar/models.json +94 -7
- package/locales/bg-BG/models.json +94 -7
- package/locales/de-DE/models.json +94 -7
- package/locales/en-US/models.json +94 -7
- package/locales/es-ES/models.json +94 -7
- package/locales/fa-IR/models.json +94 -7
- package/locales/fr-FR/models.json +94 -7
- package/locales/it-IT/models.json +94 -7
- package/locales/ja-JP/models.json +94 -7
- package/locales/ko-KR/models.json +94 -7
- package/locales/nl-NL/models.json +94 -7
- package/locales/pl-PL/models.json +94 -7
- package/locales/pt-BR/models.json +94 -7
- package/locales/ru-RU/models.json +94 -7
- package/locales/tr-TR/models.json +94 -7
- package/locales/vi-VN/models.json +94 -7
- package/locales/zh-CN/models.json +121 -34
- package/locales/zh-TW/models.json +94 -7
- package/package.json +2 -2
- package/src/config/modelProviders/ollama.ts +85 -35
- package/src/libs/agent-runtime/ollama/index.ts +25 -9
- package/src/libs/agent-runtime/utils/streams/ollama.test.ts +130 -46
- package/src/libs/agent-runtime/utils/streams/ollama.ts +19 -4
- package/src/server/modules/AgentRuntime/index.test.ts +2 -1
- package/src/server/modules/AgentRuntime/index.ts +7 -1
@@ -1,6 +1,5 @@
|
|
1
1
|
import { ModelProviderCard } from '@/types/llm';
|
2
2
|
|
3
|
-
// ref: https://ollama.com/library
|
4
3
|
const Ollama: ModelProviderCard = {
|
5
4
|
chatModels: [
|
6
5
|
{
|
@@ -8,6 +7,7 @@ const Ollama: ModelProviderCard = {
|
|
8
7
|
'Llama 3.1 是 Meta 推出的领先模型,支持高达 405B 参数,可应用于复杂对话、多语言翻译和数据分析领域。',
|
9
8
|
displayName: 'Llama 3.1 8B',
|
10
9
|
enabled: true,
|
10
|
+
functionCall: true,
|
11
11
|
id: 'llama3.1',
|
12
12
|
tokens: 128_000,
|
13
13
|
},
|
@@ -54,6 +54,84 @@ const Ollama: ModelProviderCard = {
|
|
54
54
|
id: 'codellama:70b',
|
55
55
|
tokens: 16_384,
|
56
56
|
},
|
57
|
+
{
|
58
|
+
description: 'QwQ 是一个实验研究模型,专注于提高 AI 推理能力。',
|
59
|
+
displayName: 'QwQ 32B',
|
60
|
+
enabled: true,
|
61
|
+
functionCall: true,
|
62
|
+
id: 'qwq',
|
63
|
+
releasedAt: '2024-11-28',
|
64
|
+
tokens: 128_000,
|
65
|
+
},
|
66
|
+
{
|
67
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
68
|
+
displayName: 'Qwen2.5 0.5B',
|
69
|
+
id: 'qwen2.5:0.5b',
|
70
|
+
tokens: 128_000,
|
71
|
+
},
|
72
|
+
{
|
73
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
74
|
+
displayName: 'Qwen2.5 1.5B',
|
75
|
+
id: 'qwen2.5:1.5b',
|
76
|
+
tokens: 128_000,
|
77
|
+
},
|
78
|
+
{
|
79
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
80
|
+
displayName: 'Qwen2.5 7B',
|
81
|
+
enabled: true,
|
82
|
+
functionCall: true,
|
83
|
+
id: 'qwen2.5',
|
84
|
+
tokens: 128_000,
|
85
|
+
},
|
86
|
+
{
|
87
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
88
|
+
displayName: 'Qwen2.5 72B',
|
89
|
+
id: 'qwen2.5:72b',
|
90
|
+
tokens: 128_000,
|
91
|
+
},
|
92
|
+
{
|
93
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
94
|
+
displayName: 'Qwen2.5 7B',
|
95
|
+
enabled: true,
|
96
|
+
functionCall: true,
|
97
|
+
id: 'qwen2.5',
|
98
|
+
tokens: 128_000,
|
99
|
+
},
|
100
|
+
{
|
101
|
+
description: 'CodeQwen1.5 是基于大量代码数据训练的大型语言模型,专为解决复杂编程任务。',
|
102
|
+
displayName: 'CodeQwen1.5 7B',
|
103
|
+
functionCall: true,
|
104
|
+
id: 'codeqwen',
|
105
|
+
tokens: 65_536,
|
106
|
+
},
|
107
|
+
{
|
108
|
+
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
109
|
+
displayName: 'Qwen2 0.5B',
|
110
|
+
functionCall: true,
|
111
|
+
id: 'qwen2:0.5b',
|
112
|
+
tokens: 128_000,
|
113
|
+
},
|
114
|
+
{
|
115
|
+
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
116
|
+
displayName: 'Qwen2 1.5B',
|
117
|
+
functionCall: true,
|
118
|
+
id: 'qwen2:1.5b',
|
119
|
+
tokens: 128_000,
|
120
|
+
},
|
121
|
+
{
|
122
|
+
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
123
|
+
displayName: 'Qwen2 7B',
|
124
|
+
functionCall: true,
|
125
|
+
id: 'qwen2',
|
126
|
+
tokens: 128_000,
|
127
|
+
},
|
128
|
+
{
|
129
|
+
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
130
|
+
displayName: 'Qwen2 72B',
|
131
|
+
functionCall: true,
|
132
|
+
id: 'qwen2:72b',
|
133
|
+
tokens: 128_000,
|
134
|
+
},
|
57
135
|
{
|
58
136
|
description: 'Gemma 2 是 Google 推出的高效模型,涵盖从小型应用到复杂数据处理的多种应用场景。',
|
59
137
|
displayName: 'Gemma 2 2B',
|
@@ -63,7 +141,6 @@ const Ollama: ModelProviderCard = {
|
|
63
141
|
{
|
64
142
|
description: 'Gemma 2 是 Google 推出的高效模型,涵盖从小型应用到复杂数据处理的多种应用场景。',
|
65
143
|
displayName: 'Gemma 2 9B',
|
66
|
-
enabled: true,
|
67
144
|
id: 'gemma2',
|
68
145
|
tokens: 8192,
|
69
146
|
},
|
@@ -82,7 +159,6 @@ const Ollama: ModelProviderCard = {
|
|
82
159
|
{
|
83
160
|
description: 'CodeGemma 专用于不同编程任务的轻量级语言模型,支持快速迭代和集成。',
|
84
161
|
displayName: 'CodeGemma 7B',
|
85
|
-
enabled: true,
|
86
162
|
id: 'codegemma',
|
87
163
|
tokens: 8192,
|
88
164
|
},
|
@@ -125,6 +201,7 @@ const Ollama: ModelProviderCard = {
|
|
125
201
|
description: 'Mistral 是 Mistral AI 发布的 7B 模型,适合多变的语言处理需求。',
|
126
202
|
displayName: 'Mistral 7B',
|
127
203
|
enabled: true,
|
204
|
+
functionCall: true,
|
128
205
|
id: 'mistral',
|
129
206
|
tokens: 32_768,
|
130
207
|
},
|
@@ -133,6 +210,7 @@ const Ollama: ModelProviderCard = {
|
|
133
210
|
'Mixtral 是 Mistral AI 的专家模型,具有开源权重,并在代码生成和语言理解方面提供支持。',
|
134
211
|
displayName: 'Mixtral 8x7B',
|
135
212
|
enabled: true,
|
213
|
+
functionCall: true,
|
136
214
|
id: 'mixtral',
|
137
215
|
tokens: 32_768,
|
138
216
|
},
|
@@ -140,6 +218,7 @@ const Ollama: ModelProviderCard = {
|
|
140
218
|
description:
|
141
219
|
'Mixtral 是 Mistral AI 的专家模型,具有开源权重,并在代码生成和语言理解方面提供支持。',
|
142
220
|
displayName: 'Mixtral 8x22B',
|
221
|
+
functionCall: true,
|
143
222
|
id: 'mixtral:8x22b',
|
144
223
|
tokens: 65_536,
|
145
224
|
},
|
@@ -155,6 +234,7 @@ const Ollama: ModelProviderCard = {
|
|
155
234
|
description: 'Mistral Nemo 由 Mistral AI 和 NVIDIA 合作推出,是高效性能的 12B 模型。',
|
156
235
|
displayName: 'Mixtral Nemo 12B',
|
157
236
|
enabled: true,
|
237
|
+
functionCall: true,
|
158
238
|
id: 'mistral-nemo',
|
159
239
|
tokens: 128_000,
|
160
240
|
},
|
@@ -182,6 +262,7 @@ const Ollama: ModelProviderCard = {
|
|
182
262
|
description: 'Command R 是优化用于对话和长上下文任务的LLM,特别适合动态交互与知识管理。',
|
183
263
|
displayName: 'Command R 35B',
|
184
264
|
enabled: true,
|
265
|
+
functionCall: true,
|
185
266
|
id: 'command-r',
|
186
267
|
tokens: 131_072,
|
187
268
|
},
|
@@ -189,6 +270,7 @@ const Ollama: ModelProviderCard = {
|
|
189
270
|
description: 'Command R+ 是一款高性能的大型语言模型,专为真实企业场景和复杂应用而设计。',
|
190
271
|
displayName: 'Command R+ 104B',
|
191
272
|
enabled: true,
|
273
|
+
functionCall: true,
|
192
274
|
id: 'command-r-plus',
|
193
275
|
tokens: 131_072,
|
194
276
|
},
|
@@ -220,38 +302,6 @@ const Ollama: ModelProviderCard = {
|
|
220
302
|
id: 'deepseek-coder-v2:236b',
|
221
303
|
tokens: 128_000,
|
222
304
|
},
|
223
|
-
{
|
224
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
225
|
-
displayName: 'Qwen2 0.5B',
|
226
|
-
id: 'qwen2:0.5b',
|
227
|
-
tokens: 128_000,
|
228
|
-
},
|
229
|
-
{
|
230
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
231
|
-
displayName: 'Qwen2 1.5B',
|
232
|
-
id: 'qwen2:1.5b',
|
233
|
-
tokens: 128_000,
|
234
|
-
},
|
235
|
-
{
|
236
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
237
|
-
displayName: 'Qwen2 7B',
|
238
|
-
enabled: true,
|
239
|
-
id: 'qwen2',
|
240
|
-
tokens: 128_000,
|
241
|
-
},
|
242
|
-
{
|
243
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
244
|
-
displayName: 'Qwen2 72B',
|
245
|
-
id: 'qwen2:72b',
|
246
|
-
tokens: 128_000,
|
247
|
-
},
|
248
|
-
{
|
249
|
-
description: 'CodeQwen1.5 是基于大量代码数据训练的大型语言模型,专为解决复杂编程任务。',
|
250
|
-
displayName: 'CodeQwen1.5 7B',
|
251
|
-
enabled: true,
|
252
|
-
id: 'codeqwen',
|
253
|
-
tokens: 65_536,
|
254
|
-
},
|
255
305
|
{
|
256
306
|
description: 'LLaVA 是结合视觉编码器和 Vicuna 的多模态模型,用于强大的视觉和语言理解。',
|
257
307
|
displayName: 'LLaVA 7B',
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Ollama } from 'ollama/browser';
|
1
|
+
import { Ollama, Tool } from 'ollama/browser';
|
2
2
|
import { ClientOptions } from 'openai';
|
3
3
|
|
4
4
|
import { OpenAIChatMessage } from '@/libs/agent-runtime';
|
@@ -8,8 +8,9 @@ import { LobeRuntimeAI } from '../BaseAI';
|
|
8
8
|
import { AgentRuntimeErrorType } from '../error';
|
9
9
|
import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
|
10
10
|
import { AgentRuntimeError } from '../utils/createError';
|
11
|
+
import { debugStream } from '../utils/debugStream';
|
11
12
|
import { StreamingResponse } from '../utils/response';
|
12
|
-
import { OllamaStream } from '../utils/streams';
|
13
|
+
import { OllamaStream, convertIterableToStream } from '../utils/streams';
|
13
14
|
import { parseDataUri } from '../utils/uriParser';
|
14
15
|
import { OllamaMessage } from './type';
|
15
16
|
|
@@ -45,23 +46,38 @@ export class LobeOllamaAI implements LobeRuntimeAI {
|
|
45
46
|
options: {
|
46
47
|
frequency_penalty: payload.frequency_penalty,
|
47
48
|
presence_penalty: payload.presence_penalty,
|
48
|
-
temperature:
|
49
|
-
payload.temperature !== undefined
|
50
|
-
? payload.temperature / 2
|
51
|
-
: undefined,
|
49
|
+
temperature: payload.temperature !== undefined ? payload.temperature / 2 : undefined,
|
52
50
|
top_p: payload.top_p,
|
53
51
|
},
|
54
52
|
stream: true,
|
53
|
+
tools: payload.tools as Tool[],
|
55
54
|
});
|
56
55
|
|
57
|
-
|
56
|
+
const stream = convertIterableToStream(response);
|
57
|
+
const [prod, debug] = stream.tee();
|
58
|
+
|
59
|
+
if (process.env.DEBUG_OLLAMA_CHAT_COMPLETION === '1') {
|
60
|
+
debugStream(debug).catch(console.error);
|
61
|
+
}
|
62
|
+
|
63
|
+
return StreamingResponse(OllamaStream(prod, options?.callback), {
|
58
64
|
headers: options?.headers,
|
59
65
|
});
|
60
66
|
} catch (error) {
|
61
|
-
const e = error as {
|
67
|
+
const e = error as {
|
68
|
+
error: any;
|
69
|
+
message: string;
|
70
|
+
name: string;
|
71
|
+
status_code: number;
|
72
|
+
};
|
62
73
|
|
63
74
|
throw AgentRuntimeError.chat({
|
64
|
-
error: {
|
75
|
+
error: {
|
76
|
+
...e.error,
|
77
|
+
message: String(e.error?.message || e.message),
|
78
|
+
name: e.name,
|
79
|
+
status_code: e.status_code,
|
80
|
+
},
|
65
81
|
errorType: AgentRuntimeErrorType.OllamaBizError,
|
66
82
|
provider: ModelProvider.Ollama,
|
67
83
|
});
|
@@ -6,61 +6,145 @@ import * as uuidModule from '@/utils/uuid';
|
|
6
6
|
import { OllamaStream } from './ollama';
|
7
7
|
|
8
8
|
describe('OllamaStream', () => {
|
9
|
-
|
10
|
-
|
9
|
+
describe('should transform Ollama stream to protocol stream', () => {
|
10
|
+
it('text', async () => {
|
11
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
12
|
+
|
13
|
+
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
14
|
+
start(controller) {
|
15
|
+
controller.enqueue({ message: { content: 'Hello' }, done: false } as ChatResponse);
|
16
|
+
controller.enqueue({ message: { content: ' world!' }, done: false } as ChatResponse);
|
17
|
+
controller.enqueue({ message: { content: '' }, done: true } as ChatResponse);
|
18
|
+
|
19
|
+
controller.close();
|
20
|
+
},
|
21
|
+
});
|
22
|
+
|
23
|
+
const onStartMock = vi.fn();
|
24
|
+
const onTextMock = vi.fn();
|
25
|
+
const onTokenMock = vi.fn();
|
26
|
+
const onCompletionMock = vi.fn();
|
27
|
+
|
28
|
+
const protocolStream = OllamaStream(mockOllamaStream, {
|
29
|
+
onStart: onStartMock,
|
30
|
+
onText: onTextMock,
|
31
|
+
onToken: onTokenMock,
|
32
|
+
onCompletion: onCompletionMock,
|
33
|
+
});
|
34
|
+
|
35
|
+
const decoder = new TextDecoder();
|
36
|
+
const chunks = [];
|
11
37
|
|
12
|
-
const mockOllamaStream: AsyncIterable<ChatResponse> = {
|
13
38
|
// @ts-ignore
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
39
|
+
for await (const chunk of protocolStream) {
|
40
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
41
|
+
}
|
42
|
+
|
43
|
+
expect(chunks).toEqual([
|
44
|
+
'id: chat_1\n',
|
45
|
+
'event: text\n',
|
46
|
+
`data: "Hello"\n\n`,
|
47
|
+
'id: chat_1\n',
|
48
|
+
'event: text\n',
|
49
|
+
`data: " world!"\n\n`,
|
50
|
+
'id: chat_1\n',
|
51
|
+
'event: stop\n',
|
52
|
+
`data: "finished"\n\n`,
|
53
|
+
]);
|
54
|
+
|
55
|
+
expect(onStartMock).toHaveBeenCalledTimes(1);
|
56
|
+
expect(onTextMock).toHaveBeenNthCalledWith(1, '"Hello"');
|
57
|
+
expect(onTextMock).toHaveBeenNthCalledWith(2, '" world!"');
|
58
|
+
expect(onTokenMock).toHaveBeenCalledTimes(2);
|
59
|
+
expect(onCompletionMock).toHaveBeenCalledTimes(1);
|
31
60
|
});
|
32
61
|
|
33
|
-
|
34
|
-
|
62
|
+
it('tools use', async () => {
|
63
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
35
64
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
65
|
+
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
66
|
+
start(controller) {
|
67
|
+
controller.enqueue({
|
68
|
+
model: 'qwen2.5',
|
69
|
+
created_at: new Date('2024-12-01T03:34:55.166692Z'),
|
70
|
+
message: {
|
71
|
+
role: 'assistant',
|
72
|
+
content: '',
|
73
|
+
tool_calls: [
|
74
|
+
{
|
75
|
+
function: {
|
76
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
77
|
+
arguments: { city: '杭州' },
|
78
|
+
},
|
79
|
+
},
|
80
|
+
],
|
81
|
+
},
|
82
|
+
done: false,
|
83
|
+
} as unknown as ChatResponse);
|
84
|
+
controller.enqueue({
|
85
|
+
model: 'qwen2.5',
|
86
|
+
created_at: '2024-12-01T03:34:55.2133Z',
|
87
|
+
message: { role: 'assistant', content: '' },
|
88
|
+
done_reason: 'stop',
|
89
|
+
done: true,
|
90
|
+
total_duration: 1122415333,
|
91
|
+
load_duration: 26178333,
|
92
|
+
prompt_eval_count: 221,
|
93
|
+
prompt_eval_duration: 507000000,
|
94
|
+
eval_count: 26,
|
95
|
+
eval_duration: 583000000,
|
96
|
+
} as unknown as ChatResponse);
|
97
|
+
|
98
|
+
controller.close();
|
99
|
+
},
|
100
|
+
});
|
101
|
+
const onStartMock = vi.fn();
|
102
|
+
const onTextMock = vi.fn();
|
103
|
+
const onTokenMock = vi.fn();
|
104
|
+
const onToolCall = vi.fn();
|
105
|
+
const onCompletionMock = vi.fn();
|
40
106
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
107
|
+
const protocolStream = OllamaStream(mockOllamaStream, {
|
108
|
+
onStart: onStartMock,
|
109
|
+
onText: onTextMock,
|
110
|
+
onToken: onTokenMock,
|
111
|
+
onCompletion: onCompletionMock,
|
112
|
+
onToolCall,
|
113
|
+
});
|
114
|
+
|
115
|
+
const decoder = new TextDecoder();
|
116
|
+
const chunks = [];
|
117
|
+
|
118
|
+
// @ts-ignore
|
119
|
+
for await (const chunk of protocolStream) {
|
120
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
121
|
+
}
|
122
|
+
|
123
|
+
expect(chunks).toEqual(
|
124
|
+
[
|
125
|
+
'id: chat_1',
|
126
|
+
'event: tool_calls',
|
127
|
+
`data: [{"function":{"arguments":"{\\"city\\":\\"杭州\\"}","name":"realtime-weather____fetchCurrentWeather"},"id":"realtime-weather____fetchCurrentWeather_0","index":0,"type":"function"}]\n`,
|
128
|
+
'id: chat_1',
|
129
|
+
'event: stop',
|
130
|
+
`data: "finished"\n`,
|
131
|
+
].map((i) => `${i}\n`),
|
132
|
+
);
|
133
|
+
|
134
|
+
expect(onTextMock).toHaveBeenCalledTimes(0);
|
135
|
+
expect(onStartMock).toHaveBeenCalledTimes(1);
|
136
|
+
expect(onToolCall).toHaveBeenCalledTimes(1);
|
137
|
+
expect(onTokenMock).toHaveBeenCalledTimes(0);
|
138
|
+
expect(onCompletionMock).toHaveBeenCalledTimes(1);
|
139
|
+
});
|
58
140
|
});
|
59
141
|
|
60
142
|
it('should handle empty stream', async () => {
|
61
|
-
const mockOllamaStream = {
|
62
|
-
|
63
|
-
|
143
|
+
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
144
|
+
start(controller) {
|
145
|
+
controller.close();
|
146
|
+
},
|
147
|
+
});
|
64
148
|
|
65
149
|
const protocolStream = OllamaStream(mockOllamaStream);
|
66
150
|
|
@@ -6,27 +6,42 @@ import { nanoid } from '@/utils/uuid';
|
|
6
6
|
import {
|
7
7
|
StreamProtocolChunk,
|
8
8
|
StreamStack,
|
9
|
-
convertIterableToStream,
|
10
9
|
createCallbacksTransformer,
|
11
10
|
createSSEProtocolTransformer,
|
11
|
+
generateToolCallId,
|
12
12
|
} from './protocol';
|
13
13
|
|
14
14
|
const transformOllamaStream = (chunk: ChatResponse, stack: StreamStack): StreamProtocolChunk => {
|
15
15
|
// maybe need another structure to add support for multiple choices
|
16
|
-
if (chunk.done) {
|
16
|
+
if (chunk.done && !chunk.message.content) {
|
17
17
|
return { data: 'finished', id: stack.id, type: 'stop' };
|
18
18
|
}
|
19
19
|
|
20
|
+
if (chunk.message.tool_calls && chunk.message.tool_calls.length > 0) {
|
21
|
+
return {
|
22
|
+
data: chunk.message.tool_calls.map((value, index) => ({
|
23
|
+
function: {
|
24
|
+
arguments: JSON.stringify(value.function?.arguments) ?? '{}',
|
25
|
+
name: value.function?.name ?? null,
|
26
|
+
},
|
27
|
+
id: generateToolCallId(index, value.function?.name),
|
28
|
+
index: index,
|
29
|
+
type: 'function',
|
30
|
+
})),
|
31
|
+
id: stack.id,
|
32
|
+
type: 'tool_calls',
|
33
|
+
};
|
34
|
+
}
|
20
35
|
return { data: chunk.message.content, id: stack.id, type: 'text' };
|
21
36
|
};
|
22
37
|
|
23
38
|
export const OllamaStream = (
|
24
|
-
res:
|
39
|
+
res: ReadableStream<ChatResponse>,
|
25
40
|
cb?: ChatStreamCallbacks,
|
26
41
|
): ReadableStream<string> => {
|
27
42
|
const streamStack: StreamStack = { id: 'chat_' + nanoid() };
|
28
43
|
|
29
|
-
return
|
44
|
+
return res
|
30
45
|
.pipeThrough(createSSEProtocolTransformer(transformOllamaStream, streamStack))
|
31
46
|
.pipeThrough(createCallbacksTransformer(cb));
|
32
47
|
};
|
@@ -134,6 +134,7 @@ describe('initAgentRuntimeWithUserPayload method', () => {
|
|
134
134
|
const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Ollama, jwtPayload);
|
135
135
|
expect(runtime).toBeInstanceOf(AgentRuntime);
|
136
136
|
expect(runtime['_runtime']).toBeInstanceOf(LobeOllamaAI);
|
137
|
+
expect(runtime['_runtime']['baseURL']).toEqual(jwtPayload.endpoint);
|
137
138
|
});
|
138
139
|
|
139
140
|
it('Perplexity AI provider: with apikey', async () => {
|
@@ -391,7 +392,7 @@ describe('initAgentRuntimeWithUserPayload method', () => {
|
|
391
392
|
// endpoint 不存在,应返回 DEFAULT_BASE_URL
|
392
393
|
expect(runtime['_runtime'].baseURL).toBe('https://dashscope.aliyuncs.com/compatible-mode/v1');
|
393
394
|
});
|
394
|
-
|
395
|
+
|
395
396
|
it('Unknown Provider', async () => {
|
396
397
|
const jwtPayload = {};
|
397
398
|
const runtime = await initAgentRuntimeWithUserPayload('unknown', jwtPayload);
|
@@ -33,7 +33,7 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
|
|
33
33
|
default: {
|
34
34
|
let upperProvider = provider.toUpperCase();
|
35
35
|
|
36
|
-
if (!(
|
36
|
+
if (!(`${upperProvider}_API_KEY` in llmConfig)) {
|
37
37
|
upperProvider = ModelProvider.OpenAI.toUpperCase(); // Use OpenAI options as default
|
38
38
|
}
|
39
39
|
|
@@ -43,6 +43,12 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
|
|
43
43
|
return baseURL ? { apiKey, baseURL } : { apiKey };
|
44
44
|
}
|
45
45
|
|
46
|
+
case ModelProvider.Ollama: {
|
47
|
+
const baseURL = payload?.endpoint || process.env.OLLAMA_PROXY_URL;
|
48
|
+
|
49
|
+
return { baseURL };
|
50
|
+
}
|
51
|
+
|
46
52
|
case ModelProvider.Azure: {
|
47
53
|
const { AZURE_API_KEY, AZURE_API_VERSION, AZURE_ENDPOINT } = llmConfig;
|
48
54
|
const apikey = apiKeyManager.pick(payload?.apiKey || AZURE_API_KEY);
|