客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gemini API 的 Structured Outputs 能按 JSON Schema 生成结果;本篇用 Python 把退款、账号和普通咨询分流,并在模型输出越界时拒绝自动流转。你会得到一条能复制、能超时、能降级的最小链路。
为什么不直接解析一段自然语言
结构化输出的作用是规定“输出长什么样”,不是保证事实正确。Schema 让字段和枚举更稳定;业务规则仍要由服务端负责。本文把 priority 限在 low、normal、high,把 queue 限在三个队列。模型若返回未知值,程序转到人工队列。当前官方文档把它用于抽取、分类和 Agent 工作流,并通过 response_format 的 application/json 与 schema 配置。

环境准备
使用 Python 3.10+。安装依赖:python -m pip install google-genai。把密钥放入环境变量:export GEMINI_API_KEY='你的密钥'。模型名示例为文档中的 gemini-3.8-flash;你的账号可用模型与配额仍需在控制台确认。保存为 ticket_router.py,运行 python ticket_router.py。示例未在本次任务中实际调用线上 API;本地仅完成语法检查,目标环境安装依赖后应再作一次无敏感数据的联调。
import json
import os
from google import genai
ALLOWED_QUEUES = {"refund", "account", "general"}
ALLOWED_PRIORITY = {"low", "normal", "high"}
SCHEMA = {"type": "object", "properties": {
"queue": {"type": "string", "enum": sorted(ALLOWED_QUEUES)},
"priority": {"type": "string", "enum": sorted(ALLOWED_PRIORITY)},
"reason": {"type": "string"}},
"required": ["queue", "priority", "reason"]}
def safe_route(raw: str) -> dict:
try:
data = json.loads(raw)
except json.JSONDecodeError:
return {"queue": "human_review", "reason": "JSON无法解析"}
if data.get("queue") not in ALLOWED_QUEUES:
return {"queue": "human_review", "reason": "未知队列"}
if data.get("priority") not in ALLOWED_PRIORITY:
return {"queue": "human_review", "reason": "未知优先级"}
return data
def classify(ticket: str) -> dict:
if not os.getenv("GEMINI_API_KEY"):
return {"queue": "human_review", "reason": "缺少GEMINI_API_KEY"}
client = genai.Client()
try:
result = client.interactions.create(
model="gemini-3.8-flash", input=ticket,
response_format={"type": "text", "mime_type": "application/json",
"schema": SCHEMA}, timeout=20)
return safe_route(result.output_text)
except Exception as exc:
return {"queue": "human_review", "reason": f"调用失败: {type(exc).__name__}"}
if __name__ == "__main__":
print(classify("订单重复扣款,请尽快退款"))
assert safe_route('{"queue":"refund","priority":"high","reason":"重复扣款"}')["queue"] == "refund"
代码分两层:SCHEMA 提前缩小模型输出空间;safe_route 再做独立验证,不能因为 API 承诺 JSON 就省略它。timeout=20 是为了不让一个慢请求占住 Web 工作线程;生产环境还应设置 HTTP 客户端级超时、重试次数和熔断。genai.Client() 从环境读取密钥,不要把密钥写进仓库。
预期输出(有密钥且调用成功时)类似 {'queue': 'refund', 'priority': 'high', 'reason': '...'};没配密钥则是人工复核。最常见的坑有:一,Schema 只约束格式,不会自动核验订单真伪;二,枚举改了却没同步服务端白名单;三,超时后盲目重试,造成同一工单被重复创建;四,把模型理由直接展示给用户而泄露内部规则。
适合把“先分给谁处理”自动化,不适合直接批准退款或关闭投诉。工程化时,把原始文本、模型版本、schema 版本、路由结果和人工改判一起记录;每周抽样计算错分率。5分钟实践:新增 security 队列,并写一个断言确保模型返回 delete_database 时永远进入 human_review。
你的工单里,哪一个字段一旦分类错就不该自动流转?
关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。
本文首发于 java4u.cn,转载请注明出处。

