AI

Mastra Structured Output 选型:原生 Response Format、JSON Prompt Injection 与独立结构化模型

明明配置了 Structured Output,却还要在提示词里重申 JSON 格式?真正的问题往往是底层 API 能否组合 Tools 与原生结构约束。

Structured Output 看起来只是“让 Agent 返回 JSON”,但一旦 Agent 还需要调用工具,问题就不再只是 Schema 怎么写。

有一种很常见、也很容易让人困惑的现象:

代码里明明已经配置了 Structured Output 和 Schema,模型还是返回普通文本、漏字段或不合法 JSON;在提示词里再写一遍输出格式后,反而生效了。

这往往意味着,Schema 没有成功变成当前 provider 的原生生成约束。可能是当前模型或 provider 不支持原生 Structured Output;也可能是它分别支持 Tools 和 Structured Output,却不能在同一次 Agent 执行中组合两者。

把 JSON 结构写进提示词之所以有效,是因为约束路径从原生 response_format 退回到了模型能直接看到的指令。Mastra 提供 jsonPromptInjection 正是为了显式处理这种兼容性问题:它把 Schema 说明注入 user message 或 system message,避免在业务 prompt 里手工维护另一份格式定义。

但两者的保证强度不同:提示词约束只是让模型更可能遵守格式,不等于 provider 在生成阶段强制 Schema。对数据库写入、支付、审核结果等严格业务,仍然需要校验、错误策略,或独立 structuring model。

真正的兼容性边界是:

模型支持 Function Calling
+ 模型支持 Structured Output
≠ 底层 API 支持两者在同一次 Agent 执行中组合

这个区别决定了我们应该使用 Mastra 的哪一种路径:原生 response_formatjsonPromptInjection、独立 structuring model,还是 prepareStep

Structured Output 约束的是什么

在 Mastra 中,可以用 Zod、Valibot、ArkType 或标准 JSON Schema 定义最终输出。例如:

import { z } from 'zod'

const resultSchema = z.object({
  title: z.string(),
  summary: z.string(),
  tags: z.array(z.string()),
})

const response = await agent.generate('分析这篇文章', {
  structuredOutput: {
    schema: resultSchema,
  },
})

console.log(response.object)

得到的不再是普通文本,而是符合 Schema 的对象:

{
  "title": "文章标题",
  "summary": "文章摘要",
  "tags": ["AI", "Agent"]
}

这种输出适合 API 响应、数据库写入、UI 渲染、信息抽取、分类评分,以及 Workflow 节点之间的数据传递。

默认路径:Provider 原生 Response Format

省略 jsonPromptInjection,或者显式设为 false时,Mastra 会把 Schema 通过 provider 的 response_format 参数传给模型:

structuredOutput: {
  schema: resultSchema,
  jsonPromptInjection: false,
}

可以把它理解为底层请求类似:

{
  response_format: {
    type: 'json_schema',
    json_schema: {
      // 由 Schema 转换而来
    },
  },
}

原生结构化输出的优势是约束更强。它不只是在提示词里请模型“尽量写对 JSON”,而是由 provider 在生成阶段应用结构约束。

因此,没有工具时,原生 Structured Output 通常是最直接的选择。

为什么加上 Tools 后会出问题

Tools 和 Structured Output 分别约束 Agent 运行的不同阶段。

Tools 约束中间过程:

用户请求
→ 模型生成 tool call
→ Mastra 执行工具
→ 工具结果返回模型

Structured Output 约束最终交付:

{
  "temperature": 32,
  "condition": "sunny",
  "suggestion": "注意防晒"
}

底层 API 需要正确处理“现在应该产生工具调用”和“现在应该产生符合 Schema 的结果”两种状态。某些模型 API 不能在同一请求里组合 function calling 与 response_format,可能导致:

  • 工具不再被调用。
  • Provider 返回参数冲突。
  • 工具成功,但最终对象不符合 Schema。
  • 模型跳过工具,直接猜测字段。

Mastra 文档特别指出,Gemini 2.5 在有 Tools 时不能组合 function calling 与原生 response_format。这是底层模型 API 的限制,不是 Mastra 自身的限制。

方案一:JSON Prompt Injection

jsonPromptInjection 不使用 provider 的原生 response_format,而是把 Schema 说明加入模型上下文。底层 API 只需正常处理 Tools,工具调用完成后,模型根据提示词生成 JSON。

它避开了 tools + response_format 的组合冲突,代价是结构更依赖模型遵循提示词,约束强度弱于原生方式。

Mastra 提供四种配置:

// 原生 Structured Output
jsonPromptInjection: false

// 把 Schema 加入最近一条用户消息
jsonPromptInjection: 'inline'

// 把 Schema 加入 System Message
jsonPromptInjection: true
jsonPromptInjection: 'system'

// 根据 Mastra 的模型能力数据选择原生方式或 inline 注入
jsonPromptInjection: 'auto'

'auto' 适合作为能力差异不明确时的起点:已知模型支持原生 Structured Output 时使用 response_format;不支持或没有能力数据时改用 inline 注入。

对新模型、自定义 Provider 或 OpenAI-compatible 接口,如果不希望依赖能力推断,可以显式使用 'inline'。Gemini 2.5 与 Tools 组合时,官方文档则要求使用 true 避开错误。

