M
CodeMap
mastra/core
04 · trade-offs

设计决策

读源码才能看清的 trade-off。每条决策记录三个字段:选择了什么、放弃了什么、付出什么代价。 理解了这些,代码里那些看似过度抽象的层就有了原因。

01

Agent 的 agentic loop 本身就是一个 Workflow

"Agent 每发一条消息,都需要跑 ReAct 循环(LLM → Tool → LLM)直到模型不再调工具。这个循环必须支持中断(人审 tool call)、暂停(suspend 等外部事件)、恢复(resume)。"

选择了

将 agentic 循环实现为两层嵌套 Workflow:外层 `createAgenticLoopWorkflow()` 控制迭代终止条件;内层 `createAgenticExecutionWorkflow()` 跑单次 LLM 调用 + 工具执行。最外层再套一个 prepare-stream workflow(加载 memory、解析 tools、跑 input processors)。一次 `agent.generate()` 在内部构造 3 层 workflow 实例。

放弃了

独立的 while(true) 循环实现(类似 LangChain 早期的 AgentExecutor)。简单直接,调试栈浅。

代价

极高的内部复杂度——一次 generate() 调用栈深度是普通 loop 的 3-5 倍,step 命名/快照序列化/时间旅行全都走 workflow 那一套。换来的是 durable execution 作为一等公民:suspend/resume、human-in-the-loop approval、crash recovery 都是 workflow engine 免费提供的,Agent 代码本身不用重复实现。

02

Processor 作为显式中间件管线而非隐式 hook

"memory 注入、working memory、skills 提示、browser 上下文、response caching、structured output 这些横切关注点都需要在 LLM 调用前后介入。如果每个都硬编码进 Agent 类,会是 N 个 if 分支。"

选择了

`Processor` 接口,7+ 个 lifecycle hooks(processInput / processLLMRequest / processLLMResponse / processOutput / computeStateSignal / processAPIError / processOutputStep),由 `ProcessorRunner` 按顺序执行。每个 hook 都可以修改 messages/model/tools。memory/caching/structured-output/skills/browser-context 都是内置 Processor 实现。Processor 本身可以是类实例,也可以是 Workflow(复用 DAG 能力)。

放弃了

简单的 before/after 双 hook 模型,或在 Agent 类里直接硬编码这些能力。

代价

学习曲线陡——用户必须理解 7 个 hook 各自在什么时机触发、能改什么、先后顺序怎么排。换来的是精确的阶段控制和真正的可插拔:第三方可以发布自己的 Processor,而不用 fork Agent 类。

03

Model Router 用字符串 "provider/model-id" 代替 model 实例

"用户要切模型时,每个 Agent 构造函数都传 `import { openai } from '@ai-sdk/openai'; model: openai('gpt-5')` 是 3 行样板,而且 gateway 代理、认证、多 provider fallback 都要用户自己接。"

选择了

`model: 'openai/gpt-5'` 这种字符串配置。`ModelRouterLanguageModel` 在运行时查 `provider-registry.json`(~200+ 条 provider 映射),动态 import 对应 AI SDK provider,构造 LanguageModel 实例。Gateway 认证、API key 轮换、fallback 链都在 router 层做掉。

放弃了

强制用户手动 `import { openai } from '@ai-sdk/openai'` 并传入实例(裸 AI SDK 用法)。

代价

需要维护一个不断膨胀的 provider-registry.json(每次 AI SDK 加新 provider 都要同步);动态 import 带来冷启动成本;provider 特定参数要走 `modelOptions` 透传。换来的是极致的用户体验:一行字符串搞定一切配置,多模型路由零成本。

04

所有 AI SDK 依赖通过 @internal 包间接引用

"Vercel AI SDK 是 Mastra 的底层依赖,但它大版本(v4 → v5 → v6)不兼容,API 和类型都在动。如果 Mastra 直接 import 公开 API,每次 AI SDK 升级就是一次 break change。"

选择了

包装一层 internal package(`@internal/ai-sdk-v4` / `@internal/ai-sdk-v5`),Mastra 代码只依赖 internal 包的稳定接口。同时支持多个 specificationVersion(v2/v3/v4 对应 AI SDK v5/v6 等),用户代码无感知。

放弃了

直接依赖 AI SDK 公开 API,跟随大版本升级。

代价

多一层抽象和版本映射复杂度(内部代码里随处可见 `specificationVersion` 分支),但 Mastra 本身可以独立于 AI SDK 大版本演进——用户不用为了 Mastra 升级跟着改自己的 model 调用代码。

05

Storage 采用 22+ 子域的复合接口而非单一 ORM

"Mastra 要持久化的东西五花八门:workflow 运行状态、对话 memory、agent 注册、eval score、blob、dataset、embedding、observability trace……它们的数据模型、查询模式、后端偏好(关系/键值/对象存储/向量库)完全不同。"

选择了

`MastraCompositeStore` 把存储拆成 22 个独立子域接口(WorkflowsStorage / MemoryStorage / AgentsStorage / ScoresStorage / BlobsStorage / DatasetsStorage / ...),每个域独立实现。默认全部用 InMemoryStore 零配置可用,生产环境可以任意混搭(workflow 状态用 Postgres、blob 用 S3、memory 用 Redis、vector 用 Pinecone)。

放弃了

统一的 SQL/ORM 层(所有状态塞关系库),或单一 key-value 抽象。

代价

接口面积巨大(types.ts 3086 行,整个 core 最大的类型文件),每新增功能都要扩展子域接口并给 InMemory 实现加一份。换来的是后端任意组合的自由,以及默认零配置启动(InMemoryStore 对应用户和测试场景非常友好)。