@lobehub/chat 0.156.2 → 0.157.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/package.json +32 -31
- package/src/app/api/chat/[provider]/route.test.ts +2 -2
- package/src/app/api/chat/[provider]/route.ts +1 -1
- package/src/app/api/chat/models/[provider]/route.ts +1 -1
- package/src/app/api/config.test.ts +1 -51
- package/src/app/api/openai/createBizOpenAI/auth.test.ts +52 -0
- package/src/app/api/openai/createBizOpenAI/index.ts +1 -1
- package/src/app/api/plugin/gateway/route.ts +1 -1
- package/src/app/api/text-to-image/[provider]/route.ts +61 -0
- package/src/components/GalleyGrid/index.tsx +2 -2
- package/src/config/modelProviders/anthropic.ts +3 -0
- package/src/config/modelProviders/google.ts +3 -0
- package/src/config/modelProviders/groq.ts +5 -1
- package/src/config/modelProviders/minimax.ts +10 -7
- package/src/config/modelProviders/mistral.ts +1 -0
- package/src/config/modelProviders/moonshot.ts +3 -0
- package/src/config/modelProviders/zhipu.ts +2 -6
- package/src/config/server/provider.ts +1 -1
- package/src/database/client/core/db.ts +32 -0
- package/src/database/client/core/schemas.ts +9 -0
- package/src/database/client/models/__tests__/message.test.ts +2 -2
- package/src/database/client/schemas/message.ts +10 -1
- package/src/features/AgentSetting/store/action.ts +15 -6
- package/src/features/Conversation/Actions/Assistant.tsx +3 -2
- package/src/features/Conversation/Actions/Tool.tsx +28 -0
- package/src/features/Conversation/Actions/index.ts +2 -2
- package/src/features/Conversation/Messages/Assistant/ToolCalls/index.tsx +73 -0
- package/src/features/Conversation/Messages/Assistant/ToolCalls/style.ts +25 -0
- package/src/features/Conversation/Messages/Assistant/index.tsx +51 -0
- package/src/features/Conversation/Messages/Default.tsx +4 -1
- package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/index.tsx +35 -36
- package/src/features/Conversation/Messages/Tool/index.tsx +44 -0
- package/src/features/Conversation/Messages/index.ts +3 -2
- package/src/features/Conversation/Plugins/Render/StandaloneType/Iframe.tsx +1 -1
- package/src/features/Conversation/Plugins/Render/index.tsx +11 -2
- package/src/features/Conversation/components/SkeletonList.tsx +2 -2
- package/src/features/Conversation/index.tsx +2 -3
- package/src/hooks/useTokenCount.test.ts +38 -0
- package/src/hooks/useTokenCount.ts +1 -2
- package/src/libs/agent-runtime/AgentRuntime.ts +9 -1
- package/src/libs/agent-runtime/BaseAI.ts +5 -9
- package/src/libs/agent-runtime/anthropic/index.test.ts +195 -0
- package/src/libs/agent-runtime/anthropic/index.ts +71 -15
- package/src/libs/agent-runtime/azureOpenai/index.ts +6 -5
- package/src/libs/agent-runtime/bedrock/index.ts +24 -18
- package/src/libs/agent-runtime/google/index.test.ts +154 -0
- package/src/libs/agent-runtime/google/index.ts +91 -10
- package/src/libs/agent-runtime/groq/index.test.ts +41 -72
- package/src/libs/agent-runtime/groq/index.ts +7 -0
- package/src/libs/agent-runtime/minimax/index.test.ts +2 -2
- package/src/libs/agent-runtime/minimax/index.ts +14 -37
- package/src/libs/agent-runtime/mistral/index.test.ts +0 -53
- package/src/libs/agent-runtime/mistral/index.ts +1 -0
- package/src/libs/agent-runtime/moonshot/index.test.ts +1 -71
- package/src/libs/agent-runtime/ollama/index.test.ts +197 -0
- package/src/libs/agent-runtime/ollama/index.ts +3 -3
- package/src/libs/agent-runtime/openai/index.test.ts +0 -53
- package/src/libs/agent-runtime/openrouter/index.test.ts +1 -53
- package/src/libs/agent-runtime/perplexity/index.test.ts +0 -71
- package/src/libs/agent-runtime/perplexity/index.ts +2 -3
- package/src/libs/agent-runtime/togetherai/__snapshots__/index.test.ts.snap +886 -0
- package/src/libs/agent-runtime/togetherai/fixtures/models.json +8111 -0
- package/src/libs/agent-runtime/togetherai/index.test.ts +16 -54
- package/src/libs/agent-runtime/types/chat.ts +19 -3
- package/src/libs/agent-runtime/types/index.ts +1 -0
- package/src/libs/agent-runtime/types/textToImage.ts +34 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +120 -1
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +67 -4
- package/src/libs/agent-runtime/utils/createError.ts +1 -0
- package/src/libs/agent-runtime/utils/debugStream.test.ts +70 -0
- package/src/libs/agent-runtime/utils/debugStream.ts +39 -9
- package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.test.ts +521 -0
- package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +127 -5
- package/src/libs/agent-runtime/utils/response.ts +12 -0
- package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +197 -0
- package/src/libs/agent-runtime/utils/streams/anthropic.ts +91 -0
- package/src/libs/agent-runtime/utils/streams/bedrock/claude.ts +21 -0
- package/src/libs/agent-runtime/utils/streams/bedrock/common.ts +32 -0
- package/src/libs/agent-runtime/utils/streams/bedrock/index.ts +3 -0
- package/src/libs/agent-runtime/utils/streams/bedrock/llama.test.ts +196 -0
- package/src/libs/agent-runtime/utils/streams/bedrock/llama.ts +51 -0
- package/src/libs/agent-runtime/utils/streams/google-ai.test.ts +97 -0
- package/src/libs/agent-runtime/utils/streams/google-ai.ts +68 -0
- package/src/libs/agent-runtime/utils/streams/index.ts +7 -0
- package/src/libs/agent-runtime/utils/streams/minimax.ts +39 -0
- package/src/libs/agent-runtime/utils/streams/ollama.test.ts +77 -0
- package/src/libs/agent-runtime/utils/streams/ollama.ts +38 -0
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +263 -0
- package/src/libs/agent-runtime/utils/streams/openai.ts +79 -0
- package/src/libs/agent-runtime/utils/streams/protocol.ts +100 -0
- package/src/libs/agent-runtime/zeroone/index.test.ts +1 -53
- package/src/libs/agent-runtime/zhipu/index.test.ts +1 -1
- package/src/libs/agent-runtime/zhipu/index.ts +3 -2
- package/src/locales/default/plugin.ts +3 -4
- package/src/locales/default/tool.ts +1 -0
- package/src/migrations/FromV4ToV5/fixtures/from-v1-to-v5-output.json +245 -0
- package/src/migrations/FromV4ToV5/fixtures/function-input-v4.json +96 -0
- package/src/migrations/FromV4ToV5/fixtures/function-output-v5.json +120 -0
- package/src/migrations/FromV4ToV5/index.ts +58 -0
- package/src/migrations/FromV4ToV5/migrations.test.ts +49 -0
- package/src/migrations/FromV4ToV5/types/v4.ts +21 -0
- package/src/migrations/FromV4ToV5/types/v5.ts +27 -0
- package/src/migrations/index.ts +8 -1
- package/src/services/__tests__/chat.test.ts +10 -20
- package/src/services/_url.ts +1 -1
- package/src/services/chat.ts +78 -65
- package/src/services/{imageGeneration.ts → textToImage.ts} +11 -2
- package/src/store/chat/initialState.ts +1 -1
- package/src/store/chat/selectors.ts +1 -1
- package/src/store/chat/slices/{tool → builtinTool}/action.test.ts +1 -1
- package/src/store/chat/slices/{tool → builtinTool}/action.ts +16 -4
- package/src/store/chat/slices/enchance/action.ts +25 -21
- package/src/store/chat/slices/message/action.test.ts +36 -86
- package/src/store/chat/slices/message/action.ts +98 -169
- package/src/store/chat/slices/message/initialState.ts +5 -0
- package/src/store/chat/slices/message/reducer.ts +18 -1
- package/src/store/chat/slices/message/selectors.test.ts +38 -68
- package/src/store/chat/slices/message/selectors.ts +9 -22
- package/src/store/chat/slices/plugin/action.test.ts +148 -204
- package/src/store/chat/slices/plugin/action.ts +163 -134
- package/src/store/chat/slices/share/action.test.ts +3 -3
- package/src/store/chat/slices/share/action.ts +1 -1
- package/src/store/chat/slices/topic/action.ts +7 -2
- package/src/store/chat/store.ts +2 -2
- package/src/store/tool/selectors/tool.ts +6 -24
- package/src/store/tool/slices/builtin/action.test.ts +90 -0
- package/src/store/tool/slices/store/action.test.ts +6 -2
- package/src/store/tool/slices/store/action.ts +3 -1
- package/src/tools/dalle/Render/Item/Error.tsx +50 -0
- package/src/tools/dalle/Render/Item/Image.tsx +44 -0
- package/src/tools/dalle/Render/{Item.tsx → Item/index.tsx} +20 -29
- package/src/types/llm.ts +1 -1
- package/src/types/message/index.ts +9 -4
- package/src/types/message/tools.ts +57 -0
- package/src/types/openai/chat.ts +6 -0
- package/src/utils/fetch.test.ts +450 -1
- package/src/utils/fetch.ts +338 -39
- package/src/utils/toolCall.ts +21 -0
- package/src/app/api/openai/images/createImageGeneration.ts +0 -26
- package/src/app/api/openai/images/route.ts +0 -16
- package/src/features/Conversation/Actions/Function.tsx +0 -17
- package/src/features/Conversation/Messages/Assistant.tsx +0 -26
- package/src/features/Conversation/Messages/Function.tsx +0 -35
- package/src/libs/agent-runtime/ollama/stream.ts +0 -31
- /package/src/app/api/{chat → middleware}/auth/index.test.ts +0 -0
- /package/src/app/api/{chat → middleware}/auth/index.ts +0 -0
- /package/src/app/api/{chat → middleware}/auth/utils.ts +0 -0
- /package/src/app/api/{auth.ts → openai/createBizOpenAI/auth.ts} +0 -0
- /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/PluginResultJSON.tsx +0 -0
- /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/Settings.tsx +0 -0
- /package/src/features/Conversation/{Plugins → Messages/Tool}/Inspector/style.ts +0 -0
- /package/src/store/chat/slices/{tool → builtinTool}/initialState.ts +0 -0
- /package/src/store/chat/slices/{tool → builtinTool}/selectors.ts +0 -0
- /package/src/tools/dalle/Render/{EditMode.tsx → Item/EditMode.tsx} +0 -0
|
@@ -40,24 +40,6 @@ describe('LobeMoonshotAI', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
describe('chat', () => {
|
|
43
|
-
it('should return a StreamingTextResponse on successful API call', async () => {
|
|
44
|
-
// Arrange
|
|
45
|
-
const mockStream = new ReadableStream();
|
|
46
|
-
const mockResponse = Promise.resolve(mockStream);
|
|
47
|
-
|
|
48
|
-
(instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
|
|
49
|
-
|
|
50
|
-
// Act
|
|
51
|
-
const result = await instance.chat({
|
|
52
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
53
|
-
model: 'text-davinci-003',
|
|
54
|
-
temperature: 0,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Assert
|
|
58
|
-
expect(result).toBeInstanceOf(Response);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
43
|
describe('Error', () => {
|
|
62
44
|
it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
|
|
63
45
|
// Arrange
|
|
@@ -223,59 +205,6 @@ describe('LobeMoonshotAI', () => {
|
|
|
223
205
|
});
|
|
224
206
|
});
|
|
225
207
|
|
|
226
|
-
describe('LobeMoonshotAI chat with callback and headers', () => {
|
|
227
|
-
it('should handle callback and headers correctly', async () => {
|
|
228
|
-
// 模拟 chat.completions.create 方法返回一个可读流
|
|
229
|
-
const mockCreateMethod = vi
|
|
230
|
-
.spyOn(instance['client'].chat.completions, 'create')
|
|
231
|
-
.mockResolvedValue(
|
|
232
|
-
new ReadableStream({
|
|
233
|
-
start(controller) {
|
|
234
|
-
controller.enqueue({
|
|
235
|
-
id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
|
|
236
|
-
object: 'chat.completion.chunk',
|
|
237
|
-
created: 1709125675,
|
|
238
|
-
model: 'gpt-3.5-turbo-0125',
|
|
239
|
-
system_fingerprint: 'fp_86156a94a0',
|
|
240
|
-
choices: [
|
|
241
|
-
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
|
|
242
|
-
],
|
|
243
|
-
});
|
|
244
|
-
controller.close();
|
|
245
|
-
},
|
|
246
|
-
}) as any,
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// 准备 callback 和 headers
|
|
250
|
-
const mockCallback: ChatStreamCallbacks = {
|
|
251
|
-
onStart: vi.fn(),
|
|
252
|
-
onToken: vi.fn(),
|
|
253
|
-
};
|
|
254
|
-
const mockHeaders = { 'Custom-Header': 'TestValue' };
|
|
255
|
-
|
|
256
|
-
// 执行测试
|
|
257
|
-
const result = await instance.chat(
|
|
258
|
-
{
|
|
259
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
260
|
-
model: 'text-davinci-003',
|
|
261
|
-
temperature: 0,
|
|
262
|
-
},
|
|
263
|
-
{ callback: mockCallback, headers: mockHeaders },
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
// 验证 callback 被调用
|
|
267
|
-
await result.text(); // 确保流被消费
|
|
268
|
-
expect(mockCallback.onStart).toHaveBeenCalled();
|
|
269
|
-
expect(mockCallback.onToken).toHaveBeenCalledWith('hello');
|
|
270
|
-
|
|
271
|
-
// 验证 headers 被正确传递
|
|
272
|
-
expect(result.headers.get('Custom-Header')).toEqual('TestValue');
|
|
273
|
-
|
|
274
|
-
// 清理
|
|
275
|
-
mockCreateMethod.mockRestore();
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
208
|
describe('DEBUG', () => {
|
|
280
209
|
it('should call debugStream and return StreamingTextResponse when DEBUG_MOONSHOT_CHAT_COMPLETION is 1', async () => {
|
|
281
210
|
// Arrange
|
|
@@ -306,6 +235,7 @@ describe('LobeMoonshotAI', () => {
|
|
|
306
235
|
await instance.chat({
|
|
307
236
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
308
237
|
model: 'text-davinci-003',
|
|
238
|
+
stream: true,
|
|
309
239
|
temperature: 0,
|
|
310
240
|
});
|
|
311
241
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Ollama } from 'ollama/browser';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { AgentRuntimeErrorType } from '../error';
|
|
5
|
+
import { ModelProvider } from '../types';
|
|
6
|
+
import { AgentRuntimeError } from '../utils/createError';
|
|
7
|
+
import { LobeOllamaAI } from './index';
|
|
8
|
+
|
|
9
|
+
vi.mock('ollama/browser');
|
|
10
|
+
|
|
11
|
+
describe('LobeOllamaAI', () => {
|
|
12
|
+
let ollamaAI: LobeOllamaAI;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
ollamaAI = new LobeOllamaAI({ baseURL: 'https://example.com' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.resetAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('constructor', () => {
|
|
23
|
+
it('should initialize Ollama client and baseURL with valid baseURL', () => {
|
|
24
|
+
expect(ollamaAI['client']).toBeInstanceOf(Ollama);
|
|
25
|
+
expect(ollamaAI.baseURL).toBe('https://example.com');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should throw AgentRuntimeError with invalid baseURL', () => {
|
|
29
|
+
try {
|
|
30
|
+
new LobeOllamaAI({ baseURL: 'invalid-url' });
|
|
31
|
+
} catch (e) {
|
|
32
|
+
expect(e).toEqual(AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidOllamaArgs));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('chat', () => {
|
|
38
|
+
it('should call Ollama client chat method and return StreamingResponse', async () => {
|
|
39
|
+
const chatMock = vi.fn().mockResolvedValue({});
|
|
40
|
+
vi.mocked(Ollama.prototype.chat).mockImplementation(chatMock);
|
|
41
|
+
|
|
42
|
+
const payload = {
|
|
43
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
44
|
+
model: 'model-id',
|
|
45
|
+
};
|
|
46
|
+
const options = { signal: new AbortController().signal };
|
|
47
|
+
|
|
48
|
+
const response = await ollamaAI.chat(payload as any, options);
|
|
49
|
+
|
|
50
|
+
expect(chatMock).toHaveBeenCalledWith({
|
|
51
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
52
|
+
model: 'model-id',
|
|
53
|
+
options: {
|
|
54
|
+
frequency_penalty: undefined,
|
|
55
|
+
presence_penalty: undefined,
|
|
56
|
+
temperature: undefined,
|
|
57
|
+
top_p: undefined,
|
|
58
|
+
},
|
|
59
|
+
stream: true,
|
|
60
|
+
});
|
|
61
|
+
expect(response).toBeInstanceOf(Response);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should throw AgentRuntimeError when Ollama client chat method throws an error', async () => {
|
|
65
|
+
const errorMock = {
|
|
66
|
+
message: 'Chat error',
|
|
67
|
+
name: 'ChatError',
|
|
68
|
+
status_code: 500,
|
|
69
|
+
};
|
|
70
|
+
vi.mocked(Ollama.prototype.chat).mockRejectedValue(errorMock);
|
|
71
|
+
|
|
72
|
+
const payload = {
|
|
73
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
74
|
+
model: 'model-id',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await ollamaAI.chat(payload as any);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
expect(e).toEqual(
|
|
81
|
+
AgentRuntimeError.chat({
|
|
82
|
+
error: errorMock,
|
|
83
|
+
errorType: AgentRuntimeErrorType.OllamaBizError,
|
|
84
|
+
provider: ModelProvider.Ollama,
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should abort the request when signal aborts', async () => {
|
|
91
|
+
const abortMock = vi.fn();
|
|
92
|
+
vi.mocked(Ollama.prototype.abort).mockImplementation(abortMock);
|
|
93
|
+
|
|
94
|
+
const payload = {
|
|
95
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
96
|
+
model: 'model-id',
|
|
97
|
+
};
|
|
98
|
+
const options = { signal: new AbortController().signal };
|
|
99
|
+
|
|
100
|
+
ollamaAI.chat(payload as any, options);
|
|
101
|
+
|
|
102
|
+
options.signal.dispatchEvent(new Event('abort'));
|
|
103
|
+
|
|
104
|
+
expect(abortMock).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('models', () => {
|
|
109
|
+
it('should call Ollama client list method and return ChatModelCard array', async () => {
|
|
110
|
+
const listMock = vi.fn().mockResolvedValue({
|
|
111
|
+
models: [{ name: 'model-1' }, { name: 'model-2' }],
|
|
112
|
+
});
|
|
113
|
+
vi.mocked(Ollama.prototype.list).mockImplementation(listMock);
|
|
114
|
+
|
|
115
|
+
const models = await ollamaAI.models();
|
|
116
|
+
|
|
117
|
+
expect(listMock).toHaveBeenCalled();
|
|
118
|
+
expect(models).toEqual([{ id: 'model-1' }, { id: 'model-2' }]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('buildOllamaMessages', () => {
|
|
123
|
+
it('should convert OpenAIChatMessage array to OllamaMessage array', () => {
|
|
124
|
+
const messages = [
|
|
125
|
+
{ content: 'Hello', role: 'user' },
|
|
126
|
+
{ content: 'Hi there!', role: 'assistant' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const ollamaMessages = ollamaAI['buildOllamaMessages'](messages as any);
|
|
130
|
+
|
|
131
|
+
expect(ollamaMessages).toEqual([
|
|
132
|
+
{ content: 'Hello', role: 'user' },
|
|
133
|
+
{ content: 'Hi there!', role: 'assistant' },
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('convertContentToOllamaMessage', () => {
|
|
139
|
+
it('should convert string content to OllamaMessage', () => {
|
|
140
|
+
const message = { content: 'Hello', role: 'user' };
|
|
141
|
+
|
|
142
|
+
const ollamaMessage = ollamaAI['convertContentToOllamaMessage'](message as any);
|
|
143
|
+
|
|
144
|
+
expect(ollamaMessage).toEqual({ content: 'Hello', role: 'user' });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should convert text content to OllamaMessage', () => {
|
|
148
|
+
const message = {
|
|
149
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
150
|
+
role: 'user',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const ollamaMessage = ollamaAI['convertContentToOllamaMessage'](message as any);
|
|
154
|
+
|
|
155
|
+
expect(ollamaMessage).toEqual({ content: 'Hello', role: 'user' });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should convert image_url content to OllamaMessage with images', () => {
|
|
159
|
+
const message = {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'image_url',
|
|
163
|
+
image_url: { url: 'data:image/png;base64,abc123' },
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
role: 'user',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const ollamaMessage = ollamaAI['convertContentToOllamaMessage'](message as any);
|
|
170
|
+
|
|
171
|
+
expect(ollamaMessage).toEqual({
|
|
172
|
+
content: '',
|
|
173
|
+
role: 'user',
|
|
174
|
+
images: ['abc123'],
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should ignore invalid image_url content', () => {
|
|
179
|
+
const message = {
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: 'image_url',
|
|
183
|
+
image_url: { url: 'invalid-url' },
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
role: 'user',
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const ollamaMessage = ollamaAI['convertContentToOllamaMessage'](message as any);
|
|
190
|
+
|
|
191
|
+
expect(ollamaMessage).toEqual({
|
|
192
|
+
content: '',
|
|
193
|
+
role: 'user',
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { StreamingTextResponse } from 'ai';
|
|
2
1
|
import { Ollama } from 'ollama/browser';
|
|
3
2
|
import { ClientOptions } from 'openai';
|
|
4
3
|
|
|
5
4
|
import { OpenAIChatMessage } from '@/libs/agent-runtime';
|
|
6
|
-
import { OllamaStream } from '@/libs/agent-runtime/ollama/stream';
|
|
7
5
|
import { ChatModelCard } from '@/types/llm';
|
|
8
6
|
|
|
9
7
|
import { LobeRuntimeAI } from '../BaseAI';
|
|
10
8
|
import { AgentRuntimeErrorType } from '../error';
|
|
11
9
|
import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
|
|
12
10
|
import { AgentRuntimeError } from '../utils/createError';
|
|
11
|
+
import { StreamingResponse } from '../utils/response';
|
|
12
|
+
import { OllamaStream } from '../utils/streams';
|
|
13
13
|
import { parseDataUri } from '../utils/uriParser';
|
|
14
14
|
import { OllamaMessage } from './type';
|
|
15
15
|
|
|
@@ -51,7 +51,7 @@ export class LobeOllamaAI implements LobeRuntimeAI {
|
|
|
51
51
|
stream: true,
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
return
|
|
54
|
+
return StreamingResponse(OllamaStream(response, options?.callback), {
|
|
55
55
|
headers: options?.headers,
|
|
56
56
|
});
|
|
57
57
|
} catch (error) {
|
|
@@ -190,59 +190,6 @@ describe('LobeOpenAI', () => {
|
|
|
190
190
|
});
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
describe('LobeOpenAI chat with callback and headers', () => {
|
|
194
|
-
it('should handle callback and headers correctly', async () => {
|
|
195
|
-
// 模拟 chat.completions.create 方法返回一个可读流
|
|
196
|
-
const mockCreateMethod = vi
|
|
197
|
-
.spyOn(instance['client'].chat.completions, 'create')
|
|
198
|
-
.mockResolvedValue(
|
|
199
|
-
new ReadableStream({
|
|
200
|
-
start(controller) {
|
|
201
|
-
controller.enqueue({
|
|
202
|
-
id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
|
|
203
|
-
object: 'chat.completion.chunk',
|
|
204
|
-
created: 1709125675,
|
|
205
|
-
model: 'gpt-3.5-turbo-0125',
|
|
206
|
-
system_fingerprint: 'fp_86156a94a0',
|
|
207
|
-
choices: [
|
|
208
|
-
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
|
|
209
|
-
],
|
|
210
|
-
});
|
|
211
|
-
controller.close();
|
|
212
|
-
},
|
|
213
|
-
}) as any,
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
// 准备 callback 和 headers
|
|
217
|
-
const mockCallback: ChatStreamCallbacks = {
|
|
218
|
-
onStart: vi.fn(),
|
|
219
|
-
onToken: vi.fn(),
|
|
220
|
-
};
|
|
221
|
-
const mockHeaders = { 'Custom-Header': 'TestValue' };
|
|
222
|
-
|
|
223
|
-
// 执行测试
|
|
224
|
-
const result = await instance.chat(
|
|
225
|
-
{
|
|
226
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
227
|
-
model: 'text-davinci-003',
|
|
228
|
-
temperature: 0,
|
|
229
|
-
},
|
|
230
|
-
{ callback: mockCallback, headers: mockHeaders },
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
// 验证 callback 被调用
|
|
234
|
-
await result.text(); // 确保流被消费
|
|
235
|
-
expect(mockCallback.onStart).toHaveBeenCalled();
|
|
236
|
-
expect(mockCallback.onToken).toHaveBeenCalledWith('hello');
|
|
237
|
-
|
|
238
|
-
// 验证 headers 被正确传递
|
|
239
|
-
expect(result.headers.get('Custom-Header')).toEqual('TestValue');
|
|
240
|
-
|
|
241
|
-
// 清理
|
|
242
|
-
mockCreateMethod.mockRestore();
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
193
|
describe('DEBUG', () => {
|
|
247
194
|
it('should call debugStream and return StreamingTextResponse when DEBUG_OPENAI_CHAT_COMPLETION is 1', async () => {
|
|
248
195
|
// Arrange
|
|
@@ -81,6 +81,7 @@ describe('LobeOpenRouterAI', () => {
|
|
|
81
81
|
{
|
|
82
82
|
max_tokens: 1024,
|
|
83
83
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
84
|
+
stream: true,
|
|
84
85
|
model: 'mistralai/mistral-7b-instruct:free',
|
|
85
86
|
temperature: 0.7,
|
|
86
87
|
top_p: 1,
|
|
@@ -255,59 +256,6 @@ describe('LobeOpenRouterAI', () => {
|
|
|
255
256
|
});
|
|
256
257
|
});
|
|
257
258
|
|
|
258
|
-
describe('LobeOpenRouterAI chat with callback and headers', () => {
|
|
259
|
-
it('should handle callback and headers correctly', async () => {
|
|
260
|
-
// 模拟 chat.completions.create 方法返回一个可读流
|
|
261
|
-
const mockCreateMethod = vi
|
|
262
|
-
.spyOn(instance['client'].chat.completions, 'create')
|
|
263
|
-
.mockResolvedValue(
|
|
264
|
-
new ReadableStream({
|
|
265
|
-
start(controller) {
|
|
266
|
-
controller.enqueue({
|
|
267
|
-
id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
|
|
268
|
-
object: 'chat.completion.chunk',
|
|
269
|
-
created: 1709125675,
|
|
270
|
-
model: 'mistralai/mistral-7b-instruct:free',
|
|
271
|
-
system_fingerprint: 'fp_86156a94a0',
|
|
272
|
-
choices: [
|
|
273
|
-
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
|
|
274
|
-
],
|
|
275
|
-
});
|
|
276
|
-
controller.close();
|
|
277
|
-
},
|
|
278
|
-
}) as any,
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
// 准备 callback 和 headers
|
|
282
|
-
const mockCallback: ChatStreamCallbacks = {
|
|
283
|
-
onStart: vi.fn(),
|
|
284
|
-
onToken: vi.fn(),
|
|
285
|
-
};
|
|
286
|
-
const mockHeaders = { 'Custom-Header': 'TestValue' };
|
|
287
|
-
|
|
288
|
-
// 执行测试
|
|
289
|
-
const result = await instance.chat(
|
|
290
|
-
{
|
|
291
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
292
|
-
model: 'mistralai/mistral-7b-instruct:free',
|
|
293
|
-
temperature: 0,
|
|
294
|
-
},
|
|
295
|
-
{ callback: mockCallback, headers: mockHeaders },
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
// 验证 callback 被调用
|
|
299
|
-
await result.text(); // 确保流被消费
|
|
300
|
-
expect(mockCallback.onStart).toHaveBeenCalled();
|
|
301
|
-
expect(mockCallback.onToken).toHaveBeenCalledWith('hello');
|
|
302
|
-
|
|
303
|
-
// 验证 headers 被正确传递
|
|
304
|
-
expect(result.headers.get('Custom-Header')).toEqual('TestValue');
|
|
305
|
-
|
|
306
|
-
// 清理
|
|
307
|
-
mockCreateMethod.mockRestore();
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
259
|
describe('DEBUG', () => {
|
|
312
260
|
it('should call debugStream and return StreamingTextResponse when DEBUG_OPENROUTER_CHAT_COMPLETION is 1', async () => {
|
|
313
261
|
// Arrange
|
|
@@ -40,24 +40,6 @@ describe('LobePerplexityAI', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
describe('chat', () => {
|
|
43
|
-
it('should return a StreamingTextResponse on successful API call', async () => {
|
|
44
|
-
// Arrange
|
|
45
|
-
const mockStream = new ReadableStream();
|
|
46
|
-
const mockResponse = Promise.resolve(mockStream);
|
|
47
|
-
|
|
48
|
-
(instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
|
|
49
|
-
|
|
50
|
-
// Act
|
|
51
|
-
const result = await instance.chat({
|
|
52
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
53
|
-
model: 'text-davinci-003',
|
|
54
|
-
temperature: 0,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Assert
|
|
58
|
-
expect(result).toBeInstanceOf(Response);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
43
|
describe('Error', () => {
|
|
62
44
|
it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
|
|
63
45
|
// Arrange
|
|
@@ -223,59 +205,6 @@ describe('LobePerplexityAI', () => {
|
|
|
223
205
|
});
|
|
224
206
|
});
|
|
225
207
|
|
|
226
|
-
describe('LobePerplexityAI chat with callback and headers', () => {
|
|
227
|
-
it('should handle callback and headers correctly', async () => {
|
|
228
|
-
// 模拟 chat.completions.create 方法返回一个可读流
|
|
229
|
-
const mockCreateMethod = vi
|
|
230
|
-
.spyOn(instance['client'].chat.completions, 'create')
|
|
231
|
-
.mockResolvedValue(
|
|
232
|
-
new ReadableStream({
|
|
233
|
-
start(controller) {
|
|
234
|
-
controller.enqueue({
|
|
235
|
-
id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
|
|
236
|
-
object: 'chat.completion.chunk',
|
|
237
|
-
created: 1709125675,
|
|
238
|
-
model: 'gpt-3.5-turbo-0125',
|
|
239
|
-
system_fingerprint: 'fp_86156a94a0',
|
|
240
|
-
choices: [
|
|
241
|
-
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
|
|
242
|
-
],
|
|
243
|
-
});
|
|
244
|
-
controller.close();
|
|
245
|
-
},
|
|
246
|
-
}) as any,
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// 准备 callback 和 headers
|
|
250
|
-
const mockCallback: ChatStreamCallbacks = {
|
|
251
|
-
onStart: vi.fn(),
|
|
252
|
-
onToken: vi.fn(),
|
|
253
|
-
};
|
|
254
|
-
const mockHeaders = { 'Custom-Header': 'TestValue' };
|
|
255
|
-
|
|
256
|
-
// 执行测试
|
|
257
|
-
const result = await instance.chat(
|
|
258
|
-
{
|
|
259
|
-
messages: [{ content: 'Hello', role: 'user' }],
|
|
260
|
-
model: 'text-davinci-003',
|
|
261
|
-
temperature: 0,
|
|
262
|
-
},
|
|
263
|
-
{ callback: mockCallback, headers: mockHeaders },
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
// 验证 callback 被调用
|
|
267
|
-
await result.text(); // 确保流被消费
|
|
268
|
-
expect(mockCallback.onStart).toHaveBeenCalled();
|
|
269
|
-
expect(mockCallback.onToken).toHaveBeenCalledWith('hello');
|
|
270
|
-
|
|
271
|
-
// 验证 headers 被正确传递
|
|
272
|
-
expect(result.headers.get('Custom-Header')).toEqual('TestValue');
|
|
273
|
-
|
|
274
|
-
// 清理
|
|
275
|
-
mockCreateMethod.mockRestore();
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
208
|
describe('DEBUG', () => {
|
|
280
209
|
it('should call debugStream and return StreamingTextResponse when DEBUG_PERPLEXITY_CHAT_COMPLETION is 1', async () => {
|
|
281
210
|
// Arrange
|
|
@@ -9,7 +9,7 @@ export const LobePerplexityAI = LobeOpenAICompatibleFactory({
|
|
|
9
9
|
chatCompletion: {
|
|
10
10
|
handlePayload: (payload: ChatStreamPayload) => {
|
|
11
11
|
// Set a default frequency penalty value greater than 0
|
|
12
|
-
const { presence_penalty, frequency_penalty, ...res } = payload;
|
|
12
|
+
const { presence_penalty, frequency_penalty, stream = true, ...res } = payload;
|
|
13
13
|
|
|
14
14
|
let param;
|
|
15
15
|
|
|
@@ -22,8 +22,7 @@ export const LobePerplexityAI = LobeOpenAICompatibleFactory({
|
|
|
22
22
|
param = { frequency_penalty: frequency_penalty || defaultFrequencyPenalty };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
return { ...res, ...param } as OpenAI.ChatCompletionCreateParamsStreaming;
|
|
25
|
+
return { ...res, ...param, stream } as OpenAI.ChatCompletionCreateParamsStreaming;
|
|
27
26
|
},
|
|
28
27
|
},
|
|
29
28
|
debug: {
|