目标岗位:Agent Harness 研发/工程方向(参考 ~/agent/jb/jd.md) 配套讲义:讲义 04 · Structured Output 用法:先自己口头答一遍,再对照"参考回答";重点看"考察点"和"坑"。 原则:所有回答结论先行 → 项目证据 → 原理 → 边界与改进,不要背概念。


0. 面试官视角:结构化输出题到底在考什么

结构化输出是 Agent 工程的地基——工具调用、下游解析全靠它。面试官三层递进:

  1. 概念层:JSON Schema、Pydantic、原生结构化输出是什么;
  2. 实践层:你项目里三家厂商怎么实现的?校验失败怎么处理?
  3. 判断层:为什么不能只靠 prompt?失败降级怎么设计?流式 + 结构化怎么 组合?——拉开差距的地方。

回答公式:一句话结论 → 你项目里的实现/例子 → 原理 → 边界与改进


1. 按讲义章节的题目与参考回答

Q1:为什么 Agent 系统不能依赖自然语言解析?

参考回答:模型输出要喂给代码——查词结果填卡片、工具参数发请求、SQL 要 执行。自然语言输出换行、加粗、解释文字都会破坏解析;更糟的是解析失败时 分不清是"格式错了"还是"答案错了"。所以原则是协议先行 + 校验兜底 + 失败有出路:先定义输出形状,返回后校验,不过就纠错重试或降级,绝不把脏 数据传给下游。

考察点:能不能说出"解析失败无法区分错误类型"这个深层原因。


Q2:JSON Schema 有哪些关键字段?additionalProperties 为什么重要?

参考回答:type(对象/数组/标量)、required(必填)、properties (字段类型)、数组的 items/minItems、字符串的 enumadditionalProperties: false 很重要——它禁止模型自作主张加字段,协议边界一旦被突破,下游解析 就容易悄悄出错。我 /v1/structured 的 schema 都关掉了多余字段。


Q3:Pydantic 和 JSON Schema 什么关系?你项目里用在哪?

参考回答:JSON Schema 是跨语言协议,Pydantic 是 Python 侧实现——用类定义 结构、model_validate 校验并反序列化成对象。我项目里 Pydantic 用于网关 请求入口(server/schemas.py,FastAPI 自动校验,非法请求 422);响应出口 的校验在结构化引擎里用 jsonschema 做(协议无关)。代码里要接 Pydantic 时 把模型类传进引擎即可,引擎内部不依赖具体类。

加分点:能说清"协议层用 JSON Schema、代码层用 Pydantic"的分工。


Q4:三家厂商的原生结构化输出分别怎么实现?

参考回答:OpenAI 兼容直接透传 response_format: {type: json_schema, ...}; Gemini 没有这个参数,映射成 generationConfig.responseMimeType: application/json + responseSchema;Anthropic 也没有,我用隐藏工具强制—— 定义 structured_output 工具 + tool_choice 锁定,再从 tool_use.input 提取 JSON。测试在 test_structured_output.py,三家都有用例。这说明"屏蔽 差异"是 adapter 层的活,上层只传一份 schema。


Q5:模型返回后校验什么?怎么校验?

参考回答:先宽容提取 JSON(容忍 ````` ```json ```` 围栏和前后说明文字, 用平衡括号扫描找第一个完整对象/数组),再用 jsonschema 校验:字段缺失、 类型错、多余字段都会抛 ValidationError。提取和校验分开,错误信息正好 留给下一步的纠错重试用。


Q6:校验失败怎么办?为什么"错误反喂"比无脑重发有效?

参考回答:把模型自己的输出 + 校验错误反喂给它再生成一次——模型能 看到自己哪里错了,修正比重新猜可靠。我的 structured_completion 重试循环 里,第二次调用 messages 末尾是 assistant 的原始输出 + “上面的输出不符合 要求:{error},请只输出符合 schema 的 JSON”。测试验证了反喂确实发生。 边界:反喂次数有限(默认 3 次),防止无限烧 token。


Q7:重试耗尽怎么办?降级怎么标记?

参考回答:两种出路——调用方传 fallback(缓存/默认值),返回 degraded: true 让上层知道这不是模型结果;不传 fallback 就抛 StructuredOutputError, 网关返回 422。降级必须可见:这是可观测性的一部分,不能把降级数据当正常 结果用。首页"故意失败"预设就是演示这条链路。

