@hopemyl619/deepseek 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +216 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +573 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/src/deepseek-chat-language-model.ts +98 -0
- package/src/deepseek-chat-settings.ts +13 -0
- package/src/deepseek-provider.ts +50 -0
- package/src/index.ts +31 -0
- package/src/types.ts +100 -0
- package/src/utils/chinese-params-preprocessor.ts +161 -0
- package/src/utils/request-transformer.ts +183 -0
- package/src/utils/response-transformer.ts +90 -0
- package/src/utils/stream-parser.ts +176 -0
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hopemyl619/deepseek",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DeepSeek provider for the Vercel AI SDK with Chinese parameter preprocessing and automatic finish_reason fix",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint src",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@ai-sdk/provider": "^1.0.2",
|
|
27
|
+
"@ai-sdk/provider-utils": "^2.0.4"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@ai-sdk/deepseek": "^2.0.11",
|
|
31
|
+
"@types/node": "^20.0.0",
|
|
32
|
+
"eslint": "^8.0.0",
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.0.0",
|
|
35
|
+
"vitest": "^1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"ai": "^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"ai",
|
|
42
|
+
"ai-sdk",
|
|
43
|
+
"deepseek",
|
|
44
|
+
"llm",
|
|
45
|
+
"language-model",
|
|
46
|
+
"streaming",
|
|
47
|
+
"tool-calling"
|
|
48
|
+
],
|
|
49
|
+
"author": "",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/your-username/ai-sdk-deepseek"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/your-username/ai-sdk-deepseek/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/your-username/ai-sdk-deepseek#readme"
|
|
59
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// DeepSeek Chat 模型实现
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
LanguageModelV1,
|
|
5
|
+
LanguageModelV1CallOptions,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import type { DeepSeekChatSettings, DeepSeekResponse } from './types';
|
|
8
|
+
import { validateChatSettings } from './deepseek-chat-settings';
|
|
9
|
+
import { RequestTransformer } from './utils/request-transformer';
|
|
10
|
+
import { ResponseTransformer } from './utils/response-transformer';
|
|
11
|
+
import { StreamParser } from './utils/stream-parser';
|
|
12
|
+
|
|
13
|
+
export class DeepSeekChatLanguageModel implements LanguageModelV1 {
|
|
14
|
+
readonly specificationVersion = 'v1' as const;
|
|
15
|
+
readonly provider = 'deepseek' as const;
|
|
16
|
+
readonly modelId: string;
|
|
17
|
+
readonly settings: Required<DeepSeekChatSettings>;
|
|
18
|
+
readonly defaultObjectGenerationMode: 'json' | 'tool' | undefined = undefined;
|
|
19
|
+
|
|
20
|
+
private baseURL: string;
|
|
21
|
+
private headers: () => Record<string, string>;
|
|
22
|
+
private requestTransformer: RequestTransformer;
|
|
23
|
+
private responseTransformer: ResponseTransformer;
|
|
24
|
+
private streamParser: StreamParser;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
modelId: string,
|
|
28
|
+
settings: DeepSeekChatSettings = {},
|
|
29
|
+
config: {
|
|
30
|
+
baseURL: string;
|
|
31
|
+
headers: () => Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
) {
|
|
34
|
+
this.modelId = modelId;
|
|
35
|
+
this.settings = validateChatSettings(settings);
|
|
36
|
+
this.baseURL = config.baseURL;
|
|
37
|
+
this.headers = config.headers;
|
|
38
|
+
this.requestTransformer = new RequestTransformer();
|
|
39
|
+
this.responseTransformer = new ResponseTransformer();
|
|
40
|
+
this.streamParser = new StreamParser();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async doGenerate(options: LanguageModelV1CallOptions) {
|
|
44
|
+
const requestBody = this.requestTransformer.transform(this.modelId, options);
|
|
45
|
+
|
|
46
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
...this.headers(),
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify(requestBody),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const error = await response.text();
|
|
57
|
+
throw new Error(`DeepSeek API error: ${response.status} - ${error}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const apiResponse = (await response.json()) as DeepSeekResponse;
|
|
61
|
+
return this.responseTransformer.transform(apiResponse);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async doStream(options: LanguageModelV1CallOptions) {
|
|
65
|
+
const requestBody = {
|
|
66
|
+
...this.requestTransformer.transform(this.modelId, options),
|
|
67
|
+
stream: true,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
'Accept': 'text/event-stream',
|
|
75
|
+
...this.headers(),
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(requestBody),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const error = await response.text();
|
|
82
|
+
throw new Error(`DeepSeek API error: ${response.status} - ${error}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!response.body) {
|
|
86
|
+
throw new Error('Response body is null');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
stream: this.streamParser.parse(response.body) as any,
|
|
91
|
+
rawCall: {
|
|
92
|
+
rawPrompt: requestBody as unknown,
|
|
93
|
+
rawSettings: {},
|
|
94
|
+
},
|
|
95
|
+
warnings: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// DeepSeek Chat 模型配置
|
|
2
|
+
|
|
3
|
+
import type { DeepSeekChatSettings } from './types';
|
|
4
|
+
|
|
5
|
+
export function validateChatSettings(settings: DeepSeekChatSettings = {}): Required<DeepSeekChatSettings> {
|
|
6
|
+
return {
|
|
7
|
+
temperature: settings.temperature ?? 0.7,
|
|
8
|
+
topP: settings.topP ?? 1.0,
|
|
9
|
+
maxTokens: settings.maxTokens ?? 4096,
|
|
10
|
+
presencePenalty: settings.presencePenalty ?? 0,
|
|
11
|
+
frequencyPenalty: settings.frequencyPenalty ?? 0,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// DeepSeek Provider 工厂
|
|
2
|
+
|
|
3
|
+
import { loadApiKey, withoutTrailingSlash } from '@ai-sdk/provider-utils';
|
|
4
|
+
import { DeepSeekChatLanguageModel } from './deepseek-chat-language-model';
|
|
5
|
+
import type { DeepSeekProviderSettings, DeepSeekChatSettings } from './types';
|
|
6
|
+
|
|
7
|
+
export function createDeepSeek(options: DeepSeekProviderSettings = {}) {
|
|
8
|
+
const baseURL = withoutTrailingSlash(
|
|
9
|
+
options.baseURL || 'https://api.deepseek.com/v1'
|
|
10
|
+
) ?? 'https://api.deepseek.com/v1';
|
|
11
|
+
|
|
12
|
+
const getHeaders = (): Record<string, string> => {
|
|
13
|
+
const apiKeyResult = loadApiKey({
|
|
14
|
+
apiKey: options.apiKey,
|
|
15
|
+
environmentVariableName: 'DEEPSEEK_API_KEY',
|
|
16
|
+
description: 'DeepSeek API key',
|
|
17
|
+
});
|
|
18
|
+
// loadApiKey 可能返回 undefined,使用空字符串作为默认值并确保类型为 string
|
|
19
|
+
const apiKey = (apiKeyResult ?? '') as string;
|
|
20
|
+
const authHeader: string = 'Bearer ' + apiKey;
|
|
21
|
+
const result: Record<string, string> = {
|
|
22
|
+
Authorization: authHeader,
|
|
23
|
+
};
|
|
24
|
+
if (options.headers) {
|
|
25
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
26
|
+
if (typeof value === 'string') {
|
|
27
|
+
result[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const createChatModel = (
|
|
35
|
+
modelId: string,
|
|
36
|
+
settings: DeepSeekChatSettings = {}
|
|
37
|
+
) => {
|
|
38
|
+
return new DeepSeekChatLanguageModel(modelId, settings, {
|
|
39
|
+
baseURL,
|
|
40
|
+
headers: getHeaders,
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
chat: createChatModel,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 默认导出
|
|
50
|
+
export const deepseek = createDeepSeek();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// 公开导出
|
|
2
|
+
|
|
3
|
+
export { createDeepSeek, deepseek } from './deepseek-provider';
|
|
4
|
+
export { DeepSeekChatLanguageModel } from './deepseek-chat-language-model';
|
|
5
|
+
export { validateChatSettings } from './deepseek-chat-settings';
|
|
6
|
+
export {
|
|
7
|
+
ChineseParamsPreprocessor,
|
|
8
|
+
} from './utils/chinese-params-preprocessor';
|
|
9
|
+
export {
|
|
10
|
+
RequestTransformer,
|
|
11
|
+
} from './utils/request-transformer';
|
|
12
|
+
export {
|
|
13
|
+
ResponseTransformer,
|
|
14
|
+
} from './utils/response-transformer';
|
|
15
|
+
export {
|
|
16
|
+
StreamParser,
|
|
17
|
+
} from './utils/stream-parser';
|
|
18
|
+
|
|
19
|
+
export type {
|
|
20
|
+
DeepSeekProviderSettings,
|
|
21
|
+
DeepSeekChatSettings,
|
|
22
|
+
DeepSeekMessage,
|
|
23
|
+
DeepSeekToolCall,
|
|
24
|
+
DeepSeekTool,
|
|
25
|
+
DeepSeekRequest,
|
|
26
|
+
DeepSeekChoice,
|
|
27
|
+
DeepSeekUsage,
|
|
28
|
+
DeepSeekResponse,
|
|
29
|
+
DeepSeekStreamChoice,
|
|
30
|
+
DeepSeekStreamResponse,
|
|
31
|
+
} from './types';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// DeepSeek Provider 类型定义
|
|
2
|
+
|
|
3
|
+
export interface DeepSeekProviderSettings {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
headers?: Record<string, string | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DeepSeekChatSettings {
|
|
10
|
+
temperature?: number;
|
|
11
|
+
topP?: number;
|
|
12
|
+
maxTokens?: number;
|
|
13
|
+
presencePenalty?: number;
|
|
14
|
+
frequencyPenalty?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeepSeekMessage {
|
|
18
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
19
|
+
content: string | Array<{
|
|
20
|
+
type: 'text' | 'image_url';
|
|
21
|
+
text?: string;
|
|
22
|
+
image_url?: { url: string };
|
|
23
|
+
}>;
|
|
24
|
+
tool_calls?: DeepSeekToolCall[];
|
|
25
|
+
tool_call_id?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DeepSeekToolCall {
|
|
29
|
+
id: string;
|
|
30
|
+
type: 'function';
|
|
31
|
+
function: {
|
|
32
|
+
name: string;
|
|
33
|
+
arguments: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DeepSeekTool {
|
|
38
|
+
type: 'function';
|
|
39
|
+
function: {
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
parameters?: Record<string, any>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DeepSeekRequest {
|
|
47
|
+
model: string;
|
|
48
|
+
messages: DeepSeekMessage[];
|
|
49
|
+
temperature?: number;
|
|
50
|
+
top_p?: number;
|
|
51
|
+
max_tokens?: number;
|
|
52
|
+
presence_penalty?: number;
|
|
53
|
+
frequency_penalty?: number;
|
|
54
|
+
stream?: boolean;
|
|
55
|
+
tools?: DeepSeekTool[];
|
|
56
|
+
tool_choice?: 'auto' | 'none' | 'required' | string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DeepSeekChoice {
|
|
60
|
+
index: number;
|
|
61
|
+
message: {
|
|
62
|
+
role: string;
|
|
63
|
+
content: string | null;
|
|
64
|
+
tool_calls?: DeepSeekToolCall[];
|
|
65
|
+
};
|
|
66
|
+
finish_reason: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface DeepSeekUsage {
|
|
70
|
+
prompt_tokens: number;
|
|
71
|
+
completion_tokens: number;
|
|
72
|
+
total_tokens: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DeepSeekResponse {
|
|
76
|
+
id: string;
|
|
77
|
+
object: string;
|
|
78
|
+
created: number;
|
|
79
|
+
model: string;
|
|
80
|
+
choices: DeepSeekChoice[];
|
|
81
|
+
usage: DeepSeekUsage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface DeepSeekStreamChoice {
|
|
85
|
+
index: number;
|
|
86
|
+
delta: {
|
|
87
|
+
role?: string;
|
|
88
|
+
content?: string;
|
|
89
|
+
tool_calls?: DeepSeekToolCall[];
|
|
90
|
+
};
|
|
91
|
+
finish_reason?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface DeepSeekStreamResponse {
|
|
95
|
+
id: string;
|
|
96
|
+
object: string;
|
|
97
|
+
created: number;
|
|
98
|
+
model: string;
|
|
99
|
+
choices: DeepSeekStreamChoice[];
|
|
100
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// 中文参数预处理器
|
|
2
|
+
|
|
3
|
+
import type { DeepSeekRequest } from '../types';
|
|
4
|
+
|
|
5
|
+
export class ChineseParamsPreprocessor {
|
|
6
|
+
private cityMap: Record<string, string> = {
|
|
7
|
+
// 中国城市
|
|
8
|
+
'北京': 'Beijing',
|
|
9
|
+
'上海': 'Shanghai',
|
|
10
|
+
'广州': 'Guangzhou',
|
|
11
|
+
'深圳': 'Shenzhen',
|
|
12
|
+
'杭州': 'Hangzhou',
|
|
13
|
+
'南京': 'Nanjing',
|
|
14
|
+
'成都': 'Chengdu',
|
|
15
|
+
'武汉': 'Wuhan',
|
|
16
|
+
'西安': 'Xi\'an',
|
|
17
|
+
'重庆': 'Chongqing',
|
|
18
|
+
'天津': 'Tianjin',
|
|
19
|
+
'苏州': 'Suzhou',
|
|
20
|
+
'长沙': 'Changsha',
|
|
21
|
+
'青岛': 'Qingdao',
|
|
22
|
+
'大连': 'Dalian',
|
|
23
|
+
'厦门': 'Xiamen',
|
|
24
|
+
'宁波': 'Ningbo',
|
|
25
|
+
'无锡': 'Wuxi',
|
|
26
|
+
'佛山': 'Foshan',
|
|
27
|
+
'东莞': 'Dongguan',
|
|
28
|
+
'合肥': 'Hefei',
|
|
29
|
+
'郑州': 'Zhengzhou',
|
|
30
|
+
'福州': 'Fuzhou',
|
|
31
|
+
'济南': 'Jinan',
|
|
32
|
+
'昆明': 'Kunming',
|
|
33
|
+
'沈阳': 'Shenyang',
|
|
34
|
+
'长春': 'Changchun',
|
|
35
|
+
'哈尔滨': 'Harbin',
|
|
36
|
+
'石家庄': 'Shijiazhuang',
|
|
37
|
+
'太原': 'Taiyuan',
|
|
38
|
+
'南昌': 'Nanchang',
|
|
39
|
+
'南宁': 'Nanning',
|
|
40
|
+
'贵阳': 'Guiyang',
|
|
41
|
+
'兰州': 'Lanzhou',
|
|
42
|
+
'海口': 'Haikou',
|
|
43
|
+
'呼和浩特': 'Hohhot',
|
|
44
|
+
'银川': 'Yinchuan',
|
|
45
|
+
'西宁': 'Xining',
|
|
46
|
+
'乌鲁木齐': 'Urumqi',
|
|
47
|
+
'拉萨': 'Lhasa',
|
|
48
|
+
// 国际城市
|
|
49
|
+
'伦敦': 'London',
|
|
50
|
+
'纽约': 'New York',
|
|
51
|
+
'东京': 'Tokyo',
|
|
52
|
+
'巴黎': 'Paris',
|
|
53
|
+
'柏林': 'Berlin',
|
|
54
|
+
'悉尼': 'Sydney',
|
|
55
|
+
'洛杉矶': 'Los Angeles',
|
|
56
|
+
'多伦多': 'Toronto',
|
|
57
|
+
'新加坡': 'Singapore',
|
|
58
|
+
'香港': 'Hong Kong',
|
|
59
|
+
'台北': 'Taipei',
|
|
60
|
+
'首尔': 'Seoul',
|
|
61
|
+
'曼谷': 'Bangkok',
|
|
62
|
+
'迪拜': 'Dubai',
|
|
63
|
+
'莫斯科': 'Moscow',
|
|
64
|
+
'旧金山': 'San Francisco',
|
|
65
|
+
'波士顿': 'Boston',
|
|
66
|
+
'芝加哥': 'Chicago',
|
|
67
|
+
'华盛顿': 'Washington',
|
|
68
|
+
'休斯顿': 'Houston',
|
|
69
|
+
'费城': 'Philadelphia',
|
|
70
|
+
'凤凰城': 'Phoenix',
|
|
71
|
+
'圣迭戈': 'San Diego',
|
|
72
|
+
'达拉斯': 'Dallas',
|
|
73
|
+
'圣何塞': 'San Jose',
|
|
74
|
+
'奥斯汀': 'Austin',
|
|
75
|
+
'温哥华': 'Vancouver',
|
|
76
|
+
'蒙特利尔': 'Montreal',
|
|
77
|
+
'卡尔加里': 'Calgary',
|
|
78
|
+
'渥太华': 'Ottawa',
|
|
79
|
+
'魁北克': 'Quebec',
|
|
80
|
+
'埃德蒙顿': 'Edmonton',
|
|
81
|
+
'温尼伯': 'Winnipeg',
|
|
82
|
+
'哈利法克斯': 'Halifax',
|
|
83
|
+
'维多利亚': 'Victoria',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
process(request: DeepSeekRequest): DeepSeekRequest {
|
|
87
|
+
const processed = JSON.parse(JSON.stringify(request));
|
|
88
|
+
|
|
89
|
+
// 处理消息内容
|
|
90
|
+
if (processed.messages) {
|
|
91
|
+
processed.messages = this.processMessages(processed.messages);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 处理工具参数
|
|
95
|
+
if (processed.tools) {
|
|
96
|
+
processed.tools = this.processTools(processed.tools);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return processed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private processMessages(messages: any[]): any[] {
|
|
103
|
+
return messages.map(msg => {
|
|
104
|
+
if (msg.content && typeof msg.content === 'string') {
|
|
105
|
+
msg.content = this.replaceCityNames(msg.content);
|
|
106
|
+
} else if (Array.isArray(msg.content)) {
|
|
107
|
+
msg.content = msg.content.map((part: any) => {
|
|
108
|
+
if (part.type === 'text' && part.text) {
|
|
109
|
+
part.text = this.replaceCityNames(part.text);
|
|
110
|
+
}
|
|
111
|
+
return part;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return msg;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private processTools(tools: any[]): any[] {
|
|
119
|
+
return tools.map(tool => {
|
|
120
|
+
if (tool.function?.parameters?.properties) {
|
|
121
|
+
this.processProperties(tool.function.parameters.properties);
|
|
122
|
+
}
|
|
123
|
+
if (tool.function?.description) {
|
|
124
|
+
tool.function.description = this.replaceCityNames(tool.function.description);
|
|
125
|
+
}
|
|
126
|
+
return tool;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private processProperties(properties: any): void {
|
|
131
|
+
for (const key in properties) {
|
|
132
|
+
if (properties[key].description) {
|
|
133
|
+
properties[key].description = this.replaceCityNames(properties[key].description);
|
|
134
|
+
}
|
|
135
|
+
if (properties[key].example) {
|
|
136
|
+
const exampleStr = JSON.stringify(properties[key].example);
|
|
137
|
+
properties[key].example = JSON.parse(this.replaceCityNames(exampleStr));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private replaceCityNames(text: string): string {
|
|
143
|
+
let result = text;
|
|
144
|
+
for (const [chinese, english] of Object.entries(this.cityMap)) {
|
|
145
|
+
// 使用正则表达式替换,避免替换部分匹配的词
|
|
146
|
+
const regex = new RegExp(`\\b${chinese}\\b`, 'g');
|
|
147
|
+
result = result.replace(regex, english);
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 添加自定义城市映射
|
|
153
|
+
addCityMapping(chinese: string, english: string): void {
|
|
154
|
+
this.cityMap[chinese] = english;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 批量添加城市映射
|
|
158
|
+
addCityMappings(mappings: Record<string, string>): void {
|
|
159
|
+
Object.assign(this.cityMap, mappings);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// 请求转换器
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
LanguageModelV1CallOptions,
|
|
5
|
+
LanguageModelV1Prompt,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import type { DeepSeekRequest } from '../types';
|
|
8
|
+
import { ChineseParamsPreprocessor } from './chinese-params-preprocessor';
|
|
9
|
+
|
|
10
|
+
export class RequestTransformer {
|
|
11
|
+
private preprocessor: ChineseParamsPreprocessor;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.preprocessor = new ChineseParamsPreprocessor();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
transform(modelId: string, options: LanguageModelV1CallOptions): DeepSeekRequest {
|
|
18
|
+
const transformed: DeepSeekRequest = {
|
|
19
|
+
model: modelId,
|
|
20
|
+
messages: this.transformMessages(options.prompt),
|
|
21
|
+
stream: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 添加温度参数
|
|
25
|
+
if (options.temperature !== undefined) {
|
|
26
|
+
transformed.temperature = options.temperature;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 添加 top_p 参数
|
|
30
|
+
if (options.topP !== undefined) {
|
|
31
|
+
transformed.top_p = options.topP;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 添加 max_tokens 参数
|
|
35
|
+
if (options.maxTokens !== undefined) {
|
|
36
|
+
transformed.max_tokens = options.maxTokens;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 添加 presence_penalty 参数
|
|
40
|
+
if (options.presencePenalty !== undefined) {
|
|
41
|
+
transformed.presence_penalty = options.presencePenalty;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 添加 frequency_penalty 参数
|
|
45
|
+
if (options.frequencyPenalty !== undefined) {
|
|
46
|
+
transformed.frequency_penalty = options.frequencyPenalty;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 添加工具配置
|
|
50
|
+
const mode = options.mode;
|
|
51
|
+
let toolsArray: Array<any> = [];
|
|
52
|
+
let toolChoice: any;
|
|
53
|
+
|
|
54
|
+
// 只有 'regular' 模式支持工具
|
|
55
|
+
if (mode && mode.type === 'regular') {
|
|
56
|
+
toolsArray = mode.tools ?? [];
|
|
57
|
+
toolChoice = mode.toolChoice;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (toolsArray.length > 0) {
|
|
61
|
+
transformed.tools = this.transformTools(toolsArray);
|
|
62
|
+
transformed.tool_choice = this.transformToolChoice(toolChoice);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 预处理中文参数
|
|
66
|
+
return this.preprocessor.process(transformed);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private transformMessages(messages: LanguageModelV1Prompt): Array<any> {
|
|
70
|
+
return messages.map((msg) => {
|
|
71
|
+
const transformed: any = {
|
|
72
|
+
role: msg.role,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// 处理内容
|
|
76
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
77
|
+
const contentParts: Array<any> = [];
|
|
78
|
+
let hasToolCalls = false;
|
|
79
|
+
let toolCallsArray: Array<any> = [];
|
|
80
|
+
|
|
81
|
+
for (const part of msg.content) {
|
|
82
|
+
// 处理文本内容
|
|
83
|
+
if (part.type === 'text') {
|
|
84
|
+
contentParts.push({ type: 'text', text: part.text });
|
|
85
|
+
}
|
|
86
|
+
// 处理图像内容
|
|
87
|
+
else if (part.type === 'image') {
|
|
88
|
+
// V1 image part has 'image' property, convert to data for DeepSeek API
|
|
89
|
+
const imageData = part.image;
|
|
90
|
+
if (typeof imageData === 'string') {
|
|
91
|
+
contentParts.push({ type: 'image_url', image_url: { url: imageData } });
|
|
92
|
+
} else if (imageData instanceof URL) {
|
|
93
|
+
contentParts.push({ type: 'image_url', image_url: { url: imageData.toString() } });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 处理工具调用 (V1 格式)
|
|
97
|
+
else if (part.type === 'tool-call') {
|
|
98
|
+
hasToolCalls = true;
|
|
99
|
+
toolCallsArray.push({
|
|
100
|
+
id: part.toolCallId,
|
|
101
|
+
type: 'function',
|
|
102
|
+
function: {
|
|
103
|
+
name: part.toolName,
|
|
104
|
+
arguments: typeof part.args === 'string' ? part.args : JSON.stringify(part.args),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// 处理工具结果 (V1 格式)
|
|
109
|
+
else if (part.type === 'tool-result') {
|
|
110
|
+
// Tool results in V1 don't get added to content directly
|
|
111
|
+
// They are already part of the prompt structure
|
|
112
|
+
}
|
|
113
|
+
// 处理推理内容
|
|
114
|
+
else if (part.type === 'reasoning' || part.type === 'redacted-reasoning') {
|
|
115
|
+
// DeepSeek API doesn't support reasoning content directly
|
|
116
|
+
// We'll add it as text if there's a text field
|
|
117
|
+
if ('text' in part && part.text) {
|
|
118
|
+
contentParts.push({ type: 'text', text: part.text });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 处理文件内容
|
|
122
|
+
else if (part.type === 'file') {
|
|
123
|
+
contentParts.push({
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: `[File: ${part.filename || 'unnamed'}]`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// 其他未知类型
|
|
129
|
+
else {
|
|
130
|
+
contentParts.push(part);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 设置内容
|
|
135
|
+
if (contentParts.length > 0) {
|
|
136
|
+
transformed.content = contentParts;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 添加工具调用
|
|
140
|
+
if (hasToolCalls && toolCallsArray.length > 0) {
|
|
141
|
+
transformed.tool_calls = toolCallsArray;
|
|
142
|
+
}
|
|
143
|
+
} else if (typeof msg.content === 'string') {
|
|
144
|
+
// 系统消息等内容为字符串的情况
|
|
145
|
+
transformed.content = msg.content;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return transformed;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private transformTools(tools: Array<any>): Array<any> {
|
|
153
|
+
return tools.map(tool => ({
|
|
154
|
+
type: 'function',
|
|
155
|
+
function: {
|
|
156
|
+
name: tool.name,
|
|
157
|
+
description: tool.description,
|
|
158
|
+
parameters: tool.parameters,
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private transformToolChoice(toolChoice: any): 'auto' | 'none' | 'required' | string {
|
|
164
|
+
if (toolChoice === undefined || toolChoice === 'auto') {
|
|
165
|
+
return 'auto';
|
|
166
|
+
}
|
|
167
|
+
if (toolChoice === 'none') {
|
|
168
|
+
return 'none';
|
|
169
|
+
}
|
|
170
|
+
if (toolChoice === 'required') {
|
|
171
|
+
return 'required';
|
|
172
|
+
}
|
|
173
|
+
if (typeof toolChoice === 'object' && toolChoice.type === 'tool') {
|
|
174
|
+
return JSON.stringify({
|
|
175
|
+
type: 'function',
|
|
176
|
+
function: {
|
|
177
|
+
name: toolChoice.toolName,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return 'auto';
|
|
182
|
+
}
|
|
183
|
+
}
|