@lobehub/chat 1.136.13 → 1.137.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/.cursor/rules/add-setting-env.mdc +175 -0
- package/.cursor/rules/db-migrations.mdc +25 -0
- package/.env.example +7 -0
- package/CHANGELOG.md +50 -0
- package/Dockerfile +3 -2
- package/Dockerfile.database +15 -3
- package/Dockerfile.pglite +3 -2
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +1 -0
- package/docs/self-hosting/advanced/feature-flags.mdx +25 -15
- package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +25 -15
- package/docs/self-hosting/environment-variables/basic.mdx +12 -0
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +12 -0
- package/locales/ar/setting.json +8 -0
- package/locales/bg-BG/setting.json +8 -0
- package/locales/de-DE/setting.json +8 -0
- package/locales/en-US/setting.json +8 -0
- package/locales/es-ES/setting.json +8 -0
- package/locales/fa-IR/setting.json +8 -0
- package/locales/fr-FR/setting.json +8 -0
- package/locales/it-IT/setting.json +8 -0
- package/locales/ja-JP/setting.json +8 -0
- package/locales/ko-KR/setting.json +8 -0
- package/locales/nl-NL/setting.json +8 -0
- package/locales/pl-PL/setting.json +8 -0
- package/locales/pt-BR/setting.json +8 -0
- package/locales/ru-RU/setting.json +8 -0
- package/locales/tr-TR/setting.json +8 -0
- package/locales/vi-VN/setting.json +8 -0
- package/locales/zh-CN/setting.json +8 -0
- package/locales/zh-TW/setting.json +8 -0
- package/package.json +1 -1
- package/packages/agent-runtime/examples/tools-calling.ts +4 -3
- package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +559 -29
- package/packages/agent-runtime/src/core/runtime.ts +171 -43
- package/packages/agent-runtime/src/types/instruction.ts +32 -6
- package/packages/agent-runtime/src/types/runtime.ts +2 -2
- package/packages/agent-runtime/src/types/state.ts +1 -8
- package/packages/agent-runtime/vitest.config.mts +14 -0
- package/packages/const/src/settings/image.ts +8 -0
- package/packages/const/src/settings/index.ts +3 -0
- package/packages/context-engine/src/__tests__/pipeline.test.ts +485 -0
- package/packages/context-engine/src/base/__tests__/BaseProcessor.test.ts +381 -0
- package/packages/context-engine/src/base/__tests__/BaseProvider.test.ts +392 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +346 -0
- package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +552 -0
- package/packages/database/migrations/0038_add_image_user_settings.sql +1 -0
- package/packages/database/migrations/meta/0038_snapshot.json +7580 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +6 -0
- package/packages/database/src/models/user.ts +3 -1
- package/packages/database/src/schemas/user.ts +1 -0
- package/packages/file-loaders/src/loaders/docx/index.test.ts +0 -1
- package/packages/file-loaders/src/loaders/excel/__snapshots__/index.test.ts.snap +30 -0
- package/packages/file-loaders/src/loaders/excel/index.test.ts +8 -0
- package/packages/file-loaders/src/loaders/pptx/index.test.ts +25 -0
- package/packages/file-loaders/src/utils/parser-utils.test.ts +155 -0
- package/packages/file-loaders/vitest.config.mts +8 -0
- package/packages/model-runtime/CLAUDE.md +5 -0
- package/packages/model-runtime/docs/test-coverage.md +706 -0
- package/packages/model-runtime/src/core/ModelRuntime.test.ts +231 -0
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +799 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +188 -4
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +41 -10
- package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +439 -0
- package/packages/model-runtime/src/core/streams/openai/openai.test.ts +789 -0
- package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +551 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +230 -0
- package/packages/model-runtime/src/core/usageConverters/utils/computeImageCost.test.ts +334 -37
- package/packages/model-runtime/src/providerTestUtils.ts +148 -145
- package/packages/model-runtime/src/providers/ai302/index.test.ts +60 -0
- package/packages/model-runtime/src/providers/ai302/index.ts +9 -4
- package/packages/model-runtime/src/providers/ai360/index.test.ts +1213 -1
- package/packages/model-runtime/src/providers/ai360/index.ts +9 -4
- package/packages/model-runtime/src/providers/aihubmix/index.test.ts +73 -0
- package/packages/model-runtime/src/providers/aihubmix/index.ts +6 -9
- package/packages/model-runtime/src/providers/akashchat/index.test.ts +433 -3
- package/packages/model-runtime/src/providers/akashchat/index.ts +12 -7
- package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +183 -29
- package/packages/model-runtime/src/providers/anthropic/generateObject.ts +40 -24
- package/packages/model-runtime/src/providers/azureai/index.test.ts +102 -0
- package/packages/model-runtime/src/providers/baichuan/index.test.ts +416 -26
- package/packages/model-runtime/src/providers/baichuan/index.ts +23 -20
- package/packages/model-runtime/src/providers/bedrock/index.test.ts +420 -2
- package/packages/model-runtime/src/providers/cerebras/index.test.ts +465 -0
- package/packages/model-runtime/src/providers/cerebras/index.ts +8 -3
- package/packages/model-runtime/src/providers/cohere/index.test.ts +1074 -1
- package/packages/model-runtime/src/providers/cohere/index.ts +8 -3
- package/packages/model-runtime/src/providers/cometapi/index.test.ts +439 -3
- package/packages/model-runtime/src/providers/cometapi/index.ts +8 -3
- package/packages/model-runtime/src/providers/deepseek/index.test.ts +116 -1
- package/packages/model-runtime/src/providers/deepseek/index.ts +8 -3
- package/packages/model-runtime/src/providers/fireworksai/index.test.ts +264 -3
- package/packages/model-runtime/src/providers/fireworksai/index.ts +8 -3
- package/packages/model-runtime/src/providers/giteeai/index.test.ts +325 -3
- package/packages/model-runtime/src/providers/giteeai/index.ts +23 -6
- package/packages/model-runtime/src/providers/github/index.test.ts +532 -3
- package/packages/model-runtime/src/providers/github/index.ts +8 -3
- package/packages/model-runtime/src/providers/groq/index.test.ts +344 -31
- package/packages/model-runtime/src/providers/groq/index.ts +8 -3
- package/packages/model-runtime/src/providers/higress/index.test.ts +142 -0
- package/packages/model-runtime/src/providers/higress/index.ts +8 -3
- package/packages/model-runtime/src/providers/huggingface/index.test.ts +612 -1
- package/packages/model-runtime/src/providers/huggingface/index.ts +9 -4
- package/packages/model-runtime/src/providers/hunyuan/index.test.ts +365 -1
- package/packages/model-runtime/src/providers/hunyuan/index.ts +9 -3
- package/packages/model-runtime/src/providers/infiniai/index.test.ts +71 -0
- package/packages/model-runtime/src/providers/internlm/index.test.ts +369 -2
- package/packages/model-runtime/src/providers/internlm/index.ts +10 -5
- package/packages/model-runtime/src/providers/jina/index.test.ts +164 -3
- package/packages/model-runtime/src/providers/jina/index.ts +8 -3
- package/packages/model-runtime/src/providers/lmstudio/index.test.ts +182 -3
- package/packages/model-runtime/src/providers/lmstudio/index.ts +8 -3
- package/packages/model-runtime/src/providers/mistral/index.test.ts +779 -27
- package/packages/model-runtime/src/providers/mistral/index.ts +8 -3
- package/packages/model-runtime/src/providers/modelscope/index.test.ts +232 -1
- package/packages/model-runtime/src/providers/modelscope/index.ts +8 -3
- package/packages/model-runtime/src/providers/moonshot/index.test.ts +489 -2
- package/packages/model-runtime/src/providers/moonshot/index.ts +8 -3
- package/packages/model-runtime/src/providers/nebius/index.test.ts +381 -3
- package/packages/model-runtime/src/providers/nebius/index.ts +8 -3
- package/packages/model-runtime/src/providers/newapi/index.test.ts +667 -3
- package/packages/model-runtime/src/providers/newapi/index.ts +6 -3
- package/packages/model-runtime/src/providers/nvidia/index.test.ts +168 -1
- package/packages/model-runtime/src/providers/nvidia/index.ts +12 -7
- package/packages/model-runtime/src/providers/ollama/index.test.ts +797 -1
- package/packages/model-runtime/src/providers/ollama/index.ts +8 -0
- package/packages/model-runtime/src/providers/ollamacloud/index.test.ts +411 -0
- package/packages/model-runtime/src/providers/ollamacloud/index.ts +8 -3
- package/packages/model-runtime/src/providers/openai/index.test.ts +171 -2
- package/packages/model-runtime/src/providers/openai/index.ts +8 -3
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +1647 -95
- package/packages/model-runtime/src/providers/openrouter/index.ts +12 -7
- package/packages/model-runtime/src/providers/qiniu/index.test.ts +294 -1
- package/packages/model-runtime/src/providers/qiniu/index.ts +8 -3
- package/packages/model-runtime/src/providers/search1api/index.test.ts +1131 -11
- package/packages/model-runtime/src/providers/search1api/index.ts +10 -4
- package/packages/model-runtime/src/providers/sensenova/index.test.ts +1069 -1
- package/packages/model-runtime/src/providers/sensenova/index.ts +8 -3
- package/packages/model-runtime/src/providers/siliconcloud/index.test.ts +196 -0
- package/packages/model-runtime/src/providers/siliconcloud/index.ts +8 -3
- package/packages/model-runtime/src/providers/spark/index.test.ts +293 -1
- package/packages/model-runtime/src/providers/spark/index.ts +8 -3
- package/packages/model-runtime/src/providers/stepfun/index.test.ts +322 -3
- package/packages/model-runtime/src/providers/stepfun/index.ts +8 -3
- package/packages/model-runtime/src/providers/tencentcloud/index.test.ts +182 -3
- package/packages/model-runtime/src/providers/tencentcloud/index.ts +8 -3
- package/packages/model-runtime/src/providers/togetherai/index.test.ts +359 -4
- package/packages/model-runtime/src/providers/togetherai/index.ts +12 -5
- package/packages/model-runtime/src/providers/v0/index.test.ts +341 -0
- package/packages/model-runtime/src/providers/v0/index.ts +20 -6
- package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +710 -0
- package/packages/model-runtime/src/providers/vercelaigateway/index.ts +19 -13
- package/packages/model-runtime/src/providers/vllm/index.test.ts +45 -1
- package/packages/model-runtime/src/providers/volcengine/index.test.ts +75 -0
- package/packages/model-runtime/src/providers/wenxin/index.test.ts +144 -1
- package/packages/model-runtime/src/providers/wenxin/index.ts +8 -3
- package/packages/model-runtime/src/providers/xai/index.test.ts +105 -1
- package/packages/model-runtime/src/providers/xinference/index.test.ts +70 -1
- package/packages/model-runtime/src/providers/zeroone/index.test.ts +327 -3
- package/packages/model-runtime/src/providers/zeroone/index.ts +23 -6
- package/packages/model-runtime/src/providers/zhipu/index.test.ts +908 -236
- package/packages/model-runtime/src/providers/zhipu/index.ts +8 -3
- package/packages/model-runtime/src/types/structureOutput.ts +5 -1
- package/packages/model-runtime/vitest.config.mts +7 -1
- package/packages/types/src/aiChat.ts +20 -2
- package/packages/types/src/serverConfig.ts +7 -1
- package/packages/types/src/tool/index.ts +1 -0
- package/packages/types/src/tool/tool.ts +33 -0
- package/packages/types/src/user/settings/image.ts +3 -0
- package/packages/types/src/user/settings/index.ts +3 -0
- package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +3 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +8 -3
- package/src/app/[variants]/(main)/settings/image/index.tsx +74 -0
- package/src/components/FormInput/FormSliderWithInput.tsx +40 -0
- package/src/components/FormInput/index.ts +1 -0
- package/src/envs/image.ts +27 -0
- package/src/features/Conversation/Messages/Assistant/index.tsx +1 -1
- package/src/features/Conversation/Messages/User/index.tsx +2 -2
- package/src/hooks/useFetchAiImageConfig.ts +12 -17
- package/src/locales/default/setting.ts +8 -0
- package/src/server/globalConfig/index.ts +5 -0
- package/src/server/routers/lambda/aiChat.ts +2 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/image/slices/generationConfig/action.test.ts +17 -0
- package/src/store/image/slices/generationConfig/action.ts +18 -21
- package/src/store/image/slices/generationConfig/initialState.ts +3 -2
- package/src/store/user/slices/common/action.ts +1 -0
- package/src/store/user/slices/settings/selectors/settings.ts +3 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { LobeOpenAICompatibleRuntime } from '@lobechat/model-runtime';
|
|
3
|
-
import {
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { testProvider } from '../../providerTestUtils';
|
|
6
|
-
import { LobeMistralAI } from './index';
|
|
6
|
+
import { LobeMistralAI, params } from './index';
|
|
7
7
|
|
|
8
8
|
testProvider({
|
|
9
9
|
provider: 'mistral',
|
|
@@ -11,18 +11,19 @@ testProvider({
|
|
|
11
11
|
chatModel: 'open-mistral-7b',
|
|
12
12
|
Runtime: LobeMistralAI,
|
|
13
13
|
chatDebugEnv: 'DEBUG_MISTRAL_CHAT_COMPLETION',
|
|
14
|
-
|
|
15
14
|
test: {
|
|
16
|
-
skipAPICall: true,
|
|
15
|
+
skipAPICall: true, // Mistral has custom payload handling (excludeUsage, temperature normalization)
|
|
17
16
|
},
|
|
18
17
|
});
|
|
19
18
|
|
|
19
|
+
// Mock the console.error to avoid polluting test output
|
|
20
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
21
|
+
|
|
20
22
|
let instance: LobeOpenAICompatibleRuntime;
|
|
21
23
|
|
|
22
24
|
beforeEach(() => {
|
|
23
25
|
instance = new LobeMistralAI({ apiKey: 'test' });
|
|
24
26
|
|
|
25
|
-
// 使用 vi.spyOn 来模拟 chat.completions.create 方法
|
|
26
27
|
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
|
|
27
28
|
new ReadableStream() as any,
|
|
28
29
|
);
|
|
@@ -32,35 +33,786 @@ afterEach(() => {
|
|
|
32
33
|
vi.clearAllMocks();
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
describe('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
describe('LobeMistralAI - custom features', () => {
|
|
37
|
+
describe('Debug Configuration', () => {
|
|
38
|
+
it('should disable debug by default', () => {
|
|
39
|
+
delete process.env.DEBUG_MISTRAL_CHAT_COMPLETION;
|
|
40
|
+
const result = params.debug.chatCompletion();
|
|
41
|
+
expect(result).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should enable debug when env is set', () => {
|
|
45
|
+
process.env.DEBUG_MISTRAL_CHAT_COMPLETION = '1';
|
|
46
|
+
const result = params.debug.chatCompletion();
|
|
47
|
+
expect(result).toBe(true);
|
|
48
|
+
delete process.env.DEBUG_MISTRAL_CHAT_COMPLETION;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('handlePayload', () => {
|
|
53
|
+
it('should exclude stream_options (excludeUsage)', async () => {
|
|
54
|
+
await instance.chat({
|
|
55
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
56
|
+
model: 'open-mistral-7b',
|
|
57
|
+
temperature: 0.7,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
61
|
+
expect.not.objectContaining({ stream_options: expect.anything() }),
|
|
62
|
+
expect.anything(),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should normalize temperature (divide by 2)', async () => {
|
|
67
|
+
await instance.chat({
|
|
68
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
69
|
+
model: 'open-mistral-7b',
|
|
70
|
+
temperature: 0.8,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
74
|
+
expect.objectContaining({ temperature: 0.4 }),
|
|
75
|
+
expect.anything(),
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should normalize temperature to 0.5 when temperature is 1', async () => {
|
|
80
|
+
await instance.chat({
|
|
81
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
82
|
+
model: 'open-mistral-7b',
|
|
83
|
+
temperature: 1,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({ temperature: 0.5 }),
|
|
88
|
+
expect.anything(),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should normalize temperature to 0 when temperature is 0', async () => {
|
|
93
|
+
await instance.chat({
|
|
94
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
95
|
+
model: 'open-mistral-7b',
|
|
96
|
+
temperature: 0,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
100
|
+
expect.objectContaining({ temperature: 0 }),
|
|
101
|
+
expect.anything(),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle high temperature values (2.0 normalized to 1.0)', async () => {
|
|
106
|
+
await instance.chat({
|
|
107
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
108
|
+
model: 'open-mistral-7b',
|
|
109
|
+
temperature: 2.0,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
113
|
+
expect.objectContaining({ temperature: 1.0 }),
|
|
114
|
+
expect.anything(),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should set stream to true by default', async () => {
|
|
119
|
+
await instance.chat({
|
|
120
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
121
|
+
model: 'open-mistral-7b',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({ stream: true }),
|
|
126
|
+
expect.anything(),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
40
129
|
|
|
41
|
-
(
|
|
130
|
+
it('should preserve top_p without modification', async () => {
|
|
131
|
+
await instance.chat({
|
|
132
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
133
|
+
model: 'open-mistral-7b',
|
|
134
|
+
top_p: 0.9,
|
|
135
|
+
});
|
|
42
136
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
model: 'open-mistral-7b',
|
|
48
|
-
temperature: 0.7,
|
|
49
|
-
top_p: 1,
|
|
137
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
138
|
+
expect.objectContaining({ top_p: 0.9 }),
|
|
139
|
+
expect.anything(),
|
|
140
|
+
);
|
|
50
141
|
});
|
|
51
142
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
143
|
+
it('should preserve max_tokens without modification', async () => {
|
|
144
|
+
await instance.chat({
|
|
145
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
146
|
+
model: 'open-mistral-7b',
|
|
55
147
|
max_tokens: 1024,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
151
|
+
expect.objectContaining({ max_tokens: 1024 }),
|
|
152
|
+
expect.anything(),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should include tools when provided', async () => {
|
|
157
|
+
const tools = [
|
|
158
|
+
{
|
|
159
|
+
type: 'function' as const,
|
|
160
|
+
function: { name: 'test_tool', description: 'A test tool', parameters: {} },
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
await instance.chat({
|
|
56
165
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
57
166
|
model: 'open-mistral-7b',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
167
|
+
tools,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
171
|
+
expect.objectContaining({ tools }),
|
|
172
|
+
expect.anything(),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should not include tools when not provided', async () => {
|
|
177
|
+
await instance.chat({
|
|
178
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
179
|
+
model: 'open-mistral-7b',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
183
|
+
expect(callArgs).not.toHaveProperty('tools');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should preserve messages as-is', async () => {
|
|
187
|
+
const messages = [
|
|
188
|
+
{ content: 'Hello', role: 'user' as const },
|
|
189
|
+
{ content: 'Hi there!', role: 'assistant' as const },
|
|
190
|
+
{ content: 'How are you?', role: 'user' as const },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
await instance.chat({
|
|
194
|
+
messages,
|
|
195
|
+
model: 'open-mistral-7b',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({ messages }),
|
|
200
|
+
expect.anything(),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should preserve model name', async () => {
|
|
205
|
+
await instance.chat({
|
|
206
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
207
|
+
model: 'mistral-large-latest',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
211
|
+
expect.objectContaining({ model: 'mistral-large-latest' }),
|
|
212
|
+
expect.anything(),
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle combined parameters correctly', async () => {
|
|
217
|
+
await instance.chat({
|
|
218
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
219
|
+
model: 'mistral-large-latest',
|
|
220
|
+
temperature: 1.4,
|
|
221
|
+
top_p: 0.85,
|
|
222
|
+
max_tokens: 2048,
|
|
223
|
+
tools: [
|
|
224
|
+
{
|
|
225
|
+
type: 'function' as const,
|
|
226
|
+
function: { name: 'calculator', description: 'Calculate', parameters: {} },
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
model: 'mistral-large-latest',
|
|
234
|
+
temperature: 0.7, // 1.4 / 2
|
|
235
|
+
top_p: 0.85,
|
|
236
|
+
max_tokens: 2048,
|
|
237
|
+
stream: true,
|
|
238
|
+
tools: expect.any(Array),
|
|
239
|
+
}),
|
|
240
|
+
expect.anything(),
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should not include undefined parameters in payload', async () => {
|
|
245
|
+
await instance.chat({
|
|
246
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
247
|
+
model: 'open-mistral-7b',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
251
|
+
expect(callArgs).not.toHaveProperty('temperature');
|
|
252
|
+
expect(callArgs).not.toHaveProperty('top_p');
|
|
253
|
+
expect(callArgs).not.toHaveProperty('max_tokens');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('exports', () => {
|
|
258
|
+
it('should export params object', () => {
|
|
259
|
+
expect(params).toBeDefined();
|
|
260
|
+
expect(params.provider).toBe('mistral');
|
|
261
|
+
expect(params.baseURL).toBe('https://api.mistral.ai/v1');
|
|
262
|
+
expect(params.chatCompletion).toBeDefined();
|
|
263
|
+
expect(params.debug).toBeDefined();
|
|
264
|
+
expect(params.models).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should export chatCompletion configuration', () => {
|
|
268
|
+
expect(params.chatCompletion.excludeUsage).toBe(true);
|
|
269
|
+
expect(params.chatCompletion.noUserId).toBe(true);
|
|
270
|
+
expect(params.chatCompletion.handlePayload).toBeDefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('models', () => {
|
|
275
|
+
const mockClient = {
|
|
276
|
+
models: {
|
|
277
|
+
list: vi.fn(),
|
|
61
278
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
beforeEach(() => {
|
|
282
|
+
vi.clearAllMocks();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should fetch and process models with function calling capability', async () => {
|
|
286
|
+
mockClient.models.list.mockResolvedValue({
|
|
287
|
+
data: [
|
|
288
|
+
{
|
|
289
|
+
id: 'mistral-large-latest',
|
|
290
|
+
max_context_length: 128000,
|
|
291
|
+
capabilities: {
|
|
292
|
+
function_calling: true,
|
|
293
|
+
vision: false,
|
|
294
|
+
},
|
|
295
|
+
description: 'Mistral Large model',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: 'mistral-small-latest',
|
|
299
|
+
max_context_length: 32000,
|
|
300
|
+
capabilities: {
|
|
301
|
+
function_calling: true,
|
|
302
|
+
vision: false,
|
|
303
|
+
},
|
|
304
|
+
description: 'Mistral Small model',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const models = await params.models({ client: mockClient as any });
|
|
310
|
+
|
|
311
|
+
expect(models).toHaveLength(2);
|
|
312
|
+
expect(models[0]).toMatchObject({
|
|
313
|
+
id: 'mistral-large-latest',
|
|
314
|
+
contextWindowTokens: 128000,
|
|
315
|
+
functionCall: true,
|
|
316
|
+
vision: false,
|
|
317
|
+
description: 'Mistral Large model',
|
|
318
|
+
});
|
|
319
|
+
expect(models[1]).toMatchObject({
|
|
320
|
+
id: 'mistral-small-latest',
|
|
321
|
+
contextWindowTokens: 32000,
|
|
322
|
+
functionCall: true,
|
|
323
|
+
vision: false,
|
|
324
|
+
description: 'Mistral Small model',
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should detect vision capability from model capabilities', async () => {
|
|
329
|
+
mockClient.models.list.mockResolvedValue({
|
|
330
|
+
data: [
|
|
331
|
+
{
|
|
332
|
+
id: 'pixtral-12b-latest',
|
|
333
|
+
max_context_length: 128000,
|
|
334
|
+
capabilities: {
|
|
335
|
+
function_calling: true,
|
|
336
|
+
vision: true,
|
|
337
|
+
},
|
|
338
|
+
description: 'Pixtral vision model',
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const models = await params.models({ client: mockClient as any });
|
|
344
|
+
|
|
345
|
+
expect(models).toHaveLength(1);
|
|
346
|
+
expect(models[0]).toMatchObject({
|
|
347
|
+
id: 'pixtral-12b-latest',
|
|
348
|
+
vision: true,
|
|
349
|
+
functionCall: true,
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should merge with known model list for display name and enabled status', async () => {
|
|
354
|
+
mockClient.models.list.mockResolvedValue({
|
|
355
|
+
data: [
|
|
356
|
+
{
|
|
357
|
+
id: 'mistral-large-latest',
|
|
358
|
+
max_context_length: 128000,
|
|
359
|
+
capabilities: {
|
|
360
|
+
function_calling: true,
|
|
361
|
+
vision: false,
|
|
362
|
+
},
|
|
363
|
+
description: 'Latest Mistral Large',
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const models = await params.models({ client: mockClient as any });
|
|
369
|
+
|
|
370
|
+
expect(models).toHaveLength(1);
|
|
371
|
+
// Should have displayName and enabled from LOBE_DEFAULT_MODEL_LIST
|
|
372
|
+
expect(models[0].displayName).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should handle models not in known model list', async () => {
|
|
376
|
+
mockClient.models.list.mockResolvedValue({
|
|
377
|
+
data: [
|
|
378
|
+
{
|
|
379
|
+
id: 'unknown-mistral-model',
|
|
380
|
+
max_context_length: 8192,
|
|
381
|
+
capabilities: {
|
|
382
|
+
function_calling: false,
|
|
383
|
+
vision: false,
|
|
384
|
+
},
|
|
385
|
+
description: 'Unknown model',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const models = await params.models({ client: mockClient as any });
|
|
391
|
+
|
|
392
|
+
expect(models).toHaveLength(1);
|
|
393
|
+
expect(models[0]).toMatchObject({
|
|
394
|
+
id: 'unknown-mistral-model',
|
|
395
|
+
contextWindowTokens: 8192,
|
|
396
|
+
functionCall: false,
|
|
397
|
+
vision: false,
|
|
398
|
+
displayName: undefined,
|
|
399
|
+
enabled: false,
|
|
400
|
+
description: 'Unknown model',
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should handle case-insensitive model matching', async () => {
|
|
405
|
+
mockClient.models.list.mockResolvedValue({
|
|
406
|
+
data: [
|
|
407
|
+
{
|
|
408
|
+
id: 'MISTRAL-LARGE-LATEST',
|
|
409
|
+
max_context_length: 128000,
|
|
410
|
+
capabilities: {
|
|
411
|
+
function_calling: true,
|
|
412
|
+
vision: false,
|
|
413
|
+
},
|
|
414
|
+
description: 'Large model',
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const models = await params.models({ client: mockClient as any });
|
|
420
|
+
|
|
421
|
+
expect(models).toHaveLength(1);
|
|
422
|
+
expect(models[0].id).toBe('MISTRAL-LARGE-LATEST');
|
|
423
|
+
// Should match with lowercase in LOBE_DEFAULT_MODEL_LIST
|
|
424
|
+
expect(models[0].displayName).toBeDefined();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should detect reasoning capability from known model list', async () => {
|
|
428
|
+
mockClient.models.list.mockResolvedValue({
|
|
429
|
+
data: [
|
|
430
|
+
{
|
|
431
|
+
id: 'mistral-large-latest',
|
|
432
|
+
max_context_length: 128000,
|
|
433
|
+
capabilities: {
|
|
434
|
+
function_calling: true,
|
|
435
|
+
vision: false,
|
|
436
|
+
},
|
|
437
|
+
description: 'Latest large model',
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const models = await params.models({ client: mockClient as any });
|
|
443
|
+
|
|
444
|
+
expect(models).toHaveLength(1);
|
|
445
|
+
expect(models[0]).toHaveProperty('reasoning');
|
|
446
|
+
// reasoning should be false unless specified in LOBE_DEFAULT_MODEL_LIST
|
|
447
|
+
expect(models[0].reasoning).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should handle empty model list', async () => {
|
|
451
|
+
mockClient.models.list.mockResolvedValue({
|
|
452
|
+
data: [],
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const models = await params.models({ client: mockClient as any });
|
|
456
|
+
|
|
457
|
+
expect(models).toEqual([]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should combine multiple capabilities correctly', async () => {
|
|
461
|
+
mockClient.models.list.mockResolvedValue({
|
|
462
|
+
data: [
|
|
463
|
+
{
|
|
464
|
+
id: 'pixtral-large-latest',
|
|
465
|
+
max_context_length: 128000,
|
|
466
|
+
capabilities: {
|
|
467
|
+
function_calling: true,
|
|
468
|
+
vision: true,
|
|
469
|
+
},
|
|
470
|
+
description: 'Multimodal large model',
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const models = await params.models({ client: mockClient as any });
|
|
476
|
+
|
|
477
|
+
expect(models).toHaveLength(1);
|
|
478
|
+
expect(models[0]).toMatchObject({
|
|
479
|
+
id: 'pixtral-large-latest',
|
|
480
|
+
contextWindowTokens: 128000,
|
|
481
|
+
functionCall: true,
|
|
482
|
+
vision: true,
|
|
483
|
+
description: 'Multimodal large model',
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should preserve context window tokens accurately', async () => {
|
|
488
|
+
mockClient.models.list.mockResolvedValue({
|
|
489
|
+
data: [
|
|
490
|
+
{
|
|
491
|
+
id: 'model-1',
|
|
492
|
+
max_context_length: 4096,
|
|
493
|
+
capabilities: { function_calling: false, vision: false },
|
|
494
|
+
description: 'Small context',
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: 'model-2',
|
|
498
|
+
max_context_length: 32768,
|
|
499
|
+
capabilities: { function_calling: true, vision: false },
|
|
500
|
+
description: 'Medium context',
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
id: 'model-3',
|
|
504
|
+
max_context_length: 200000,
|
|
505
|
+
capabilities: { function_calling: true, vision: true },
|
|
506
|
+
description: 'Large context',
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const models = await params.models({ client: mockClient as any });
|
|
512
|
+
|
|
513
|
+
expect(models).toHaveLength(3);
|
|
514
|
+
expect(models[0].contextWindowTokens).toBe(4096);
|
|
515
|
+
expect(models[1].contextWindowTokens).toBe(32768);
|
|
516
|
+
expect(models[2].contextWindowTokens).toBe(200000);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should handle mixed capability models', async () => {
|
|
520
|
+
mockClient.models.list.mockResolvedValue({
|
|
521
|
+
data: [
|
|
522
|
+
{
|
|
523
|
+
id: 'text-only-model',
|
|
524
|
+
max_context_length: 8192,
|
|
525
|
+
capabilities: {
|
|
526
|
+
function_calling: true,
|
|
527
|
+
vision: false,
|
|
528
|
+
},
|
|
529
|
+
description: 'Text only',
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
id: 'vision-model',
|
|
533
|
+
max_context_length: 16384,
|
|
534
|
+
capabilities: {
|
|
535
|
+
function_calling: false,
|
|
536
|
+
vision: true,
|
|
537
|
+
},
|
|
538
|
+
description: 'Vision only',
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
id: 'basic-model',
|
|
542
|
+
max_context_length: 4096,
|
|
543
|
+
capabilities: {
|
|
544
|
+
function_calling: false,
|
|
545
|
+
vision: false,
|
|
546
|
+
},
|
|
547
|
+
description: 'Basic model',
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const models = await params.models({ client: mockClient as any });
|
|
553
|
+
|
|
554
|
+
expect(models).toHaveLength(3);
|
|
555
|
+
expect(models[0].functionCall).toBe(true);
|
|
556
|
+
expect(models[0].vision).toBe(false);
|
|
557
|
+
expect(models[1].functionCall).toBe(false);
|
|
558
|
+
expect(models[1].vision).toBe(true);
|
|
559
|
+
expect(models[2].functionCall).toBe(false);
|
|
560
|
+
expect(models[2].vision).toBe(false);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should handle all properties from API response', async () => {
|
|
564
|
+
mockClient.models.list.mockResolvedValue({
|
|
565
|
+
data: [
|
|
566
|
+
{
|
|
567
|
+
id: 'complete-model',
|
|
568
|
+
max_context_length: 100000,
|
|
569
|
+
capabilities: {
|
|
570
|
+
function_calling: true,
|
|
571
|
+
vision: true,
|
|
572
|
+
},
|
|
573
|
+
description: 'A complete test model',
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const models = await params.models({ client: mockClient as any });
|
|
579
|
+
|
|
580
|
+
expect(models).toHaveLength(1);
|
|
581
|
+
const model = models[0];
|
|
582
|
+
expect(model).toHaveProperty('id');
|
|
583
|
+
expect(model).toHaveProperty('contextWindowTokens');
|
|
584
|
+
expect(model).toHaveProperty('functionCall');
|
|
585
|
+
expect(model).toHaveProperty('vision');
|
|
586
|
+
expect(model).toHaveProperty('description');
|
|
587
|
+
expect(model).toHaveProperty('displayName');
|
|
588
|
+
expect(model).toHaveProperty('enabled');
|
|
589
|
+
expect(model).toHaveProperty('reasoning');
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should handle API errors gracefully', async () => {
|
|
593
|
+
mockClient.models.list.mockRejectedValue(new Error('Network error'));
|
|
594
|
+
|
|
595
|
+
await expect(params.models({ client: mockClient as any })).rejects.toThrow('Network error');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should handle null or undefined data gracefully', async () => {
|
|
599
|
+
mockClient.models.list.mockResolvedValue({
|
|
600
|
+
data: null as any,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
await expect(params.models({ client: mockClient as any })).rejects.toThrow();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should handle model with missing capabilities', async () => {
|
|
607
|
+
mockClient.models.list.mockResolvedValue({
|
|
608
|
+
data: [
|
|
609
|
+
{
|
|
610
|
+
id: 'incomplete-model',
|
|
611
|
+
max_context_length: 8192,
|
|
612
|
+
capabilities: {} as any,
|
|
613
|
+
description: 'Model with missing capabilities',
|
|
614
|
+
},
|
|
615
|
+
],
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const models = await params.models({ client: mockClient as any });
|
|
619
|
+
|
|
620
|
+
expect(models).toHaveLength(1);
|
|
621
|
+
expect(models[0].functionCall).toBeUndefined();
|
|
622
|
+
expect(models[0].vision).toBeUndefined();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('should handle model with null capabilities', async () => {
|
|
626
|
+
mockClient.models.list.mockResolvedValue({
|
|
627
|
+
data: [
|
|
628
|
+
{
|
|
629
|
+
id: 'null-caps-model',
|
|
630
|
+
max_context_length: 8192,
|
|
631
|
+
capabilities: null as any,
|
|
632
|
+
description: 'Model with null capabilities',
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
await expect(params.models({ client: mockClient as any })).rejects.toThrow();
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it('should filter out falsy values', async () => {
|
|
641
|
+
mockClient.models.list.mockResolvedValue({
|
|
642
|
+
data: [
|
|
643
|
+
{
|
|
644
|
+
id: 'valid-model',
|
|
645
|
+
max_context_length: 8192,
|
|
646
|
+
capabilities: {
|
|
647
|
+
function_calling: false,
|
|
648
|
+
vision: false,
|
|
649
|
+
},
|
|
650
|
+
description: 'Valid model',
|
|
651
|
+
},
|
|
652
|
+
null,
|
|
653
|
+
undefined,
|
|
654
|
+
].filter(Boolean),
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const models = await params.models({ client: mockClient as any });
|
|
658
|
+
|
|
659
|
+
expect(models).toHaveLength(1);
|
|
660
|
+
expect(models[0].id).toBe('valid-model');
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
describe('handlePayload - edge cases', () => {
|
|
665
|
+
it('should handle payload with frequency_penalty', async () => {
|
|
666
|
+
await instance.chat({
|
|
667
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
668
|
+
model: 'open-mistral-7b',
|
|
669
|
+
frequency_penalty: 0.5,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
673
|
+
// frequency_penalty is not part of the parameters handled by resolveParameters in mistral
|
|
674
|
+
expect(callArgs).not.toHaveProperty('frequency_penalty');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('should handle payload with presence_penalty', async () => {
|
|
678
|
+
await instance.chat({
|
|
679
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
680
|
+
model: 'open-mistral-7b',
|
|
681
|
+
presence_penalty: 0.5,
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
685
|
+
// presence_penalty is not part of the parameters handled by resolveParameters in mistral
|
|
686
|
+
expect(callArgs).not.toHaveProperty('presence_penalty');
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('should handle empty messages array', async () => {
|
|
690
|
+
await instance.chat({
|
|
691
|
+
messages: [],
|
|
692
|
+
model: 'open-mistral-7b',
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
696
|
+
expect.objectContaining({
|
|
697
|
+
messages: [],
|
|
698
|
+
}),
|
|
699
|
+
expect.anything(),
|
|
700
|
+
);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should handle tool_choice parameter', async () => {
|
|
704
|
+
const tools = [
|
|
705
|
+
{
|
|
706
|
+
type: 'function' as const,
|
|
707
|
+
function: { name: 'test_tool', description: 'A test tool', parameters: {} },
|
|
708
|
+
},
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
await instance.chat({
|
|
712
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
713
|
+
model: 'open-mistral-7b',
|
|
714
|
+
tools,
|
|
715
|
+
tool_choice: 'auto',
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
719
|
+
expect(callArgs.tools).toEqual(tools);
|
|
720
|
+
// tool_choice is not explicitly handled in mistral handlePayload
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it('should handle response_format parameter', async () => {
|
|
724
|
+
await instance.chat({
|
|
725
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
726
|
+
model: 'open-mistral-7b',
|
|
727
|
+
response_format: { type: 'json_object' },
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
|
|
731
|
+
// response_format is not explicitly handled in mistral handlePayload
|
|
732
|
+
expect(callArgs).not.toHaveProperty('response_format');
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('should handle very small temperature values', async () => {
|
|
736
|
+
await instance.chat({
|
|
737
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
738
|
+
model: 'open-mistral-7b',
|
|
739
|
+
temperature: 0.01,
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
743
|
+
expect.objectContaining({
|
|
744
|
+
temperature: 0.005, // 0.01 / 2
|
|
745
|
+
}),
|
|
746
|
+
expect.anything(),
|
|
747
|
+
);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('should handle decimal temperature values', async () => {
|
|
751
|
+
await instance.chat({
|
|
752
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
753
|
+
model: 'open-mistral-7b',
|
|
754
|
+
temperature: 1.5,
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
758
|
+
expect.objectContaining({
|
|
759
|
+
temperature: 0.75, // 1.5 / 2
|
|
760
|
+
}),
|
|
761
|
+
expect.anything(),
|
|
762
|
+
);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should handle very small top_p values', async () => {
|
|
766
|
+
await instance.chat({
|
|
767
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
768
|
+
model: 'open-mistral-7b',
|
|
769
|
+
top_p: 0.01,
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
773
|
+
expect.objectContaining({
|
|
774
|
+
top_p: 0.01,
|
|
775
|
+
}),
|
|
776
|
+
expect.anything(),
|
|
777
|
+
);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should handle large max_tokens values', async () => {
|
|
781
|
+
await instance.chat({
|
|
782
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
783
|
+
model: 'open-mistral-7b',
|
|
784
|
+
max_tokens: 100000,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
788
|
+
expect.objectContaining({
|
|
789
|
+
max_tokens: 100000,
|
|
790
|
+
}),
|
|
791
|
+
expect.anything(),
|
|
792
|
+
);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should handle complex message with multiple roles', async () => {
|
|
796
|
+
const messages = [
|
|
797
|
+
{ content: 'You are a helpful assistant', role: 'system' as const },
|
|
798
|
+
{ content: 'Hello', role: 'user' as const },
|
|
799
|
+
{ content: 'Hi! How can I help?', role: 'assistant' as const },
|
|
800
|
+
{ content: 'What is 2+2?', role: 'user' as const },
|
|
801
|
+
{ content: '2+2 equals 4', role: 'assistant' as const },
|
|
802
|
+
{ content: 'Thanks', role: 'user' as const },
|
|
803
|
+
];
|
|
804
|
+
|
|
805
|
+
await instance.chat({
|
|
806
|
+
messages,
|
|
807
|
+
model: 'open-mistral-7b',
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
811
|
+
expect.objectContaining({
|
|
812
|
+
messages,
|
|
813
|
+
}),
|
|
814
|
+
expect.anything(),
|
|
815
|
+
);
|
|
816
|
+
});
|
|
65
817
|
});
|
|
66
818
|
});
|