@lobehub/chat 1.34.6 → 1.35.0
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 +25 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/changelog/v1.json +9 -0
- package/docs/self-hosting/environment-variables/analytics.mdx +31 -2
- package/package.json +2 -2
- package/src/config/modelProviders/ollama.ts +14 -13
- 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
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
## [Version 1.35.0](https://github.com/lobehub/lobe-chat/compare/v1.34.6...v1.35.0)
|
6
|
+
|
7
|
+
<sup>Released on **2024-12-01**</sup>
|
8
|
+
|
9
|
+
#### ✨ Features
|
10
|
+
|
11
|
+
- **misc**: Support ollama tools use.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's improved
|
19
|
+
|
20
|
+
- **misc**: Support ollama tools use, closes [#3327](https://github.com/lobehub/lobe-chat/issues/3327) ([72d8835](https://github.com/lobehub/lobe-chat/commit/72d8835))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.34.6](https://github.com/lobehub/lobe-chat/compare/v1.34.5...v1.34.6)
|
6
31
|
|
7
32
|
<sup>Released on **2024-12-01**</sup>
|
package/README.md
CHANGED
@@ -285,14 +285,14 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
|
285
285
|
|
286
286
|
<!-- AGENT LIST -->
|
287
287
|
|
288
|
-
| Recent Submits
|
289
|
-
|
|
290
|
-
| [
|
291
|
-
| [
|
292
|
-
| [
|
293
|
-
| [
|
294
|
-
|
295
|
-
> 📊 Total agents: [<kbd>**
|
288
|
+
| Recent Submits | Description |
|
289
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
290
|
+
| [AI Assistant for Course Content and Teaching Guidelines](https://chat-preview.lobehub.com/market?agent=course-prep-teaching-guide-ai)<br/><sup>By **[HNaga](https://github.com/HNaga)** on **2024-11-29**</sup> | This AI assistant is designed to help educators and instructors prepare comprehensive course content and provide practical teaching guidelines. It leverages advanced NLP capabilities to generate lesson plans, suggest engaging teaching strategies, and offer insights into educational best practices.<br/>`education` `teaching` `course-design` `content-creation` `ai-assistance` `curriculum-development` `instructional-design` |
|
291
|
+
| [Backend Development Assistant](https://chat-preview.lobehub.com/market?agent=backend-assistant)<br/><sup>By **[zeno980](https://github.com/zeno980)** on **2024-11-26**</sup> | Skilled in backend development tasks<br/>`backend-development` `ai-technology` `web-applications` `spring` `sql` |
|
292
|
+
| [Bilingual Dictionary Expert](https://chat-preview.lobehub.com/market?agent=english-chinese-dictionary-expert)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2024-11-26**</sup> | Expert in bilingual English-Chinese vocabulary translation and analysis<br/>`translation` `language-learning` `vocabulary` `dictionary` |
|
293
|
+
| [SSC Incremental](https://chat-preview.lobehub.com/market?agent=great-for-analysis-coding-and-rubber-ducking)<br/><sup>By **[Base03](https://github.com/Base03)** on **2024-11-26**</sup> | Claude minus the Reddit<br/>`technology` `analysis` `software` `ai` `research` |
|
294
|
+
|
295
|
+
> 📊 Total agents: [<kbd>**447**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
296
296
|
|
297
297
|
<!-- AGENT LIST -->
|
298
298
|
|
package/README.zh-CN.md
CHANGED
@@ -274,14 +274,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
274
274
|
|
275
275
|
<!-- AGENT LIST -->
|
276
276
|
|
277
|
-
| 最近新增
|
278
|
-
|
|
279
|
-
| [
|
280
|
-
| [
|
281
|
-
| [
|
282
|
-
| [
|
283
|
-
|
284
|
-
> 📊 Total agents: [<kbd>**
|
277
|
+
| 最近新增 | 助手说明 |
|
278
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
279
|
+
| [课程内容和教学指南的 AI 助手](https://chat-preview.lobehub.com/market?agent=course-prep-teaching-guide-ai)<br/><sup>By **[HNaga](https://github.com/HNaga)** on **2024-11-29**</sup> | 这个 AI 助手旨在帮助教育工作者和讲师准备全面的课程内容并提供实用的教学指南。它利用先进的自然语言处理能力生成课程计划,建议引人入胜的教学策略,并提供教育最佳实践的见解。<br/>`教育` `教学` `课程设计` `内容创作` `人工智能助手` `课程开发` `教学设计` |
|
280
|
+
| [后端开发助手](https://chat-preview.lobehub.com/market?agent=backend-assistant)<br/><sup>By **[zeno980](https://github.com/zeno980)** on **2024-11-26**</sup> | 擅长后端开发任务<br/>`后端开发` `ai技术` `web应用` `spring` `sql` |
|
281
|
+
| [双语词典专家](https://chat-preview.lobehub.com/market?agent=english-chinese-dictionary-expert)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2024-11-26**</sup> | 双语英语 - 中文词汇翻译和分析专家<br/>`翻译` `语言学习` `词汇` `词典` |
|
282
|
+
| [SSC 增量](https://chat-preview.lobehub.com/market?agent=great-for-analysis-coding-and-rubber-ducking)<br/><sup>By **[Base03](https://github.com/Base03)** on **2024-11-26**</sup> | Claude 减去 Reddit<br/>`技术` `分析` `软件` `人工智能` `研究` |
|
283
|
+
|
284
|
+
> 📊 Total agents: [<kbd>**447**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
285
285
|
|
286
286
|
<!-- AGENT LIST -->
|
287
287
|
|
package/changelog/v1.json
CHANGED
@@ -80,5 +80,34 @@ We have integrated several free/open-source data analytics services in LobeChat
|
|
80
80
|
- Default: `https://analytics.umami.is/script.js`
|
81
81
|
- Example: `https://umami.your-site.com/script.js`
|
82
82
|
|
83
|
-
|
84
|
-
|
83
|
+
## Langfuse Observability
|
84
|
+
|
85
|
+
[Langfuse](https://langfuse.com/) is an [open-source](https://github.com/langfuse/langfuse) LLM Observability platform. By enabling the Langfuse integration, you can trace your chat data with Langfuse to develop, monitor, and evaluate the use of your LobeChat.
|
86
|
+
|
87
|
+
### `ENABLE_LANGFUSE`
|
88
|
+
|
89
|
+
- Type: Required
|
90
|
+
- Description: Determines if Langfuse analytics is enabled.
|
91
|
+
- Default: `1`
|
92
|
+
- Example: `1`
|
93
|
+
|
94
|
+
### `LANGFUSE_SECRET_KEY`
|
95
|
+
|
96
|
+
- Type: Required
|
97
|
+
- Description: Langfuse API secret key. Can be created by signing up for [Langfuse Cloud](https://cloud.langfuse.com) or by self-hosting Langfuse.
|
98
|
+
- Default: \`\`
|
99
|
+
- Example: `sk-lf-...`
|
100
|
+
|
101
|
+
### `LANGFUSE_PUBLIC_KEY`
|
102
|
+
|
103
|
+
- Type: Required
|
104
|
+
- Description: Langfuse API public key. Can be created by signing up for [Langfuse Cloud](https://cloud.langfuse.com) or by self-hosting Langfuse.
|
105
|
+
- Default: \`\`
|
106
|
+
- Example: `pk-lf-...`
|
107
|
+
|
108
|
+
### `LANGFUSE_HOST`
|
109
|
+
|
110
|
+
- Type: Required
|
111
|
+
- Description: Langfuse host address. Use `https://us.cloud.langfuse.com` if your Langfuse project is in the US data region.
|
112
|
+
- Default: `https://cloud.langfuse.com`
|
113
|
+
- Example: `https://cloud.langfuse.com`
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.35.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -181,7 +181,7 @@
|
|
181
181
|
"numeral": "^2.0.6",
|
182
182
|
"nuqs": "^1.20.0",
|
183
183
|
"officeparser": "^4.2.0",
|
184
|
-
"ollama": "^0.5.
|
184
|
+
"ollama": "^0.5.10",
|
185
185
|
"openai": "^4.68.1",
|
186
186
|
"openapi-fetch": "^0.9.8",
|
187
187
|
"partial-json": "^0.1.7",
|
@@ -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
|
},
|
@@ -221,28 +221,29 @@ const Ollama: ModelProviderCard = {
|
|
221
221
|
tokens: 128_000,
|
222
222
|
},
|
223
223
|
{
|
224
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
225
|
-
displayName: 'Qwen2 0.5B',
|
226
|
-
id: 'qwen2:0.5b',
|
224
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
225
|
+
displayName: 'Qwen2.5 0.5B',
|
226
|
+
id: 'qwen2.5:0.5b',
|
227
227
|
tokens: 128_000,
|
228
228
|
},
|
229
229
|
{
|
230
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
231
|
-
displayName: 'Qwen2 1.5B',
|
232
|
-
id: 'qwen2:1.5b',
|
230
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
231
|
+
displayName: 'Qwen2.5 1.5B',
|
232
|
+
id: 'qwen2.5:1.5b',
|
233
233
|
tokens: 128_000,
|
234
234
|
},
|
235
235
|
{
|
236
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
237
|
-
displayName: 'Qwen2 7B',
|
236
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
237
|
+
displayName: 'Qwen2.5 7B',
|
238
238
|
enabled: true,
|
239
|
-
|
239
|
+
functionCall: true,
|
240
|
+
id: 'qwen2.5',
|
240
241
|
tokens: 128_000,
|
241
242
|
},
|
242
243
|
{
|
243
|
-
description: 'Qwen2 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
244
|
-
displayName: 'Qwen2 72B',
|
245
|
-
id: 'qwen2:72b',
|
244
|
+
description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
|
245
|
+
displayName: 'Qwen2.5 72B',
|
246
|
+
id: 'qwen2.5:72b',
|
246
247
|
tokens: 128_000,
|
247
248
|
},
|
248
249
|
{
|
@@ -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);
|