news 2026/9/5 21:07:16

DeerFlow 行为测试实战:用 Monocle Test Tools 为 SuperAgent 编写基于 Trace 的离线与在线断言

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeerFlow 行为测试实战:用 Monocle Test Tools 为 SuperAgent 编写基于 Trace 的离线与在线断言

DeerFlow 行为测试实战:用 Monocle Test Tools 为 SuperAgent 编写基于 Trace 的离线与在线断言

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

DeerFlow 的行为测试套件(backend/tests/monocle/)解决一个长期存在的 Agent 测试难题:Agent 的行为(路由、工具选择、token 成本)是运行时产物,难以用传统单测锁定。本篇基于该目录的 README 与源码,讲解如何用 Monocle 将一次真实运行记录为结构化 trace,再把 trace 变成"黄金行为参考",用离线测试固化"正确行为长什么样",用在线(live)测试强制后续每次运行都复现该行为。读完后,你能够理解这套两层测试的分工与取舍,掌握monocle_trace_asserter的完整流式断言 API,并知道如何为新问题编写自己的行为守护测试。

核心理念:Trace 不是样本数据,而是带标签的正确性参考

该套件的工作方式在 README 中定义得很清晰:

  1. 用 Monocle 对 Agent 插桩,让它回答一个具体问题;
  2. 当运行结果符合预期(调用了预期的 agent、发出了预期的工具调用、token 消耗合理)后,把这次运行捕获为 trace;
  3. 这个 trace 就是该问题的"黄金参考"——它记录的是正确行为,而不是一份普通样本数据;
  4. 把它转成断言(离线示例演示了具体做法),再把同样的断言指向在线 Agent对同一问题的真实运行,于是之后每次运行都必须复现当初的行为。

原文档对此的概括是:离线测试用于"锁定好行为的样子"(pin down what good looks like),在线测试负责"对真实运行强制执行它"。

Monocle 记录的 trace 是结构化的:一次 agent 调用、每一次工具调用、token 用量、时间戳,全部以 OpenTelemetry 风格的 span 表示。仓库里提交的 示例 trace 共包含 17 个 span,其span.type分布为:1 个workflow、1 个agentic.turn、1 个agentic.invocation(对应langgraph.graph.state.CompiledStateGraph.stream)、6 个agentic.tool.invocation(1 次web_search+ 5 次web_fetch)、4 个inference.framework与 4 个inference.modelapi。断言正是读这些 span 的nameentity.*.name(agent 名、工具名)以及 input/output 事件来做的。

两层测试的分工:一个离线示例 + 两个在线守护

套件由两层组成(见 test_deerflow.py 的文件头注释):

  • 一个离线示例test_assertion_api_example:从文件加载已录制的 trace,把该套件用到的全部流式断言词汇集中展示在一处。它不需要任何密钥和网络。但因为断言对象是冻结的 JSON,它守护的是trace 格式与 asserter 接线,而不是 DeerFlow 的行为本身——应当把它当作编写自定义断言的"工作样例"(worked example)。
  • 两个在线测试test_web_research_livetest_sandbox_write_file_live:端到端驱动真实 Agent,断言真实运行发出的 trace。它们才是真正的行为守护:任何改动只要改变了路由、工具选择或 token 成本,就会在这里被捕获。它们通过MONOCLE_LIVE_TESTS=1显式 opt-in,默认跳过——因此即使在一份凭证和config.yaml齐全的检出中,普通运行也绝不会花模型 token、不触网、不写沙箱。

在线测试还有一处容易被忽略的设计:输出文本每次运行都会变化,因此在线断言只校验结构 + 宽松的 token 预算,并且刻意省略了时长断言——一个要做 LLM 调用与网络 I/O 的在线运行天然波动,墙钟时间断言只会带来 flaky。这一点在 test_deerflow.py#L84-L87 的注释中写得很明白。

目录结构与各文件职责

文件职责
test_deerflow.py离线示例 + 两个在线测试,外加一个守卫 live 开关行为的门控测试
conftest.pyrun_agentfixture(仅供 live 路径使用)
_helpers.py路径常量与run_deerflow()
traces/离线示例加载的已录制 trace
requirements.txt该套件的独立依赖

conftest.py顶部有一处刻意的设计:它把本目录插入sys.path(镜像 backend 根conftest.py的做法),让_helpers在任何 pytest import 模式下都可导入;而.env的加载被限定在 live fixture 内部,收集或运行离线测试时永远不会读取任何秘密

live_tests_enabled():默认关闭的门控

_helpers.py#L20-L30 中:

