Mastra 条件逻辑工作流测试指南:从 Playground 调试到分支路由验证
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
导读
本文是 Mastra Workflow 系列课程中"条件分支"单元(第 18~21 课)的收尾实战篇,聚焦如何对已经构建好的条件工作流进行系统化测试与调试。你将掌握:如何把条件工作流注册进 Mastra 配置、如何通过 Playground 与程序化方式验证不同输入的路由结果、条件评估的底层执行机制(含源码佐证),以及一套可复用的条件排查方法论。
本课在课程体系中的定位
在开始测试之前,先回顾你已经完成的链路:
- 理解条件分支:掌握
.branch()的基本语法与"条件为真则执行对应步骤"的语义; - 创建条件步骤:实现了
assessContentStep(内容评估)与quickProcessingStep/generalProcessingStep两个处理步骤; - 构建条件工作流:用
.then(assessContentStep).branch([...]).commit()组装出conditionalWorkflow。
本课(第 21 课)的任务是:用不同类型、不同长度的内容去"轰击"这个条件工作流,验证它是否按预期路由到不同的处理路径。测试不是可选项——条件逻辑的正确性直接决定后续功能的质量。
注册新工作流:让 Mastra 知道它的存在
要让测试能够进行,第一步是把工作流注册到 Mastra 实例中。编辑入口文件src/mastra/index.ts,将条件工作流加入workflows配置:
// In src/mastra/index.ts import { contentWorkflow, aiContentWorkflow, parallelAnalysisWorkflow, conditionalWorkflow, } from './workflows/content-workflow' export const mastra = new Mastra({ workflows: { contentWorkflow, aiContentWorkflow, parallelAnalysisWorkflow, conditionalWorkflow, // Add the conditional workflow }, // ... rest of configuration })几点说明:
workflows是一个以工作流 id 为键的注册表,注册后工作流才能被 Playground、mastra dev与程序化调用发现;- 条件工作流与普通工作流共用同一注册入口,没有任何特殊的"条件工作流"注册标记;
- 从源码结构看,注册后框架会基于工作流的
id与步骤流(step flow)建立可执行的图结构,后续 Playground 的流程可视化正是依赖这份图定义(相关实现见 packages/core/src/workflows/workflow.ts 与 packages/core/src/workflows/evented/workflow.ts)。
在 Playground 中测试条件工作流
注册完成后,重启开发服务并在 Playground 中找到conditionalWorkflow,开始系统性测试。请务必覆盖不同内容长度和不同内容类型,建议按以下矩阵逐项验证:
| 测试用例 | 输入内容特征 | 预期路由 |
|---|---|---|
| 短内容 | 少于 50 词的简单文本(平均词长 ≤ 5) | quick-processing(快速处理) |
| 中等内容 | 50~200 词 | general-processing(通用处理) |
| 长内容 | 超过 200 词 | general-processing(通用处理) |
| 复杂内容 | 平均词长 > 5 甚至 > 7 的文本 | 视category组合而定 |
对照我们之前定义的评估规则(见 19-creating-conditional-steps.md):
wordCount >= 50→medium,wordCount >= 200→long;- 平均词长
> 5→moderate,> 7→complex。
由于 构建条件工作流 中的分支条件是category === 'short' && complexity === 'simple',只有同时满足短且简单的内容才会进入快速处理路径,其余一律走通用处理路径。测试时若发现"中等长度但简单"的内容走了通用路径,这并非 bug,而是复合条件(AND)的预期行为——这恰恰是条件逻辑测试要确认的边界。
程序化测试:不依赖 UI 的验证方式
除了 Playground,还可以通过代码直接触发工作流,便于把测试用例固化成自动化测试。Mastra 支持在注册实例上获取工作流并执行:
const result = await mastra.getWorkflow('conditional-workflow').execute({ triggerData: { content: 'Short and simple text here...', // < 50 词,平均词长短 type: 'blog', }, }) console.log(result.results) // 检查 processingType 是否为 'quick'将不同输入封装成数组循环断言,即可得到一张可重复执行的回归测试表。关于 Playground 的完整用法,可参考系列课程 07-using-playground.md。
理解流程:条件路由的四个阶段
测试时要"带着模型"去观察结果。条件工作流的完整执行链路分四步:
- 评估步骤(Assessment step):
assessContentStep先运行,分析内容并产出category(short/medium/long)与complexity(simple/moderate/complex)等元数据,写入工作流状态; - 分支条件求值(Branch conditions):
.branch()中注册的每个条件函数针对评估结果逐一求值; - 匹配步骤执行(Matching step):条件为真的分支对应的步骤执行,产出该路径的处理结果;
- 结果汇总(Results):输出数据中会体现实际走了哪条处理路径(例如
processingType: 'quick'或'general'),据此反推路由是否正确。
源码视角:条件是如何被并发求值的
这四个阶段并非文字上的"走流程",其底层实现在packages/core/src/workflows/handlers/control-flow.ts的executeConditional函数中(见 control-flow.ts#L348-L538)。关键事实:
- 所有条件通过
Promise.all并发求值(L395-L496),而不是串行短路——这正是系列课程反复强调的"多个条件同时为真时,对应步骤并行执行"的来源; - 求值为真的条件索引被收集为
truthyIndexes,只有这些索引对应的步骤会被运行(L498); - 求值过程会产生
WORKFLOW_CONDITIONAL与WORKFLOW_CONDITIONAL_EVAL两类可观测性 Span,并记录conditionCount、truthyIndexes、selectedSteps等属性(L378-L392、L533-L538)——这意味着你可以在追踪后端直接查看"哪个条件为真、哪个分支被选中"。
branch()方法的签名与存储逻辑在 packages/core/src/workflows/workflow.ts#L2407-L2469:它接收[条件, 步骤]元组数组,将条件函数、步骤引用与序列化条件一起压入stepFlow与serializedStepFlow,并从元组第二项提取步骤类型以完成 TypeScript 类型推导。
条件函数的类型契约
在 packages/core/src/workflows/step.ts#L74-L125 中可以看到条件函数的正式定义:
ConditionFunctionParams复用ExecuteFunctionParams,但移除了setState与suspend——条件函数是只读判断,不能修改状态;ConditionFunction的返回类型是Promise<boolean>,即条件函数必须返回布尔值(或 Promise 包裹的布尔值)。
这解释了为什么条件里只能做"判断"而不能做"写入":评估阶段是并发的,任何副作用写入都会破坏确定性。若确需在分支前准备数据,应放在评估步骤中完成。
调试条件:当路由不符合预期时
如果某个条件"没有按预期工作",按照以下顺序排查(这正是本课文档给出的调试清单的展开版):
1. 检查评估步骤的输出
路由决策完全依赖assessContentStep产出的category与complexity。先在 Playground 或日志中确认这两个字段的真实值:
console.log(`📋 Assessment: ${category} content, ${complexity} complexity`)例如你本以为输入是 "short",但wordCount统计的是content.trim().split(/\s+/)的结果——连续多个空格、换行符、全角字符都会影响词数,导致评估结果与直觉不符。
2. 核对条件逻辑是否符合预期
回到.branch()的注册处,逐条核对:
.branch([ // Branch 1: Short and simple content [async ({ inputData }) => inputData.category === 'short' && inputData.complexity === 'simple', quickProcessingStep], // Branch 2: Everything else [ async ({ inputData }) => !(inputData.category === 'short' && inputData.complexity === 'simple'), generalProcessingStep, ], ])注意这里两个条件是互斥互补的:要么走快速路径,要么走通用路径,任何输入都恰好命中一个分支。如果你改动了第一个条件而忘记同步第二个(否定形式),就会出现"无分支命中"或"双分支命中"的意外。
3. 在隔离环境中测试单个条件
把单个条件函数抽出来单独跑,排除工作流其他环节的干扰:
const isShortAndSimple = async ({ inputData }) => inputData.category === 'short' && inputData.complexity === 'simple' await isShortAndSimple({ inputData: { category: 'short', complexity: 'simple' }, // ... 补齐 ConditionFunctionParams 的其余字段 })4. 借助日志与可观测性追踪条件求值
- 在条件函数内添加
console.log输出中间值,跟踪每个条件分支的求值结果; - 若项目接入了可观测性,直接查看
WORKFLOW_CONDITIONAL_EVALSpan 的result属性(见 control-flow.ts#L455-L464),可以确认每个条件实际返回了true还是false; - 从源码看,条件求值若抛出异常,会被捕获并等价于
false处理(返回null,见 control-flow.ts#L467-L493),同时产生WORKFLOW_CONDITION_EVALUATION_FAILED错误并记录result: false属性。因此"条件没生效"有时其实是"条件抛错了"——先看错误日志,再怀疑逻辑。
5. 组合条件的运算规则
多条件判断支持标准逻辑运算符,构建测试用例时按真值表设计输入:
&&(AND):两个条件都为真才命中。例如"短且简单"需要category === 'short'与complexity === 'simple'同时成立;||(OR):任一条件为真即命中;!(NOT):条件取反。例如上面 Branch 2 的否定形式,与 Branch 1 构成全覆盖路由。
求值规则再强调一次:条件按注册顺序依次存储,但并发求值;多个条件为真时对应步骤并行运行;全部为假时跳过整个分支继续执行后续步骤(相关语义在 18-understanding-conditional-branching.md 中有完整说明)。
分支的好处:为什么值得为它写测试
条件工作流带来的价值,恰恰也是测试的重点关注项:
- 智能路由(Intelligent routing):让合适的内容走合适的处理路径,测试要确认"正确的内容到了正确的路径";
- 性能优化(Performance optimization):简单内容跳过重处理。注意在 19-creating-conditional-steps.md 中,
generalProcessingStep用setTimeout(resolve, 500)模拟了更重的处理——测试时可以对比两类路径的耗时,量化分支带来的收益; - 定制化体验(Customized experience):不同场景走不同处理策略(如快速处理只给 1 条建议,通用处理给 3 条),测试要验证推荐内容的差异;
- 可扩展逻辑(Scalable logic):新增条件与处理路径只需在
.branch()数组里追加元组,但每新增一个分支,就应该为它补充对应的测试用例。
仓库中的真实条件分支实例
当前仓库的 examples 中就有两个可直接运行的条件分支示范,可作为测试用例设计的参考:
文本长度分支
examples/agent/src/mastra/workflows/index.ts#L220-L265 中的lessComplexWorkflow,基于文本长度分两条路径:
.branch([ [async ({ inputData: { text } }) => text.length <= 10, shortTextStep], [async ({ inputData: { text } }) => text.length > 10, longTextStep], ])它与课程示例几乎同构(互斥互补的两个条件),分支后用.map()把short-text或long-text的结果统一回写到text字段——这个"分支后归一化"的手法值得借鉴:分支会产生以步骤 id 为键的异构结果,下游需要合并处理。
内容类型分支
examples/agent/src/mastra/workflows/content-moderation.ts#L237-L273 中的branchingModerationWorkflow展示了基于内容特征的路由:
.branch([ // If message looks like it might contain PII (has @ or numbers), do PII check [ async ({ inputData }) => { const data = inputData as any; const text = JSON.stringify(data.messages || []); return text.includes('@') || /\d{3}/.test(text); }, piiStep, ], // Otherwise, do toxicity check [async () => true, toxicityStep], ])注意它的第二个条件是async () => true——一个恒真兜底分支,保证任何未命中 PII 检查的内容都会进入毒性检查。这与课程里"否定形式"的兜底写法殊途同归:条件分支务必保证全覆盖,否则会出现无分支命中的静默跳过。测试这类工作流时,async () => true兜底分支的用例是必测项。
结语与下一步
至此,你已完成条件工作流的注册、Playground 测试、流程理解与条件调试的完整闭环。测试的产出是一张"输入特征 → 预期路由"的对照表:内容越短越简单走快速路径,其余走通用路径;若发现偏差,按"查评估输出 → 核对条件 → 隔离单测 → 看追踪日志"的顺序逐层定位。
下一步课程将进入流式输出(streaming):学习如何把工作流结果流式地返回给用户,以获得更好的交互体验(见本课程下一课 22-conclusion.md 之前的流式内容)。流式输出同样需要结合本课的分支测试方法,确保每个分支路径都能正确产生流式结果。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考