@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// 响应转换器 - V1 API 兼容版本
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
LanguageModelV1,
|
|
5
|
+
LanguageModelV1FinishReason,
|
|
6
|
+
LanguageModelV1CallWarning,
|
|
7
|
+
LanguageModelV1FunctionToolCall,
|
|
8
|
+
} from '@ai-sdk/provider';
|
|
9
|
+
import type { DeepSeekResponse } from '../types';
|
|
10
|
+
|
|
11
|
+
// V1 API 返回结果类型
|
|
12
|
+
export interface V1GenerateResult {
|
|
13
|
+
text?: string;
|
|
14
|
+
reasoning?: string;
|
|
15
|
+
usage: {
|
|
16
|
+
promptTokens: number;
|
|
17
|
+
completionTokens: number;
|
|
18
|
+
};
|
|
19
|
+
finishReason: LanguageModelV1FinishReason;
|
|
20
|
+
toolCalls?: LanguageModelV1FunctionToolCall[];
|
|
21
|
+
rawCall: {
|
|
22
|
+
rawPrompt: unknown;
|
|
23
|
+
rawSettings: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
warnings?: LanguageModelV1CallWarning[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ResponseTransformer {
|
|
29
|
+
transform(apiResponse: DeepSeekResponse): V1GenerateResult {
|
|
30
|
+
const choice = apiResponse.choices?.[0];
|
|
31
|
+
const message = choice?.message || {};
|
|
32
|
+
|
|
33
|
+
// 修复 finish_reason
|
|
34
|
+
const finishReason = this.fixFinishReason(
|
|
35
|
+
choice?.finish_reason,
|
|
36
|
+
message.tool_calls
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
text: message.content || '',
|
|
41
|
+
finishReason: finishReason as LanguageModelV1FinishReason,
|
|
42
|
+
usage: {
|
|
43
|
+
promptTokens: apiResponse.usage?.prompt_tokens || 0,
|
|
44
|
+
completionTokens: apiResponse.usage?.completion_tokens || 0,
|
|
45
|
+
},
|
|
46
|
+
rawCall: {
|
|
47
|
+
rawPrompt: '',
|
|
48
|
+
rawSettings: {},
|
|
49
|
+
},
|
|
50
|
+
toolCalls: this.transformToolCalls(message.tool_calls),
|
|
51
|
+
warnings: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fixFinishReason(
|
|
56
|
+
originalFinishReason: string | undefined,
|
|
57
|
+
toolCalls: any[] | undefined
|
|
58
|
+
): string {
|
|
59
|
+
// 如果有 tool_calls 但 finish_reason 是 "stop",修复为 "tool-calls"
|
|
60
|
+
if (toolCalls && toolCalls.length > 0 && originalFinishReason === 'stop') {
|
|
61
|
+
return 'tool-calls';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 映射其他 finish_reason 值
|
|
65
|
+
const finishReasonMap: Record<string, string> = {
|
|
66
|
+
'stop': 'stop',
|
|
67
|
+
'length': 'length',
|
|
68
|
+
'content_filter': 'content-filter',
|
|
69
|
+
'tool_calls': 'tool-calls',
|
|
70
|
+
'function_call': 'tool-calls',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return finishReasonMap[originalFinishReason || ''] || 'stop';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private transformToolCalls(toolCalls: any[] | undefined): LanguageModelV1FunctionToolCall[] {
|
|
77
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return toolCalls.map((tc: any) => ({
|
|
82
|
+
toolCallType: 'function' as const,
|
|
83
|
+
toolCallId: tc.id,
|
|
84
|
+
toolName: tc.function.name,
|
|
85
|
+
args: typeof tc.function.arguments === 'string'
|
|
86
|
+
? tc.function.arguments
|
|
87
|
+
: JSON.stringify(tc.function.arguments),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// 流解析器
|
|
2
|
+
|
|
3
|
+
import type { LanguageModelV1StreamPart } from '@ai-sdk/provider';
|
|
4
|
+
import type { DeepSeekStreamResponse, DeepSeekUsage } from '../types';
|
|
5
|
+
|
|
6
|
+
interface DeepSeekStreamChunk {
|
|
7
|
+
id: string;
|
|
8
|
+
object: string;
|
|
9
|
+
created: number;
|
|
10
|
+
model: string;
|
|
11
|
+
choices: Array<{
|
|
12
|
+
index: number;
|
|
13
|
+
delta: {
|
|
14
|
+
role?: string;
|
|
15
|
+
content?: string;
|
|
16
|
+
tool_calls?: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'function';
|
|
19
|
+
function: {
|
|
20
|
+
name: string;
|
|
21
|
+
arguments: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
finish_reason?: string;
|
|
26
|
+
}>;
|
|
27
|
+
usage?: DeepSeekUsage;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class StreamParser {
|
|
31
|
+
async *parse(body: ReadableStream<Uint8Array>): AsyncIterable<LanguageModelV1StreamPart> {
|
|
32
|
+
const reader = body.getReader();
|
|
33
|
+
const decoder = new TextDecoder('utf-8');
|
|
34
|
+
let buffer = '';
|
|
35
|
+
let partialJson = '';
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
while (true) {
|
|
39
|
+
const { done, value } = await reader.read();
|
|
40
|
+
if (done) break;
|
|
41
|
+
|
|
42
|
+
buffer += decoder.decode(value, { stream: true });
|
|
43
|
+
const lines = buffer.split('\n');
|
|
44
|
+
buffer = lines.pop() || '';
|
|
45
|
+
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
if (line.trim() === '') continue;
|
|
48
|
+
if (line.startsWith(':')) continue; // SSE 注释
|
|
49
|
+
|
|
50
|
+
if (line.startsWith('data: ')) {
|
|
51
|
+
const data = line.slice(6).trim();
|
|
52
|
+
if (data === '[DONE]') {
|
|
53
|
+
yield* this.processPartialJson(partialJson);
|
|
54
|
+
partialJson = '';
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// 尝试解析 JSON
|
|
60
|
+
const parsed = JSON.parse(data);
|
|
61
|
+
yield* this.processPartialJson(partialJson);
|
|
62
|
+
partialJson = '';
|
|
63
|
+
yield* this.processChunk(parsed);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// JSON 解析失败,可能是部分数据
|
|
66
|
+
partialJson += data;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 处理剩余的部分 JSON
|
|
73
|
+
yield* this.processPartialJson(partialJson);
|
|
74
|
+
} finally {
|
|
75
|
+
reader.releaseLock();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async *processPartialJson(partialJson: string): AsyncIterable<LanguageModelV1StreamPart> {
|
|
80
|
+
if (!partialJson.trim()) return;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(partialJson);
|
|
84
|
+
yield* this.processChunk(parsed);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// 仍然无法解析,跳过
|
|
87
|
+
console.warn('Failed to parse partial JSON:', partialJson);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async *processChunk(chunk: DeepSeekStreamChunk): AsyncIterable<LanguageModelV1StreamPart> {
|
|
92
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
93
|
+
|
|
94
|
+
// 工具调用增量 - JSON in content 格式
|
|
95
|
+
if (delta?.content) {
|
|
96
|
+
const toolCallData = this.tryParseToolCall(delta.content);
|
|
97
|
+
if (toolCallData) {
|
|
98
|
+
yield {
|
|
99
|
+
type: 'tool-call-delta',
|
|
100
|
+
toolCallType: 'function',
|
|
101
|
+
toolCallId: toolCallData.id || `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
102
|
+
toolName: toolCallData.name,
|
|
103
|
+
argsTextDelta: JSON.stringify(toolCallData.arguments || {}),
|
|
104
|
+
} as LanguageModelV1StreamPart;
|
|
105
|
+
} else {
|
|
106
|
+
// 普通文本增量
|
|
107
|
+
yield {
|
|
108
|
+
type: 'text-delta',
|
|
109
|
+
textDelta: delta.content,
|
|
110
|
+
} as LanguageModelV1StreamPart;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 工具调用增量 - 标准 tool_calls 格式
|
|
115
|
+
if (delta?.tool_calls && delta.tool_calls.length > 0) {
|
|
116
|
+
for (const toolCall of delta.tool_calls) {
|
|
117
|
+
yield {
|
|
118
|
+
type: 'tool-call-delta',
|
|
119
|
+
toolCallType: 'function',
|
|
120
|
+
toolCallId: toolCall.id,
|
|
121
|
+
toolName: toolCall.function?.name || '',
|
|
122
|
+
argsTextDelta: toolCall.function?.arguments || '',
|
|
123
|
+
} as LanguageModelV1StreamPart;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 完成信号
|
|
128
|
+
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
129
|
+
if (finishReason) {
|
|
130
|
+
// V1 API: usage 在 finish 类型的 part 中
|
|
131
|
+
const usage = chunk.usage
|
|
132
|
+
? {
|
|
133
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
134
|
+
completionTokens: chunk.usage.completion_tokens,
|
|
135
|
+
}
|
|
136
|
+
: { promptTokens: 0, completionTokens: 0 };
|
|
137
|
+
|
|
138
|
+
yield {
|
|
139
|
+
type: 'finish',
|
|
140
|
+
finishReason: this.mapFinishReason(finishReason),
|
|
141
|
+
usage,
|
|
142
|
+
} as LanguageModelV1StreamPart;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private mapFinishReason(reason: string): 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown' {
|
|
147
|
+
const reasonMap: Record<string, 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown'> = {
|
|
148
|
+
'stop': 'stop',
|
|
149
|
+
'length': 'length',
|
|
150
|
+
'content_filter': 'content-filter',
|
|
151
|
+
'tool_calls': 'tool-calls',
|
|
152
|
+
'function_call': 'tool-calls',
|
|
153
|
+
'error': 'error',
|
|
154
|
+
};
|
|
155
|
+
return reasonMap[reason] || 'unknown';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private tryParseToolCall(content: string): { id?: string; name: string; arguments: Record<string, unknown> } | null {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(content);
|
|
161
|
+
// 检查是否是工具调用格式: { name: "...", arguments: {...} }
|
|
162
|
+
if (parsed && typeof parsed.name === 'string' && (parsed.arguments === undefined || typeof parsed.arguments === 'object')) {
|
|
163
|
+
return {
|
|
164
|
+
id: parsed.id,
|
|
165
|
+
name: parsed.name,
|
|
166
|
+
arguments: parsed.arguments || {},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// JSON 解析失败,不是工具调用
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|