加分点:把"降级标记"和"可观测性/数据可信度"挂钩。


Q8:结构化输出和 Tool Calling 是什么关系?

参考回答:两者解决的是同一个问题的两条路径——让模型输出结构化内容。Tool Calling 是模型先决定"调哪个工具 + 传什么参数",参数天然是结构化 JSON; 结构化输出是"直接约束回复格式"。Anthropic 没有原生 response_format,我 就是用隐藏工具模拟的——这说明协议边界是可以被抽象抹平的。生产上 Agent 主循环用 Tool Calling,数据提取类任务用结构化输出。


Q9:流式 + 结构化输出怎么处理?(判断层)

参考回答:我项目里的诚实边界是 Anthropic 流式 + 结构化暂不支持(直接报错 提示用非流式),OpenAI 兼容和 Gemini 走的是"流式出文本增量,结束时再整体 校验"。更专业的做法是 Anthropic 的 input_json_delta——流式解析 JSON 增量 片段;但最终还是要等完整输出才能做 schema 校验,所以流式对结构化主要改善 的是"感知延迟",校验时机不会变。这是我标注的下一步。

:别吹"流式结构化完全支持"——项目里 Anthropic 确实不支持。


Q10:输出格式协议设计有什么建议?

参考回答:Schema 版本化(字段演进不要原地改);状态类字段用 enum 收窄 取值;必填字段越少越稳、可选给默认值;additionalProperties 按需开关; 降级结果带标记。本质是"把和模型的协议当成和第三方 API 的协议来设计"。


Q11:你项目里结构化输出的测试有哪些?

参考回答:test_structured_output.py 覆盖三家厂商的 response_format 转换; test_structured.py 覆盖 JSON 提取(围栏/数组/无 JSON)、Schema 校验、 成功路径、错误反喂重试(验证第二次 messages 内容)、降级 fallback、无 fallback 抛 422、以及 /v1/structured 端点行为。


Q12:如果模型总是输出非法 JSON,你会怎么排查?(判断层)

参考回答:按成本从低到高排查——先看 schema 是否合理(必填字段太多、 enum 太苛刻);再看 prompt 是否给了清晰协议;再看模型能力(弱模型 + 复杂 schema 就是不稳定);最后才考虑换模型或加解析容错(json_repair)。我会先 用 attempts 和错误分布数据判断是"随机失败"还是"系统性失败"——随机失败 靠重试,系统性失败要改 schema/prompt/模型。


2. 通用回答技巧

2.1 用 PREP 结构

  • Point:“结构化输出 = 协议先行 + 校验兜底 + 失败有出路”;
  • Reason:Agent 的下游是代码,自然语言不可靠;
  • Example:三家 adapter + /v1/structured 引擎 + 首页故意失败演示;
  • Point:边界——Anthropic 流式不支持、无质量回归集,下一步。

2.2 弹药库

  • 首页"结构化输出"面板(三个预设 + attempts + 降级标记);
  • structured.py 引擎与 test_structured.py
  • test_structured_output.py 三家转换测试;
  • 对话面板的 response_format 下拉。

2.3 诚实边界清单

  • Anthropic 流式 + 结构化输出不支持(有 input_json_delta 方案);
  • 普通 chat 响应出口不强制校验(只有结构化路径严格校验);
  • JSON 提取是平衡括号扫描,生产建议 json_repair;
  • 无"结构化输出质量回归集"。

2.4 反问环节

  • “贵司结构化输出失败率怎么监控?降级路径有审计吗?”
  • “流式 + 结构化输出在你们线上是怎么组合的?”

3. 一分钟项目介绍(开场自述模板 · 结构化输出版)

“在 Anna 项目里,我实现了一条完整的结构化输出链路:三家厂商的原生 结构化输出由 adapter 屏蔽差异,上层只传一份 JSON Schema;返回后先用 宽容提取 + jsonschema 校验,失败时把模型自己的输出和校验错误反喂给它 重试,最多 3 次;重试耗尽走 fallback 降级并标记 degraded,没有 fallback 就明确报 422。首页有演示面板,可以现场看 attempts 和降级过程。我特别想 强调两点:一是协议层和 Pydantic 层分工要清楚,二是降级必须可见——不能 把缓存数据当模型结果用。诚实边界是 Anthropic 流式结构化还没支持,下一步 补 input_json_delta。”