_TRUTHY = {"1", "true", "yes", "on"} def live_tests_enabled() -> bool: """... Off by default so the plain `pytest backend/tests/monocle/` run can never spend model tokens, hit the network, or write to a sandbox — even on a fully configured checkout where credentials and ``config.yaml`` are present.""" return os.getenv("MONOCLE_LIVE_TESTS", "").strip().lower() in _TRUTHY

这个门控本身还有一个自守护的测试test_live_gate_defaults_off(test_deerflow.py#L43-L55):用monkeypatch验证未设置变量时返回False、设为"1"时返回True、设为"0"时又回到False。这就是"普通pytest运行不可能发起模型调用、网络请求或沙箱写入"这一承诺的测试级证据。

run_agentfixture 与run_deerflow()

conftest.py#L21-L42 的run_agentfixture 按顺序执行三层检查,任何一层不满足就pytest.skip

  1. live_tests_enabled()为假 → 跳过(提示设置MONOCLE_LIVE_TESTS=1);
  2. deerflow不可导入(例如只装了 test-tools 的 venv)→ 跳过;
  3. config.yaml不存在 → 跳过。

通过后它load_dotenv(REPO_ROOT / ".env")(可选),然后返回run_deerflow。后者([_helpers.py#L33-L42)的要点是不做任何硬编码模型覆盖

def run_deerflow(message: str) -> str: from deerflow.client import DeerFlowClient client = DeerFlowClient(config_path=str(CONFIG_PATH)) return client.chat(message, thread_id=f"monocle-test-{uuid.uuid4().hex[:8]}")

模型完全由config.yaml解析,所以 live 测试走的是 DeerFlow 自己的模型解析路径——config.yaml可以选任意 provider(OpenAI、Anthropic、Gemini 等),没有任何硬编码的 key 要求。每次运行用一个随机thread_idmonocle-test-xxxx)隔离会话。DeerFlowClient本体位于 client.py,其 docstring 说明chat()在没有 checkpointer 时是无状态的、thread_id仅用于文件隔离——这正是 live 测试用一次性 thread 的底层原因。

插桩由谁负责?

_helpers.py 的头注释点明了分工:Monocle 插桩由 Test Tools 的 validator(随monocle_trace_asserterfixture 安装)所有,run_deerflow只负责驱动 Agent;已装好的插桩负责捕获运行中的 span。也就是说,测试代码里看不到任何显式的tracer.start_span——trace 的产出完全由monocle_test_tools接管。

提交的 Trace:为什么整体提交、为什么"改名即失败"

traces/web_research_ev_battery.json 是一次真实运行的完整、未修改的录制(约 192 KB、17 个 span),整体提交是为了让离线示例解析的是一份真迹而非手工拼造的 fixture。原文档明确指出它因此内嵌了录制当时点的 DeerFlow system prompt 以及该运行从网页抓取的内容(从 trace 内容可以印证:span 事件的data.input里就包含 "You are DeerFlow 2.0, an open-source super agent" 开头的系统提示,以及entity.2.name = gpt-4o这类录制时的模型信息)。其中不含任何凭证。

离线断言钉死在这份具体 trace 与monocle_apptrace0.8.8 的 span 形状上LangGraph这个 agent span 名、工具名、输入措辞,三者中任何一个被重命名都会让离线测试在行为未变的情况下失败。这就是它的取舍:它守护 trace 格式契约,代价是 prompt、工具或模型变化时必须重新录制 trace

离线示例:完整流式断言词汇一览

test_deerflow.py#L61-L81 的test_assertion_api_example是整个套件断言词汇的集中展示,断言对象是一份"固态 EV 电池调研"的录制运行:

def test_assertion_api_example(monocle_trace_asserter: TraceAssertion): # 从文件加载录制 trace(离线数据源) monocle_trace_asserter.with_trace_source("file", trace_path=EXAMPLE_TRACE) # agent 断言:名为 LangGraph 的 agent 被调用,且输入包含指定措辞 monocle_trace_asserter.called_agent("LangGraph").contains_input("solid-state EV batteries") # 输出断言:任意一个关键词出现在输出中即可 monocle_trace_asserter.contains_any_output("solid-state", "battery", "batteries", "EV") # 工具断言:指定 agent 下调用过 web_search monocle_trace_asserter.called_tool("web_search", "LangGraph") # 工具调用次数用"下限"而非精确值(源码注释见下) monocle_trace_asserter.called_tool("web_fetch", "LangGraph", min_count=2) # 否定断言:不该出现的工具 monocle_trace_asserter.does_not_call_tool("image_search", "LangGraph") # token / 时长预算 monocle_trace_asserter.under_token_limit(100_000) monocle_trace_asserter.under_duration(60, span_type="workflow")

其中called_tool("web_fetch", "LangGraph", min_count=2)的注释值得单独学习(test_deerflow.py#L75-L78):录制里实际有 5 次web_fetch,但断言的意图是"至少抓了几个来源";fetch 次数运行之间真实会波动,所以保持下限(floor)而不收紧到精确次数。这是写行为断言时"断言意图,而不是偶然数值"的一个具体示范。

在线测试:把同一套断言指向真实运行

两个在线测试展示了 live 路径的写法——用validator.test_workflow(run_agent, {"test_input": (...)})驱动 Agent,之后断言真实运行发出的 trace:

def test_web_research_live(monocle_trace_asserter: TraceAssertion, run_agent): """Live web-research path: the agent researches and uses ``web_search``.""" monocle_trace_asserter.validator.test_workflow( run_agent, {"test_input": ("Research the current state of solid-state EV batteries in 2025 and write a 1-page markdown briefing with sources.",)}, ) monocle_trace_asserter.called_agent("LangGraph").contains_input("solid-state EV batteries") monocle_trace_asserter.contains_any_output("solid-state", "battery", "batteries", "EV") monocle_trace_asserter.called_tool("web_search", "LangGraph") monocle_trace_asserter.under_token_limit(200_000) def test_sandbox_write_file_live(monocle_trace_asserter: TraceAssertion, run_agent): """Live sandbox path: the agent authors a file with ``write_file`` and stays off the web.""" monocle_trace_asserter.validator.test_workflow( run_agent, {"test_input": ("Write a Python script that prints the first 10 Fibonacci numbers and save it to a file named fib.py in the sandbox.",)}, ) monocle_trace_asserter.called_agent("LangGraph").contains_input("Fibonacci") monocle_trace_asserter.called_tool("write_file") monocle_trace_asserter.does_not_call_tool("web_search", "LangGraph") monocle_trace_asserter.under_token_limit(100_000)

两个 live 测试各锁定一条行为路径:研究类问题必须走web_search;沙箱写文件类问题必须调write_file且不上网does_not_call_tool("web_search", "LangGraph")是路由层面的否定守护)。token 预算在 live 下放宽到 200_000(研究)/ 100_000(写文件),比离线示例的 100_000 更宽松,正对应"live 波动更大"的取舍。

依赖隔离:为什么这套测试"不在 CI 里跑"

requirements.txt 只有两行实质内容,其头注释解释了隔离动机:

# Kept out of backend/pyproject.toml on purpose: monocle_test_tools hard-depends # on the ML eval stack (bert-score, sentence-transformers, transformers -> torch # + the CUDA wheels, ~48 packages, +950 lines in uv.lock). monocle_test_tools==0.8.8 python-dotenv>=1.0

monocle_test_tools硬依赖 ML 评测栈(torch、transformers、sentence-transformers 等),所以它是独立的 requirements 安装而非 backend 依赖——把这份重量挡在应用锁定的依赖之外。版本钉在 0.8.8 另有原因:离线示例用到的 file trace 数据源with_trace_source("file", trace_path=...)在 0.7.x 中不存在。

由此产生一个直接推论(README 原话):这些测试没有一条跑在 CI 里——make test会收集整个模块然后干净地跳过(pytest.importorskip("monocle_test_tools", ...),见 test_deerflow.py#L30-L34),连离线示例在内。这是一套按需(on-demand)套件:在改动 Agent 行为、工具或路由时,本地安装 requirements 后运行;或者在专门的 CI job 里装上这些依赖后运行。

另一个值得注意的接线细节:monocle_trace_asserterfixture 由monocle_test_tools自带的 pytest 插件提供,安装后通过pytest11entry point自动注册,无需任何pytest_plugins配置。

运行方式:完整命令与适用前提

从仓库根目录(pip 方式):

# from the repo root pip install -r backend/tests/monocle/requirements.txt # offline — no network, no keys; the live tests skip unless opted in pytest backend/tests/monocle/ # opt in to the live behavioural tests (real model calls + web requests) MONOCLE_LIVE_TESTS=1 pytest backend/tests/monocle/

按 backend 约定(在backend/下,用 uv):

uv pip install -r tests/monocle/requirements.txt uv run pytest tests/monocle/ # offline MONOCLE_LIVE_TESTS=1 uv run pytest tests/monocle/ # + live

行为契约总结(均来自 README 的 "Run" 一节):

  • live 测试是按设计 opt-in:没有MONOCLE_LIVE_TESTS=1时,即便检出中凭证和config.yaml齐全也会跳过,因此默认命令永远不花 token、不写沙箱;
  • opt-in 之后仍有兜底:DeerFlow app 不可导入或config.yaml缺失时依然跳过;
  • 模型凭证由配置选定的模型自身校验——config.yaml可选任意 provider,不存在硬编码的 key 要求;
  • DeerFlow 的web_search基于 DuckDuckGo,不需要自己的 key。

如何添加你自己的行为测试

README 给出的四步流程,结合源码即可落地:

  1. 在 Monocle 下运行 DeerFlow,捕获一次你满意的运行 trace(Monocle 默认把 trace JSON 写到.monocle/)。这与仓库的 tracing 配置一致:MONOCLE_TRACING开关、MONOCLE_EXPORTERS选择导出器(默认file,即.monocle/下的 trace JSON),实现与配置细节见 tracing/monocle.py、tracing 模块说明 及其测试 test_monocle_tracing.py;
  2. 离线示例:把 trace 移入 traces/,用monocle_trace_asserter.with_trace_source("file", trace_path=path)加载;
  3. 行为测试:通过run_agentfixture 驱动在线 Agent,再调用monocle_trace_asserter.validator.test_workflow(run_agent, {"test_input": (...)})
  4. 用流式 API 断言:called_agent(...)called_tool(...)(支持min_count下限)、contains_input/contains_any_output(...)under_token_limit(...)under_duration(..., span_type="workflow"),以及否定式的does_not_call_tool(...)

实践上,先照抄test_assertion_api_example的形状把断言在冻结 trace 上跑通,再把同一组断言从文件源切到test_workflow的 live 源——这正是该套件"离线固化、在线执法"两步法的具体操作形态。

边界说明:为什么内容/质量评测没有接入

套件只覆盖结构性断言。内容/质量评测(evals)刻意未接入,原因是当前版本monocle_test_tools的本地 eval 与 file 加载的 trace 无法组合(README "Evaluations (note)" 一节):

  • 声明式的test_spans[].evalcomparer:"metric")会被validator.validate()静默忽略——_evaluate_span没有任何调用点,于是本应失败的断言(如缺失必需关键词)会"空洞地"通过;
  • 流式check_eval()路径是按 Okahu eval-service 的签名(filtered_spans=)接的线,而本地 evaluator(如keyword_presence)不接受该参数,会抛TypeError

因此选择省略本地 eval,而不是把它们加进来变成空洞的 no-op——宁缺毋滥的原则同样体现在评测层。若需要对回答内容做质量打分,Okahu eval 层(需要OKAHU_API_KEY)仍是可选项;这也与 tracing/monocle.py 中okahuexporter 要求OKAHU_API_KEY的配置校验相呼应(见 test_monocle_tracing.py#L203-L216)。

小结

这套backend/tests/monocle/套件提供了一个可复制的 Agent 行为测试范式:用录制 trace 作为"黄金行为参考",用离线层守护 trace 格式与断言接线、用显式 opt-in 的在线层守护真实行为;依赖被刻意隔离在独立的requirements.txt,默认运行零副作用(不花 token、不触网、不写沙箱)。当你在 DeerFlow 中修改了路由逻辑、工具集或 prompt 之后,这套按需套件就是验证"Agent 是否仍按预期行为"的合适工具:pip install -r backend/tests/monocle/requirements.txt,先跑离线,再视需要以MONOCLE_LIVE_TESTS=1打开在线守护。

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 21:06:39

如何5分钟还原Windows 11经典界面?ExplorerPatcher完整上手指南

如何5分钟还原Windows 11经典界面?ExplorerPatcher完整上手指南 【免费下载链接】ExplorerPatcher This project aims to enhance the working environment on Windows 项目地址: https://gitcode.com/GitHub_Trending/ex/ExplorerPatcher 刚把电脑升到 Wind…

作者头像 李华
网站建设 2026/9/5 21:02:52

Python实战项目别盲目刷:按阶段拆练才是高效提升就业技能的关键

先给结论:单靠“刷完 202 个项目”不能保证就业,但如果你把 202 个项目当“训练题库”来拆、来改、来总结,那它确实比啃语法书管用得多。这份清单的价值不在于让你一个个“抄”完,而在于让你按阶段、按技术方向挑着练,…

作者头像 李华
网站建设 2026/9/5 21:02:44

Wand-Enhancer 四步补丁指南:解锁 WeMod 本地增强与手机远程控制

Wand-Enhancer 四步补丁指南:解锁 WeMod 本地增强与手机远程控制 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 如果你也遇到过这种情…

作者头像 李华
网站建设 2026/9/5 21:01:32

Rocky Linux 上让 Codex CLI 调用 DeepSeek-V4-Pro 实战

说明:这是一篇纯技术实操向博文,目标是解决“Rocky Linux 服务器上让 Codex CLI 正常调用 DeepSeek-V4-Pro”这件事。全程只聊技术,不涉及任何网络边界或敏感话题,请放心阅读、直接按步骤抄。我在两套 Rocky Linux 环境&#xff0…

作者头像 李华