This commit is contained in:
2026-04-18 13:05:29 +08:00
parent 09d4c0c31b
commit 5032701c38
77 changed files with 8538 additions and 2413 deletions

View File

@@ -309,6 +309,92 @@ export class UpstreamLlmClient {
return content;
}
async streamMessageContent(params: {
systemPrompt: string;
userPrompt: string;
model?: string;
signal?: AbortSignal;
timeoutMs?: number;
debugLabel?: string;
onUpdate?: (text: string) => void;
}) {
const response = await this.requestCompletion(
{
model: params.model,
stream: true,
messages: [
{ role: 'system', content: params.systemPrompt },
{ role: 'user', content: params.userPrompt },
],
},
{
signal: params.signal,
timeoutMs: params.timeoutMs,
debugLabel: params.debugLabel,
},
);
if (!response.body) {
throw upstreamError('LLM 流式响应体不可用');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let accumulatedText = '';
for (;;) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
while (buffer.includes('\n\n')) {
const boundary = buffer.indexOf('\n\n');
const eventBlock = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
for (const rawLine of eventBlock.split(/\r?\n/u)) {
const line = rawLine.trim();
if (!line.startsWith('data:')) {
continue;
}
const data = line.slice(5).trim();
if (!data || data === '[DONE]') {
continue;
}
try {
const parsed = JSON.parse(data) as {
choices?: Array<{
delta?: {
content?: string;
};
}>;
};
const delta = parsed.choices?.[0]?.delta?.content;
if (typeof delta === 'string' && delta.length > 0) {
accumulatedText += delta;
params.onUpdate?.(accumulatedText);
}
} catch {
// Ignore malformed SSE frames from the upstream model.
}
}
}
}
const content = accumulatedText.trim();
if (!content) {
throw upstreamError('LLM 返回内容为空');
}
return content;
}
async forwardCompletion(
request: ExpressRequest,
body: Record<string, unknown>,