Hermes Agent 流式输出架构解析
一个支持多消费者、多平台的实时流式输出设计
前言
在 AI 对话应用中,实时流式输出(Streaming Output)能显著提升用户体验——用户无需等待完整响应即可看到正在生成的内容。Hermes Agent 采用了一套精心设计的流式输出架构,支持同时向 CLI、TUI、第三方平台等多个目标推送内容。
本文将深入解析这一架构的设计思路和实现细节。
一、整体架构
Hermes 的流式输出采用回调机制 + 多消费者模式:
┌─────────────────────────────────────────────────────────────┐
│ Agent (核心引擎) │
│ │
│ run_conversation() → 流式调用 → stream_callback() │
└───────────────────────────┼─────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────────┐ ┌──────────────┐ ┌──────────────────┐
│ CLI │ │ TUI Gateway │ │ Gateway │
│ 终端输出 │ │ Web UI 接口 │ │ 多平台消费者 │
└──────────────┘ └──────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
本地终端显示 WebSocket推送 第三方平台编辑
核心思想:Agent 只需产生一次流式内容,通过回调分发到多个消费者。
二、核心组件详解
2.1 Agent 层 —— 内容生产者
Agent 是流式输出的起点,负责调用 LLM 的流式 API 并将内容通过回调分发。
工作流程:
- 接收用户消息和回调函数
- 调用流式 API,逐 token 获取响应
- 每获取一个 token,调用一次回调
- 响应结束时,调用回调并传入
None表示结束
关键设计:
- 回调函数作为参数传入,实现解耦
- Agent 不需要知道有多少消费者
- 支持可中断的流式调用
2.2 CLI 消费者 —— 本地终端
CLI 是最直接的消费者,负责将流式内容输出到终端。
工作流程:
- 接收增量文本(一个或多个 token)
- 过滤推理/思考标签(如
<think>等) - 进行行缓冲,逐行输出到终端
- 使用 prompt_toolkit 实现兼容终端的实时打印
关键设计:
- 行缓冲:避免逐字符输出导致的闪烁
- 标签过滤:用户只看最终内容,不看推理过程
- KawaiiSpinner:加载动画提升体验
2.3 TUI Gateway —— Web UI 桥接
TUI Gateway 提供 Web/桌面 UI 的 JSON-RPC 接口。
工作流程:
- 接收增量文本
- 可选:实时 Markdown 渲染
- 通过 WebSocket 发送
message.delta事件 - 前端收到事件后更新显示
关键设计:
- JSON-RPC 协议,支持多种前端
- 可选渲染字段,平衡性能和显示效果
2.4 Gateway Stream Consumer —— 多平台分发
这是支持多平台的核心组件,使用异步队列管理分发。
工作流程:
- 接收增量文本,加入线程安全队列
- 异步任务持续消费队列
- 将内容分发到所有已注册的平台
- 各平台使用自己的方式编辑消息(如 Telegram 的
edit_message_text)
关键设计:
- 线程安全队列:同步 Agent 和异步 UI 的桥接
- 统一接口:不同平台实现相同接口
- 自适应退避:API 调用失败时自动重试
三、多消费者设计
3.1 消费者列表
| 消费者 | 用途 | 输出方式 |
|---|---|---|
| CLI | 本地命令行 | 终端实时打印 |
| TUI Gateway | Web/桌面 UI | WebSocket 推送 |
| Telegram | Telegram 机器人 | 编辑消息 API |
| Discord | Discord 机器人 | 编辑消息 API |
| Slack | Slack 机器人 | 编辑消息 API |
3.2 平台桥接机制
所有平台消费者都实现统一的接口,核心只有两个方法:
- edit_message(delta):编辑已发送的消息,追加新内容
- finalize_message(text):消息完成后调用,进行最终处理
以 Telegram 为例:
- 发送初始消息后获得 message_id
- 每次收到增量,调用 Telegram API 编辑该消息
- 由于 Telegram 限制,编辑间隔不能太频繁(1.5秒左右)
3.3 消息累积策略
由于各平台 API 的限制(如编辑间隔、字符数限制),消费者不会每收到一个 token 就调用 API,而是:
- 累积缓冲:收集一定量文本后再编辑
- 定时刷新:每隔固定时间(如 1.5 秒)编辑一次
- 光标提示:在消息末尾显示
▉表示正在输入
四、事件协议
4.1 流式增量事件
{
"type": "message.delta",
"session_id": "会话ID",
"payload": {
"text": "正在分析...",
"rendered": "<渲染后的文本>" // 可选
}
}
4.2 消息完成事件
{
"type": "message.complete",
"session_id": "会话ID",
"payload": {
"text": "完整响应内容",
"reasoning": "推理过程", // 可选
"status": "complete" // 或 interrupted/error
}
}
4.3 推理块过滤
AI 响应中可能包含 <reasoning>...</reasoning> 等推理标记,这些对用户不可见,需要过滤:
- 打开标签(
<think>)时开始过滤 - 关闭标签(``)时恢复输出
- 过滤后的内容不显示给用户
五、设计亮点
5.1 回调机制实现解耦
Agent ──回调──▶ 多个消费者
Agent 不需要知道有多少消费者,只需调用 stream_callback(text)。新增消费者只需注册回调,无需修改 Agent 代码。
5.2 线程安全队列
Agent 运行在独立线程,而 UI 需要在主线程更新。通过线程安全队列桥接:
Agent 线程 ──Queue.put()──▶ 队列 ──Queue.get()──▶ UI 线程
5.3 自适应退避
当平台 API 调用失败(如频率限制)时,自动增加重试间隔:
第1次失败 → 等待 1.5 秒
第2次失败 → 等待 3 秒
第3次失败 → 等待 6 秒
5.4 Markdown 流式渲染
部分消费者支持实时 Markdown 渲染:
- 接收增量文本
- 逐步更新渲染结果
- 用户看到逐步格式化的内容
六、借鉴要点
如果要在其他项目中实现类似的流式输出架构,需要关注以下几点:
6.1 回调接口设计
定义统一的流式回调接口,参数只需一个增量文本,结束时应传入 None。
6.2 行缓冲输出
不要逐字符输出,应该累积到换行符或一定长度后再输出,避免终端闪烁。
6.3 多消费者管理
使用列表管理所有注册的消费者,广播式调用即可。
6.4 线程安全
如果涉及多线程,确保队列操作的原子性。
6.5 平台适配
不同平台有不同的 API 限制,需要针对性处理(如编辑间隔、消息长度)。
七、总结
Hermes Agent 的流式输出架构展示了如何优雅地处理实时内容分发:
| 特性 | 实现方式 |
|---|---|
| 解耦 | 回调机制 + 多消费者 |
| 线程安全 | Queue 队列桥接 |
| 跨平台 | 统一接口 + 各自实现 |
| 容错 | 自适应退避 |
| 美观 | 行缓冲 + Markdown 渲染 |
这种设计使得同一个 AI 响应可以同时服务于 CLI、Web UI、第三方平台等多种场景,极大提升了架构的灵活性和可扩展性。
本文档基于 Hermes Agent 项目源码分析编写
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)