架构选型:三个开源项目的上下文管理哲学——Hermes、CoPaw 与 WeClaw 的横向对比
专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
本文是模块八第 2 篇,深入对比三个开源项目的上下文管理架构设计,帮助你为自己的 Agent 项目做出正确的技术选型。
作者与项目
作者简介:翁勇刚 WENG YONGGANG 新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者 理念:"再复杂的技术,也能用代码讲清楚"
- 项目地址:https://github.com/wyg5208/weclaw.git
- 官网地址:https://weclaw.link
- 作者 CSDN:https://blog.csdn.net/yweng18
- PyPI:[待发布]
- 欢迎 Star、Fork、贡献代码
摘要
本文结构概览: 本文从三个开源项目的源码出发,逐层剖析 Hermes-Agent 的"一体化压缩流水线"、CoPaw 的"记忆分层架构"、WeClaw 旧方案的"截断优先策略",通过关键差异表揭示各自的架构哲学,最后给出 WeClaw 选择"借鉴 Hermes 流水线模式"的完整决策推导。
背景:上下文管理是 LLM Agent 的核心基础设施,不同项目基于各自的场景特点做出了截然不同的架构选择。理解这些差异,是做出正确技术选型的前提。
核心问题:三种架构的压缩时机、容灾策略、Tool 保护、成本控制有何不同?在什么场景下应该选择哪种方案?
解决方案:提炼出 7 个关键维度进行横向对比,结合代码级分析给出选型指南。
关键成果:
- 完整剖析三种架构的核心设计差异
- 给出可复用的"上下文管理选型决策矩阵"
- 明确 WeClaw 的技术选型推导过程
适合读者:正在构建 LLM Agent 项目、需要做上下文管理技术选型的架构师和开发者
阅读时长:约 12 分钟
关键词:架构对比、Hermes-Agent、CoPaw、压缩流水线、记忆分层、技术选型
一、三个项目,三种哲学
[图片: 三架构对比图 | 生成方式: 文生图 PROMPT: "Three architecture diagrams side by side: Left shows a linear pipeline flow (compress after each call), Center shows a three-layer memory stack (working/episodic/semantic), Right shows a simple truncation bar, each with distinct color coding and flow arrows, clean technical style, white background"]
1.1 项目背景速览
| 项目 | 定位 | 技术栈 | 核心场景 |
|---|---|---|---|
| Hermes-Agent | 通用 AI Agent 框架 | Python + LiteLLM | 多工具编排的复杂任务 |
| CoPaw | 桌面 AI 助手 | TypeScript + AgentScope | 日常对话+长期记忆 |
| WeClaw | 跨平台 AI 助手 | Python + PySide6 + FastAPI | 桌面端+PWA+CLI 多端协同 |
三个项目的共同点:都面临"上下文窗口不够用"的问题。但各自的解决思路截然不同。
二、Hermes-Agent:一体化压缩流水线
2.1 核心架构
Hermes 采用 Pluggable Engine 架构,上下文管理核心由两个组件协作:
- ContextEngine(抽象基类,207 行):定义压缩接口,支持插件替换
- ContextCompressor(核心实现,1415 行):包含完整的压缩逻辑
# Hermes 的压缩触发时机:每次 API 调用之后
class PluggableEngine:
async def run(self, messages):
while not done:
# 1. 调用 LLM
response = await self.call_llm(messages)
# 2. 执行工具
results = await self.execute_tools(response)
# 3. 追加结果
messages.extend(results)
# 4. 关键:每轮结束后触发压缩检查
messages = await self.context_engine.compress(messages)
return response
2.2 三阶段预处理
在调用 LLM 生成摘要之前,Hermes 先做三轮无损预处理,降低 LLM 输入 token:
原始消息 → [Pass 1: MD5 去重] → [Pass 2: 信息摘要替换] → [Pass 3: 参数截断] → LLM 摘要
- Pass 1:相同 tool_result 只保留最后一条(基于 MD5 哈希)
- Pass 2:为 20+ 种高频工具生成一行摘要(如
search → "搜索 'python async',返回 5 条结果") - Pass 3:截断超过 500 字符的 tool_call arguments
2.3 三级容灾
Level 1: 辅助模型 (AuxiliaryClient)
↓ 失败/超时
Level 2: 主模型重试 (30s 超时, 429/503 快速跳过)
↓ 也失败
Level 3: 静态回退 (提取文件路径+工具名, 零 LLM 成本)
这是 Hermes 最精妙的设计:无论如何都不会让压缩失败导致上下文丢失。
2.4 设计哲学总结
"宁可多花一分钱,也不丢一条消息" —— 以可靠性为第一优先级,通过容灾和预处理控制成本
三、CoPaw:记忆分层架构
3.1 核心架构
CoPaw 基于 AgentScope 框架,上下文管理由 MemoryCompactionHook(214 行)驱动,委托给 ReMeLightMemoryManager(391 行)执行。
┌──────────────────────────────────────┐
│ 工作记忆 (Working Memory) │
│ 当前会话的最近 N 轮消息 │
│ recent_n 参数控制保留数量 │
├──────────────────────────────────────┤
│ 情景记忆 (Episodic Memory) │
│ 历史对话的结构化摘要 │
│ 异步生成,持久化到文件系统 │
├──────────────────────────────────────┤
│ 语义记忆 (Semantic Memory) │
│ 长期知识库,向量+全文检索 │
│ 基于 ChromaDB / local store │
└──────────────────────────────────────┘
3.2 双压缩通道
CoPaw 的压缩分为两条并行通道:
通道 A:Tool Result Compact(工具结果压缩)
# 对旧消息中的 tool_result 做专门处理
def compact_tool_results(self, messages, recent_n=20):
old_messages = messages[:-recent_n]
for msg in old_messages:
if msg["role"] == "tool" and len(msg["content"]) > self.old_max_bytes:
msg["content"] = truncate_with_summary(msg["content"])
return messages
通道 B:Context Compact(上下文整体压缩)
# 将旧消息压缩为摘要
async def compact_context(self, messages, recent_n=20):
old_messages = messages[:-recent_n]
summary = await self.reme_client.summarize(old_messages)
# 摘要写入向量数据库(支持后续语义检索)
await self.memory_store.add(summary)
return [summary_msg] + messages[-recent_n:]
3.3 ReMeLight 语义检索
CoPaw 的独特能力是跨会话的语义检索:
# 在新对话开始时,召回相关的历史记忆
async def recall(self, query, top_k=5):
results = await self.reme_client.search(query, top_k=top_k)
# 将相关记忆注入到当前上下文中
context_msgs = [format_recall(r) for r in results]
return context_msgs
3.4 设计哲学总结
"记忆不应该消失,只是需要被检索" —— 以长期记忆为核心,通过语义检索在有限窗口中召回最相关的信息
四、WeClaw 旧方案:截断优先策略
4.1 核心架构
WeClaw 旧方案的上下文管理逻辑直接嵌入在 DialogManager(原文件约 1352 行)中,采用简单的截断策略:
# WeClaw 旧方案的核心逻辑
class DialogManager:
def get_messages(self, session_id, max_tokens=None):
messages = self._load_from_db(session_id)
# 策略 1:消息数超限 → 截断
if len(messages) > MAX_MESSAGES:
messages = messages[-MAX_MESSAGES:]
# 策略 2:Token 超限 → 截断
total_tokens = estimate_tokens(messages)
if total_tokens > max_tokens:
# 从头部开始删除,直到不超限
while estimate_tokens(messages) > max_tokens:
messages.pop(0)
return messages
4.2 为什么在 128K 窗口下失效?
旧方案的阈值计算公式是:threshold = max(32K, context_window * 0.6)
| 模型窗口 | 计算阈值 | 实际问题 |
|---|---|---|
| 32K | 32K | 合理,会正常触发 |
| 128K | 76.8K | 偏大,压缩触发较晚 |
| 1M | 600K | 过大,几乎不会触发 |
| 2M | 1.2M | 超过绝对上限,永远不触发 |
核心问题:阈值基于模型的原始窗口大小,而非实际有效窗口。一个 1M 窗口的模型,实际安全可用空间可能只有 600-800K,但阈值已经设为 600K,加上 System Prompt 和输出预留,留给压缩的空间几乎为零。
4.3 旧方案的另一个致命缺陷:无摘要
# 旧方案截断后,早期消息永久丢失
messages = messages[-MAX_MESSAGES:]
# 用户 20 轮前的分析请求?没了。
# 用户之前讨论的量化策略?没了。
# 之前搜索到的关键信息?没了。
4.4 设计哲学总结
"简单就是好——直到不够用" —— 在 32K 窗口时代够用,但在大窗口时代完全失效
五、关键差异表 —— 七个维度的横向对比
[图片: 架构对比热力图 | 生成方式: 文生图 PROMPT: "A heatmap comparison table with 7 rows (dimensions) and 3 columns (projects), cells colored from red (poor) to green (excellent), with dimension labels on the left and project names on top, clean data visualization style"]
5.1 压缩时机
| 项目 | 触发时机 | 优劣分析 |
|---|---|---|
| Hermes | API 调用之后 | 在自然边界触发,不干扰推理循环 |
| CoPaw | 消息追加之后 (Hook) | 时机灵活,但可能在工具执行中间触发 |
| WeClaw 旧 | 获取消息之时 | 实时但可能截断在 tool_call 中间 |
5.2 Tool 配对保护
| 项目 | 策略 | 可靠性 |
|---|---|---|
| Hermes | 移除孤儿 tool_call + 添加 stub result | 高 |
| CoPaw | 间接保护(按轮次保留) | 中 |
| WeClaw 旧 | 无保护 | 低(可能切断配对) |
5.3 旧结果处理
| 项目 | 策略 | 效果 |
|---|---|---|
| Hermes | 三阶段无损预处理(去重+摘要+截断) | 压缩前减少 40-60% token |
| CoPaw | 双通道(tool compact + context compact) | 工具结果专门优化 |
| WeClaw 旧 | 直接丢弃 | 零成本但信息全失 |
5.4 容灾机制
| 项目 | 容灾级别 | 极端情况表现 |
|---|---|---|
| Hermes | 三级(辅助→主模型→静态) | 最坏情况保留结构化文件摘要 |
| CoPaw | 单级(LLM 摘要) | LLM 失败时降级为简单截断 |
| WeClaw 旧 | 无 | 直接截断,无容灾 |
5.5 反抖动
| 项目 | 机制 | 说明 |
|---|---|---|
| Hermes | 连续 2 次节省 <10% 时暂停 | 避免反复压缩相同内容 |
| CoPaw | 标记压缩而非删除 | 天然避免重复压缩 |
| WeClaw 旧 | 无 | N/A |
5.6 System Prompt 管理
| 项目 | 策略 | 说明 |
|---|---|---|
| Hermes | 无显式管理 | SP 管理不在上下文引擎职责内 |
| CoPaw | 无显式管理 | 依赖框架层 |
| WeClaw | 三级预算控制(总 40K + 技能 8K + 文件 3K) | 最精细的 SP 管理 |
5.7 跨会话记忆
| 项目 | 支持 | 说明 |
|---|---|---|
| Hermes | 不支持 | 专注单会话压缩 |
| CoPaw | 支持(ReMeLight 向量检索) | 核心差异化能力 |
| WeClaw | 不支持 | 通过 SQLite 存储历史但无语义检索 |
六、决策推导 —— WeClaw 为什么选择"借鉴而非照搬"
6.1 选型决策树
[图片: 架构选型决策树 | 生成方式: 文生图 PROMPT: "A decision tree flowchart for choosing LLM context management architecture, branching by: context window size (small/medium/large), latency requirement (low/medium/high), cost budget (tight/moderate/flexible), and multi-session support (yes/no), clean technical diagram, white background"]
6.2 排除 CoPaw 的推理
WeClaw 排除记忆分层方案的三个理由:
- 场景不匹配:WeClaw 用户通常在单一会话中完成完整任务,无需跨会话语义召回
- 复杂度过高:引入向量数据库(ChromaDB)增加了部署依赖和维护成本
- 质量不可控:向量检索的"假阳性"召回可能误导模型,尤其在技术对话场景中
# 向量检索的风险示意
query = "中芯国际的投资价值"
results = await vector_store.search(query, top_k=3)
# 可能召回:
# 1. "中芯国际的财报分析" ← 相关 ✓
# 2. "芯片行业趋势报告" ← 半相关 △
# 3. "国际形势对科技股的影响" ← 不相关但向量相似 ✗
6.3 借鉴 Hermes 的理由
WeClaw 选择 Hermes 流水线模式作为基础,并做本地化适配:
- 压缩时机自然:API 调用后的边界不干扰 ReAct 推理
- 可靠性设计完备:三级容灾的理念与 WeClaw "不失忆"的目标一致
- 可扩展性好:Pluggable Engine 模式便于未来替换或增强
6.4 WeClaw 的差异化增强
在借鉴 Hermes 的基础上,WeClaw 做了四项独特增强:
| 增强项 | Hermes | WeClaw |
|---|---|---|
| SP 管理 | 无显式管理 | 三级预算控制 + 膨胀监控 |
| 压缩触发 | 固定阈值 | 分档自适应(三档 + 封顶) |
| 异步压缩 | 同步 | 异步后台 + 快照 hash 保护 |
| 截断通知 | 无 | 自动追加到 SP 中提示模型 |
七、总结与展望
7.1 核心要点回顾
- 三种哲学各有侧重:Hermes 重可靠性、CoPaw 重长期记忆、WeClaw 旧方案重简单
- 七个维度决定选型:压缩时机、Tool 保护、旧结果处理、容灾、反抖动、SP 管理、跨会话
- 借鉴不等于照搬:WeClaw 在 Hermes 基础上增加了分档阈值、异步压缩、SP 管理等差异化能力
7.2 选型速查表
你的项目需要跨会话语义检索吗?
├── 是 → 考虑记忆分层方案(类似 CoPaw)
└── 否 → 你的模型窗口 >= 128K 吗?
├── 是 → 考虑压缩流水线(类似 Hermes/WeClaw 新方案)
└── 否 → 简单截断可能就够了
下期预告:《压缩阈值:为什么 1M 窗口下 AI 从不压缩》
- 旧公式
threshold = max(32K, window * 0.6)的致命缺陷 - 三档自适应策略的设计与实现
- 为什么阈值需要"绝对上限封顶"
- 从代码到测试的完整验证过程
敬请期待!
附录 A:代码规模对比
| 项目 | 核心文件 | 行数 | 职责 |
|---|---|---|---|
| Hermes | context_engine.py | ~1415 行 | 压缩+预处理+容灾 |
| CoPaw | memory_manager.py | ~391 行 | 记忆管理+向量检索 |
| WeClaw | context_engine.py | ~852 行 | 压缩+截断+SP 管理 |
附录 B:参考资料
- Hermes-Agent GitHub
- CoPaw GitHub
- AgentScope Framework
- 上一篇:《当 AI 在百万 Token 长对话中失忆》(本系列第 46 篇)
- 下一篇:《压缩阈值:为什么 1M 窗口下 AI 从不压缩》(本系列第 48 篇)
版权声明:本文为 CSDN 博主「翁勇刚」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。