
提示工程进阶:结构化输出与工具调用
当大模型从聊天框走向生产系统,提示词的目标就变了:不再追求「回答得漂亮」,而是结果可以被程序稳定解析。这篇讲两个最常踩坑的环节——结构化输出和工具调用。
结构化输出的三种做法
按可靠性从低到高排序:
1. 靠提示词要求返回 JSON
只输出 JSON,不要任何解释,格式如下:
{"title": string, "tags": string[], "summary": string}
实现最快,但模型经常在前后加一句「好的,以下是结果」或者在 JSON 里写注释,导致解析失败。适合快速验证。
2. 限定解码(Constrained Decoding)
把 JSON Schema 编译成状态机,在每一步解码时把不合法的 token 概率直接置零。这样生成的文本在语法上一定是合法 JSON。
{
"type": "object",
"properties": {
"title": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } },
"summary": { "type": "string" }
},
"required": ["title", "tags", "summary"]
}
主流推理框架(如 vLLM 的 guided decoding、各家的 JSON Mode)都支持。代价是约束太复杂时可能略微影响生成质量。
3. 结构化输出 + 事后校验
即使语法合法,语义仍可能不满足要求。所以解析之后一定要过一遍校验:字段是否缺失、枚举值是否越界、数值是否在合理区间。校验失败就带上错误信息重试一次,通常能修好九成问题。
for attempt in range(2):
raw = call_model(prompt)
try:
data = Schema.model_validate_json(raw)
break
except ValidationError as e:
prompt = f"{prompt}\n上一次输出不合法:{e}\n请只返回合法 JSON。"
工具调用:把模型的判断放进确定性流程
工具调用的本质是让模型输出一个结构化的调用意图,由外部代码执行。几个实践要点:
- 描述比命名重要。工具名要和函数对应,但真正影响模型选择的是描述。描述里要写清「什么时候用」「什么时候不要用」。
- 参数尽量扁平。嵌套三层的参数结构,模型填错的概率会明显上升。宁可拆成多个工具。
- 区分「无参数可填」和「不需要调用」。把工具参数全部设为可选,模型容易在信息不足时硬编一个默认值;更好的做法是让它在缺少必要信息时先反问。
- 执行结果要回灌。把工具返回的原始结果(或摘要)作为新的消息交回模型,并且在提示词里说明「工具结果可能不完整或出错,请据此判断」。
- 限制步数。给循环设一个上限(例如 8 步),并让模型在达到上限时总结当前进展,避免无休止地反复调用。
tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "按订单号查询订单状态;仅当用户提供了订单号时使用,不要猜测订单号。",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "纯数字订单号"}},
"required": ["order_id"],
},
},
}
]
别忽略错误路径
生产环境里,模型调用失败、超时、被限流都是常态。至少要准备:
- 超时与重试(带指数退避),重试次数不要超过 2 次。
- 降级方案:解析失败时回退到纯文本输出,再走人工兜底。
- 日志:把提示词、原始输出、校验错误都记下来,这是后续调优唯一可靠的依据。
小结
结构化输出的关键在约束与校验,工具调用的关键在描述与边界。提示词写得再漂亮,也比不上「让非法输出根本生成不出来,生成出来就一定能被发现」。