echo-agent 前身为 2025 年 11 月启动的个人助理项目 fubot,最初面向长期陪伴型个人智能体,围绕认知记忆、上下文延续、用户偏好沉淀、任务闭环与持续自我优化展开。随着真实场景迭代,项目逐步形成多入口接入、统一事件模型、消息总线、Agent Loop、多模型抽象、工具调用、MCP 接入、任务调度、权限审批、运行轨迹、长期记忆和受控自演进等能力。目前已支持微信、QQ、CLI、Gateway、Webhook、Cron 等入口,服务用户超过 20 万、累计下载超过 50 万,是面向长期运行、记忆增强和可持续成长智能体的开源 Agent Runtime。

项目地址:https://github.com/fuyuxiang/echo-agent

你改了一段工具描述,Agent 终于会主动调用 knowledge_search 了。又顺手改了上下文压缩模板,长对话看起来更省 token。

第二天,另一个用例开始失败:模型把工具参数拼错,审批逻辑没有触发,Gateway 的 wait 请求迟迟等不到 final event。

这就是 Agent 测试的真实难点。它不是“有没有测试”,而是测试什么才算覆盖了系统真正的风险

本篇只讲一个点:Agent 测试的目标不是追求漂亮覆盖率,而是把模型、工具、状态、权限和通道共同组成的行为,固定成可回归的工程契约。

问题入口

普通 Web 服务的行为边界相对清楚:请求进来,读写数据库,返回 HTTP 响应。测试可以围绕输入、数据库状态和输出结构展开。

Agent 不一样。用户说“帮我修复测试失败”,系统可能经过上下文构造、模型推理、工具选择、审批判断、命令执行、会话保存、消息投递和后台整理。最终回答只是结果的一部分,很多风险藏在过程里。

如果只断言最终文本等于某句话,测试会非常脆弱。模型表达略有变化就失败,但真正危险的工具越权可能没有被发现。

如果只测底层函数,又覆盖不到 Agent 的真实工作方式。PathPolicy 单独能拒绝越界路径,不代表模型工具调用进入执行器前一定经过了审批。

会返回正确文本,只说明这一轮看起来没错;能否长期演进,要看关键能力有没有进入可回归边界。

所以 Agent 测试不能只问“答案对不对”。它还要问:是否调用正确工具,是否遵守权限,是否保存状态,是否处理异步取消,是否能解释每次行动依据。

测试分层

Agent 系统仍然需要传统测试金字塔,但这座金字塔在 Agent 里会变形。

底层是确定性测试。路径规则、状态迁移、配置覆盖、数据模型序列化、文本拆分、token 估算,这些行为不应该随着模型变化而变化。

中间是集成测试。MessageBus、Session、Storage、Pipeline、Tools、Security、Memory、Skills、Gateway、Scheduler、Tasks、MCP 这些模块必须一起验证契约。

上层是少量端到端测试。它验证真实入口是否连通:CLI 启动、Gateway 请求、WebSocket 推送、调度任务投递、多 Agent 委派、MCP 工具接入。

Agent 还需要第四层:行为评估。它处理的是不适合精确断言的语义质量,例如回答是否包含关键事实,是否引用知识库结果,是否在合理迭代次数内完成。

层级主要对象断言方式边界
单元测试规则、状态机、数据模型精确断言不判断自然语言质量
集成测试模块契约、事件流、存储协作结构化断言不覆盖全部真实平台
端到端测试用户可见主链路入口到出口的链路断言不负责细粒度定位
行为评估工具选择、事实覆盖、效率指标与数据集不替代安全测试

为了不停留在抽象层面,下面以 echo-agent 的实现为例。

echo-agent 当前测试目录覆盖 bussessionstoragepipelinetoolssecuritymemoryskillsknowledgemodelsmcpgatewayschedulertasksplanninga2aevaluation 等子系统。

这份结构的价值不是文件多,而是每个测试文件都在回答一个问题:这个模块给系统新增了哪类不可破坏的契约?

契约优先

Agent 测试不应该机械地按“新增一个类就测一个类”组织,而应从契约开始。

