mcpField
N
n8n:create-instance-ai-eval

n8n:create-instance-ai-eval

github.com/n8n-io测试与质量209.2k132 次安装今天更新
代码仓库

关于 n8n:create-instance-ai-eval

用于编写和校准 Instance AI 评估用例,并将其推送到指定 LangTracer 套件的技能。内容涵盖用例类型、从真实失败中取材,以及依据实际构建结果验证预期。

主要功能

  1. 01编写工作流和 Agent 评估用例
  2. 02从五种用例类型中选择
  3. 03从真实失败中寻找用例
  4. 04依据实际构建校准预期
  5. 05推送用例至指定 LangTracer 套件
  6. 06支持检查点或自主工作模式

适用场景

  1. 01为工作流构建创建评估
  2. 02测试多轮对话行为
  3. 03将真实追踪中发现的能力缺口编入用例
  4. 04将用例发布到合适的 LangTracer 套件

MCP 相关

技能说明如何使用 LangTracer MCP 发现和检查对话、聚类及评估数据,并使用 LangSmith 核验原始追踪。它也提及用于套件和用例操作的 MCP 工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/n8n-io/n8n --skill n8n:create-instance-ai-eval

安装后在 Claude Code 中输入/n8n:create-instance-ai-eval确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

case-shapes.md
running-evals.md
sourcing-cases.md

详细介绍

创建 Instance AI 评测

每个评测都是一个 JSON 用例。在 packages/@n8n/instance-ai/evaluations/data/workflows/ 中编写工作流用例。在 packages/@n8n/instance-ai/evaluations/data/agents/ 中编写独立的 Agent 用例,并遵循 create-agent-builder-eval 技能。 磁盘加载器会自动发现这两个目录中的 *.json 文件。LangTracer 套件是用例的持久存放位置。用例需通过 harness/schema.ts 验证(.strict() — 未知键会导致加载失败)。评测 README 提供了完整的字段参考;本技能着重介绍如何编写。

现在不再推荐将新的用例 JSON 提交到仓库。 在本地编写文件(不提交),使用真实构建进行校准,然后通过 eval:langtracer-push 推送到 LangTracer 套件(参见推送到 LangTracer 套件)—— 即驱动者选择的套件(参见先询问目标套件)—— 可以是 n8n CI 运行的汇总用例集 baseline,也可以是 agents 等专用能力套件。 套件是用例的存放位置;评测 CLI 会通过 --source langtracer 从中读取用例。你仍需编写 JSON 文件——它只是推送的输入,而不是要提交的构件。

种子用例。 inline 种子会与用例一起推送 — 用例写入 API 会原样存储它,因此套件和其他用例一样,是它的存放位置。只有 seed.mode: "replay" 用例会被拒绝(列在 skipped: 下):它会在运行时根据 LangSmith 跟踪记录重建,因此在该跟踪记录被清理后就会失效,且没有持久存放位置。也不要提交 replay 用例 — 应从中派生合成用例。参见 case-shapes.md。

先询问目标套件

在获取素材、起草或运行任何内容之前,先询问驱动者本次会话中创建的每个评测应放入哪个 LangTracer 套件。 两种自主级别都要询问,包括自主模式。绝不要自行选择套件,也不要默默使用默认套件。将用例推送到错误的套件,会导致它在错误的 CI 流程中运行,并向错误的团队发出警报。

在同一条消息中同时询问套件和自主级别。推荐使用 baseline:

  • baseline(推荐):通用用例集。Instance AI (INS) 团队会监控它,并且它每晚运行。
  • 其他任何套件,例如 agents 或 node-gauntlet:调用 list_suites 并提供实时列表。不要使用硬编码列表,因为套件会新增或停用。

规则:

  • 驱动者的回答适用于本次会话中的所有用例。不要针对每个用例重复询问。
  • 如果驱动者已在请求中指定套件,用一行复述并继续。不要再次询问。
  • 确认套件类型与用例相符。只有允许此类型的套件才能接收构建错误(capability_gap)用例。如果套件不允许,请告知驱动者并重新询问。
  • 在最终决策记录中以链接形式注明所选套件的 slug。

先设定自主程度

在查找、起草或运行任何内容之前,先判断操作者希望参与到什么程度,并复述确认。 此技能有两种自主程度。如果请求已明确说明级别(例如“只需编写并自行校准”与“每一步都先停下来问我”,或明确指定模式),就采用该级别,用一行说明,并指出如何切换(例如“说‘校准时停下来问我’即可添加检查点”)。如果不明确,就先问操作者一个问题,请其在两种级别中选择,然后再开始工作。

此技能有四个自然的决策关口——选择(要编码哪种真实故障)、形态 + 预期(原型、必备项、范围缩减)、校准(对每个红色结果分类,并决定保留/放宽/移除)、以及推送(类型 + 层级)。自主程度决定每个关口的处理方式:

级别由谁决定何时停止行为
autonomousAgent从头到尾运行全部四个关口;最后向操作者报告决策日志,供其审核——其中包含推送的案例、其测试套件和来源线程的链接(分享链接,不要只给 ID);如果操作者选择记录观察结果,则包含观察结果 ID;对于任何保留的能力缺口红色结果,还要包含 Linear 工单提案(能力缺口 → 提议创建 Linear 工单)。
checkpoint操作者逐个关口决定在每个关口暂停,并给出简洁的提案 + 建议;操作者可以说“继续”或指示调整。在校准关口,将刚构建的线程在实时实例上的链接和登录凭据交给操作者,让其确认前自行审核实际对话和工作流(见下文)。

两种级别的校准都需要特殊处理。 如果校准结论会改变案例的含义——即把真正的能力缺口红色结果判定为由测试框架导致的红色结果,或进行任何会让已知有问题的构建通过的放宽——就必须明确呈现(checkpoint 模式下交互式呈现;autonomous 模式下记录在决策日志中),绝不能悄悄提交。这是唯一一个出错后会悄然破坏测试套件的判断,因此绝不会完全自动提交。

记录来源观察结果是可选项,不是第五个关口。 真实线程通过选择后,可询问是否使用 LangTracer 的 create_observation 工具保存选择原因和开发者观察到的情况(参见 sourcing-cases.md)。如果操作者拒绝、在 autonomous 模式下尚未表明偏好,或写入失败,就继续评测。绝不要因观察记录而阻塞起草、校准或推送。create_observation 和 update_observation 只会写入 LangTracer;不要添加 LangSmith 反馈或同步步骤。

Checkpoint 模式下的校准——在实例上审核真实线程。 校准结论对信任至关重要,因此在 checkpoint 模式下,不要要求操作者相信你对运行结果的判断。你已使用 --keep-workflows(第 4 步)基于实时实例构建案例,因此线程和工作流仍然存在——请提供直接链接,让操作者自行查看:

  • 线程:<base-url>/assistant/<threadId>——案例运行时的确切对话(运行输出会显示 threadId;构建的工作流会显示为 BUILT (<id>),并可通过 <base-url>/workflow/<id> 打开)。
  • **登录:**评测登录所用的邮箱和密码(你在实例上创建的所有者账户——参见 running-evals.md;本地默认种子账户为 [email protected] / PlaywrightTest123)。

对每个红色结果,提供:断言、结果是通过还是未通过、你建议的分类(真正的能力缺口/测试框架限制/噪声)以及保留/放宽/移除的建议,还有审核链接。操作者登录后查看线程和工作流,并在你将结论写回案例的 description 前确认或提出调整。

共享链接,不要只给裸 ID

