@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,313 +1,985 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { LobeOpenAICompatibleRuntime } from '@lobechat/model-runtime';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { testProvider } from '../../providerTestUtils';
|
|
6
|
+
import { LobeZhipuAI, params } from './index';
|
|
7
|
+
|
|
8
|
+
testProvider({
|
|
9
|
+
provider: 'zhipu',
|
|
10
|
+
defaultBaseURL: 'https://open.bigmodel.cn/api/paas/v4',
|
|
11
|
+
chatModel: 'glm-4',
|
|
12
|
+
Runtime: LobeZhipuAI,
|
|
13
|
+
chatDebugEnv: 'DEBUG_ZHIPU_CHAT_COMPLETION',
|
|
14
|
+
test: {
|
|
15
|
+
skipAPICall: true, // Skip because Zhipu has custom handlePayload that normalizes temperature
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Mock the console.error to avoid polluting test output
|
|
20
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
21
|
+
|
|
22
|
+
let instance: LobeOpenAICompatibleRuntime;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
instance = new LobeZhipuAI({ apiKey: 'test' });
|
|
26
|
+
|
|
27
|
+
// Mock chat.completions.create
|
|
28
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
|
|
29
|
+
new ReadableStream() as any,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
5
32
|
|
|
6
|
-
|
|
7
|
-
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
8
36
|
|
|
9
|
-
|
|
10
|
-
|
|
37
|
+
describe('LobeZhipuAI - custom features', () => {
|
|
38
|
+
describe('Debug Configuration', () => {
|
|
39
|
+
it('should disable debug by default', () => {
|
|
40
|
+
delete process.env.DEBUG_ZHIPU_CHAT_COMPLETION;
|
|
41
|
+
const result = params.debug.chatCompletion();
|
|
42
|
+
expect(result).toBe(false);
|
|
43
|
+
});
|
|
11
44
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
45
|
+
it('should enable debug when env is set', () => {
|
|
46
|
+
process.env.DEBUG_ZHIPU_CHAT_COMPLETION = '1';
|
|
47
|
+
const result = params.debug.chatCompletion();
|
|
48
|
+
expect(result).toBe(true);
|
|
49
|
+
delete process.env.DEBUG_ZHIPU_CHAT_COMPLETION;
|
|
50
|
+
});
|
|
15
51
|
});
|
|
16
52
|
|
|
17
|
-
describe('
|
|
18
|
-
|
|
53
|
+
describe('handlePayload', () => {
|
|
54
|
+
describe('Web Search Feature', () => {
|
|
55
|
+
it('should add web_search tool when enabledSearch is true', async () => {
|
|
56
|
+
await instance.chat({
|
|
57
|
+
enabledSearch: true,
|
|
58
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
59
|
+
model: 'glm-4',
|
|
60
|
+
temperature: 0,
|
|
61
|
+
});
|
|
19
62
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
63
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
tools: expect.arrayContaining([
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
type: 'web_search',
|
|
68
|
+
web_search: expect.objectContaining({
|
|
69
|
+
enable: true,
|
|
70
|
+
result_sequence: 'before',
|
|
71
|
+
search_engine: 'search_std',
|
|
72
|
+
search_result: true,
|
|
73
|
+
}),
|
|
74
|
+
}),
|
|
75
|
+
]),
|
|
76
|
+
}),
|
|
77
|
+
expect.anything(),
|
|
78
|
+
);
|
|
23
79
|
});
|
|
24
80
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
new ReadableStream() as any,
|
|
28
|
-
);
|
|
29
|
-
});
|
|
81
|
+
it('should use custom search engine from env', async () => {
|
|
82
|
+
process.env.ZHIPU_SEARCH_ENGINE = 'search_pro';
|
|
30
83
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
84
|
+
const payload = params.chatCompletion.handlePayload({
|
|
85
|
+
enabledSearch: true,
|
|
86
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
87
|
+
model: 'glm-4',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(payload.tools).toContainEqual(
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
type: 'web_search',
|
|
93
|
+
web_search: expect.objectContaining({
|
|
94
|
+
search_engine: 'search_pro',
|
|
95
|
+
}),
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
delete process.env.ZHIPU_SEARCH_ENGINE;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should merge web_search with existing tools', async () => {
|
|
103
|
+
const existingTool = {
|
|
104
|
+
type: 'function' as const,
|
|
105
|
+
function: { name: 'test_tool', parameters: {} },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
await instance.chat({
|
|
109
|
+
enabledSearch: true,
|
|
110
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
111
|
+
model: 'glm-4',
|
|
112
|
+
temperature: 0,
|
|
113
|
+
tools: [existingTool],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
tools: expect.arrayContaining([
|
|
119
|
+
existingTool,
|
|
120
|
+
expect.objectContaining({ type: 'web_search' }),
|
|
121
|
+
]),
|
|
122
|
+
}),
|
|
123
|
+
expect.anything(),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should not add web_search tool when enabledSearch is false', async () => {
|
|
128
|
+
await instance.chat({
|
|
129
|
+
enabledSearch: false,
|
|
130
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
131
|
+
model: 'glm-4',
|
|
132
|
+
temperature: 0,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const callArgs = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
136
|
+
expect(callArgs.tools).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should use existing tools without web_search when enabledSearch is not set', async () => {
|
|
140
|
+
const existingTool = {
|
|
141
|
+
type: 'function' as const,
|
|
142
|
+
function: { name: 'test_tool', parameters: {} },
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
await instance.chat({
|
|
146
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
147
|
+
model: 'glm-4',
|
|
148
|
+
temperature: 0,
|
|
149
|
+
tools: [existingTool],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
tools: [existingTool],
|
|
155
|
+
}),
|
|
156
|
+
expect.anything(),
|
|
157
|
+
);
|
|
36
158
|
});
|
|
37
|
-
expect(result).toBeInstanceOf(Response);
|
|
38
159
|
});
|
|
39
160
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
|
|
55
|
-
],
|
|
56
|
-
});
|
|
57
|
-
controller.close();
|
|
58
|
-
},
|
|
59
|
-
}) as any,
|
|
161
|
+
describe('Model-specific max_tokens constraints', () => {
|
|
162
|
+
it('should limit max_tokens to 1024 for glm-4v models', async () => {
|
|
163
|
+
await instance.chat({
|
|
164
|
+
max_tokens: 2000,
|
|
165
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
166
|
+
model: 'glm-4v',
|
|
167
|
+
temperature: 0.5,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
max_tokens: 1024,
|
|
173
|
+
}),
|
|
174
|
+
expect.anything(),
|
|
60
175
|
);
|
|
176
|
+
});
|
|
61
177
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
178
|
+
it('should limit max_tokens to 15300 for glm-zero-preview model', async () => {
|
|
179
|
+
await instance.chat({
|
|
180
|
+
max_tokens: 20000,
|
|
181
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
182
|
+
model: 'glm-zero-preview',
|
|
183
|
+
temperature: 0.5,
|
|
184
|
+
});
|
|
68
185
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
186
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
187
|
+
expect.objectContaining({
|
|
188
|
+
max_tokens: 15_300,
|
|
189
|
+
}),
|
|
190
|
+
expect.anything(),
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should allow max_tokens lower than constraint for glm-4v', async () => {
|
|
195
|
+
await instance.chat({
|
|
196
|
+
max_tokens: 512,
|
|
197
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
198
|
+
model: 'glm-4v',
|
|
199
|
+
temperature: 0.5,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
203
|
+
expect.objectContaining({
|
|
204
|
+
max_tokens: 512,
|
|
205
|
+
}),
|
|
206
|
+
expect.anything(),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should not limit max_tokens for other models', async () => {
|
|
211
|
+
await instance.chat({
|
|
212
|
+
max_tokens: 4000,
|
|
213
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
214
|
+
model: 'glm-4',
|
|
215
|
+
temperature: 0.5,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
219
|
+
expect.objectContaining({
|
|
220
|
+
max_tokens: 4000,
|
|
221
|
+
}),
|
|
222
|
+
expect.anything(),
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('glm-4-alltools temperature and top_p constraints', () => {
|
|
228
|
+
it('should clamp temperature to [0.01, 0.99] for glm-4-alltools', async () => {
|
|
229
|
+
await instance.chat({
|
|
72
230
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
73
|
-
model: '
|
|
231
|
+
model: 'glm-4-alltools',
|
|
74
232
|
temperature: 0,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
236
|
+
expect.objectContaining({
|
|
237
|
+
temperature: 0.01,
|
|
238
|
+
}),
|
|
239
|
+
expect.anything(),
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should clamp high temperature to 0.99 for glm-4-alltools', async () => {
|
|
244
|
+
await instance.chat({
|
|
245
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
246
|
+
model: 'glm-4-alltools',
|
|
247
|
+
temperature: 2.0, // Will be normalized to 1.0, then clamped to 0.99
|
|
248
|
+
});
|
|
78
249
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
250
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
251
|
+
expect.objectContaining({
|
|
252
|
+
temperature: 0.99,
|
|
253
|
+
}),
|
|
254
|
+
expect.anything(),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should clamp top_p to [0.01, 0.99] for glm-4-alltools', async () => {
|
|
259
|
+
await instance.chat({
|
|
260
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
261
|
+
model: 'glm-4-alltools',
|
|
262
|
+
temperature: 0.5,
|
|
263
|
+
top_p: 0,
|
|
264
|
+
});
|
|
83
265
|
|
|
84
|
-
|
|
85
|
-
|
|
266
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
267
|
+
expect.objectContaining({
|
|
268
|
+
top_p: 0.01,
|
|
269
|
+
}),
|
|
270
|
+
expect.anything(),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
86
273
|
|
|
87
|
-
|
|
88
|
-
|
|
274
|
+
it('should clamp high top_p to 0.99 for glm-4-alltools', async () => {
|
|
275
|
+
await instance.chat({
|
|
276
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
277
|
+
model: 'glm-4-alltools',
|
|
278
|
+
temperature: 0.5,
|
|
279
|
+
top_p: 1,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
283
|
+
expect.objectContaining({
|
|
284
|
+
top_p: 0.99,
|
|
285
|
+
}),
|
|
286
|
+
expect.anything(),
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should normalize and preserve temperature in range for glm-4-alltools', async () => {
|
|
291
|
+
await instance.chat({
|
|
292
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
293
|
+
model: 'glm-4-alltools',
|
|
294
|
+
temperature: 1.0, // Will be normalized to 0.5
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
298
|
+
expect.objectContaining({
|
|
299
|
+
temperature: 0.5,
|
|
300
|
+
}),
|
|
301
|
+
expect.anything(),
|
|
302
|
+
);
|
|
303
|
+
});
|
|
89
304
|
});
|
|
90
305
|
|
|
91
|
-
|
|
92
|
-
|
|
306
|
+
describe('Temperature normalization', () => {
|
|
307
|
+
it('should normalize temperature by dividing by 2', async () => {
|
|
308
|
+
await instance.chat({
|
|
309
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
310
|
+
model: 'glm-4',
|
|
311
|
+
temperature: 1.0,
|
|
312
|
+
});
|
|
93
313
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
temperature: 1.6,
|
|
101
|
-
top_p: 1,
|
|
314
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
315
|
+
expect.objectContaining({
|
|
316
|
+
temperature: 0.5,
|
|
317
|
+
}),
|
|
318
|
+
expect.anything(),
|
|
319
|
+
);
|
|
102
320
|
});
|
|
103
321
|
|
|
104
|
-
|
|
322
|
+
it('should normalize high temperature', async () => {
|
|
323
|
+
await instance.chat({
|
|
324
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
325
|
+
model: 'glm-4',
|
|
326
|
+
temperature: 1.6,
|
|
327
|
+
});
|
|
105
328
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
329
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
330
|
+
expect.objectContaining({
|
|
331
|
+
temperature: 0.8,
|
|
332
|
+
}),
|
|
333
|
+
expect.anything(),
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should handle temperature 0 correctly', async () => {
|
|
338
|
+
await instance.chat({
|
|
339
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
340
|
+
model: 'glm-4',
|
|
341
|
+
temperature: 0,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
345
|
+
expect.objectContaining({
|
|
346
|
+
temperature: 0,
|
|
347
|
+
}),
|
|
348
|
+
expect.anything(),
|
|
349
|
+
);
|
|
350
|
+
});
|
|
109
351
|
});
|
|
110
352
|
|
|
111
|
-
|
|
112
|
-
|
|
353
|
+
describe('Thinking mode for GLM-4.5 models', () => {
|
|
354
|
+
it('should include thinking type for glm-4.5 models', async () => {
|
|
355
|
+
await instance.chat({
|
|
356
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
357
|
+
model: 'glm-4.5',
|
|
358
|
+
temperature: 0.5,
|
|
359
|
+
thinking: { type: 'enabled', budget_tokens: 1000 },
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
363
|
+
expect.objectContaining({
|
|
364
|
+
thinking: { type: 'enabled' },
|
|
365
|
+
}),
|
|
366
|
+
expect.anything(),
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should include thinking for glm-4.5-turbo', async () => {
|
|
371
|
+
await instance.chat({
|
|
372
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
373
|
+
model: 'glm-4.5-turbo',
|
|
374
|
+
temperature: 0.5,
|
|
375
|
+
thinking: { type: 'disabled', budget_tokens: 0 },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
379
|
+
expect.objectContaining({
|
|
380
|
+
thinking: { type: 'disabled' },
|
|
381
|
+
}),
|
|
382
|
+
expect.anything(),
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should not include thinking for non-4.5 models', async () => {
|
|
387
|
+
await instance.chat({
|
|
388
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
389
|
+
model: 'glm-4',
|
|
390
|
+
temperature: 0.5,
|
|
391
|
+
thinking: { type: 'enabled', budget_tokens: 1000 },
|
|
392
|
+
});
|
|
113
393
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
{ content: 'Hello', role: 'user' },
|
|
117
|
-
{ content: [{ type: 'text', text: 'Hello again' }], role: 'user' },
|
|
118
|
-
],
|
|
119
|
-
model: 'glm-4-alltools',
|
|
120
|
-
temperature: 0,
|
|
121
|
-
top_p: 1,
|
|
394
|
+
const callArgs = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
395
|
+
expect(callArgs.thinking).toBeUndefined();
|
|
122
396
|
});
|
|
123
397
|
|
|
124
|
-
|
|
398
|
+
it('should handle undefined thinking gracefully for 4.5 models', async () => {
|
|
399
|
+
await instance.chat({
|
|
400
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
401
|
+
model: 'glm-4.5',
|
|
402
|
+
temperature: 0.5,
|
|
403
|
+
});
|
|
125
404
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
405
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
406
|
+
expect.objectContaining({
|
|
407
|
+
thinking: { type: undefined },
|
|
408
|
+
}),
|
|
409
|
+
expect.anything(),
|
|
410
|
+
);
|
|
411
|
+
});
|
|
129
412
|
});
|
|
130
413
|
|
|
131
|
-
describe('
|
|
132
|
-
it('should
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
414
|
+
describe('Stream parameter', () => {
|
|
415
|
+
it('should always set stream to true', async () => {
|
|
416
|
+
await instance.chat({
|
|
417
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
418
|
+
model: 'glm-4',
|
|
419
|
+
temperature: 0.5,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
423
|
+
expect.objectContaining({
|
|
424
|
+
stream: true,
|
|
425
|
+
}),
|
|
426
|
+
expect.anything(),
|
|
144
427
|
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should override stream parameter to true', async () => {
|
|
431
|
+
await instance.chat({
|
|
432
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
433
|
+
model: 'glm-4',
|
|
434
|
+
stream: false,
|
|
435
|
+
temperature: 0.5,
|
|
436
|
+
});
|
|
145
437
|
|
|
146
|
-
|
|
438
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
439
|
+
expect.objectContaining({
|
|
440
|
+
stream: true,
|
|
441
|
+
}),
|
|
442
|
+
expect.anything(),
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('Preserve other payload properties', () => {
|
|
448
|
+
it('should preserve all other properties', async () => {
|
|
449
|
+
await instance.chat({
|
|
450
|
+
frequency_penalty: 0.5,
|
|
451
|
+
max_tokens: 100,
|
|
452
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
453
|
+
model: 'glm-4',
|
|
454
|
+
presence_penalty: 0.3,
|
|
455
|
+
temperature: 0.5,
|
|
456
|
+
top_p: 0.9,
|
|
457
|
+
});
|
|
147
458
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
459
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
|
460
|
+
expect.objectContaining({
|
|
461
|
+
frequency_penalty: 0.5,
|
|
462
|
+
max_tokens: 100,
|
|
151
463
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
152
|
-
model: '
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
error: { message: 'Bad Request' },
|
|
160
|
-
status: 400,
|
|
161
|
-
},
|
|
162
|
-
errorType: bizErrorType,
|
|
163
|
-
provider: 'zhipu',
|
|
164
|
-
});
|
|
165
|
-
}
|
|
464
|
+
model: 'glm-4',
|
|
465
|
+
presence_penalty: 0.3,
|
|
466
|
+
temperature: 0.25, // Normalized from 0.5
|
|
467
|
+
top_p: 0.9,
|
|
468
|
+
}),
|
|
469
|
+
expect.anything(),
|
|
470
|
+
);
|
|
166
471
|
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
167
474
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
475
|
+
describe('handleStream', () => {
|
|
476
|
+
describe('Tool calls index fixing for GLM-4.5', () => {
|
|
477
|
+
it('should fix negative tool_calls index to positive', async () => {
|
|
478
|
+
const mockStream = new ReadableStream({
|
|
479
|
+
start(controller) {
|
|
480
|
+
controller.enqueue({
|
|
481
|
+
choices: [
|
|
482
|
+
{
|
|
483
|
+
delta: {
|
|
484
|
+
tool_calls: [
|
|
485
|
+
{ index: -1, id: 'call_1', function: { name: 'tool1', arguments: '{}' } },
|
|
486
|
+
{ index: -1, id: 'call_2', function: { name: 'tool2', arguments: '{}' } },
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
finish_reason: null,
|
|
490
|
+
index: 0,
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
created: 1234567890,
|
|
494
|
+
id: 'chatcmpl-123',
|
|
495
|
+
model: 'glm-4.5',
|
|
496
|
+
object: 'chat.completion.chunk',
|
|
497
|
+
});
|
|
498
|
+
controller.close();
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
503
|
+
|
|
504
|
+
const result = await instance.chat({
|
|
505
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
506
|
+
model: 'glm-4.5',
|
|
507
|
+
temperature: 0.5,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Read the stream to trigger the transform
|
|
511
|
+
const reader = result.body?.getReader();
|
|
512
|
+
const chunks = [];
|
|
513
|
+
if (reader) {
|
|
514
|
+
let done = false;
|
|
515
|
+
while (!done) {
|
|
516
|
+
const { value, done: isDone } = await reader.read();
|
|
517
|
+
done = isDone;
|
|
518
|
+
if (value) {
|
|
519
|
+
chunks.push(new TextDecoder().decode(value));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
173
522
|
}
|
|
523
|
+
|
|
524
|
+
// The transform should have fixed the negative indices
|
|
525
|
+
expect(chunks).toBeDefined();
|
|
174
526
|
});
|
|
175
527
|
|
|
176
|
-
it('should
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
528
|
+
it('should preserve positive tool_calls index', async () => {
|
|
529
|
+
const mockStream = new ReadableStream({
|
|
530
|
+
start(controller) {
|
|
531
|
+
controller.enqueue({
|
|
532
|
+
choices: [
|
|
533
|
+
{
|
|
534
|
+
delta: {
|
|
535
|
+
tool_calls: [
|
|
536
|
+
{ index: 0, id: 'call_1', function: { name: 'tool1', arguments: '{}' } },
|
|
537
|
+
{ index: 1, id: 'call_2', function: { name: 'tool2', arguments: '{}' } },
|
|
538
|
+
],
|
|
539
|
+
},
|
|
540
|
+
finish_reason: null,
|
|
541
|
+
index: 0,
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
created: 1234567890,
|
|
545
|
+
id: 'chatcmpl-123',
|
|
546
|
+
model: 'glm-4',
|
|
547
|
+
object: 'chat.completion.chunk',
|
|
548
|
+
});
|
|
549
|
+
controller.close();
|
|
182
550
|
},
|
|
183
|
-
};
|
|
184
|
-
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
|
|
551
|
+
});
|
|
185
552
|
|
|
186
|
-
|
|
553
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
187
554
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
errorType: bizErrorType,
|
|
203
|
-
provider: 'zhipu',
|
|
204
|
-
});
|
|
555
|
+
const result = await instance.chat({
|
|
556
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
557
|
+
model: 'glm-4',
|
|
558
|
+
temperature: 0.5,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Read the stream
|
|
562
|
+
const reader = result.body?.getReader();
|
|
563
|
+
if (reader) {
|
|
564
|
+
let done = false;
|
|
565
|
+
while (!done) {
|
|
566
|
+
const { value, done: isDone } = await reader.read();
|
|
567
|
+
done = isDone;
|
|
568
|
+
}
|
|
205
569
|
}
|
|
570
|
+
|
|
571
|
+
expect(result).toBeDefined();
|
|
206
572
|
});
|
|
207
573
|
|
|
208
|
-
it('should
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
574
|
+
it('should handle chunks without tool_calls', async () => {
|
|
575
|
+
const mockStream = new ReadableStream({
|
|
576
|
+
start(controller) {
|
|
577
|
+
controller.enqueue({
|
|
578
|
+
choices: [
|
|
579
|
+
{
|
|
580
|
+
delta: {
|
|
581
|
+
content: 'Hello',
|
|
582
|
+
},
|
|
583
|
+
finish_reason: null,
|
|
584
|
+
index: 0,
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
created: 1234567890,
|
|
588
|
+
id: 'chatcmpl-123',
|
|
589
|
+
model: 'glm-4',
|
|
590
|
+
object: 'chat.completion.chunk',
|
|
591
|
+
});
|
|
592
|
+
controller.close();
|
|
593
|
+
},
|
|
594
|
+
});
|
|
215
595
|
|
|
216
|
-
instance
|
|
217
|
-
apiKey: 'test',
|
|
596
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
218
597
|
|
|
219
|
-
|
|
598
|
+
const result = await instance.chat({
|
|
599
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
600
|
+
model: 'glm-4',
|
|
601
|
+
temperature: 0.5,
|
|
220
602
|
});
|
|
221
603
|
|
|
222
|
-
|
|
604
|
+
expect(result).toBeInstanceOf(Response);
|
|
605
|
+
});
|
|
223
606
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
607
|
+
it('should handle chunks without choices', async () => {
|
|
608
|
+
const mockStream = new ReadableStream({
|
|
609
|
+
start(controller) {
|
|
610
|
+
controller.enqueue({
|
|
611
|
+
created: 1234567890,
|
|
612
|
+
id: 'chatcmpl-123',
|
|
613
|
+
model: 'glm-4',
|
|
614
|
+
object: 'chat.completion.chunk',
|
|
615
|
+
});
|
|
616
|
+
controller.close();
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
621
|
+
|
|
622
|
+
const result = await instance.chat({
|
|
623
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
624
|
+
model: 'glm-4',
|
|
625
|
+
temperature: 0.5,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
expect(result).toBeInstanceOf(Response);
|
|
242
629
|
});
|
|
243
630
|
|
|
244
|
-
it('should
|
|
245
|
-
|
|
246
|
-
|
|
631
|
+
it('should handle empty tool_calls array', async () => {
|
|
632
|
+
const mockStream = new ReadableStream({
|
|
633
|
+
start(controller) {
|
|
634
|
+
controller.enqueue({
|
|
635
|
+
choices: [
|
|
636
|
+
{
|
|
637
|
+
delta: {
|
|
638
|
+
tool_calls: [],
|
|
639
|
+
},
|
|
640
|
+
finish_reason: null,
|
|
641
|
+
index: 0,
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
created: 1234567890,
|
|
645
|
+
id: 'chatcmpl-123',
|
|
646
|
+
model: 'glm-4',
|
|
647
|
+
object: 'chat.completion.chunk',
|
|
648
|
+
});
|
|
649
|
+
controller.close();
|
|
650
|
+
},
|
|
651
|
+
});
|
|
247
652
|
|
|
248
|
-
|
|
653
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
249
654
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
} catch (e) {
|
|
258
|
-
expect(e).toEqual({
|
|
259
|
-
endpoint: 'https://open.bigmodel.cn/api/paas/v4',
|
|
260
|
-
errorType: 'AgentRuntimeError',
|
|
261
|
-
provider: 'zhipu',
|
|
262
|
-
error: {
|
|
263
|
-
name: genericError.name,
|
|
264
|
-
cause: genericError.cause,
|
|
265
|
-
message: genericError.message,
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
655
|
+
const result = await instance.chat({
|
|
656
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
657
|
+
model: 'glm-4',
|
|
658
|
+
temperature: 0.5,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
expect(result).toBeInstanceOf(Response);
|
|
269
662
|
});
|
|
270
|
-
});
|
|
271
663
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Arrange
|
|
275
|
-
const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流
|
|
276
|
-
const mockDebugStream = new ReadableStream({
|
|
664
|
+
it('should handle mixed tool_calls indices', async () => {
|
|
665
|
+
const mockStream = new ReadableStream({
|
|
277
666
|
start(controller) {
|
|
278
|
-
controller.enqueue(
|
|
667
|
+
controller.enqueue({
|
|
668
|
+
choices: [
|
|
669
|
+
{
|
|
670
|
+
delta: {
|
|
671
|
+
tool_calls: [
|
|
672
|
+
{ index: 0, id: 'call_1', function: { name: 'tool1', arguments: '{}' } },
|
|
673
|
+
{ index: -1, id: 'call_2', function: { name: 'tool2', arguments: '{}' } },
|
|
674
|
+
{ index: 2, id: 'call_3', function: { name: 'tool3', arguments: '{}' } },
|
|
675
|
+
],
|
|
676
|
+
},
|
|
677
|
+
finish_reason: null,
|
|
678
|
+
index: 0,
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
created: 1234567890,
|
|
682
|
+
id: 'chatcmpl-123',
|
|
683
|
+
model: 'glm-4.5',
|
|
684
|
+
object: 'chat.completion.chunk',
|
|
685
|
+
});
|
|
279
686
|
controller.close();
|
|
280
687
|
},
|
|
281
|
-
})
|
|
282
|
-
mockDebugStream.toReadableStream = () => mockDebugStream; // 添加 toReadableStream 方法
|
|
688
|
+
});
|
|
283
689
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
690
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
691
|
+
|
|
692
|
+
const result = await instance.chat({
|
|
693
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
694
|
+
model: 'glm-4.5',
|
|
695
|
+
temperature: 0.5,
|
|
287
696
|
});
|
|
288
697
|
|
|
289
|
-
//
|
|
290
|
-
const
|
|
698
|
+
// Read the stream to trigger the transform
|
|
699
|
+
const reader = result.body?.getReader();
|
|
700
|
+
if (reader) {
|
|
701
|
+
let done = false;
|
|
702
|
+
while (!done) {
|
|
703
|
+
const { value, done: isDone } = await reader.read();
|
|
704
|
+
done = isDone;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
291
707
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());
|
|
708
|
+
expect(result).toBeDefined();
|
|
709
|
+
});
|
|
295
710
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
711
|
+
it('should handle multiple chunks with tool_calls', async () => {
|
|
712
|
+
const mockStream = new ReadableStream({
|
|
713
|
+
start(controller) {
|
|
714
|
+
// First chunk with tool_call
|
|
715
|
+
controller.enqueue({
|
|
716
|
+
choices: [
|
|
717
|
+
{
|
|
718
|
+
delta: {
|
|
719
|
+
tool_calls: [{ index: -1, id: 'call_1', function: { name: 'tool1' } }],
|
|
720
|
+
},
|
|
721
|
+
finish_reason: null,
|
|
722
|
+
index: 0,
|
|
723
|
+
},
|
|
724
|
+
],
|
|
725
|
+
created: 1234567890,
|
|
726
|
+
id: 'chatcmpl-123',
|
|
727
|
+
model: 'glm-4.5',
|
|
728
|
+
object: 'chat.completion.chunk',
|
|
729
|
+
});
|
|
730
|
+
// Second chunk with arguments
|
|
731
|
+
controller.enqueue({
|
|
732
|
+
choices: [
|
|
733
|
+
{
|
|
734
|
+
delta: {
|
|
735
|
+
tool_calls: [{ index: -1, function: { arguments: '{"a":1}' } }],
|
|
736
|
+
},
|
|
737
|
+
finish_reason: null,
|
|
738
|
+
index: 0,
|
|
739
|
+
},
|
|
740
|
+
],
|
|
741
|
+
created: 1234567890,
|
|
742
|
+
id: 'chatcmpl-123',
|
|
743
|
+
model: 'glm-4.5',
|
|
744
|
+
object: 'chat.completion.chunk',
|
|
745
|
+
});
|
|
746
|
+
controller.close();
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
(instance['client'].chat.completions.create as any).mockResolvedValue(mockStream);
|
|
751
|
+
|
|
752
|
+
const result = await instance.chat({
|
|
300
753
|
messages: [{ content: 'Hello', role: 'user' }],
|
|
301
|
-
model: '
|
|
302
|
-
temperature: 0,
|
|
754
|
+
model: 'glm-4.5',
|
|
755
|
+
temperature: 0.5,
|
|
303
756
|
});
|
|
304
757
|
|
|
305
|
-
//
|
|
306
|
-
|
|
758
|
+
// Read the stream
|
|
759
|
+
const reader = result.body?.getReader();
|
|
760
|
+
if (reader) {
|
|
761
|
+
let done = false;
|
|
762
|
+
while (!done) {
|
|
763
|
+
const { value, done: isDone } = await reader.read();
|
|
764
|
+
done = isDone;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
expect(result).toBeDefined();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe('exports', () => {
|
|
774
|
+
it('should export params object', () => {
|
|
775
|
+
expect(params).toBeDefined();
|
|
776
|
+
expect(params.provider).toBe('zhipu');
|
|
777
|
+
expect(params.baseURL).toBe('https://open.bigmodel.cn/api/paas/v4');
|
|
778
|
+
expect(params.chatCompletion).toBeDefined();
|
|
779
|
+
expect(params.debug).toBeDefined();
|
|
780
|
+
expect(params.models).toBeDefined();
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should export chatCompletion configuration with handlePayload', () => {
|
|
784
|
+
expect(params.chatCompletion.handlePayload).toBeDefined();
|
|
785
|
+
expect(typeof params.chatCompletion.handlePayload).toBe('function');
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it('should export chatCompletion configuration with handleStream', () => {
|
|
789
|
+
expect(params.chatCompletion.handleStream).toBeDefined();
|
|
790
|
+
expect(typeof params.chatCompletion.handleStream).toBe('function');
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('should export debug configuration', () => {
|
|
794
|
+
expect(params.debug.chatCompletion).toBeDefined();
|
|
795
|
+
expect(typeof params.debug.chatCompletion).toBe('function');
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it('should export models function', () => {
|
|
799
|
+
expect(params.models).toBeDefined();
|
|
800
|
+
expect(typeof params.models).toBe('function');
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
describe('models', () => {
|
|
805
|
+
const mockFetch = vi.fn();
|
|
806
|
+
const originalFetch = global.fetch;
|
|
807
|
+
|
|
808
|
+
beforeEach(() => {
|
|
809
|
+
global.fetch = mockFetch;
|
|
810
|
+
vi.clearAllMocks();
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
afterEach(() => {
|
|
814
|
+
global.fetch = originalFetch;
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('should fetch and process models with correct headers', async () => {
|
|
818
|
+
mockFetch.mockResolvedValue({
|
|
819
|
+
json: async () => ({
|
|
820
|
+
rows: [
|
|
821
|
+
{
|
|
822
|
+
description: 'GLM-4 model',
|
|
823
|
+
modelCode: 'glm-4',
|
|
824
|
+
modelName: 'GLM-4',
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
description: 'GLM-4V model',
|
|
828
|
+
modelCode: 'glm-4v',
|
|
829
|
+
modelName: 'GLM-4V',
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
}),
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
836
|
+
await params.models({ client: mockClient as any });
|
|
837
|
+
|
|
838
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
839
|
+
'https://open.bigmodel.cn/api/fine-tuning/model_center/list?pageSize=100&pageNum=1',
|
|
840
|
+
{
|
|
841
|
+
headers: {
|
|
842
|
+
'Authorization': 'Bearer test_api_key',
|
|
843
|
+
'Bigmodel-Organization': 'lobehub',
|
|
844
|
+
'Bigmodel-Project': 'lobechat',
|
|
845
|
+
},
|
|
846
|
+
method: 'GET',
|
|
847
|
+
},
|
|
848
|
+
);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('should process model list correctly', async () => {
|
|
852
|
+
mockFetch.mockResolvedValue({
|
|
853
|
+
json: async () => ({
|
|
854
|
+
rows: [
|
|
855
|
+
{
|
|
856
|
+
description: 'GLM-4 model',
|
|
857
|
+
modelCode: 'glm-4',
|
|
858
|
+
modelName: 'GLM-4',
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
}),
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
865
|
+
const models = await params.models({ client: mockClient as any });
|
|
866
|
+
|
|
867
|
+
expect(models).toBeDefined();
|
|
868
|
+
expect(Array.isArray(models)).toBe(true);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it('should transform modelCode to id and modelName to displayName', async () => {
|
|
872
|
+
mockFetch.mockResolvedValue({
|
|
873
|
+
json: async () => ({
|
|
874
|
+
rows: [
|
|
875
|
+
{
|
|
876
|
+
description: 'Test model',
|
|
877
|
+
modelCode: 'test-model-code',
|
|
878
|
+
modelName: 'Test Model Name',
|
|
879
|
+
},
|
|
880
|
+
],
|
|
881
|
+
}),
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
885
|
+
const models = await params.models({ client: mockClient as any });
|
|
886
|
+
|
|
887
|
+
// processModelList will merge with LOBE_DEFAULT_MODEL_LIST
|
|
888
|
+
// Check that fetch was called and data was processed
|
|
889
|
+
expect(mockFetch).toHaveBeenCalled();
|
|
890
|
+
expect(models).toBeDefined();
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it('should handle empty model list', async () => {
|
|
894
|
+
mockFetch.mockResolvedValue({
|
|
895
|
+
json: async () => ({
|
|
896
|
+
rows: [],
|
|
897
|
+
}),
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
901
|
+
const models = await params.models({ client: mockClient as any });
|
|
902
|
+
|
|
903
|
+
expect(models).toEqual([]);
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
it('should handle multiple models', async () => {
|
|
907
|
+
mockFetch.mockResolvedValue({
|
|
908
|
+
json: async () => ({
|
|
909
|
+
rows: [
|
|
910
|
+
{
|
|
911
|
+
description: 'GLM-4 model',
|
|
912
|
+
modelCode: 'glm-4',
|
|
913
|
+
modelName: 'GLM-4',
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
description: 'GLM-4V model',
|
|
917
|
+
modelCode: 'glm-4v',
|
|
918
|
+
modelName: 'GLM-4V',
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
description: 'GLM-4-Air model',
|
|
922
|
+
modelCode: 'glm-4-air',
|
|
923
|
+
modelName: 'GLM-4-Air',
|
|
924
|
+
},
|
|
925
|
+
],
|
|
926
|
+
}),
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
930
|
+
const models = await params.models({ client: mockClient as any });
|
|
931
|
+
|
|
932
|
+
expect(models).toBeDefined();
|
|
933
|
+
expect(Array.isArray(models)).toBe(true);
|
|
934
|
+
});
|
|
307
935
|
|
|
308
|
-
|
|
309
|
-
|
|
936
|
+
it('should include description in model data', async () => {
|
|
937
|
+
mockFetch.mockResolvedValue({
|
|
938
|
+
json: async () => ({
|
|
939
|
+
rows: [
|
|
940
|
+
{
|
|
941
|
+
description: 'This is a test description',
|
|
942
|
+
modelCode: 'glm-4',
|
|
943
|
+
modelName: 'GLM-4',
|
|
944
|
+
},
|
|
945
|
+
],
|
|
946
|
+
}),
|
|
310
947
|
});
|
|
948
|
+
|
|
949
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
950
|
+
await params.models({ client: mockClient as any });
|
|
951
|
+
|
|
952
|
+
// Verify the API was called correctly
|
|
953
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
954
|
+
expect.any(String),
|
|
955
|
+
expect.objectContaining({
|
|
956
|
+
headers: expect.objectContaining({
|
|
957
|
+
Authorization: 'Bearer test_api_key',
|
|
958
|
+
}),
|
|
959
|
+
}),
|
|
960
|
+
);
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
it('should handle API errors gracefully', async () => {
|
|
964
|
+
mockFetch.mockRejectedValue(new Error('API Error'));
|
|
965
|
+
|
|
966
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
967
|
+
|
|
968
|
+
await expect(params.models({ client: mockClient as any })).rejects.toThrow('API Error');
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it('should use correct API endpoint', async () => {
|
|
972
|
+
mockFetch.mockResolvedValue({
|
|
973
|
+
json: async () => ({ rows: [] }),
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const mockClient = { apiKey: 'test_api_key' };
|
|
977
|
+
await params.models({ client: mockClient as any });
|
|
978
|
+
|
|
979
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
980
|
+
'https://open.bigmodel.cn/api/fine-tuning/model_center/list?pageSize=100&pageNum=1',
|
|
981
|
+
expect.anything(),
|
|
982
|
+
);
|
|
311
983
|
});
|
|
312
984
|
});
|
|
313
985
|
});
|