新增工具的契约是:schema 是否准确,参数是否能校验,副作用是否经过审批,执行结果是否结构化,失败是否能被模型和人理解。

新增通道的契约是:平台消息是否能无损变成 InboundEvent,统一输出是否能按平台能力表达,通道失败是否不破坏消息总线。

新增 provider 的契约是:不同厂商响应能否稳定转成统一的 LLMResponseToolCallRequest,错误、重试、流式输出和 usage 统计是否能被上层一致处理。

新增长期状态的契约是:状态能否持久化、迁移、并发访问和异常恢复,并且不会污染不该污染的上下文。

最有价值的测试不是证明某段代码存在,而是证明某个架构边界没有被破坏。

以“修复测试失败”为例:意图是修复失败;状态包括仓库文件、测试日志、依赖版本、历史会话;能力包括读文件、运行测试、改代码、提交补丁。测试不能只看最后有没有一句“已修复”,而要验证模型是否看到必要上下文,是否调用允许工具,工具结果是否回灌到下一轮推理,失败时是否留下可诊断信息。

测试替身

Agent 测试必须承认模型输出不完全确定。即使 temperature 为 0,不同 provider、不同模型版本、不同上下文压缩结果,也可能让输出发生变化。

解决办法不是把所有行为绑定在固定自然语言上,而是把不确定性关进边界。

FakeProvider 固定模型响应,FakeTool 固定工具结果,tmp_path 固定文件系统范围,mock channel 固定平台回执,mock approval 固定审批结果,短周期 scheduler 或 fixed clock 固定时间推进。

一个最小测试可以写成这样:

provider = FakeProvider([
    LLMResponse.tool_call("read_file", {"path": "tests/test_app.py"}),
    LLMResponse.text("已定位断言失败"),
])
tool = FakeTool("read_file", result="assert actual == expected")
approval = MockApproval(allow_readonly=True)
​
event = InboundEvent.text_message(
    channel="test",
    sender_id="u1",
    chat_id="c1",
    text="帮我分析测试失败",
)
​
result = await agent_loop.handle_event(event)
​
assert approval.checked("read_file")
assert tool.called_with(path="tests/test_app.py")
assert result.saved_to_session is True
assert result.outbound_event.kind == "final"

这段伪代码不关心模型最后怎么措辞。它关心的是系统契约:工具有没有进入审批,参数有没有传对,结果有没有保存,最终事件有没有投递。

fake provider 的核心价值,是把“模型是否聪明”从“系统是否正确”中分离出来。系统正确性先由测试替身测稳,真实模型质量再交给评估集观察。

阶段边界

旧式 AgentLoop 常把上下文构造、模型调用、工具执行、历史保存和消息发送写在一个大函数里。这种写法能跑,但测试失败时很难定位。

echo-agent 将处理过程拆成 ContextStageInferenceStageResponseStage,测试边界因此更清晰。

ContextStage 测系统如何“看见世界”:输入事件如何加载会话,历史消息如何进入上下文,记忆、知识、技能和运行元数据如何注入,token 压力下是否触发压缩。

InferenceStage 测系统如何“推理与行动”:ready tools 是否经过过滤,模型路由候选是否按健康状态生成,工具调用循环是否遵守最大轮数,工具结果是否以正确格式反馈给模型。

ResponseStage 测系统如何“落盘与输出”:最终回答是否保存到会话,输出是否通过 MessageBus 投递,后台整理任务是否在回答后启动,错误响应是否保留足够上下文。

阶段化测试的收益很直接:失败时能判断问题来自哪个阶段。

安全回归

Agent 安全测试要默认怀疑模型。模型可能调用错误工具,传错路径,重复执行,相信注入内容,忽略审批提示。

echo-agent 中,ApprovalGate 位于工具执行前,综合风险分类、权限策略、路径策略、工具策略、Smart Approval 和审批结果。这里的测试重点不是证明“正常操作能成功”,而是证明“危险操作能被拒绝”。

