function registerLlmRoutes(app, options) { const llmBaseUrl = options.llmBaseUrl; const llmApiKey = options.llmApiKey; // 非流式调用已迁移至云函数 proxyHub(cloud-functions/08-proxyHub.js)。 // 但流式 SSE 调用必须走本地 Express(云函数不支持流式输出),由前端 fetch 直连。 app.post('/api/llm/chat/stream', async (req, res) => { try { const { model, messages, temperature, max_tokens, ...rest } = req.body || {}; if (!Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ error: '缺少 messages 参数' }); } const payload = { model: model || 'gpt-4o', messages, temperature: temperature ?? 0.7, max_tokens: max_tokens || 4096, ...rest, stream: true, }; const url = `${llmBaseUrl}/v1/chat/completions`; console.log(`🤖 [Stream] LLM Chat 请求: model=${payload.model}, messages=${messages.length}条`); const upstream = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${llmApiKey}`, }, body: JSON.stringify(payload), }); if (!upstream.ok) { const errText = await upstream.text(); console.error('🤖 [Stream] 上游错误:', upstream.status, errText); return res.status(upstream.status).json({ error: errText || `upstream ${upstream.status}` }); } res.setHeader('Content-Type', 'text/event-stream; charset=utf-8'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); res.flushHeaders?.(); const reader = upstream.body.getReader(); const onAbort = () => { try { reader.cancel(); } catch {} }; req.on('close', onAbort); try { while (true) { const { value, done } = await reader.read(); if (done) break; if (value) res.write(Buffer.from(value)); } } finally { req.off('close', onAbort); res.end(); } } catch (err) { console.error('🤖 [Stream] 异常:', err.message); if (!res.headersSent) { res.status(500).json({ error: `LLM 流式请求失败: ${err.message}` }); } else { try { res.end(); } catch {} } } }); } module.exports = { registerLlmRoutes };