每个 lang-tracer 实体都有可分享的网页,但 CLI 和 MCP 提供给你的是数字 ID——eval:langtracer-push 会打印 + created <slug> (#621),get_eval_run 会返回运行编号,list_conversations 会返回对话线程 ID。ID 无法点击:使用者必须自己去查找。凡是在面向人阅读的内容中提到用例、套件、线程、集群或运行——例如检查点提案、运行结束后的决策日志、PR 描述、Linear 工单或 Slack 消息——都应将其显示为链接,并在标签中保留 ID:

pushed as [#621](https://lang-tracer.n8n-maintenance.workers.dev/test-cases/621)

使用网页基址(LANGTRACER_URL,生产环境中为 https://lang-tracer.n8n-maintenance.workers.dev)构建链接。绝不要使用 API 基址——${LANGTRACER_URL}/api/v1 和 /api/mcp 是机器端点,指向其中任一端点的链接对使用者来说都会返回 404,或直接显示 JSON。

实体URLID 来源
测试用例<base>/test-cases/<id>推送输出 (#<id>);create_test_case / search_test_cases
套件<base>/suites/<suiteId>推送标题 Suite "<slug>" (#<id>);list_suites
源对话<base>/conversations/<threadId>list_conversations / get_conversation
集群报告<base>/clusters/<id>list_cluster_runs / get_latest_cluster_run
评估运行(扫描)<base>/results?sweep=<runId>list_eval_runs / get_eval_run(runId 就是 sweeps.id,即“run #N”)

有两个链接不属于 lang-tracer,也不使用此基址:构建后的线程(<base-url>/assistant/<threadId>)和工作流(<base-url>/workflow/<id>)位于运行评估所用的 n8n 实例上。如果两者都相关——例如审查校准失败或撰写能力缺口工单时——请分别标注并提供两个链接,这样任何人都不必猜测链接指向哪个主机。

最佳用例的来源

最有力的用例记录的是真实失败,而非虚构的前提。两个连接能帮你找到并核实这类用例:LangTracer 将真实对话聚类为能力缺口主题(大规模发现实际失败),而 LangSmith 保存原始追踪记录(核实运行中具体发生了什么)。LangTracer 是发现层;持久化产物几乎总是根据所学内容编写的合成用例(仅按照 case-shapes.md 使用 seed.mode: "replay")。关于如何连接 MCP,以及如何完成发现 → 核实 → 编写的工作流,请参阅 sourcing-cases.md。

先选定案例形状

案例库包含五种原型。起草前先确定你要写哪一种——它决定了字段、评分方式以及验证方法。它们可以组合使用(带种子的案例仍可断言结果),但主要形状决定了工作内容。

原型回答的问题主要字段
构建(默认)Agent 构建的工作流是否真的能运行?outcomeExpectations + executionScenarios
行为/流程Agent 的对话是否正确(提出恰当的澄清问题、不重复询问、遵从更正、尊重计划审批)?processExpectations + 多轮导演脚本;通常仅构建
凭据在特定凭据视图下,构建结果的行为是否正确?credentials[]
带种子从已有前置工作的对话中途开始,并驱动待测轮次seed(编写时使用 mode: "inline";本地检查时使用 "replay")
上下文对话变长后,Agent 是否仍能正常工作——是否复用已读取的内容,并在观察记忆压缩历史记录后保留重要信息?processExpectations + 长 seed;对于涉及压缩的案例,使用 requiresMemoryCompaction

构建的完整说明见下文。其他四种原型、导演脚本词汇以及种子模式详见 case-shapes.md。

对于工作流构建案例,评审器还会看到Token 基准数据:每轮输入和输出 Token 数会内嵌在各自的转录轮次标题中,并提供整个构建的总量、缓存读写明细以及开场步骤的成本。因此,预期可以直接引用成本或消耗(例如“第 2 轮不会重新读取同一节点的 schema”)。这些数字仅涵盖编排器自身的 LLM 步骤。委派的 Agent 构建会在子 Agent 中运行,其步骤不会出现在快照中,因此不要在 Agent 案例中编写成本预期。这些数字来自运行调试快照,因此被测实例必须设置 N8N_INSTANCE_AI_RUN_DEBUG_ENABLED=true;否则评审器会读取 (no run debug captured),成本预期将无法评分。

评审器还会看到线程的观察记录行——观察记忆压缩内容后保留的信息——因此,记忆案例可以对摘要本身进行评分。该数据块通过单独的 REST 读取获取,无需任何标志。

核心原则(适用于所有类型)

先根据意图编写预期,再以真实构建进行校准。 预先确定任何正确解决方案都必须满足什么要求——也就是用户实际表达的内容隐含的必备条件——然后实际构建一次工作流,以校准细致程度:放宽过度具体的要求,确认必备条件确实可实现,并发现 Agent 合理地以其他方式满足要求的情况。不要把某次观察到的构建过程照搬成断言——那会让评估过度拟合成“Agent 是否复现了那次运行”,而不是“Agent 是否解决了问题”。

用用户的口吻撰写对话。 像用户那样陈述目标和实际限制——不要提及节点类型、搭建结构,也不要在提示词中重述 outcomeExpectations。如果对话把构建过程都说清楚了,这个案例测试的就只有 Agent 是否能遵循指令,而预期也变得同义反复;用户的要求与正确工作流的实现方式之间的差距,才是要测试的能力。即使锚点确实是遵循用户提出的技术偏好,也要把它表述为用户的需求和限制(“我需要字段 X,但内置节点没有提供它,所以请直接从 API 获取”),而不是实现规范(“使用 HTTP Request 节点”)。

除非用户另有要求(或案例专门用于测试非英语处理能力),否则请用英语撰写对话。 有来源的真实对话往往不是英语——改写提示词时,请将其意图翻译成英语,并采用用户的口吻;需要保留的是失败模式,而不是原始语言。

精简为能复现问题的最短多轮对话。 有来源的真实对话往往很长(数十轮的前期设置、调试和离题内容)——不要照抄。提炼为仍能驱动待测构建或行为的最少轮次。每个保留的轮次都必须有必要性:只有当某轮是承重内容时才保留——例如 Agent 必须主动询问的值(通过 director note 暂不提供,直到 Agent 询问)、案例要测试的纠正或反驳,或决定是否进行构建的计划审批。如果删掉某轮不会改变测试内容,就删掉。只要整个请求无需承重交互就能一次说清,就合并为单轮;只有存在这类交互时才保留多轮,并让每个 director 脚本都放在同一轮中(不要为了分步推进而虚构助手“完成”轮次——请参见case-shapes.md)。最简对话能隔离待测能力;照搬对话则会让噪音掩盖能力,并把测试变成指令遵循测试。

按构建规模而不仅是断言数量来确定范围。 有来源的真实提示词常常包罗万象(“可用于生产、永久运行、每天发布 3 条动态和8 条故事”,“每天生成 50 篇文章”),很容易超出约 900 秒的构建预算(请参见“已知的测试框架限制”)。忠实地缩减范围是合理的编写方式:减少批次大小,删去多个并行流水线中的一个,或合并相邻的 AI 步骤,让案例能在预算内完成构建——然后在案例的 description 中注明缩减内容(“原始请求还要求每天发布 8 条故事的流水线;已缩减为仅包含动态发布,以确保能在预算内完成构建”)。保留待测能力;删减组合规模。无法完成构建的案例什么也测不了。

工作流

这些步骤对应 先设置自主级别 中的四道关卡: 来源收集(步骤 1 之前)是选择关卡,其中包括可选的记录来源观察的提议;步骤 1–2 是形状 + 预期关卡;步骤 5–6 是校准关卡;步骤 7–8 是推送关卡。在自主模式下,你会依次完成所有关卡,并在决策日志中总结;在检查点模式下,你会在每道关卡暂停并提交提案,而在校准时,你会将线程链接和登录信息交给驾驶者,以便其检查真实构建(参见先设置自主级别)。无论自主级别如何,校准(步骤 6)都会明确呈现会改变结论的判定。

  1. 先说明必须满足的条件。 仅根据对话,列出每个正确工作流必须执行的操作(触发类型、必要操作、门控条件)——这些内容将成为初稿中的 outcomeExpectations。必填字段:conversation(≥1 轮,且第一轮为 user)、complexity、tags,以及 executionScenarios / processExpectations / outcomeExpectations 中的至少一项。
  2. 根据下面的模板起草案例;验证其能否加载(参见“验证”)。
  3. 批量运行前,先用一个案例对环境进行冒烟测试。 先端到端运行单个案例。这能以约 1/N 的成本验证身份验证 / 模型 / --base-url / 构建的 dist;这不同于验证案例。如果该案例在执行时崩溃(尤其是遇到你预计每个案例都会出现的相同错误),请先修复环境再运行批量(参见“失败就是信号” → 环境检查)。跑完 15 个案例才发现它们都因过期的 dist 而崩溃,会白白浪费一次完整运行。
  4. 构建一次,针对正在运行的实例进行构建(参见 running-evals.md),并使用 --keep-workflows 保留构建好的工作流,以便检查。
  5. 检查——阅读构建好的工作流(运行结果会打印 BUILT (<id>);通过 GET /rest/workflows/<id> 获取),并查看 HTML 报告中的对话记录,了解 Agent 实际做了什么。
  6. 校准——完善断言,绝不为了通过而放宽标准。 修正确实不合适的断言:如果某项选择在对话中没有明确规定,就放宽对它的限定(避免有效的替代方案构建错误地失败);收紧可能放过错误构建的断言;并调整 executionScenarios 的表述,使其符合工作流在模拟数据上的运行方式。但如果某个场景因构建确实存在缺陷,或测试框架无法执行该场景而失败,那次失败就是结果——保留它并说明原因(参见下文“失败就是信号”)。绝不要为了让运行通过而删除场景、弱化断言,或降级为仅构建检查。当案例结果为通过时,这也是需要争取的结果,不能直接接受:先确认前置条件确实触发,再从原始对话线程重新推导,之后才能将该案例称为回归保护(参见先复现,再重新分类)。
  7. 推送到测试套件——不要提交 JSON。 校准完成后,使用 eval:langtracer-push 将案例推送到专门整理的 lang-tracer 测试套件(参见推送到 lang-tracer 测试套件);测试套件才是案例的归属地,而不是代码仓库。不要提交 data/{workflows,agents}/*.json 文件(或在推送到测试套件后将其删除)。将新案例 JSON 提交到代码仓库已不再是推荐做法。(inline 种子数据会随案例一起推送;只有 replay 案例会被拒绝——它只是本地临时数据;参见 case-shapes.md。)对于有来源的案例,最后通过 MCP 将其关联到来源对话线程 / 发现的问题——参见将推送的案例关联到其来源。
  8. 返回链接;如果案例发现了缺口,还要提出工单建议。 报告推送的案例时,使用 <base>/test-cases/<id>,而不是 #<id>(参见分享链接,绝不只分享 ID);如果校准后仍有真实的能力缺口导致案例失败,请为此提出 Linear 工单(参见能力缺口 → 提出 Linear 工单),不要让这个缺口只留在无人负责的失败案例中。

可以使用 --iterations N 测量不稳定性(pass@k / pass^k)——怀疑案例具有非确定性,或准备将其提升到门控层级时再使用,不要把它作为常规步骤(每次迭代都需要完整构建并执行)。

自检:如果你无法想象一种合理的错误构建,能让该案例稳定地变成失败,说明断言过于宽松,起不到保护作用。 确认前置条件确实触发,而不只是结果显示为绿色。 对于任何条件式断言——“当 X 发生时,Agent 做了 Y”(大多数 processExpectations 和所有行为用例)——通过有两种解读:Agent 做了 Y,或者 X 从未发生,断言只是空洞地通过了。依赖模拟对象产生特定故障的行为用例(例如,将 AI 节点模拟为空,导致下游解析节点失败)是典型陷阱:如果模拟对象改为返回可解析的数据,故障就不会发生,用例便毫无防护作用,却仍显示绿色。校准时必须读取执行跟踪记录和 Agent 的 finalText(验证器快照中的 buildTrace.finalText,或 HTML 报告),并确认 X 确实发生了——直接循环的 eval-results.json 不会持久化各项断言的评判理由,因此仅凭通过或失败无法判断你得到的是哪种解读。

否定形式最容易让人误判。 如果断言表述为“Agent 没有用错误参数调用 X”,那么 Agent 正确调用了 X 时会通过,完全没有调用 X 时也会通过。这是两种截然相反的结果,而评判器却都会报告为绿色。因此,任何关于工具误用的断言,在相信其通过之前,都要确认工具确实被调用:解析 testCases[].transcriptPerRun[][].steps[] 中的调用。注意,转录会将多操作工具归入一个简单的 toolName(nodes、workflows、credentials),因此要读取 args.action 才能确定实际调用的操作——按 nodes[explore-resources] 筛选会一无所获,看起来像是干净地通过了。在此语料库上的测量结果是:一批包含五个工具误用用例的用例,在首次校准运行中得分达到 100%,其中三个用例通过时都未实际调用被测工具,只是空洞地通过了。

有来源依据但无法复现的故障,还不能算回归防护——首先要重新推导前置条件。 行为确实会随版本而变化,而一个用例从能力缺口(红色)转变为回归防护(绿色,能捕获问题再次出现),是合理且有价值的结果。但应通过排查得出这一结论,而不是默认如此:显示绿色更常意味着你的用例根本没有设置出该情境,而不是构建者改进了。见下文。

先复现,再重新分类

基于真实故障构建的用例在首次校准运行中恢复绿灯,这是最常见的结果;“构建肯定有所改进”则是最常见的错误结论。通常的原因是,你根据线程的摘要——主题标签、观察描述、自己写的一句话备注——来编写用例,而你假设的触发条件并非实际触发条件。降级任何内容之前,先回到原始线程。

找出具体轮次,而不是话题。 定位失败的确切工具调用,然后阅读它之前紧接着的助手文本。Agent 通常会直接说出自己的意图,而那句话就是前置条件。接着问,是什么状态让这次调用变得必要——而不是对话讨论的是什么。

本语料中的一个实例。来源发现:“Agent 会臆造 nodes[explore-resources] 方法名。”假设的触发条件:用户切换模型提供商。基于干净的提供商切换构建的用例恢复了绿灯——Agent 直接设置了模型 ID,根本没有调用工具。原始线程说得很清楚:

“Groq Chat Model 的模型无效(你的 Groq 凭据未提供 llama3-8b-8192)。让我列出有效模型并修复它。”

前置条件从来不是切换。它是已有的模型 ID 在运行时被提供商拒绝,且凭据已连接——正是这种状态让枚举模型变得必要。基于这一条件重建后,同一个用例在首次运行就复现了故障,Agent 连续臆造了两个方法名。

三个步骤可以将无法复现的用例变成可复现的用例。满足于加一道防护之前,按顺序尝试:

  1. 修正前置条件。 重建种子和实时轮次,以复现来源线程中的状态,而不是它讨论的主题。
  2. 将断言移到首次调用。 如果某种机制会让 Agent 自我纠正,那么按最终状态评分会得到绿灯,但它仍然是真实缺陷——浪费的往返调用和猜测出的架构正是发现的问题。按首次调用的正确性评分后,此前被斥为“会自我纠正、评估较弱”的机制,变成了一个在 2 次运行中有 2 次复现的缺口。
  3. 保留实际捕获到的内容。 复现运行往往会因一个不同的真实缺陷而亮红灯,而不是你针对的那个。这仍然是能力缺口的发现——保留红灯,将描述改为针对实际缺陷,并在其中明确说明最初针对的机制并未复现。

知道何时停止。 有些机制在这个测试框架中结构性地无法触达,无论怎样重新推导都无法改变这一点。最明显的例子:种子会在评分轮次开始时恢复一个全新的工作区文件,因此 Agent 的第一个 old_str 总是来自它刚读过的文件。需要跨许多轮积累漂移的故障——str_replace 字节保真这一类问题,也就是生产环境中由 Agent 导致的最大一类工具调用故障,占线程的 15%——即使在多轮编辑链中每次编辑后都进行构建,也无法复现。最多尝试约三次,然后将这一负面结果写入用例的 description,如实记录这一发现,并考虑真正的缺陷是否应该进入工单,而不是评估用例。

红灯是信号——将它呈现出来,而不是绕过去

校准的目的是调整断言的合理尺度,而不是让用例通过。当某次运行使场景或预期亮红灯时,先对红灯进行分类——然后保留它。

先排除环境问题。 在将任何红灯视为用例信号之前,先检查整次运行中的故障模式。如果每个用例都以相同方式失败——每个场景都出现相同的执行错误,而构建成功,或者每个构建都在启动前以相同方式报错(Agent error: Something went wrong…,且没有工具调用)——那几乎肯定不是用例的问题;这是环境损坏,最常见的情况是切换分支或工作树后留下了过期的 dist。需要了解两种模式:

  • 过时的 packages/core / packages/cli dist — 一次重构移动了一个运行时导出,而构建后的 dist 仍调用旧导出,因此 构建成功,但每次执行都会以相同方式失败(例如,createDeferredPromise 移至 @n8n/utils 后出现 (0 , n8n_workflow_1.createDeferredPromise) is not a function)。

  • 过时或构建不完整的 @n8n/instance-ai dist — 每次运行都会在 构建前 出错(Agent error…,工具调用次数为 0),实例日志显示 dist/skills/*.js 中出现 Cannot find module '@/utils/...':构建的 tsc-alias 步骤(会将 @/ 路径别名改写为相对 require)未完成,因此 dist 内部不一致。

  • 不同步的 node_modules — pnpm build 本身会很早就因 Cannot find module '@n8n/<pkg>' 而失败,尽管该包是声明过的 workspace:* 依赖,并且有 dist/。消费者的 node_modules 中缺少工作区符号链接(通常发生在切换分支或工作树后)。使用 ls -d packages/<consumer>/node_modules/@n8n/<pkg> 确认;运行普通的 pnpm install 即可修复——无需更重的 pnpm reset --full。

修复问题,不要围绕问题调整:按顺序运行完整的 pnpm build(针对特定目标的 --filter 构建可能因其他无关的过时依赖类型错误而失败;对于 instance-ai 这种情况,cd packages/@n8n/instance-ai && pnpm build 会运行 tsc && tsc-alias),然后重启实例——正在运行的 node 进程会将旧 dist 保留在内存中,因此重建磁盘上的文件不会产生任何变化,直到重启为止(而按环境变量模式执行 kill 会漏掉该进程——请终止实际的 lsof -t -iTCP:<port> PID)。重新探测一个用例,确认它能构建并执行,然后重新运行批量测试。只有排除了一致的环境故障后,才适用以下三类情况:

  • 真实的构建 / 能力缺口 — Agent 的工作流有误,或缺少用户要求的内容(分支连接错误、缺少重试、字段键错误)。这正是评测的目的。保持红色。不要放宽断言或删除场景;当前显示为红色的缺口是现阶段的能力信号,并且会在构建器改进后成为防止问题再次引入的保护。然后针对该缺口提议创建 Linear 工单——请参阅能力缺口 → 提议创建 Linear 工单。
  • 测试框架限制 — 构建正确,但模拟 / 执行层无法覆盖该路径(参阅下文的“已知测试框架限制”)。保留该场景,并在其 description 中说明这次红色结果由测试框架导致,而非构建缺陷——避免有人将其误读为产品 Bug。如果它每次运行都会硬失败,则不要将其纳入门控层级;待测试框架获得相应能力后,该场景便能发挥作用,无需重新编写。
  • 真正的非确定性 — 同一个构建结果在不同运行中时而通过、时而失败。这才是真正的“噪声”。在称其为不稳定之前,先用 --iterations N 确认,然后将其移出分层并注明;删除是最后手段。

对于每个保留的失败用例,都要在其 description 中添加便于检索的前缀,让后来者一眼就能区分这两种情况。对于由测试框架导致的失败,使用 Harness note: …(指出限制及构建为何仍然正确);对于真实缺口,使用 Capability-gap finding: current build reds because <X> — a real builder bug (flips to a regression guard once fixed)。统一的前缀便于检索语料,也能避免测试框架导致的失败被误读为产品 Bug。

绝对不要做的一件事是通过削弱用例检查来绕过失败——删除失败场景、放宽断言直到错误的构建也能通过,或悄悄改成仅构建测试。这样会让测试套件看起来比产品实际情况更好,与评测的职责背道而驰:Bug 和测试框架缺口就是要交付的结果,因此要突出它们,而不是绕开它们。如果你发现自己正在编辑某个用例,让一个已知有问题的构建也能通过,请停下来。 由谁确认分类取决于自主程度。 在 checkpoint 模式下,保留/放宽/删除的决定由驾驶员确认:你将线程链接和登录信息交给他们(参见先设置自主程度),让他们审查真实对话和工作流,然后将双方达成一致的 Harness note: / Capability-gap finding: 前缀写回案例的 description。在 autonomous 模式下,Agent 会在运行结束时的决策日志中明确提出分类。无论哪种模式,分类都会公开说明,绝不会悄悄提交——将框架问题导致的红色误判为真实能力缺口(或反过来),是唯一会悄然破坏测试套件的校准错误。

能力缺口 → 提议创建 Linear 工单

一个保留的能力缺口红色案例,是你刚刚比任何错误报告都更准确地描述出的产品 Bug。但测试套件中的红色案例不会自动分配给任何人:没有工单,这个缺口就会作为永久噪声留在 CI 中,下一个查看运行结果的人会以为已经有人负责处理。因此,一旦某个红色案例被归类为真实缺口(且驾驶员已根据自主程度确认),就为它提议创建 Linear 工单。

先复现。这是默认做法,不是硬性规定。 只有在案例复现了该缺口后,才提议创建工单。这意味着案例在真实构建中呈红色,已确认前置条件触发,并且你重新阅读了原始线程(参见先复现,再重新分类)。仅凭轨迹分析得出的缺口仍只是一个假设。为此创建工单会占用负责团队的时间。驾驶员可以批准例外情况,例如框架在大约三次尝试后仍无法触达相关机制。在这种情况下,工单必须说明评测并未复现该缺口。未经驾驶员批准,绝不要为未复现的缺口创建工单。

提议,不要创建。 根据 AGENTS.md,未经请求,绝不要创建 Linear 工单。将草稿交给驾驶员——在 checkpoint 模式下通过交互方式,在 autonomous 模式下通过决策日志——内容包括标题、团队和正文,然后由他们决定是否继续。以下两种情况无需提议:

  • 该缺口已有工单。 检查案例页面上关联的议题,并对驾驶员或源线程提到的任何候选标识符运行 get_linear_ticket_context <TEAM-N>,然后再起草工单,以免重复。
  • 红色案例并非能力缺口。 Harness note: 红色案例属于 lang-tracer/框架问题,而真正的非确定性则属于案例维护工作。两者都不应进入构建团队的待办队列。

工单草稿正文应包含让该缺口可采取行动所需的全部信息,这些信息在校准过程中都已掌握:

  • 评测案例,以链接形式提供——<base>/test-cases/<id>(参见分享链接,不要只给标识符)。这是复现依据,也是工单中最有价值的一行。
  • 失败内容,逐字照录——失败的 outcomeExpectation / processExpectation 或场景名称,以及评判器给出的理由。不要转述:修复者会搜索确切文本。
  • 构建实际执行的内容——具体缺陷(分支连线错误、字段键错误、缺少门控),以及指向真实证据的链接:源对话(<base>/conversations/<threadId>)以及评测实例上的构建线程和工作流(<base-url>/assistant/<threadId>、<base-url>/workflow/<id>)。
  • 影响范围(如已知)——主题集群或该缺口涉及的真实对话数量(<base>/clusters/<id>),能让「一个红色案例」变成可用于排定优先级的 Bug。 请从案例页面创建工单,以建立链接。 <base>/test-cases/<id> 页面上的 创建 Linear 问题 对话框会创建工单 并 将其关联到 案例;正是这个关联,才能让 get_linear_ticket_context <TEAM-N> 随后通过一次调用返回案例、其场景、源对话和分析结果。案例↔工单关联只能通过该界面写入——lang-tracer 的 MCP 和 /api/v1 对此均为只读——因此,如果工单是通过其他方式创建的(例如使用你的测试框架提供的 Linear MCP,或直接在 Linear 中创建),请明确说明它尚未关联,请操作人员在案例页面完成关联;同时,通过 update_test_case 将标识符和 URL 写入案例的 description,以免丢失溯源信息。

接着在描述前缀中加入工单信息,让语料库能双向检索:Capability-gap finding: current build reds because <X> — a real builder bug (flips to a regression guard once fixed). Tracked in [<TEAM-123>](<ticket url>)。当 构建本身 有误时——而不仅仅是在构建正确的情况下某个场景未通过——请使用 --set-kind capability_gap 将其推送到此类型的套件中(参见 推送到 lang-tracer 套件)。

示例

最简构建案例:

{
  "description": "What this case tests.",
  "conversation": [{ "role": "user", "text": "<the build prompt>" }],
  "complexity": "medium",
  "tags": ["build", "<nodes>", "<concepts>"],
  "triggerType": "schedule",
  "outcomeExpectations": ["<a must-have any correct workflow satisfies>"],
  "executionScenarios": [
    {
      "name": "happy-path",
      "description": "<what this run exercises>",
      "dataSetup": "<what the external services return>",
      "successCriteria": "<observable proof the run succeeded>"
    }
  ]
}

包含多轮导演脚本的完整案例(在被询问前不提供某个值,并对错误方案提出异议):

{
  "description": "Scheduled GitHub-bugs digest to Slack. Repo and channel are withheld until the agent asks; the plan must filter to the 'bug' label before it's approved.",
  "conversation": [
    { "role": "user", "text": "Every weekday at 9am, fetch this week's open bugs from our GitHub repo and post a short summary to Slack." },
    { "role": "assistant", "text": "Which repo and which Slack channel should I use?" },
    { "role": "user", "text": [
        "[Withhold the repo and channel until the agent asks; then say the repo is 'acme/widgets' and the channel is '#eng-bugs'.",
        "When the agent shows a plan or setup card, reject it unless it filters issues to the 'bug' label — a digest of ALL issues is wrong. Once it filters to bugs, approve.]"
    ] }
  ],
  "messageBudget": 8,
  "complexity": "medium",
  "tags": ["behaviour", "build", "schedule", "http-request", "slack"],
  "triggerType": "schedule",
  "processExpectations": [
    "The agent asked for the repo and Slack channel before building, since the prompt named neither.",
    "The agent's final plan filtered issues to the 'bug' label — if its first attempt didn't, it corrected after the user pushed back rather than summarizing all issues."
  ],
  "outcomeExpectations": [
    "A Schedule Trigger runs the workflow on a recurring weekday-morning cadence.",
    "Open issues are fetched from GitHub (HTTP Request or GitHub node) and filtered to the 'bug' label before the summary is built.",
    "One Slack message summarizing the fetched bugs is posted to the #eng-bugs channel the user gave."
  ],
  "executionScenarios": [
    {
      "name": "posts-bug-digest",
      "description": "Three open bugs are returned; a summary is posted to Slack",
      "dataSetup": "The GitHub issues request returns three open issues labelled 'bug' ('Login 500', 'Timezone off by one', 'CSV export truncates'). The Slack postMessage call returns { \"ok\": true, \"ts\": \"1700000000.0003\" }.",
      "successCriteria": "The run completes without errors and posts one Slack message to #eng-bugs that references the three fetched bug titles."
    }
  ]
}

各部分的作用:

  • conversation[0] 会原样发送给构建器。 开场轮次是真正的 提示词——切勿在其中加入 [director note](它会原样泄露给用户)。
  • [方括号内] 的轮次是给用户代理的导演脚本,用于描述行为,绝不会说出来。此处要求它在被询问前不提供数值,并拒绝遗漏标签筛选条件的方案。将整个脚本放在一个轮次中,并在其中说明顺序(不要伪造助手“完成”轮次来安排步骤——参见 case-shapes.md)。applies-each-change-when-asked(位于 baseline LangTracer 套件中)是很好的实际示例。
  • dataSetup 仅描述外部服务会返回什么。 这是测试框架控制的层(如下所述)。

dataSetup 和模拟层

测试框架通过拦截发往外部服务的出站 HTTP 请求,并让 LLM 根据节点配置和 API 文档生成响应来进行模拟。它不允许你直接设置节点输出,也不会模拟 n8n 内部机制(Code/Set/Merge/IF/Switch 会使用模拟数据真实运行;触发器和数据库节点会获得 LLM 生成的固定数据)。因此:

  • 将 dataSetup 写成各外部服务返回的内容(“GitHub 请求返回了三个标记为 bug 的 issue”),以及工作流的 Data Tables 在运行前存储的内容(“orders 表中已存在订单 #42”),而不是节点输出或其他内部状态。
  • 最有力的场景会测试外部服务响应——测试框架对这类内容的复现最为准确。
  • Data Table 节点会使用场景中存储的行真实运行。每个场景运行前,测试框架会清空工作流使用的所有 Data Table,并插入模拟模型根据你的 dataSetup 生成的初始行。之后,每个 Data Table 节点都会真实运行,因此可以测试变更检测/去重/“上次看到”场景;在 dataSetup 中描述存储的行即可。不要让场景依赖某一行的 createdAt 或 updatedAt 时间(例如“某行是一周前创建的”):这些列由表设置,因此测试框架会将此类场景报告为框架问题。生成的行会受到引导,但不会逐字节精确匹配,因此不要断言这些行的确切值。如需精确行数据,请在场景的 seedDataTables 中声明该表:测试框架会按原样插入这些行。需要注意的是,基于工作流静态数据的去重/变更检测(removeItemsSeenInPreviousExecutions、$getWorkflowStaticData)无法预置数据,因为每次运行时静态数据都会清空,因此此类场景会无效地显示为红色(所有内容都会被视为“新内容”)。若要创建可预置数据的变更检测场景,请在提示词中说明“将状态保存在数据表中”;否则,请接受静态数据导致的红色结果属于测试框架限制,并将相关逻辑写入 outcomeExpectations。注意,Agent 也可能自行选择基于静态数据去重。
  • **Message an Agent 会使用其真实模型运行被调用的 Agent。**测试框架会模拟该 Agent 的工具、MCP 服务和网页搜索,并将这些调用记录在调用节点下。它会关闭 Agent 的记忆、向量存储和聊天集成。目前仅支持 OpenAI 模型,且须设置 EVAL_OPENAI_API_KEY(CI 会设置):请为 Agent 指定 openai/* 模型,并声明 openAiApi 凭据。
  • 不要断言依赖模拟生成的精确数量(“恰好 7 篇帖子”)。请写成“少于原来的 10 篇”。
会导致场景始终显示为红色的已知测试框架限制

这些情况会导致正确构建也稳定显示为红色。不要设法绕过这些限制——请根据你希望的行为编写场景,并在 description 中注明红色结果由测试框架导致(参见上文“A red is signal”):

  • 为设置留空的资源定位器字段(Google Sheets / Drive / Calendar 等节点选择器)。Agent 会合理地将文档 / 文件夹 / 日历 ID 留空,供用户在设置时选择;模拟层会替换为 __evalMockResource,随后节点查找该 ID 时就会崩溃(“Sheet with ID __evalMockResource not found” 或 “Cannot read properties of undefined”)。任何成功路径经过此类节点的场景,都会在下游执行前彻底失败。
  • 触发器固定数据和 Data Table 起始行由 LLM 生成,因此无法做到字节级精确。 你可以通过 dataSetup 控制它们(见上文的模拟层部分——你可以影响触发器发出的内容或表格中保存的数据),但由于这些值是生成的,基于它们断言精确值或数量会导致测试不稳定。应断言结构 / 分支 / 相对事实,而不是具体数值。(此处仍存在的硬性失败:轮询 / 表单触发器仍偶尔会完全无法加载——“workflow not found”。)
  • 模拟响应形状——LLM 生成的模拟响应可能缺少真实的封装结构,导致下游解析 / 格式化节点崩溃。常见且可复现的形状如下(在正确构建中都会导致红色失败):
    • OpenAI 结构化输出——过去,模拟响应会返回纯粹的 {content: "..."},而不是 Responses 封装结构(output[0].content[0].text),因此 Structured Output Parser / Information Extractor / Text Classifier 无法获取内容并报错 Model output doesn't fit required format。Responses 封装规范化器(PR #33578,已合并)修复了扁平封装的情况,因此现在许多此类流程都能顺利执行。对于声明了严格 additionalProperties: false 的结构化输出架构,仍存在一个范围更窄的残余红色失败:规范化后的 output 包装器(以及模拟响应臆造的任何额外字段,如 subject / date)违反了严格架构,因此节点仍会拒绝模拟输出。旧问题和残余问题都是由模拟层导致,而非构建本身——应在 outcomeExpectations 中记录正确性,并注明红色失败由测试框架导致。
    • Gmail 模拟响应会将标头作为顶层首字母大写字段(From、Subject)返回,而不是放在 payload.headers 下,因此读取发件人 / 主题的 Code/Filter 节点会得到空字符串(例如,“拦截 no-reply 发件人”的安全关卡会放行所有内容)。应在 outcomeExpectations 中断言此类关卡的连接 / 顺序,而非断言其在场景中的运行效果。
    • 较少见的 API(例如 Gemini 的顶层 candidates)也可能以同样方式缺少封装结构。
    • Google Drive 可恢复上传——发起上传的模拟响应会遗漏携带会话 URL 的 Location 标头,因此 Drive 文件上传节点会因 400 错误而失败。任何将生成的图像 / 文件上传到 Drive 的构建都可能因此出现红色失败。
  • Agent 工具节点无法单独执行。 AI-Agent 工具节点(toolHttpRequest 以及其他仅有 supplyData、没有 execute 方法的 LangChain 节点)只有在 Agent 调用时才会运行;测试框架直接执行这类节点会报错:has a "supplyData" method but no "execute" method。对于使用 HTTP 请求工具的聊天触发器 / AI Agent 构建案例,这几乎总会导致红色失败——构建本身是正确的,因此应在 outcomeExpectations 中记录正确性(Agent 已连接触发器 + 模型 + 工具),并注明执行失败由测试框架导致。
  • 轮询 / 等待循环无法快进。 工作流提交异步任务后轮询其完成状态(生成 → 轮询状态直至就绪 → 下载)时,无法确定性地推进模拟状态;而 Wait 节点会按真实时间运行,因此场景会因执行超时而出现红色失败(framework_issue)。构建本身是正确的——应在 outcomeExpectations 中记录正确性,并注明红色失败由测试框架导致。
  • 构建可能超时,且完全没有评分结果——运行报告会显示 BUILD FAILED: Run timed out,且没有任何已评分预期。不要假设是“规格太大”:更常见的原因是单提示词案例中,Agent 提出了澄清性的 ask-user 问题,随后构建因问题未获回答而一直挂起,直至达到每次迭代的超时限制(见 case-shapes.md——只有确认问题会自动获准)。在将超时归因于规格大小之前,先判断其原因:查看报告 / 追踪记录中的 Agent 最终响应(它是否提出了问题?标记了不可行性?还是真的在处理一个超大构建时持续运转?),并单独重新运行该案例(--concurrency 1)——并发既会掩盖停滞的构建(触及上限),也可能仅因任务排在每实例构建上限(默认值为 4)的队列后面,就让完全正常的构建超时。因此,单独运行要么能在几秒内揭示真实原因,要么会直接通过。按原因处理:单独运行后通过 → 原因是并发争用,而非案例本身(将大批次拆分到不同通道中运行——见 running-evals.md);因澄清问题而停滞 → 编写多轮对话,并添加预先回答该问题的导演备注;确实不可行 → 这是关于不可行性 / 诚实行为的案例(processExpectations),而非构建案例;规格确实过大 → 超时本身就是一项发现,但应注明这一点,避免将零分误认为已评分的失败。

outcomeExpectations 与 processExpectations

两者都是由同一个 Sonnet 评审器评分的自然语言断言,且各自计为一个通过率单位。它们评判的方面不同:

  • outcomeExpectations — 最终工作流,依据工作流 JSON 评判。断言节点选择和配置、连接拓扑与分支连线、数据/表达式引用、触发频率、门控条件。适用于所有运行,包括预构建/MCP 运行(无需对话记录)。
  • processExpectations — Agent 在构建过程中的行为,依据对话记录评判。断言是否提出澄清问题(或是否重复提问)、用户可见行为、计划/审批处理、批量操作、是否遵循更正、执行顺序。不要断言子 Agent 或工具名称等内部机制(参见“让预期不涉及内部机制”)。这类预期需要对话记录,因此在预构建/MCP 运行中会跳过。

经验法则:关于产物的断言属于结果预期;关于对话或 Agent 沿途所做选择的断言属于过程预期。没有 executionScenarios 的用例是有效的仅构建用例,由这些预期和工作流检查进行评分。

为每条断言确定合适的尺度

根据实际告知 Agent 的内容设定合适尺度。每个正确的构建都能通过、错误或敷衍的构建会失败的断言,才算尺度合适;而且它只应要求 Agent 做到对话中明确指定的内容,而不是某次运行中的任意选择。两种常见问题:

  • 过于严格 — 锁定了对话中未明确指定的选择。如果提示词从未指定供应商,那么“调用 flightaware.com”会导致使用其他数据源的有效构建失败。但如果对话中明确指定了,就应锁定该选择——用户说“通过 Gmail 给我发邮件”时,“通过 Gmail 节点发送”就是正确且必需的,并非过于严格。
  • 过于宽泛 — 非解决方案也能通过。“从某处获取数据”会让一个能获取数据但从不比较的工作流通过——这无法证明实现了提示词要求的变更检测。

快速检查——替换测试:对于用户所要求的内容,合理的替代实现是否仍能通过?示例(航班状态用例,其中数据源和渠道均未指定):

判定断言原因
❌ 过于严格“有一个调用 flightaware.com 的 HTTP Request 节点”未指定供应商;使用 AeroDataBox 的有效构建会失败。(如果用户确实说了“抓取 FlightAware”,这样断言才正确。)
❌ 过于严格“通过 HTTP Request 节点发布”锁定了传输方式,而第一方节点才是惯用方式——例如,访问 Instagram Graph API 的正确方式是使用 Facebook Graph API 节点,因此使用该节点的有效构建会失败。应断言能力(“通过 Graph API 发布到 Instagram,使用 Facebook Graph API 节点或 HTTP Request”),而非实现机制。
❌ 过于宽泛“从某处获取航班数据”一个能获取数据但从不比较的工作流也能通过——这无法证明实现了变更检测。
✅ 合适“保存先前观察到的状态,并将其与刚获取的状态进行比较”这是核心行为;无论供应商和存储方案如何替换,都能通过。
✅ 合适“仅在检测到变更的分支发送提醒,并由条件控制”证明了门控逻辑,同时不锁定节点或渠道。

对于对话中仅通过暗示表达的意图(偏好但未明确指定的渠道),应将其放入 processExpectations,而非 outcomeExpectations。

让预期摆脱内部机制

预期描述用户或产物能看到什么,而不是 Instance AI 的内部构建方式。内部架构会变:子 Agent、工具名称、委派、规划步骤和运行顺序都会改变。预期若点名这些内容,设计一变就会失败,即使行为依然正确。它也无法继续验证该行为:如果构建通过另一种方式解决了问题,测试就会失败。

预期中不要点名以下内容:

  • 子 Agent、委派或移交(“子 Agent 运行……”、“编排器委派……”)。
  • 内部工具名称或调用次数(“调用 build-agent”、“调用 search-nodes 两次”)。
  • 用户看不到的内部阶段、任务列表或步骤顺序。
  • 哪个组件生成了结果。

要写明以下内容:

  • 用户在对话中看到的内容:提出的问题、供用户审批的计划、最终回复中的说法。
  • 产物包含的内容:工作流或 Agent、其配置以及行为方式。
  • 用户可见的执行顺序,但仅限用户提出要求时(“构建前先询问”)。

改写测试。删掉内部术语,写出该术语所产生的结果。

❌ 机械描述✅ 意图描述
“子 Agent 在报告成功前运行工作流”“最终回复只报告经构建验证的结果;不会声称未经测试的步骤有效”
“编排器在委派前调用 ask-user”“缺少 Slack 频道时,Agent 会在构建前询问用户,不会自行猜测”
“构建器调用 search-nodes 查找触发器”“工作流从符合所请求计划的触发器开始”

第二项测试:设想内部机制被替换成一个使用不同工具的 Agent。正确的构建仍必须通过测试。如果该预期会因此失败或变得毫无意义,就要改写。

某些机制确实是合理的测试对象:例如,为特定工具契约提供保障的案例(比如工具报告错误的方式出现回归)。这类案例应尽量少。请在 description 中说明原因。不要在同一案例中混合机制预期和意图预期。

校准步骤不是预期。 查看对话记录,以确认构建已运行或已触达子 Agent,是很好的校准检查。把这些内容留在你自己的运行记录中,不要复制到案例 JSON。

稳健设计与测试框架不稳定性

这是两件不同的事——要分开处理:

  • 稳健的断言设计(始终如此)。 Agent 未指定的选择每次运行都可能不同。针对未指定来源编写与来源无关的 outcomeExpectations,不是对测试框架不稳定性的妥协,而是正确的断言。
  • 测试框架的局限(明确说明,不要掩盖)。 无论 dataSetup 如何设置,有些路径都会在正确构建时硬性失败——例如导致 Sheets/Drive/Calendar 节点崩溃的空资源定位器字段、无法加载的轮询触发器(见上文“已知测试框架局限”)。(有状态的 Data Table 读取不再属于此类——这些表会从 dataSetup 开始,节点也会实际运行;只有精确值断言仍不可靠。)正确做法是记录,而非绕过:在 description 中注明该局限,并将会导致硬性失败的场景排除在门控层级之外。 只有当场景每次运行的结果都不确定地变化时,它才是真正值得消除的噪声——如果场景因已记录的测试框架原因而稳定失败,它就是测试框架目前无法测试哪些内容的记录,应予以保留。

负面执行场景

不要只测试顺利路径,但只应断言提示词实际要求的正常处理。大多数由 Agent 构建的工作流默认不会添加错误处理,因此,“工作流遇到错误输入时崩溃”是合理的构建问题,而不是测试用例的缺陷。在确实需要正常处理的情况下,编写 successCriteria 时,除了描述正确操作,还应尽可能描述错误操作未发生,例如“未发送警报”“运行无错误地完成”:源为空/未找到、源错误/超时、响应格式错误。

与始终运行的工作流检查之间的关系

每次成功构建还会接受约 28 项始终运行的二元检查评分,涵盖 7 个维度(结构、拓扑、参数正确性、意图、AI 连接、工艺质量、安全)—— binaryChecks/checks/。 这些检查范围广、可见性低。即使某项二元检查名义上已涵盖相关内容,为你的具体案例编写有针对性的预期仍然很有价值——具名的案例级断言,比埋在 28 项检查标准中的一行更能清晰呈现此处的行为。不要仅仅因为存在通用检查就跳过断言。

场景失败时,验证器会标记一个失败类别(builder_issue、mock_issue、framework_issue、verification_failure、build_failure)。请将其视为提示,而非事实依据——我们曾遇到节点配置错误却被标记为 mock_issue,也遇到过真正的模拟问题却被标记为构建错误。请打开 HTML 报告,检查实际执行情况和生成的工作流,再判断问题出在你的案例、构建还是测试框架。

运行产物

  • workflow-eval-report.html(位于本次运行的 .data/ 目录中)——价值最高的视图:完整对话记录(含工具调用)、逐节点执行跟踪、被拦截的确切请求和模拟响应、Phase-1 提示、验证器的推理过程,以及工作流检查标准。面向人工阅读;调试时从这里开始。
  • eval-results.json——结构化结果(机器可读的产物;即使没有 LangSmith,直接循环也会生成此文件)。适合由大模型或脚本解析。并发执行时,要按案例追溯结果,请解析此文件,而不是流式详细日志——存在多个执行通道时,日志行会在不同案例间交错,因此无法可靠地将流中的 [scenario] FAIL 行对应到具体案例。权威字段:testCases[].buildSuccessCount、buildExpectationResultsPerRun[][].{pass,reason} 和 scenarios[].runs[].{passed,failureCategory,rootCause,execErrors}。
  • eval-pr-comment.md——生成的 PR 评论(汇总 + 回归比较),始终会写入。

验证(运行前)

cd packages/@n8n/instance-ai
pnpm exec tsx -e "import {loadWorkflowTestCasesWithFiles} from './evaluations/data/workflows/index.ts'; console.log(loadWorkflowTestCasesWithFiles('<slug>')[0].fileSlug)"

# For a standalone Agent case:
pnpm exec tsx -e "import {loadAgentEvalTestCasesWithFiles} from './evaluations/data/agents/index.ts'; console.log(loadAgentEvalTestCasesWithFiles('<slug>')[0].fileSlug)"

推送到 lang-tracer 套件

校准好一个案例后,将它(以及其他案例)推送到精心整理的 lang-tracer 套件中,而不是提交 JSON。eval:langtracer-push 通过 REST API 执行插入或更新:在套件中创建缺失的案例,更新内容发生偏差的案例,其余案例保持不变,且绝不删除案例。这与 --source langtracer(从套件拉取)正好相反。

cd packages/@n8n/instance-ai
# <suite> is the slug the driver chose (e.g. baseline)
# preview first — no writes:
pnpm exec dotenvx run -f .env.eval -- pnpm eval:langtracer-push --suite <suite> --dry-run --changed
# then push (drop --dry-run):
pnpm exec dotenvx run -f .env.eval -- pnpm eval:langtracer-push --suite <suite> --changed
  • 选择器(至少需要一个——避免意外推送全部案例):位置参数 <slugs...>(精确文件 slug)、--changed(新增/未跟踪 + 已暂存 + 已修改的 data/{workflows,agents}/*.json,非常适合刚编写完未提交案例时使用)、--filter/--tier(可搭配修饰选项 --exclude)。
  • 选择性验证。 精确 slug 和 --changed 只读取指定文件,因此 data/workflows 中无关的无效文件(在较新分支上编写的案例、尚未完成的草稿)不会阻止推送。--filter 只解析 slug 匹配的文件;--tier 会从每个文件内部读取 tier,因此会解析所有文件。解析到的无效文件会打印 ⚠ skipped invalid case file …,推送会跳过该文件并继续。你明确指定的文件如果无效,推送仍会失败。
  • 指定多个位置参数 slug?跳过 pnpm,直接调用脚本。 pnpm eval:langtracer-push … slugA slugB 会将这些 slug 作为一个合并后的参数传递 ("slugA slugB"),因此没有案例文件匹配,也不会推送任何内容。可以通过 pnpm 使用不带位置参数的选择器(--changed),或直接运行脚本,让每个 slug 单独作为 argv 传入:pnpm exec dotenvx run -f .env.eval -- pnpm exec tsx evaluations/cli/langtracer-push.ts --suite <slug> <slug1> <slug2> …。
  • 环境变量: LANGTRACER_URL + LANGTRACER_API_KEY(lt_ bearer;一个密钥即可用于 MCP + REST)——将它们放入 .env.eval,并通过 pnpm exec dotenvx 运行。
  • 选项: --set-kind regression|capability_gap(默认 regression,必须与套件的 kind 匹配)、--contains-user-data(默认值为 synthetic)。如果某个案例的构建正确(结果预期通过),但由于构建器 Bug 而包含当前执行失败的场景,它仍是一个 regression 案例——它用于防止问题复发;只有在构建本身有误时,才应使用 capability_gap。
  • 更新时也会同步场景: PATCH /cases/:id 会按名称协调 executionScenarios(原位更新、插入新增项、删除缺失项——lang-tracer #48),因此修改场景后重新推送,就像修改其他字段一样。早于此改动的 lang-tracer 部署会静默忽略该键;如果推送的场景修改未生效,请在 lang-tracer UI 中更新场景。
  • 以链接而非 #ids 报告已推送内容。 CLI 会打印 + created <slug> (#621) / ~ updated <slug> (#621, rev 3) 和套件标题——这就是 id,没有其他信息。将每个 id 转换为 <base>/test-cases/<id>(套件则转换为 <base>/suites/<suiteId>),然后再交给驱动程序,这样他们就能直接打开刚编写的案例,而不必费力寻找。参见分享链接,不要只给 id。
  • inline 种子会随案例一起推送: 案例写入 API 会原样存储它,因此有种子的案例和其他案例一样可以保存在套件中。只有 replay 案例会被拒绝——推送会在 skipped: 下列出它,因为它会在运行时从 LangSmith 跟踪记录中重建,并会在该跟踪记录被清理或删除后失效。也不要提交 replay 案例;应改为派生一个持久的合成案例作为产物。
将已推送的案例关联到其来源(溯源步骤——务必执行)

如果一个溯源案例没有关联回它所记录的对话/发现,就会成为孤立案例:六个月后,没人能说清它防范的是什么真实故障。push CLI 不会携带溯源信息,因此推送后,请通过 lang-tracer MCP 对新案例 ID 调用一次 update_test_case 来关联来源(push 会打印该 ID):

  1. sourceThreadId — 来源对话的 thread ID(如果案例锚定到其中某个特定运行/步骤,还需填写 sourceRunId)。 这是数据库层面的关联,所有按版本汇总、对话浮动条以及 ?sourceThreadId= 查询都会基于它进行联接——下面的标签/描述只是其上的人类可读层,不能替代它。该 thread 必须已导入 lang-tracer(按溯源流程对其运行 get_conversation_analysis 即可);source_kind 由服务端推导,而且此关联只能在编写的案例上编辑——记录在晋升操作中的溯源信息不可更改。
  2. expectedBehavior — 案例所强制执行的规则,用一个段落描述;以及 failurePattern — 来源 thread 中实际发生的情况,并附上轮次引用。如果案例来自 get_conversation_analysis,请从分析的 extractedCases 条目中复制/改写这些内容。

然后调用 add_case_tags(仅追加;目标是 LT 侧的 tags 数组,而不是 evalTags,因此不会在评估运行中往返传递):添加一个能力标签(例如 instruction-persistence)。标签规范化很严格(转为小写并使用 kebab 格式);冒号格式的标签会被静默丢弃。还要在案例的 description 中保留 thread ID 和轮次引用(撰写者惯用的 “Sourced from thread ” 是约定写法)——描述是唯一一个在所有地方都会显示的字段。

运行

你需要一个已启用 Instance AI 且沙盒可用的 n8n 实例;将评估指向该实例。测试框架支持三种模式——直接循环(不使用 LangSmith;仅生成 eval-results.json)、LangSmith(还会记录实验并进行回归比较),以及 预构建(使用 --prebuilt-workflows,对现有工作流评分)。使用 --filter <slug> / --tier <name> / --exclude 缩小运行范围。运行配方、并行通道、层级和基线请参阅 running-evals.md,完整标志列表请参阅 README。如果想手动检查构建结果,请使用 --keep-workflows——在 checkpoint 模式校准中,驱动程序会通过此选项在实例上打开已构建的 thread(<base-url>/assistant/<threadId>)和工作流。

其他评估测试框架(非此技能)

独立的 Agent 案例请使用 create-agent-builder-eval skill。 另外三个测试框架各自有独立的数据目录和 CLI:eval:subagent(工作流构建兼容性语料库,按二元检查评分)、eval:discovery(断言首跳工具/派发路由,不需要 n8n 服务)、eval:pairwise(与 ai-workflow-builder.ee 进行构建结果的头对头比较)。此处不涉及这些框架的编写方式——请参阅 README 中同名章节。

评论

支持 Markdown