安全对象允许路径拒绝路径
路径策略工作区内读取父目录跳转、符号链接越界、敏感路径
工具权限低风险只读工具不在 allowed_tools 中的工具
审批系统已批准中风险操作高风险工具、审批超时、用户拒绝
MCP 接入只读 server 工具注入式描述、冲突工具名、协议异常
多 Agentworker 使用交集工具worker 调用 blocked tools、递归越界

拒绝路径必须返回可解释错误,不能崩溃,也不能沉默。允许路径也要测,因为过度保守会让 Agent 不可用。

生产级 Agent 至少要有这些可检验项:tool call trace,工具风险分级,只读/低风险写/高风险写区分,审批节点,路径策略,失败重试与取消处理,任务状态持久化,评估集与回归测试,以及每次行动依据的可复盘记录。

长期状态

记忆、技能、任务、工作流和调度器让 Agent 不再是一次性问答系统,也让测试多了时间维度。

记忆测试要使用临时存储,覆盖写入、检索、更新、删除、压缩、审查和后台 consolidation。尤其要验证无关记忆不会被检索进上下文,低置信度或过期记忆不会影响回答,用户记忆和环境记忆不会混淆。

技能测试要覆盖 agentskills.io 兼容布局、名称规则、只读 builtin 根、用户可写根、允许子目录、辅助文件大小限制、原子写入和删除。上下文注入时只应注入技能摘要,不能把所有 SKILL.md 正文无条件塞进模型。

任务、工作流与调度测试要覆盖状态迁移、重试、取消、暂停、恢复、step 依赖和 CRON、INTERVAL、ONCE、EVENT、CONDITION 触发器,并避免依赖真实长时间等待。

这些测试保护的是 Agent 的长期行为,避免系统在夜间任务、重试、长会话压缩和状态迁移中悄悄退化。

CI 与回放

测试数量上来以后,不能每次提交都跑所有东西。分层 CI 是 Agent 工程的现实需要。

快速层适合每次提交,覆盖单元测试、配置、数据模型、路径策略和文本处理。集成层适合 PR,覆盖 bus、pipeline、tools、session、storage、memory、skills、workflow、gateway。安全层应在 PR 和发布前运行,路径越界、危险工具、审批、MCP 安全和权限策略不应被跳过。扩展层适合定时或发布前运行,覆盖真实 provider smoke test、端到端 Gateway、长会话压缩和较大 eval 数据集。

如果 CI 时间过长,优先拆层,而不是删除测试。Agent 的错误往往跨模块传播,删掉回归测试会让后续演进成本迅速上升。

回放测试也应该进入长期策略。线上 trace 或录制轨迹可以重新执行,用来检查新版本是否保持行为:消息、上下文、工具调用、审批结果、状态变化和最终输出是否仍然符合预期。

回放不要求完全复现外部世界。工具响应可以录制,模型输出可以固定或重新生成,外部时间可以模拟,用户审批可以脚本化。关键是保留轨迹结构,让复杂多轮问题能沉淀为可重复测试。

小结

Agent 测试不是把随机性消灭掉,而是把随机性关进明确边界。

确定性规则用单元测试固定;模块协作用集成测试固定;入口链路用少量端到端测试固定;语义质量用评估集管理;真实失败用回放测试沉淀;安全边界用允许路径和拒绝路径同时校准。

echo-agent 的测试策略背后有一个朴素判断:Agent 架构会持续变化,但系统契约不能随意变化。只要工具执行前必须审批、危险路径必须拒绝、会话状态必须保存、最终事件必须投递、评估集必须回归,这个系统就有持续演进的底座。

测试最终服务的不是测试报告,而是架构信心。它告诉开发者哪里可以安全修改,哪里必须阻断发布,哪里只是模型波动,哪里暴露了结构问题。没有这套回归机制,Agent 越强,越难维护;有了它,能力增长才不会变成不可控的复杂度。

(全篇完)


本文为 echo-agent 设计笔记系列第 30 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣,欢迎加入技术交流群参与日常讨论。下一篇我们将探讨 《Agent 架构的长期演进与维护》,敬请期待。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