流式文本输出是大型语言模型(LLMs)和聊天机器人日益流行的功能。流式处理通过逐步发送文本,而不是等待生成完成,提供了更加互动的体验。我们将探讨如何利用 Langchain 代理和 FastAPI 实现流式处理。
Streaming 概述
流式处理的目标是尽快开始显示用户生成的文本,而不是在全部完成后。其优势包括:
- 降低延迟 — 用户可以更快地看到初始结果
- 增加参与度 — 输出看起来更加“活跃”
- 提前预览 — 能够预览并取消不良生成
- 进度跟踪 — 用户知道生成正在进行中
对于较短的文本,流式处理没必要。但对于较长的输出或速度较慢的模型,它极大地提升了体验。
在底层,流式处理的工作方式是从 LLM 生成器中生成 token,并在它们可用时即时提供,而不是缓冲完整的结果。然后再将这些令牌逐步发送给客户端。
Terminal 查看流式效果
最简单的流式实现是将 Token 打印到终端,无需 API 即可验证。
要将基本流式处理添加到 Langchain 大型语言模型(LLM)中:
llm = Chat(
streaming=True,
callbacks=StreamingStdoutCallbackHandler()
)
- `streaming=True` 开启流式返回能力
- `StreamingStdoutCallbackHandler` 打印每一个新 Token
文本将逐行流式输出,而不是一次性输出。例如:
用户:给我讲个故事
LLM:从前
LLM:有
LLM:一个
LLM:时候
而不是:
LLM:从前有一个时候
回调处理程序会在每个令牌从生成中变得可用时启用流式传输。
Ageng 中使用流式返回
当使用Langchain Agent 时,流式处理逻辑变得更加复杂。代理封装了 LLM,并提供了额外的功能,如状态跟踪、工具链接等。
复杂性产生的原因是,代理将其输出格式化为类似于以下的JSON结构:
{
"action": "text",
"action_input": "Once upon a time…"
}
并不是直接返回文本。因此,我们需要逻辑来提取 action_input 字段。
Langchain提供了一个 FinalAnswerCallbackHandler,它通过仅打印最终文本来处理这个问题。但它的功能有限。
我更喜欢使用自定义回调处理程序来获得更多自主性:
class MyStreamingCallback(StreamingStdoutCallbackHandler):
def __init__(self):
self.content = ""
self.final_answer = False
async def on_lm_new_token(self, token: TokenizedLMOutput):
self.content += token
if "final_answer" in self.content:
self.final_answer = True
self.content = ""
if self.final_answer:
print(token)
agent.callbacks = MyStreamingCallback()
这会将所有令牌累积到 self.content 中。一旦它看到 ”final_answer” 标记,它就会提取令牌并将它们进行流式传输。
核心包括:
- 检测最终答案以处理多个动作
- 在生成之间重置状态
- 仅打印所需的文本令牌
有了这个回调处理程序,我们就可以正确地从 Langchain 代理流式传输文本。