@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,16 +1,335 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { ModelProvider } from 'model-bank';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
4
|
|
|
4
5
|
import { testProvider } from '../../providerTestUtils';
|
|
5
|
-
import { LobeStepfunAI } from './index';
|
|
6
|
+
import { LobeStepfunAI, params } from './index';
|
|
6
7
|
|
|
7
8
|
const provider = ModelProvider.Stepfun;
|
|
8
9
|
const defaultBaseURL = 'https://api.stepfun.com/v1';
|
|
9
10
|
|
|
10
11
|
testProvider({
|
|
11
12
|
Runtime: LobeStepfunAI,
|
|
12
|
-
provider,
|
|
13
|
-
defaultBaseURL,
|
|
14
13
|
chatDebugEnv: 'DEBUG_STEPFUN_CHAT_COMPLETION',
|
|
15
14
|
chatModel: 'stepfun',
|
|
15
|
+
defaultBaseURL,
|
|
16
|
+
provider,
|
|
17
|
+
test: {
|
|
18
|
+
skipAPICall: true,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('LobeStepfunAI - custom features', () => {
|
|
23
|
+
let instance: InstanceType<typeof LobeStepfunAI>;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
instance = new LobeStepfunAI({ apiKey: 'test_api_key' });
|
|
27
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
|
|
28
|
+
new ReadableStream() as any,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('params export', () => {
|
|
33
|
+
it('should export params with correct structure', () => {
|
|
34
|
+
expect(params).toBeDefined();
|
|
35
|
+
expect(params.provider).toBe(ModelProvider.Stepfun);
|
|
36
|
+
expect(params.baseURL).toBe('https://api.stepfun.com/v1');
|
|
37
|
+
expect(params.debug).toBeDefined();
|
|
38
|
+
expect(params.chatCompletion).toBeDefined();
|
|
39
|
+
expect(params.models).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should have debug.chatCompletion function', () => {
|
|
43
|
+
expect(typeof params.debug?.chatCompletion).toBe('function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return false when DEBUG_STEPFUN_CHAT_COMPLETION is not set', () => {
|
|
47
|
+
delete process.env.DEBUG_STEPFUN_CHAT_COMPLETION;
|
|
48
|
+
expect(params.debug?.chatCompletion()).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return true when DEBUG_STEPFUN_CHAT_COMPLETION is set to 1', () => {
|
|
52
|
+
process.env.DEBUG_STEPFUN_CHAT_COMPLETION = '1';
|
|
53
|
+
expect(params.debug?.chatCompletion()).toBe(true);
|
|
54
|
+
delete process.env.DEBUG_STEPFUN_CHAT_COMPLETION;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('handlePayload', () => {
|
|
59
|
+
it('should add web_search tool when enabledSearch is true', async () => {
|
|
60
|
+
await instance.chat({
|
|
61
|
+
enabledSearch: true,
|
|
62
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
63
|
+
model: 'step-1-8k',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
67
|
+
expect(calledPayload.tools).toContainEqual({
|
|
68
|
+
function: {
|
|
69
|
+
description: 'use web_search to search information on the internet',
|
|
70
|
+
},
|
|
71
|
+
type: 'web_search',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should merge web_search with existing tools', async () => {
|
|
76
|
+
await instance.chat({
|
|
77
|
+
enabledSearch: true,
|
|
78
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
79
|
+
model: 'step-1-8k',
|
|
80
|
+
tools: [{ function: { name: 'test' }, type: 'function' }],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
84
|
+
expect(calledPayload.tools).toHaveLength(2);
|
|
85
|
+
expect(calledPayload.tools[0]).toEqual({ function: { name: 'test' }, type: 'function' });
|
|
86
|
+
expect(calledPayload.tools[1]).toEqual({
|
|
87
|
+
function: {
|
|
88
|
+
description: 'use web_search to search information on the internet',
|
|
89
|
+
},
|
|
90
|
+
type: 'web_search',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should not add web_search tool when enabledSearch is false', async () => {
|
|
95
|
+
await instance.chat({
|
|
96
|
+
enabledSearch: false,
|
|
97
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
98
|
+
model: 'step-1-8k',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
102
|
+
expect(calledPayload.tools).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should not add web_search tool when enabledSearch is undefined', async () => {
|
|
106
|
+
await instance.chat({
|
|
107
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
108
|
+
model: 'step-1-8k',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
112
|
+
expect(calledPayload.tools).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should preserve existing tools when enabledSearch is false', async () => {
|
|
116
|
+
await instance.chat({
|
|
117
|
+
enabledSearch: false,
|
|
118
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
119
|
+
model: 'step-1-8k',
|
|
120
|
+
tools: [{ function: { name: 'test' }, type: 'function' }],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
124
|
+
expect(calledPayload.tools).toHaveLength(1);
|
|
125
|
+
expect(calledPayload.tools[0]).toEqual({ function: { name: 'test' }, type: 'function' });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should set stream to false when tools are present', async () => {
|
|
129
|
+
await instance.chat({
|
|
130
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
131
|
+
model: 'step-1-8k',
|
|
132
|
+
tools: [{ function: { name: 'test' }, type: 'function' }],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
136
|
+
expect(calledPayload.stream).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should set stream to false when web_search is enabled', async () => {
|
|
140
|
+
await instance.chat({
|
|
141
|
+
enabledSearch: true,
|
|
142
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
143
|
+
model: 'step-1-8k',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
147
|
+
expect(calledPayload.stream).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should set stream to true when no tools are present', async () => {
|
|
151
|
+
await instance.chat({
|
|
152
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
153
|
+
model: 'step-1-8k',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
157
|
+
expect(calledPayload.stream).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should preserve other payload properties', async () => {
|
|
161
|
+
await instance.chat({
|
|
162
|
+
max_tokens: 100,
|
|
163
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
164
|
+
model: 'step-1-8k',
|
|
165
|
+
temperature: 0.7,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
|
|
169
|
+
expect(calledPayload.temperature).toBe(0.7);
|
|
170
|
+
expect(calledPayload.max_tokens).toBe(100);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('models function - keyword detection', () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
vi.clearAllMocks();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should detect function call from step-1- keyword', async () => {
|
|
180
|
+
const mockClient = {
|
|
181
|
+
models: {
|
|
182
|
+
list: vi.fn().mockResolvedValue({
|
|
183
|
+
data: [{ id: 'step-1-8k' }],
|
|
184
|
+
}),
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const models = await params.models!({ client: mockClient as any });
|
|
189
|
+
expect(models).toBeDefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should detect function call from step-1o- keyword', async () => {
|
|
193
|
+
const mockClient = {
|
|
194
|
+
models: {
|
|
195
|
+
list: vi.fn().mockResolvedValue({
|
|
196
|
+
data: [{ id: 'step-1o-8k' }],
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const models = await params.models!({ client: mockClient as any });
|
|
202
|
+
expect(models).toBeDefined();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should detect function call from step-1v- keyword', async () => {
|
|
206
|
+
const mockClient = {
|
|
207
|
+
models: {
|
|
208
|
+
list: vi.fn().mockResolvedValue({
|
|
209
|
+
data: [{ id: 'step-1v-8k' }],
|
|
210
|
+
}),
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const models = await params.models!({ client: mockClient as any });
|
|
215
|
+
expect(models).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should detect function call from step-2- keyword', async () => {
|
|
219
|
+
const mockClient = {
|
|
220
|
+
models: {
|
|
221
|
+
list: vi.fn().mockResolvedValue({
|
|
222
|
+
data: [{ id: 'step-2-16k' }],
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const models = await params.models!({ client: mockClient as any });
|
|
228
|
+
expect(models).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should detect vision from step-1o- keyword', async () => {
|
|
232
|
+
const mockClient = {
|
|
233
|
+
models: {
|
|
234
|
+
list: vi.fn().mockResolvedValue({
|
|
235
|
+
data: [{ id: 'step-1o-8k' }],
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const models = await params.models!({ client: mockClient as any });
|
|
241
|
+
expect(models).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should detect vision from step-r1-v- keyword', async () => {
|
|
245
|
+
const mockClient = {
|
|
246
|
+
models: {
|
|
247
|
+
list: vi.fn().mockResolvedValue({
|
|
248
|
+
data: [{ id: 'step-r1-v-8k' }],
|
|
249
|
+
}),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const models = await params.models!({ client: mockClient as any });
|
|
254
|
+
expect(models).toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should detect vision from step-1v- keyword', async () => {
|
|
258
|
+
const mockClient = {
|
|
259
|
+
models: {
|
|
260
|
+
list: vi.fn().mockResolvedValue({
|
|
261
|
+
data: [{ id: 'step-1v-8k' }],
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const models = await params.models!({ client: mockClient as any });
|
|
267
|
+
expect(models).toBeDefined();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should detect reasoning from step-r1- keyword', async () => {
|
|
271
|
+
const mockClient = {
|
|
272
|
+
models: {
|
|
273
|
+
list: vi.fn().mockResolvedValue({
|
|
274
|
+
data: [{ id: 'step-r1-8k' }],
|
|
275
|
+
}),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const models = await params.models!({ client: mockClient as any });
|
|
280
|
+
expect(models).toBeDefined();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle case-insensitive keyword matching', async () => {
|
|
284
|
+
const mockClient = {
|
|
285
|
+
models: {
|
|
286
|
+
list: vi.fn().mockResolvedValue({
|
|
287
|
+
data: [{ id: 'STEP-1-8K' }, { id: 'Step-R1-Turbo' }],
|
|
288
|
+
}),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const models = await params.models!({ client: mockClient as any });
|
|
293
|
+
expect(models).toBeDefined();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should handle models without matching keywords', async () => {
|
|
297
|
+
const mockClient = {
|
|
298
|
+
models: {
|
|
299
|
+
list: vi.fn().mockResolvedValue({
|
|
300
|
+
data: [{ id: 'other-model' }],
|
|
301
|
+
}),
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const models = await params.models!({ client: mockClient as any });
|
|
306
|
+
expect(models).toBeDefined();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should merge abilities from known models', async () => {
|
|
310
|
+
const mockClient = {
|
|
311
|
+
models: {
|
|
312
|
+
list: vi.fn().mockResolvedValue({
|
|
313
|
+
data: [{ id: 'step-1-8k' }],
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const models = await params.models!({ client: mockClient as any });
|
|
319
|
+
expect(models).toBeDefined();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should handle empty model list', async () => {
|
|
323
|
+
const mockClient = {
|
|
324
|
+
models: {
|
|
325
|
+
list: vi.fn().mockResolvedValue({
|
|
326
|
+
data: [],
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const models = await params.models!({ client: mockClient as any });
|
|
332
|
+
expect(models).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
16
335
|
});
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ChatModelCard } from '@lobechat/types';
|
|
2
2
|
import { ModelProvider } from 'model-bank';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
OpenAICompatibleFactoryOptions,
|
|
6
|
+
createOpenAICompatibleRuntime,
|
|
7
|
+
} from '../../core/openaiCompatibleFactory';
|
|
5
8
|
|
|
6
9
|
export interface StepfunModelCard {
|
|
7
10
|
id: string;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
export const
|
|
13
|
+
export const params = {
|
|
11
14
|
baseURL: 'https://api.stepfun.com/v1',
|
|
12
15
|
chatCompletion: {
|
|
13
16
|
handlePayload: (payload) => {
|
|
@@ -76,4 +79,6 @@ export const LobeStepfunAI = createOpenAICompatibleRuntime({
|
|
|
76
79
|
.filter(Boolean) as ChatModelCard[];
|
|
77
80
|
},
|
|
78
81
|
provider: ModelProvider.Stepfun,
|
|
79
|
-
}
|
|
82
|
+
} satisfies OpenAICompatibleFactoryOptions;
|
|
83
|
+
|
|
84
|
+
export const LobeStepfunAI = createOpenAICompatibleRuntime(params);
|
|
@@ -1,13 +1,192 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { ModelProvider } from 'model-bank';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
4
|
|
|
4
5
|
import { testProvider } from '../../providerTestUtils';
|
|
5
|
-
import { LobeTencentCloudAI } from './index';
|
|
6
|
+
import { LobeTencentCloudAI, TencentCloudModelCard, params } from './index';
|
|
6
7
|
|
|
8
|
+
const provider = ModelProvider.TencentCloud;
|
|
9
|
+
const defaultBaseURL = 'https://api.lkeap.cloud.tencent.com/v1';
|
|
10
|
+
|
|
11
|
+
// Basic provider tests
|
|
7
12
|
testProvider({
|
|
8
13
|
Runtime: LobeTencentCloudAI,
|
|
9
|
-
|
|
10
|
-
defaultBaseURL: 'https://api.lkeap.cloud.tencent.com/v1',
|
|
14
|
+
bizErrorType: 'ProviderBizError',
|
|
11
15
|
chatDebugEnv: 'DEBUG_TENCENT_CLOUD_CHAT_COMPLETION',
|
|
12
16
|
chatModel: 'DeepSeek-R1',
|
|
17
|
+
defaultBaseURL,
|
|
18
|
+
invalidErrorType: 'InvalidProviderAPIKey',
|
|
19
|
+
provider,
|
|
20
|
+
test: {
|
|
21
|
+
skipAPICall: true,
|
|
22
|
+
skipErrorHandle: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Custom feature tests
|
|
27
|
+
describe('LobeTencentCloudAI - custom features', () => {
|
|
28
|
+
describe('params export', () => {
|
|
29
|
+
it('should export params object', () => {
|
|
30
|
+
expect(params).toBeDefined();
|
|
31
|
+
expect(params.baseURL).toBe(defaultBaseURL);
|
|
32
|
+
expect(params.provider).toBe(provider);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should have correct debug configuration', () => {
|
|
36
|
+
expect(params.debug?.chatCompletion).toBeDefined();
|
|
37
|
+
|
|
38
|
+
// Test debug = false
|
|
39
|
+
delete process.env.DEBUG_TENCENT_CLOUD_CHAT_COMPLETION;
|
|
40
|
+
expect(params.debug?.chatCompletion?.()).toBe(false);
|
|
41
|
+
|
|
42
|
+
// Test debug = true
|
|
43
|
+
process.env.DEBUG_TENCENT_CLOUD_CHAT_COMPLETION = '1';
|
|
44
|
+
expect(params.debug?.chatCompletion?.()).toBe(true);
|
|
45
|
+
|
|
46
|
+
// Cleanup
|
|
47
|
+
delete process.env.DEBUG_TENCENT_CLOUD_CHAT_COMPLETION;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('models function', () => {
|
|
52
|
+
let mockClient: any;
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
mockClient = {
|
|
56
|
+
models: {
|
|
57
|
+
list: vi.fn(),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should transform TencentCloud models to ChatModelCard format', async () => {
|
|
63
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'deepseek-r1' }, { id: 'gpt-4o-mini' }];
|
|
64
|
+
|
|
65
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
66
|
+
|
|
67
|
+
const result = await params.models!({ client: mockClient });
|
|
68
|
+
|
|
69
|
+
expect(result).toHaveLength(2);
|
|
70
|
+
expect(result[0]).toMatchObject({
|
|
71
|
+
id: 'deepseek-r1',
|
|
72
|
+
reasoning: true, // Contains 'deepseek-r1' keyword
|
|
73
|
+
});
|
|
74
|
+
expect(result[1]).toMatchObject({
|
|
75
|
+
id: 'gpt-4o-mini',
|
|
76
|
+
reasoning: false,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should detect reasoning models by deepseek-r1 keyword', async () => {
|
|
81
|
+
const mockModels: TencentCloudModelCard[] = [
|
|
82
|
+
{ id: 'deepseek-r1-distill' },
|
|
83
|
+
{ id: 'deepseek-r1-pro' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
87
|
+
|
|
88
|
+
const result = await params.models!({ client: mockClient });
|
|
89
|
+
|
|
90
|
+
expect(result[0].reasoning).toBe(true);
|
|
91
|
+
expect(result[1].reasoning).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should use case-insensitive matching for deepseek-r1 keyword', async () => {
|
|
95
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'DEEPSEEK-R1-UPPER' }];
|
|
96
|
+
|
|
97
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
98
|
+
|
|
99
|
+
const result = await params.models!({ client: mockClient });
|
|
100
|
+
|
|
101
|
+
expect(result[0].reasoning).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should handle models not in LOBE_DEFAULT_MODEL_LIST', async () => {
|
|
105
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'unknown-custom-model' }];
|
|
106
|
+
|
|
107
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
108
|
+
|
|
109
|
+
const result = await params.models!({ client: mockClient });
|
|
110
|
+
|
|
111
|
+
expect(result[0]).toMatchObject({
|
|
112
|
+
contextWindowTokens: undefined,
|
|
113
|
+
displayName: undefined,
|
|
114
|
+
enabled: false,
|
|
115
|
+
functionCall: false,
|
|
116
|
+
id: 'unknown-custom-model',
|
|
117
|
+
reasoning: false,
|
|
118
|
+
vision: false,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle empty model list', async () => {
|
|
123
|
+
mockClient.models.list.mockResolvedValue({ data: [] });
|
|
124
|
+
|
|
125
|
+
const result = await params.models!({ client: mockClient });
|
|
126
|
+
|
|
127
|
+
expect(result).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should get abilities from knownModel in LOBE_DEFAULT_MODEL_LIST', async () => {
|
|
131
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'gpt-4o' }];
|
|
132
|
+
|
|
133
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
134
|
+
|
|
135
|
+
const result = await params.models!({ client: mockClient });
|
|
136
|
+
|
|
137
|
+
// Should get all abilities from knownModel if exists
|
|
138
|
+
expect(result[0]).toHaveProperty('functionCall');
|
|
139
|
+
expect(result[0]).toHaveProperty('vision');
|
|
140
|
+
expect(result[0]).toHaveProperty('reasoning');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should merge reasoning from keyword and knownModel abilities', async () => {
|
|
144
|
+
const mockModels: TencentCloudModelCard[] = [
|
|
145
|
+
{ id: 'deepseek-r1' }, // Has keyword
|
|
146
|
+
{ id: 'regular-model' }, // No keyword
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
150
|
+
|
|
151
|
+
const result = await params.models!({ client: mockClient });
|
|
152
|
+
|
|
153
|
+
// First model should have reasoning from keyword
|
|
154
|
+
expect(result[0].reasoning).toBe(true);
|
|
155
|
+
|
|
156
|
+
// Second model should not have reasoning (no keyword, not in default list with reasoning)
|
|
157
|
+
expect(result[1].reasoning).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should fallback to undefined for contextWindowTokens when not in known models', async () => {
|
|
161
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'unknown-model' }];
|
|
162
|
+
|
|
163
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
164
|
+
|
|
165
|
+
const result = await params.models!({ client: mockClient });
|
|
166
|
+
|
|
167
|
+
expect(result[0].contextWindowTokens).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should fallback to false for enabled when not in known models', async () => {
|
|
171
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'unknown-model' }];
|
|
172
|
+
|
|
173
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
174
|
+
|
|
175
|
+
const result = await params.models!({ client: mockClient });
|
|
176
|
+
|
|
177
|
+
expect(result[0].enabled).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle models without reasoning keyword', async () => {
|
|
181
|
+
const mockModels: TencentCloudModelCard[] = [{ id: 'claude-3-opus' }, { id: 'gemini-pro' }];
|
|
182
|
+
|
|
183
|
+
mockClient.models.list.mockResolvedValue({ data: mockModels });
|
|
184
|
+
|
|
185
|
+
const result = await params.models!({ client: mockClient });
|
|
186
|
+
|
|
187
|
+
// Both models should not have reasoning from keyword
|
|
188
|
+
expect(result[0].reasoning).toBe(false);
|
|
189
|
+
expect(result[1].reasoning).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
13
192
|
});
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ChatModelCard } from '@lobechat/types';
|
|
2
2
|
import { ModelProvider } from 'model-bank';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
OpenAICompatibleFactoryOptions,
|
|
6
|
+
createOpenAICompatibleRuntime,
|
|
7
|
+
} from '../../core/openaiCompatibleFactory';
|
|
5
8
|
|
|
6
9
|
export interface TencentCloudModelCard {
|
|
7
10
|
id: string;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
export const
|
|
13
|
+
export const params = {
|
|
11
14
|
baseURL: 'https://api.lkeap.cloud.tencent.com/v1',
|
|
12
15
|
debug: {
|
|
13
16
|
chatCompletion: () => process.env.DEBUG_TENCENT_CLOUD_CHAT_COMPLETION === '1',
|
|
@@ -42,4 +45,6 @@ export const LobeTencentCloudAI = createOpenAICompatibleRuntime({
|
|
|
42
45
|
.filter(Boolean) as ChatModelCard[];
|
|
43
46
|
},
|
|
44
47
|
provider: ModelProvider.TencentCloud,
|
|
45
|
-
}
|
|
48
|
+
} satisfies OpenAICompatibleFactoryOptions;
|
|
49
|
+
|
|
50
|
+
export const LobeTencentCloudAI = createOpenAICompatibleRuntime(params);
|