本文不讲"让模型回答得像 JSON"。生产环境里,这个说法不够用。真正能入库的结构化输出至少要满足三件事:JSON 能解析,字段符合 schema,失败时能重试或进入人工队列。
Gemini 的 structured output 适合做文本抽取、分类、工具调用参数生成。Google 官方文档说明,Gemini API 可以配置模型按 JSON Schema 生成结果,结构化输出模式支持 JSON Schema 的一个子集。Google 后续也在 Blog 和 X 上宣布过对 JSON Schema 支持和属性顺序遵循的增强。
下面用"客服对话抽取工单字段"做一个后端流程。
1. 先定义入库字段
示例输入:
text
客户王女士反馈:6 月 10 日下单的 A-2309 收到后少了一个电源适配器。
她已经催过两次,希望今天给处理结果,不接受只退款。
客服备注:客户情绪急,建议优先转售后主管。
目标 JSON:
json
{
"customer_name": "王女士",
"order_id": "A-2309",
"issue_type": "missing_item",
"missing_item": "电源适配器",
"urgency": "high",
"refund_only_acceptable": false,
"suggested_department": "after_sales_manager",
"need_human_review": true
}
字段设计要克制。不要把"摘要、分类、建议、风险判断"都塞进一个字段。字段越小,越容易校验,也越容易统计准确率。
2. 给模型一个 schema
用 JSON Schema 表达字段约束:
json
{
"type": "object",
"properties": {
"customer_name": { "type": ["string", "null"] },
"order_id": { "type": ["string", "null"] },
"issue_type": {
"type": "string",
"enum": ["missing_item", "damaged_item", "delivery_delay", "refund_request", "other"]
},
"missing_item": { "type": ["string", "null"] },
"urgency": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"refund_only_acceptable": { "type": "boolean" },
"suggested_department": {
"type": "string",
"enum": ["customer_service", "after_sales", "after_sales_manager", "finance", "other"]
},
"need_human_review": { "type": "boolean" }
},
"required": [
"customer_name",
"order_id",
"issue_type",
"missing_item",
"urgency",
"refund_only_acceptable",
"suggested_department",
"need_human_review"
]
}
注意两个坑。
第一,Gemini structured output 支持 JSON Schema 子集。复杂的递归、默认值、部分 anyOf 用法、SDK 层的 schema 转换,可能在不同版本里出现兼容问题。GitHub 上 python-genai、LangChain、Vercel AI SDK 都有过相关讨论。
第二,schema 不要在 prompt 里重复写一遍。Google Gen AI Python SDK 的 README 里也提醒过,不要在输入提示词里重复给出 JSON 示例,否则可能影响生成质量。
3. 服务端流程
推荐链路如下:
text
原始文本
-> 脱敏
-> Gemini structured output
-> JSON 解析
-> schema 校验
-> 业务规则校验
-> 入库或人工复核
业务规则校验举例:
pseudo
if order_id is null:
route_to_manual_review("missing_order_id")
if urgency == "high" and need_human_review == false:
mark_warning("high_urgency_without_review")
if issue_type != "missing_item":
missing_item must be null
这里不要只相信模型。模型负责"读懂文本并给出初稿",后端负责"保证数据能被系统消费"。
4. 失败重试怎么做
常见失败有四类。
JSON 解析失败:通常是模型返回了额外文字,或上下游 SDK 没有正确启用 JSON/structured output。先检查 response_mime_type、response_schema 或对应 SDK 参数。
schema 校验失败:字段缺失、类型错误、枚举值不在范围内。可以把错误信息带回模型重试一次,但不要无限重试。
业务规则失败:例如退款意愿和原文相反。这类问题需要进入人工抽检或样本回流。
网络和服务失败:API 超时、限流、代理链路不稳定。这里要有幂等 key、重试间隔、死信队列和告警。
一个简单策略:
pseudo
max_retry = 2
for i in range(max_retry + 1):
result = call_model(input, schema)
if json_parse_ok(result) and schema_ok(result) and business_ok(result):
save(result)
break
else:
create_manual_task(input, last_error)
5. 入库表设计
不要只存抽取结果。建议至少存四类数据:
sql
ticket_extract_result
- id
- source_text_id
- model_name
- schema_version
- extracted_json
- validation_status
- confidence_label
- manual_review_status
- created_at
schema_version 很重要。字段调整后,历史数据才能被解释。model_name 也要保存,比如 gemini-3.5-flash、gpt-5.5、claude-opus-4.8,这样后续做模型对比才有依据。
6. 国内接入时要提前处理的问题
如果团队在国内直接调用 Google、OpenAI 或 Anthropic 官方 API,通常要提前评估这些限制:
网络出口是否稳定,P95/P99 延迟是否能接受;账号、付款、发票和企业采购流程是否匹配;用户数据是否需要脱敏或本地留存;是否涉及跨境传输审批;业务高峰期是否有备用模型或备用供应商。
结构化抽取看起来只是一个小接口,实际上经常在客服、财务、工单、售后这些链路里跑。任何一次超时、字段错位或账单异常,都可能影响业务系统。
7. 词元无忧 API 的接入位置
如果你不想在 POC 阶段分别接 Gemini、GPT-5.5、Claude Opus 4.8,可以把词元无忧 API 放在模型网关层。
它支持主流模型统一接入,接入方式对标 OpenAI 官方 API,也支持各家官方格式。对已有 OpenAI SDK 使用经验的团队来说,可以先把调用层抽象出来,再在不同模型之间做效果、成本和延迟对比。
国内团队比较在意的点还包括按实际用量计费、无预付、人民币企业结算、专线优化和 ICP 备案。这些不直接提高抽取准确率,但会决定项目能不能从 demo 走到生产。
8. 发布前检查清单
- 是否定义了 schema,而不是只靠 prompt 要求 JSON。
- 是否做了 JSON 解析、schema 校验和业务规则校验。
- 是否保存了原文、模型名、schema 版本和错误日志。
- 是否有失败重试、人工复核和死信队列。
- 是否处理了国内接入的网络、结算、合规和数据脱敏问题。
- 是否用真实样本统计字段级准确率,而不是只看几条演示数据。