Qwen-Agent 流式输出实战:3 步把 vLLM 响应从"憋大招"变"边说边想"
【免费下载链接】Qwen-AgentAgent framework and applications built upon Qwen>=3.0, featuring Function Calling, MCP, Code Interpreter, RAG, Chrome extension, etc.项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen-Agent
对话界面卡在第 5 秒还没吐出一个字——大概率是模型在"憋大招":整段生成完才一次性返回。Qwen-Agent 作为 Qwen 生态的 Agent 框架(内置 Function Calling、代码解释器、RAG 等能力),只要把模型服务切到 vLLM 后端并走流式输出,就能把首字延迟从"整段生成时间"压到"首个 token 的时间",本文讲清原理并给你一套 3 步跑通的实操流程。
场景:一次代码解释器任务的体感对比
别急,先建立一个直观印象。拿 Qwen-Agent 里的代码解释器场景举例:你问它统计各大洲人口分布并画图。
批处理模式下,你要盯着光标转圈,等几十行 Python、执行日志、饼图全部生成完,界面才"啪"地刷出一整块。
流式模式下,"Thought: 我需要……" 先出来,紧接着代码一行行落下,图表渲染完再追加结论——每个 token 到手即显示,过程可见,焦虑感直接减半。
| 维度 | 整段批处理 | vLLM 流式输出 |
|---|---|---|
| 首字延迟 | 等于整段生成时间 | 约等于首 token 时间 |
| 输出过程 | 一次性刷新 | 逐 token 递增渲染 |
| 中断感知 | 卡死感 | 随时可打断、可见进度 |
图 1:代码解释器中流式生成的过程记录,思考、代码与执行结果逐段出现
这种差别在长回复里最扎眼,回答越短,两种模式的体感越接近。
核心机制:数据到底怎么流
一句话概括:vLLM 负责"逐 token 吐",openai SDK 负责"逐 chunk 收",Qwen-Agent 负责"逐帧渲染"。
拆开看有四层:
- 推理引擎:vLLM 本身是 LLM 推理引擎,靠 PagedAttention 和连续批处理把生成吞吐拉上去,并对外暴露 OpenAI 兼容协议——也就是说,任何会说 OpenAI 方言的客户端都能直接接上。
- LLM 抽象层:
qwen_agent/llm/base.py里注册了统一的 chat 接口,model_type指定后端即可切换。 - 适配层:
qwen_agent/llm/oai.py中的TextChatAtOAI把配置里的model_server作为 base_url 传给 OpenAI 客户端,vLLM 不需要任何专属代码就能被 Qwen-Agent 消费。 - 上层渲染:Agent 拿到增量 Message 后逐帧推给 Web UI。
流式的核心就在_chat_stream,看这两段关键逻辑(qwen_agent/llm/oai.py):
response = self._chat_complete_create(model=self.model, messages=messages, stream=True, **generate_cfg) if delta_stream: for chunk in response: if chunk.choices and chunk.choices[0].delta.content: yield [Message(role=ASSISTANT, content=chunk.choices[0].delta.content)] # 只推增量 else: full_response = '' for chunk in response: full_response += chunk.choices[0].delta.content # 累积完整内容 yield [Message(role=ASSISTANT, content=full_response)]这里有个关键点:delta_stream决定上层每次收到的是"新增片段"还是"累积全文"。全流模式还能顺手把碎片化的 tool_calls 拼回完整函数调用——Function Calling 走流式时靠的就是这段。基类里甚至有句自嘲:delta 模式给后处理和重试带来麻烦,现在默认推荐全流模式。
图 2:Qwen-Agent 对话界面,模型按用户选定的上下文流式作答
动手:3 步从配置到跑通
第 1 步:起一个 vLLM 服务
pip install vllm python -m vllm.entrypoints.openai.api_server \ --model Qwen2-7B-Instruct \ --host 0.0.0.0 \ --port 8000✅ 验证点:看到Uvicorn running on ...:8000后,另开终端执行curl http://127.0.0.1:8000/v1/models,能列出模型名即说明协议层通了。
第 2 步:把 Qwen-Agent 指向 vLLM
改qwen_server/server_config.json,或直接给run_server.py传参,后者会替你写回配置文件:
python run_server.py \ -m http://127.0.0.1:8000/v1 \ -k EMPTY \ -l Qwen2-7B-Instruct注意-m要写到/v1结尾,这是 OpenAI 兼容协议的路径约定。
✅ 验证点:终端会依次拉起 database、workstation、assistant 三个子进程,日志里打印的 server 配置应包含你的model_server地址。
第 3 步:打开 Web UI 验流式
访问http://127.0.0.1:7864,在对话里抛一个需要长回答的问题。
✅ 验证点:首 token 到达时文字就开始出现,而不是转圈几秒后整段刷出——这一刻你就确认流式链路是通的。
图 3:Web UI 中问答的流式呈现效果
性能怎么验证
先给一张定性对比表,方便你建立预期(具体数字依赖模型与硬件,请以自测为准):
| 指标 | 整段批处理 | vLLM 流式 | 说明 |
|---|---|---|---|
| 首字延迟 | 等于整段生成时间 | 约等于首 token 时间 | 收益核心 |
| 输出过程 | 一次性渲染 | 逐 token 追加 | 可随时打断 |
| 客户端内存压力 | 全量缓存后渲染 | 增量持有 | 长回复更明显 |
想拿硬数据,自己在_chat_stream首尾各打一个时间戳,对比stream=True和stream=False两种调用:前者首个 yield 的耗时就是 TTFT。收益在长回复、代码解释器这类场景最明显;如果你只问"今天星期几",两种模式的差别基本感知不到——流式优化救的是"长",不是"短"。
进阶与避坑
三个最常见的翻车现场:
- 服务起来了但 Qwen-Agent 连不上→ 确认
-m结尾是/v1,且 vLLM 端--port与地址一致;跨机器访问时记得--host 0.0.0.0并把server_host设为0.0.0.0。 - 流式中途断流→ 多半是 GPU 显存不够或上下文超
--max-model-len,服务端 OOM 会直接掐断 SSE 流,调小 max-model-len 或降低并发即可。 - 工具调用解析不出来→ vLLM 侧要开
--enable-auto-tool-choice与对应的--tool-call-parser,Qwen-Agent 侧oai后端才能收到完整 tool_calls。
想再挖深一点:抓一段 SSE 原始流看看 token 间隔分布是否稳定;或者把--gpu-memory-utilization从 0.9 往下调,观察吞吐与显存占用的取舍曲线。
参考资源
- 官方文档:
qwen-agent-docs/website/content/en/guide/get_started/configuration.md(配置说明)、qwen-agent-docs/website/content/en/guide/core_moduls/llm.md(LLM 层介绍) - 核心源码入口:
qwen_agent/llm/base.py(统一接口)、qwen_agent/llm/oai.py(OpenAI 兼容适配) - 示例脚本:
examples/function_calling.py(流式函数调用最小用例)
试试把批处理请求的"发请求到出首字"和流式请求各记一个时间戳,跑 10 轮取平均——这条曲线会比我写的任何表格都更有说服力。
【免费下载链接】Qwen-AgentAgent framework and applications built upon Qwen>=3.0, featuring Function Calling, MCP, Code Interpreter, RAG, Chrome extension, etc.项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen-Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考