1
This commit is contained in:
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user