从写了两三年 React 的前端视角切入 AI Agent,最反直觉的一点是:我不需要先补 Python,也不需要啃 LangChain 那套厚重的抽象。今年年初我用一个周末的时间,把团队内部一个依赖检查的"手动流程"改成了带工具调用、跨会话记忆的 Agent 服务,选的技术栈就是标题里这个 Mastra。这篇文章不打算写成官方文档的复读,而是想老老实实讲清楚:一个习惯了组件树、状态管理、异步请求的前端,为什么在 Agent 框架选型时选 Mastra,以及第一次真刀真枪搭工具调用 Agent 会遇到哪些文档里没写的问题。如果你也是前端,正在犹豫要不要转全栈或者做 AI 应用,这篇笔记应该能帮你少走弯路。
1. 从"组件树"到"Agent 节点":前端经验是这块最好的翻译器
很多前端朋友一听"Agent 开发"就心虚,觉得那是后端算法工程师的领域。但真正上手之后我发现,前端这几年的积累在 Agent 场景里几乎是无缝迁移的,区别只是换了一套名词。Mastra 恰好把这一层翻译工作做得特别顺,因为它本身是 TypeScript 优先的框架,原语和前端熟悉的节奏非常接近。
1.1 状态管理就是 Agent 记忆的预演
React 里的useState、Redux、Zustand,本质都在解决同一个问题:数据在多个动作之间怎么保持一致。Agent 的 Memory 也是这个事。早期我写聊天机器人没接记忆,用户问完"我上次让你查的那个包后来更新了吗",模型一脸茫然。后来给 Agent 配上内存存储,它才能在多次会话里记住上下文。你完全可以这么理解:前端的状态是你页面上的"短期记忆",Agent 的 Memory 是跨请求的"长期记忆",你早就懂这个概念了,只是没意识到它叫"上下文"。
Mastra 对记忆的处理比很多框架更贴近前端直觉。它把记忆分成线程内的短期上下文和跨线程的持久化存储,配置方式类似给 store 加中间件。我第一次看到Agent的配置项里有memory字段时,脑子里立刻浮现的是 Redux 中间件那种"往 store 里塞自定义能力"的模式——这种熟悉感让学习成本大幅下降。
1.2 请求封装就是工具调用的雏形
前端最日常的工作是什么?封装 API、拦截错误、处理 loading 状态、做防抖。这些活儿放在 Agent 里,就是工具调用的骨架。Mastra 的Tool概念,说白了就是一个"带描述和参数校验的异步函数":你告诉模型这个函数是干什么的、需要什么参数,模型根据你的描述决定什么时候调用它。这就跟你在组件里维护一个fetchUser函数,然后根据用户操作决定是否调用它一个道理。
我写下第一个工具函数时感受特别深:这不就是把我平时写的api/getLatestVersion.ts包了一层 schema 描述吗?前端强项恰恰是"协调各种异步服务并把它们暴露成清晰接口",这正是 Agent 工具层最需要的能力。所以别再觉得自己不懂 Agent,你缺的只是一个把已有技能翻译过去的框架。
1.3 组件生命周期与 Workflow 的编排思路
React 的useEffect依赖数组、组件挂载卸载、事件冒泡,这些"时序控制"的经验,放到 Agent 的多步任务编排里正好对应 Workflow。Mastra 的 Workflow 让我定义一个任务有几个阶段、谁先谁后、谁依赖谁的输出。比如"先查 npm 上这个包的最新版本,再比对 package.json 里的旧版本,最后生成升级建议",就是一个典型的串行工作流。前端天天写"这个请求等那个请求的结果再发起",理解起来毫无障碍。
2. 别人都在用 LangChain.js / Vercel AI SDK / OpenAI Assistants,我选 Mastra 的理由
选型那几天我也纠结过,毕竟前排那几位名气大得多。但把每个框架都装进一个真实 Demo 里跑了一遍之后,结论反而清晰了:它们解决的问题层级不一样,Mastra 正好卡在我需要的位置。
| 框架 | 核心定位 | 语言体验 | 记忆/持久化 | 工作流编排 | 我的评价 |
|---|---|---|---|---|---|
| LangChain.js | 通用 Agent/RAG 框架 | 偏 Python 思维,TS 支持较糙 | 有,但概念重 | 有,Runnable/Graph 抽象偏绕 | 功能最全,学得最累 |
| Vercel AI SDK | AI 对话 UI + 流式渲染 | 原生 TS,体验极好 | 无内置,需自接 | 无,本质是请求层 | 做聊天页面首选,做 Agent 要拼积木 |
| OpenAI Assistants API | 平台托管式 Agent 运行 | TS 封装好 | 在线程内,绑定平台 | 靠 Run/Step 轮询 | 省事但锁平台,换模型基本重写 |
| Mastra | TypeScript 原生 Agent 运行时 | 原生 TS,API 精炼 | 内置多存储适配器 | 内置 Workflow 原语 | 前端上手最快,能力边界清晰 |
2.1 LangChain.js 的问题:抽象比业务还复杂
LangChain 生态确实大,但给我最大的感受是"抽象层数比实际业务还多"。Chain、Runnable、LCEL、AgentExecutor,一堆名词砸过来,最后可能只是为了实现"模型调一个函数"。它的很多设计思路源于 Python 世界的学术社区,文档和社区案例也以 Python 为主,TS 版本总有一种"移植过来"的疏离感。我学了两个下午,发现自己还在研究怎么把两个 Runnable 拼在一起,而我的真实需求不过是"给它一个工具,让它帮我查版本号"。
Mastra 则几乎没有这种负担。它一共就那么几个核心原语:Agent、Tool、Workflow、Memory、RAG、Evaluator。每个原语做的事情一句话能讲清楚,写起来也和写普通 TypeScript 模块差不多。对半路转型的前端来说,"原语少"意味着可以快速建立心智模型,而不是先修一门框架哲学课。
2.2 Vercel AI SDK:优秀的 UI 库,但不是 Agent 运行时
Vercel AI SDK 的useChat体验确实舒服,前端几乎没有学习成本就能接一个流式聊天框。但当我试图往里面加"工具调用链""跨会话记忆""多步骤任务"的时候,发现它更像一个"AI 请求封装层",并没有内置 Agent 运行时。这意味着我要自己设计状态持久化、自己维护工具执行的循环、自己处理模型返回的 tool_call 再二次请求。不是说不行,而是这些逻辑本应该由框架承担,而不是让每个开发者重新发明一遍。
Mastra 也复用了 Vercel AI SDK 的模型 provider 生态,但它把 Agent 运行时的职责补全了:状态管理、工具执行、记忆存储、工作流编排都内置好了。对我来说这就够了——我不想在一个"UI 库"上面再铺三层自己的基础设施。
2.3 为什么不用 OpenAI 官方那套
OpenAI Assistants API 开箱即用,线程和文件搜索都帮你托管了,Demo 一个小时就能跑通。但它的代价是:你的 Agent 逻辑和 OpenAI 平台深度绑定,换模型供应商基本等于重写,线程状态也拿不回来自己控制,排查问题全靠平台日志。对于个人项目和需要一个备选模型策略的产品,这种绑定不是一个好选择。Mastra 的模型 provider 是可插拔的,今天用 OpenAI,明天可以换成其他厂商,代码只改一处配置,这个灵活性对我很重要。
3. 动手搭第一个能跑的工具调用 Agent
理论说再多不如跑通一个真实例子。下面是我搭的第一个 Agent:它能响应"帮我查一下 React 最新版本"这类问题,然后调用真实的 npm registry 接口返回数据。
3.1 创建项目
Mastra 提供了脚手架命令,可以直接生成带示例的工程:
npx create-mastra@latest my-first-agent如果想和 Next.js 一起用,可以选择带 Next.js 模板的版本。我本地用的是基础模板,目录结构大致是src/mastra下面放 agent 和工作流定义。这一步没什么坑,跟着提示选就行。唯一提醒一下:Node 版本至少 18 以上,否则依赖安装会有兼容性问题。
3.2 定义一个最简单的 Agent
import { Agent } from "@mastra/core"; import { openai } from "@ai-sdk/openai"; export const packageAgent = new Agent({ name: "package-checker", instructions: "你是一个前端依赖检查助手。当用户询问某个 npm 包的版本或升级建议时,使用 getPackageInfo 工具查询真实数据,并根据返回结果组织回答。", model: openai("gpt-4o"), });这里instructions很关键,它相当于"系统提示词 + 行为边界"。我第一次写的时候把规则写得很长很细,结果模型反而抓不住重点。后来把 instructions 精简成:"什么场景必须调工具、回答的风格是什么、禁止做的事情是什么",准确率提升不少。记住:工具怎么用不是你用自然语言堆给模型的,而是写在工具描述里的。
3.3 定义工具:把 API 封装成模型能用的函数
工具是 Agent 和外部世界交互的接口。我写了一个查询 npm 包信息的工具:
import { Tool } from "@mastra/core"; import { z } from "zod"; const getPackageInfo = new Tool({ id: "getPackageInfo", description: "查询 npm 上指定包的最新版本号、描述和发布时间。当用户问某个包的情况时使用。", inputSchema: z.object({ packageName: z.string().describe("npm 包名,如 react、next、vue"), }), execute: async ({ context: { packageName } }) => { const res = await fetch(`https://registry.npmjs.org/${packageName}`); const data = await res.json(); const latest = data["dist-tags"]?.latest; const desc = data.description || "无描述"; return { packageName, latest, desc }; }, });然后把这个工具挂到 Agent 上:
export const packageAgent = new Agent({ name: "package-checker", instructions: "……", tools: { getPackageInfo }, model: openai("gpt-4o"), });有个细节值得说:inputSchema里的describe字段会直接影响模型生成参数的质量。模型并不是自己"猜"参数,而是根据这个 schema 的描述来决定填什么。描述写得越贴近真实语气(比如"当用户问某个包的情况时使用"),调用准确率越高。我一开始随手写了"包名",结果模型经常把"最新版本"当成包名传给工具,后来把描述补充成"如 react、next、vue"这样带例子的句子,错误立刻少了。
3.4 跑起来测试
Mastra 提供了本地开发命令行,类似 Next.js 的 dev server:
npm run dev启动后可以在终端直接和 Agent 对话,也可以打开 localhost 的 playground 页面调试。我习惯用 playground,因为它能看到模型完整的思考步骤、工具调用参数和返回结果,排查问题特别方便。第一次测试我输入"react 现在到哪个版本了",Agent 先调用getPackageInfo,拿到latest: "19.0.0"后组织了一句完整回答返回。那一刻的感受是:原来工具调用没想象中那么玄,本质就是"模型决定调哪个函数、填什么参数、把你的返回结果融入回答"。
顺带提一句,工具执行时如果网络请求很慢,模型会卡在等结果。建议在execute里加超时控制,或者用Promise.race兜底,别让 Agent 无限等下去。这个我一开始没注意,后来遇到一次 npm registry 响应超时,整个对话卡了一分多钟,体验很差。
4. 记忆、向量检索和工作流:Agent 不只是"对话框"
第一个 Demo 跑通之后,很快会发现"单次问答"不够用。用户希望 Agent 记得上次聊过什么,甚至希望它能自动完成一串任务。Mastra 在这一层给了三条路径:Memory、RAG、Workflow。
4.1 Memory 配置:跨会话记住用户状态
默认情况下 Memory 存在本地,重启就丢。要让 Agent 真正记得用户,需要配置持久化存储。Mastra 支持多种存储适配器,本地开发我用的是 Postgres:
import { PostgresStore } from "@mastra/pg"; const pgStore = new PostgresStore({ connectionString: "postgres://localhost:5432/mastra", }); const agent = new Agent({ name: "checker", memory: { storage: pgStore }, instructions: "……", tools: { getPackageInfo }, model: openai("gpt-4o"), });配好之后,Agent 就能在多个会话之间保留用户偏好。比如用户之前说过"我关心 React 的更新,Vue 不用管",之后它再回答时就会主动偏向 React。这个能力对应的前端心智模型就是全局 Store:之前你写 Zustand 是为了让组件共享状态,现在你配置 memory 是为了让 Agent 共享记忆,思路完全一致。
4.2 RAG:让 Agent 能读文档
如果想让 Agent 回答私有知识库的问题,就得走 RAG。Mastra 提供向量存储抽象,流程三步:把文本切片、嵌入向量、存进向量库。查询时把用户问题也嵌入,然后找最相关的片段拼进上下文。我做过一个实验性质的"公司前端规范问答 Agent",用到的核心代码大致是:
import { PgVector } from "@mastra/pg"; import { embedMany } from "ai"; const vectorStore = new PgVector("postgres://localhost:5432/mastra"); await vectorStore.createIndex({ indexName: "docs", dimension: 1536 }); // 切分文档、生成向量并存入向量库RAG 本身是个大话题,但对前端来说关键点在于:你不用自己写向量检索算法,Mastra 已经把存取和相似度查询封装成了接口,你要做的只是"喂文档进去,查询时取结果"。真正要花精力的是文档切分的颗粒度——切太碎会丢上下文,切太大会超 token。我在实践中发现,按章节切分通常比固定字符数切分效果更稳。
4.3 Workflow:把多个步骤编排成自动化任务
如果说单 Agent 是"问答机器人",Workflow 就是"自己会干活的流水线"。典型场景:用户发一个项目路径,Agent 自动完成"检查依赖版本→对比当前项目依赖→生成升级建议报告"。Mastra 的 Workflow 可以声明多个 Step,明确依赖关系。概念上很像前端的 Pipe 或中间件链,只是每个环节都可能调用 LLM 或工具。
import { Workflow, Step } from "@mastra/core"; const checkWorkflow = new Workflow({ steps: { fetchDeps: new Step({ id: "fetchDeps", execute: async ({ context }) => { // 读取 package.json 的依赖列表 return { deps: ["react", "next"] }; }, }), checkLatest: new Step({ id: "checkLatest", dependsOn: ["fetchDeps"], execute: async ({ context, context: { deps } }) => { // 逐个调用 npm registry 查最新版本 return { suggestions: "react 需要从 18 升到 19" }; }, }), }, });用dependsOn声明依赖后,Workflow 会按拓扑顺序执行,这一步就是 useEffect 依赖数组的翻版。我第一次写完这段代码,心里只有一个想法:前端多年的调度经验终于有了用武之地。
5. 前端转过来最容易踩的 5 个坑
这部分是我真实记录下来的问题清单,按踩坑时间排序,每个都花了我至少一小时排查。
5.1 提示词写成长篇小说,模型反而不会用工具
我第一次给 Agent 写 instructions 时,把业务规则、工具用法、语气偏好全部塞进去,写了 300 多字。结果模型经常该调工具时不调,或者答非所问。后来发现,instructions 应该只负责"角色和策略",工具怎么调用、参数怎么填,全交给工具自己的description和inputSchema描述。两者分工明确之后,模型表现立刻变稳了。这个教训对前端来说特别深:就像你写组件时把"样式"和"逻辑"拆开,提示词和组织代码的道理是相通的。
5.2 工具出参和入参类型没对齐,模型二次调用连环出错
这是最隐蔽的坑。inputSchema定义了入参结构,但如果execute返回的东西和模型预期不一致,模型下一步可能继续调同一个工具,参数却填错。比如我有个工具返回了{ latest: "19.0.0" },但模型在回答里想引用data.latestVersion,自然拿不到值。解决办法是保持返回对象的 key 名称稳定、语义单一,最好把返回结构也用 TypeScript 类型声明并导出让模型通过 schema 感知。Mastra 的 Tool 其实支持声明输出 schema,别偷懒不写,后面调试能省一半时间。
5.3 模型密钥别打到前端 bundle
和 Next.js 集成时最容易犯的错:环境变量命名不带NEXT_PUBLIC_前缀,却在客户端组件里引用密钥。这样模型 key 会被打进浏览器 bundle,等于公开。正确的做法是 Agent 只跑在服务端 Route Handler 或 Server Action 里,环境变量只在服务端读。我自己有一次测试时图方便在页面组件里直接调 agent,结果浏览器 network 面板里看到了 key 的 MD5 片段,幸亏及时发现。这条对任何 AI 项目都成立,不只是 Mastra。
5.4 本地存储没接数据库,上下文说没就没
不配存储适配器时,Memory 默认落在内存里,服务一重启所有会话记录清零。开发时感觉不明显,因为反正每次对话都是新的调试,但一旦开始做真实功能,用户会投诉"你刚才明明答应给我记录偏好的"。如果你本地没有 Postgres,可以先接 LibSQL 适配器,一个本地文件就能持久化,或者直接用 Docker 起一个 Postgres 容器。这步配置十分钟搞定,别跳过。
5.5 工具调用慢导致请求超时,前端以为 Agent 挂了
我的工具里有一个请求 npm registry 的接口,正常情况下 200ms 返回,但偶尔会卡到几十秒。第一次遇到时前端页面一直转圈,用户以为服务挂了。后来才意识到:工具执行是受模型调用周期约束的,一旦超过模型 provider 的超时时间,整个请求就会失败。解决思路有两个:一是给工具内部的网络请求设置合理的超时(比如 5 秒),二是让前端走流式响应,先把"正在查询 xxx"这种中间状态推给用户,减少等待焦虑。Mastra 对流式事件的支持是内置的,前端用ReadableStream就能逐步接收 Agent 状态。
6. 什么时候该用 Mastra,什么时候别被框架绑架
选型到最后,还是要回答"这个框架适合什么"。
我的使用场景是:一个带工具调用、需要跨会话记忆、可能要跑多个步骤的前端技术栈项目。Mastra 把 Agent 运行时、记忆、工作流、RAG 这些能力以 TypeScript 原语的形式集成在一个地方,我可以用熟悉的语言和心智模型快速构建,同时保留部署层面的自由——它能以 Node 服务或 Serverless 函数的方式跑在主流平台上。如果你的情况和我类似,它值得优先考虑。
反过来,如果只是给 Next.js 项目加一个聊天弹窗,Vercel AI SDK 的useChat加一个 route 就够了,完全不需要引入整套 Agent 运行时。如果团队里全是 Python 工程师,那就跟生态走,别为了 TypeScript 强行换框架。框架本身不是越重越好,关键是它的原语能不能覆盖你最短的闭环路径。
我自己的判断方法是:先把核心功能翻译成"有没有触发外部工具/有没有长期记忆/有没有多步骤流程"这三个问题,如果三个里面有任意两个是"有",Mastra 这类完整 Agent 运行时才有价值;如果全是"没有",那就老老实实轻量化。
最后分享一个实操小技巧:Mastra 的 playground 里能看到每次工具调用的完整入参出参,我习惯在每轮对话后点开看一次,比任何日志都好用。毕竟前端出身的人,直觉是"哪里不对就点哪里"。用它把模型"自作主张"的时刻记录下来,你会对提示词和工具描述怎么做微调,比看十篇教程都管用。