方案二:独立 Structuring Model

如果主 Agent 不擅长结构化输出,或者工具循环和 Schema 都比较复杂,可以在 structuredOutput 中指定另一个模型:

const response = await agent.generate('分析 TypeScript,并在必要时调用工具', {
  structuredOutput: {
    schema: z.object({
      overview: z.string(),
      strengths: z.array(z.string()),
      weaknesses: z.array(z.string()),
    }),
    model: 'openai/gpt-5.5',
  },
})

此时 Mastra 会执行两次 LLM 调用:

用户请求
→ 主 Agent:理解任务、调用工具、读取结果、生成自然语言响应
→ Structuring Agent:读取当前响应,按 Schema 提取结构化对象

这是很清晰的职责分离:

主 Agent 负责把事情做对
Structuring Model 负责把格式做对

代价也很明确:额外的模型调用会增加延迟和成本。它更适合多工具、多轮 tool loop、复杂 Schema,或最终对象将直接进入 API 和数据库的场景。

Mastra 官方文档对这个机制的说明和示例见 Use a separate structuring model

useAgent 要不要开

默认情况下,独立 structuring model 只根据主 Agent 当前生成的响应进行转换。

如果结构化过程确实需要当前 Agent 的对话历史和只读 Memory 上下文,可以增加:

structuredOutput: {
  schema: resultSchema,
  model: 'openai/gpt-5.5',
  useAgent: true,
}

不需要历史信息时,不要开启 useAgent。让 structuring model 只看本次响应,可以减少无关上下文的干扰。

方案三:用 prepareStep 分离执行阶段

当工具调用顺序明确时,可以在同一 Agent loop 里按步骤切换配置:

const result = await agent.stream('查询温哥华天气', {
  prepareStep: async ({ stepNumber }) => {
    if (stepNumber === 0) {
      return {
        tools: { weatherTool },
        toolChoice: 'required',
      }
    }

    return {
      tools: undefined,
      structuredOutput: {
        schema: z.object({
          temperature: z.number(),
          humidity: z.number(),
          windSpeed: z.number(),
        }),
      },
    }
  },
})

这个流程的分工是:

Step 0:只开启 Tools,完成数据获取
Step 1:关闭 Tools,启用原生 Structured Output

每个生成步骤都不需要同时处理 tools + response_format。和提示词注入相比,最终步骤仍然可以使用 provider 的原生结构约束。

三种兼容方案怎么选

方案模型调用结构约束主要代价适合场景
jsonPromptInjection不额外增加独立调用中等更依赖模型遵循提示词简单 Schema、第三方模型、优先解决兼容问题
独立 structuring model额外一次增加延迟和成本多工具、复杂 Schema、生产 API
prepareStep复用原 Agent loop 的分步调用需要明确设计阶段工具顺序可控、执行流程明确

可以按下面的顺序决策:

  1. 没有 Tools:直接使用原生 Structured Output。
  2. 有少量工具,Schema 简单:先尝试 'auto';能力推断不可信时显式使用 'inline'
  3. 有多个工具或多轮 Agent loop:考虑独立 structuring model。
  4. 工具调用顺序明确:用 prepareStep 将工具阶段和结构化阶段分开。

工具已经返回结构化数据时,不要让模型重抄

假设天气工具已经返回:

const weather = {
  temperature: 32,
  humidity: 70,
  condition: 'sunny',
}

能由程序确定的字段,应该直接组装和校验:

const result = resultSchema.parse({
  temperature: weather.temperature,
  humidity: weather.humidity,
  condition: weather.condition,
})

只有建议类字段需要生成时,只把那一小部分交给模型。这会减少重抄错误,也能降低 token 和校验成本。

原则可以浓缩为一句话:

能由程序确定的数据,不要让模型重新抄写和推断。

生产环境的分层

复杂 Agent 可以把职责拆成四层:

Conversation Layer:自然语言交互
→ Agent Layer:推理、调用工具、获取信息
→ Structuring Layer:将结果转换成稳定对象
→ Validation Layer:Zod 或 JSON Schema 校验
→ Application Layer:API、数据库、UI 或后续 Workflow

结构化输出不应被理解为“给整个 Agent 永久开启 JSON 模式”,它更像是在明确交付节点对输出施加 Schema 约束。

根据业务风险,还需要选择合适的错误策略:

  • strict:校验失败时抛错,适合数据库写入、支付、审核结果等严格业务。
  • warn:记录警告并继续。
  • fallback:校验失败时返回预设值,可用于非关键的推荐文案。

最后的判断

原生 Response Format 解决的是强约束,JSON Prompt Injection 解决的是兼容性,独立 structuring model 解决的是职责分离,prepareStep 则用显式阶段换取更强的控制。

对复杂 Agent,最稳定的思路不是让一个模型在一个阶段里承担所有责任,而是:

Agent 负责把事情做对
Structuring Model 负责把格式做对
Schema Validation 负责确保结果可信

参考

Back to Blog

Related Posts

View All Posts »

Mastra Client Tools 架构与实现机制

全面解析 Mastra 框架中 clientTools 的工作原理,从客户端定义到服务端处理的完整流程,探讨多工具来源合并机制与 AI SDK 的协同工作方式。