在SvelteKit中实现LangChain聊天流Token实时展示
SvelteKit + LangChain 实现实时流式显示ChatGPT返回Token
我正在开发一个基于SvelteKit和LangChain的项目,希望点击按钮后UI能实时显示聊天流返回的Token。但用表单action的实现遇到了困难,尤其是不知道怎么处理LangChain ChatOpenAI模型中的handleLLMNewToken回调。当前代码如下:
当前实现代码
+page.server.ts
import type { Actions } from './$types'; import { OPENAI_API_KEY } from '$env/static/private'; import type { RequestEvent } from '@sveltejs/kit'; import { ChatOpenAI } from "langchain/chat_models/openai" import { HumanChatMessage } from 'langchain/schema'; const message = `Hello World!` const model = new ChatOpenAI({ openAIApiKey: OPENAI_API_KEY, streaming: true, modelName: 'gpt-3.5-turbo', callbacks: [ { handleLLMNewToken(token) { // Don't know what to do here }, } ] }); export const actions = { chat: async (event: RequestEvent) => { const msg = await model.call([new HumanChatMessage(message)]) return { success: true, message: msg.text, } } } satisfies Actions;
+page.svelte
<script lang="ts"> import { enhance } from '$app/forms'; export let form; $: response = form?.message; </script> <div> {#if response} {response} {/if} </div> <div> <form method="POST" action="?/chat" use:enhance> <button class=""> Generate </button> </form> </div>
解决方案:改用Server-Sent Events(SSE)实现流式响应
表单action是一次性返回完整结果,无法实现实时Token推送,必须改用SSE(服务器推送事件)来处理流式输出。
1. 创建SSE API端点(替换原有表单action)
新建src/routes/api/chat/+server.ts,作为流式响应的接口:
import { OPENAI_API_KEY } from '$env/static/private'; import { ChatOpenAI } from "langchain/chat_models/openai"; import { HumanChatMessage } from 'langchain/schema'; export async function POST({ request }) { const { message } = await request.json(); const model = new ChatOpenAI({ openAIApiKey: OPENAI_API_KEY, streaming: true, modelName: 'gpt-3.5-turbo', }); // 创建可读流用于推送Token const stream = new ReadableStream({ async start(controller) { await model.call( [new HumanChatMessage(message)], { callbacks: [ { handleLLMNewToken(token) { // 按SSE格式推送单个Token controller.enqueue(`data: ${JSON.stringify({ token })}\n\n`); }, handleLLMEnd() { // 结束流式传输 controller.close(); } } ] } ); } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); }
2. 前端修改为监听SSE并实时更新UI
更新+page.svelte,去掉表单enhance逻辑,改用fetch请求SSE接口:
<script lang="ts"> let response = ''; let isLoading = false; async function generate() { isLoading = true; response = ''; const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Hello World!' }) }); if (!res.ok) { isLoading = false; return; } const reader = res.body?.getReader(); if (!reader) return; const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); // 解析SSE格式的数据,提取Token并拼接 const dataLines = chunk.split('\n').filter(line => line.startsWith('data:')); for (const line of dataLines) { const data = JSON.parse(line.slice(5)); response += data.token; } } isLoading = false; } </script> <div> {response} {#if isLoading} <span>生成中...</span> {/if} </div> <div> <button on:click={generate} disabled={isLoading}> {isLoading ? '生成中...' : 'Generate'} </button> </div>
关键说明
- 表单action是同步返回完整结果,不支持流式推送,SSE是实现实时Token展示的最优方案;
- 在LangChain的
handleLLMNewToken回调中,按照SSE的标准格式(data: 内容\n\n)向客户端推送每个Token; - 前端通过
ReadableStream逐段读取SSE响应,解析后实时更新UI,实现打字机式的输出效果。
内容的提问来源于stack exchange,提问作者wolfeweeks
相关产品推荐
相关产品推荐

