Skip to content

Commit 4bc2a54

Browse files
committed
feat: make markdown example work
1 parent 79a97c7 commit 4bc2a54

File tree

5 files changed

+261
-26
lines changed

5 files changed

+261
-26
lines changed

src/controllers/openaiController.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ export function handleChatCompletions(req: Request, res: Response) {
5555

5656
// 流式响应
5757
if (request.stream) {
58-
res.setHeader('Content-Type', 'text/plain');
58+
res.setHeader('Content-Type', 'text/event-stream');
5959
res.setHeader('Cache-Control', 'no-cache');
6060
res.setHeader('Connection', 'keep-alive');
6161
res.setHeader('Access-Control-Allow-Origin', '*');
6262
res.setHeader('Access-Control-Allow-Headers', '*');
6363

64+
// 刷新头部,确保客户端立即收到头部信息
65+
res.flushHeaders();
66+
6467
const streamGenerator = createChatCompletionStream(request);
6568

6669
for (const chunk of streamGenerator) {

src/data/mockData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ $$
282282
283283
通过以上示例,您可以全面了解 Markdown 的基本语法和样式应用。实际使用中可根据需要组合不同元素,创建结构清晰、格式美观的文档内容。`,
284284
streamChunks: [
285-
"你是一个复读机,把我给你的内容打印出来,内容是\n\n\n# Markdown 样式全功能演示\n\n",
285+
"# Markdown 样式全功能演示\n\n",
286286
"本文档全面展示 Markdown 的所有基础语法,涵盖标题、段落、列表、链接、图片、代码块、表格、引用等常见元素,",
287287
"并包含特殊符号和扩展功能的使用示例。\n\n---\n\n## 1. 标题层级(1-6级)\n\n",
288288
"# 一级标题(H1)\n## 二级标题(H2)\n### 三级标题(H3)\n#### 四级标题(H4)\n",

src/services/openaiService.ts

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
104104
// 验证模型
105105
const model = findModelById(request.model);
106106
if (!model) {
107-
const errorChunk = `data: ${JSON.stringify(formatErrorResponse(`模型 '${request.model}' 不存在`))}\\n\\n`;
107+
const errorChunk = `data: ${JSON.stringify(formatErrorResponse(`模型 '${request.model}' 不存在`))}\n\n`;
108108
yield errorChunk;
109109
return;
110110
}
@@ -116,7 +116,7 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
116116
.find(msg => msg.role === 'user');
117117

118118
if (!lastUserMessage) {
119-
const errorChunk = `data: ${JSON.stringify(formatErrorResponse('未找到用户消息'))}\\n\\n`;
119+
const errorChunk = `data: ${JSON.stringify(formatErrorResponse('未找到用户消息'))}\n\n`;
120120
yield errorChunk;
121121
return;
122122
}
@@ -126,21 +126,31 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
126126

127127
const id = generateChatCompletionId();
128128
const timestamp = getCurrentTimestamp();
129+
const systemFingerprint = `fp_${Math.random().toString(36).substr(2, 10)}_mock`;
129130

130-
// 发送开始chunk
131+
// 发送第一个chunk - role 和空 content
131132
const startChunk: ChatCompletionStreamChunk = {
132133
id,
133134
object: 'chat.completion.chunk',
134135
created: timestamp,
135136
model: request.model,
137+
system_fingerprint: systemFingerprint,
136138
choices: [{
137139
index: 0,
138-
delta: { role: 'assistant' },
139-
finish_reason: undefined
140-
}]
140+
delta: {
141+
role: 'assistant',
142+
content: ''
143+
},
144+
logprobs: null,
145+
finish_reason: null
146+
}],
147+
usage: null
141148
};
142149

143-
yield `data: ${JSON.stringify(startChunk)}\\n\\n`;
150+
yield `data: ${JSON.stringify(startChunk)}\n\n`;
151+
152+
let totalTokens = 0;
153+
let completionTokens = 0;
144154

145155
// 如果有预定义的流式chunk,使用它们
146156
if (testCase.streamChunks && testCase.streamChunks.length > 0) {
@@ -150,34 +160,42 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
150160
object: 'chat.completion.chunk',
151161
created: timestamp,
152162
model: request.model,
163+
system_fingerprint: systemFingerprint,
153164
choices: [{
154165
index: 0,
155166
delta: { content: chunkContent },
156-
finish_reason: undefined
157-
}]
167+
logprobs: null,
168+
finish_reason: null
169+
}],
170+
usage: null
158171
};
159172

160-
yield `data: ${JSON.stringify(chunk)}\\n\\n`;
173+
completionTokens += calculateTokens(chunkContent);
174+
yield `data: ${JSON.stringify(chunk)}\n\n`;
161175
}
162176
} else {
163177
// 否则将完整响应分割成chunks
164178
const words = testCase.response.split(' ');
165-
for (let i = 0; i < words.length; i += 3) {
166-
const chunkContent = words.slice(i, i + 3).join(' ') + (i + 3 < words.length ? ' ' : '');
179+
for (let i = 0; i < words.length; i += 1) {
180+
const chunkContent = words[i] + (i < words.length - 1 ? ' ' : '');
167181

168182
const chunk: ChatCompletionStreamChunk = {
169183
id,
170184
object: 'chat.completion.chunk',
171185
created: timestamp,
172186
model: request.model,
187+
system_fingerprint: systemFingerprint,
173188
choices: [{
174189
index: 0,
175190
delta: { content: chunkContent },
176-
finish_reason: undefined
177-
}]
191+
logprobs: null,
192+
finish_reason: null
193+
}],
194+
usage: null
178195
};
179196

180-
yield `data: ${JSON.stringify(chunk)}\\n\\n`;
197+
completionTokens += calculateTokens(chunkContent);
198+
yield `data: ${JSON.stringify(chunk)}\n\n`;
181199
}
182200
}
183201

@@ -188,6 +206,7 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
188206
object: 'chat.completion.chunk',
189207
created: timestamp,
190208
model: request.model,
209+
system_fingerprint: systemFingerprint,
191210
choices: [{
192211
index: 0,
193212
delta: {
@@ -196,28 +215,46 @@ export function* createChatCompletionStream(request: ChatCompletionRequest): Gen
196215
arguments: JSON.stringify(testCase.functionCall.arguments)
197216
}
198217
},
199-
finish_reason: undefined
200-
}]
218+
logprobs: null,
219+
finish_reason: null
220+
}],
221+
usage: null
201222
};
202223

203-
yield `data: ${JSON.stringify(functionChunk)}\\n\\n`;
224+
yield `data: ${JSON.stringify(functionChunk)}\n\n`;
204225
}
205226

206-
// 发送结束chunk
227+
// 计算 token 使用量
228+
const promptTokens = calculateTokens(lastUserMessage.content);
229+
totalTokens = promptTokens + completionTokens;
230+
231+
// 发送最后一个chunk - 包含 finish_reason 和 usage
207232
const endChunk: ChatCompletionStreamChunk = {
208233
id,
209234
object: 'chat.completion.chunk',
210235
created: timestamp,
211236
model: request.model,
237+
system_fingerprint: systemFingerprint,
212238
choices: [{
213239
index: 0,
214-
delta: {},
240+
delta: { content: '' },
241+
logprobs: null,
215242
finish_reason: model.type === 'function' && testCase.functionCall ? 'function_call' : 'stop'
216-
}]
243+
}],
244+
usage: {
245+
prompt_tokens: promptTokens,
246+
completion_tokens: completionTokens,
247+
total_tokens: totalTokens,
248+
prompt_tokens_details: {
249+
cached_tokens: 0
250+
},
251+
prompt_cache_hit_tokens: 0,
252+
prompt_cache_miss_tokens: promptTokens
253+
}
217254
};
218255

219-
yield `data: ${JSON.stringify(endChunk)}\\n\\n`;
220-
yield 'data: [DONE]\\n\\n';
256+
yield `data: ${JSON.stringify(endChunk)}\n\n`;
257+
yield `data: [DONE]\n\n`;
221258
}
222259

223260
/**

src/types/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export interface ChatCompletionStreamChunk {
6565
object: string;
6666
created: number;
6767
model: string;
68+
system_fingerprint?: string;
6869
choices: Array<{
6970
index: number;
7071
delta: {
@@ -75,8 +76,19 @@ export interface ChatCompletionStreamChunk {
7576
arguments?: string;
7677
};
7778
};
78-
finish_reason?: string;
79+
logprobs?: null;
80+
finish_reason?: string | null;
7981
}>;
82+
usage?: {
83+
prompt_tokens: number;
84+
completion_tokens: number;
85+
total_tokens: number;
86+
prompt_tokens_details?: {
87+
cached_tokens: number;
88+
};
89+
prompt_cache_hit_tokens?: number;
90+
prompt_cache_miss_tokens?: number;
91+
} | null;
8092
}
8193

8294
export interface ImageGenerationRequest {

0 commit comments

Comments
 (0)