面试官问:「模型调工具总传错参数怎么办?」——参数幻觉的四层治理,别再只会加 prompt 了
《AI Agent 面试 30 问》· 第 03 问 前两篇讲的是「该不该让模型决策」和「怎么组织决策」。这篇讲决策落地时最常炸的一环:模型说它要调工具,但参数是错的。
先分清两种幻觉
这是这道题最重要的分水岭,很多人一上来就答偏:
- 事实幻觉(factual hallucination):模型编造不存在的信息。难治,只能靠 RAG、引用溯源、外部校验来兜。
- 参数幻觉(argument hallucination):编造不存在的字段、类型写错、枚举值瞎填、该传 ID 却传了名字。这个好治——它本质是「格式 + 约束」问题,可以用工程手段压到很低。
面试时先说这一句,比直接背方案有效得多:「参数幻觉不是知识问题,是约束问题。」
四类成因,对号入座才能对症:
| 成因 | 表现 | 例子 |
|---|---|---|
| schema 描述含糊 / 字段过多 | 选错字段、张冠李戴 | 同时有 user_id 和 account_id,模型填反 |
| 参数需要模型「推算」 | 算错、编造 | 让它算「三天后」的日期、把「北京」转成三字码 |
| 上下文里没有该信息 | 幻觉填补成看似合理的值 | 用户没说人数,模型自己填 2 |
| 解码自由度太高 | 类型漂移 | "3" vs 3、"true" vs true、结果多包一层 |
一、真题还原
「你们线上有没有遇到模型调工具传错参数?怎么解决的?」
三层考察意图:
- 知不知道成因(「模型笨」不算答案);
- 有没有工程手段(schema、校验、重试、约束解码);
- 知不知道边界(哪些治不了、只能兜)。
60 分答案基本是这三句:「在 prompt 里写清楚」「加几个 few-shot 例子」「让它输出前自己检查一遍」。
问题在于:这三条都是软约束,是概率性的。模型这次对了,不代表下次对;参数一多、上下文一长,准确率立刻掉。
90 分答案的核心是一次思路转换:从「让模型别错」换成「错了能被拦住、还能自愈」。
四层:降低出错率 → 拦住错误 → 自愈 → 兜底。
二、第一层:把 schema 写对,能消掉一半问题
大部分参数幻觉是 schema 设计出来的。四条原则:
1. 用枚举代替自由文本
cabin 不要写成「舱位,字符串」,写成 enum: ["economy", "business", "first"]。模型的自由度直接被砍掉。
2. 字段越少越好 每多一个可选字段,就多一次选错的机会。能拆成两个工具就别塞成一个巨型工具。
3. 描述里写「什么时候不要用」 只写「什么时候用」不够。加上「信息不全时先追问,不要猜测」,比写十遍「不要编造」有用。
4. 别让模型算,让工具算 日期换算、单位转换、ID 映射,全部推到工具内部。模型只需要把用户原话里的「下周三」原样传进去。
{
"name": "book_flight",
"description": "预订航班。仅在用户明确给出出发地、目的地和日期时调用;信息不全时先向用户追问,不要自行补全。",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["from", "to", "date"],
"properties": {
"from": {
"type": "string",
"description": "出发城市三字码,如 PEK、SHA",
"pattern": "^[A-Z]{3}$"
},
"to": {
"type": "string",
"description": "到达城市三字码,如 PEK、SHA",
"pattern": "^[A-Z]{3}$"
},
"date": {
"type": "string",
"description": "出发日期,格式严格为 YYYY-MM-DD;用户说相对时间时原样传原文,由服务端换算",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"cabin": {
"type": "string",
"enum": ["economy", "business", "first"],
"default": "economy"
}
}
}
}
三个细节值得单独说:
additionalProperties: false:直接禁止模型编字段。很多框架默认允许,这就是幻觉的温床。pattern/enum:把校验前置到 schema,配合 structured output 时,模型在生成阶段就能收到约束。default:有合理默认值的字段标出来,模型不用猜。
三、第二层:服务端校验,永远不信任模型
核心原则:模型传来的参数,和用户从浏览器传来的参数,信任级别是一样的——都要校验。
def validate(tool_name, args):
errors = []
schema = SCHEMAS[tool_name]
# 1. 结构校验:未知字段、缺失必填
for k in list(args):
if k not in schema["properties"]:
errors.append({"field": k, "expect": "已知字段", "actual": "未知字段,已丢弃"})
args.pop(k)
for k in schema.get("required", []):
if k not in args:
errors.append({"field": k, "expect": "必填", "actual": "缺失"})
# 2. 约束校验:枚举、正则
for k, v in args.items():
rule = schema["properties"].get(k, {})
if "enum" in rule and v not in rule["enum"]:
errors.append({"field": k, "expect": rule["enum"], "actual": v})
if "pattern" in rule and not re.fullmatch(rule["pattern"], str(v)):
errors.append({"field": k, "expect": rule["pattern"], "actual": v})
# 3. 业务校验:格式对了,值存在吗
if tool_name == "book_flight" and not errors:
for k in ("from", "to"):
if not airport_exists(args[k]):
errors.append({"field": k, "expect": "已存在的机场三字码", "actual": args[k]})
return errors
第 3 层最容易漏:格式对不代表值对。AAA 能过正则,但它不是任何机场。业务校验是最后一道关。
四、第三层:错误要结构化回灌,让它自己改
校验没过,别直接抛异常给用户,也别原样重试。把具体错在哪喂回去:
def call_tool_safely(name, raw_args, max_retry=2):
args = dict(raw_args)
errors = []
for _ in range(max_retry + 1):
errors = validate(name, args)
if not errors:
return {"ok": True, "data": TOOLS[name](**args)}
# 关键:错误信息要「可执行」,不是堆栈
args = llm.repair(
tool=name,
bad_args=args,
errors=errors, # [{"field": "to", "expect": "^[A-Z]{3}$", "actual": "北京"}]
)
return {"ok": False, "reason": "参数校验多次失败,已转人工", "errors": errors}
对比三种回灌质量,差别巨大:
| 回灌方式 | 模型看到的 | 修复成功率 |
|---|---|---|
| 抛原始异常 | KeyError: 'to' | 低,模型不知道要传什么 |
| 只说不合格 | 「参数不合法,请重试」 | 低,它大概率原样再试一次 |
| 结构化说明 | to 期望三字码如 PEK,实际收到「北京」 | 高 |
再补三条:
- 只回灌错误字段,别把整个 schema 再塞一遍,省 token 也减少干扰;
- 限次数(2 次够了),第 3 次还错说明不是格式问题而是理解问题,该降级;
- 写操作要幂等,重试不能造成重复下单——用请求级幂等 key,而不是指望模型「记得别重复」。
五、第四层:兜底,承认有些治不了
有些参数幻觉治不好,只能兜:
- 用户没说、模型自己编的(比如人数默认填 2)→ 把「信息不全必须追问」写进工具描述,并设为 required,让它在 schema 层面就过不去;
- 需求本身有歧义(「订最近的航班」)→ 主动澄清,别猜;
- 高风险操作(转账、删数据、发邮件)→ 强制人工确认(HITL),把参数展示给用户看一眼再执行;
- 实在不行 → 明确告诉用户「我没能完成,因为 X」,别硬编一个成功结果。
一句在面试里很加分的话:「Agent 的可靠性不来自模型不出错,而来自出错之后系统还能收敛。」
六、连环追问
Q1:prompt 里明明写了「不要编造」,为什么还编? prompt 是软约束,靠的是模型在这个上下文里的概率倾向;schema 和校验是硬约束,不满足就执行不了。想让模型「不」做什么,靠约束比靠嘱咐可靠。
Q2:加重试会不会死循环、烧钱? 会,所以要三条:只在校验失败时重试(不是所有异常)、次数上限 2、错误信息必须具体(否则重试等于重掷骰子)。重试调用可以走更小更快的模型,成本可控。
Q3:工具一多,模型选错工具怎么办?
工具描述写清「何时用 / 何时不用」;工具总数控制在 10 个以内,超了就做分组路由(先选类目,再在类目内选工具);名字别起得太像——get_user 和 get_user_info 这种最容易混。
Q4:参数里有敏感信息怎么办?
服务端做权限校验,不信任模型带来的任何身份字段——user_id 必须从会话上下文注入,不能让模型传。日志里对参数脱敏。
Q5:流式输出和工具调用冲突吗? 不冲突。文本可以流式吐;工具调用那段本质是一次完整结构化输出,等参数收全再解析。硬要流式解析参数,只会得到半截 JSON。
Q6:怎么度量这块做得好不好? 三个数:工具调用成功率、schema 校验失败率、平均重试次数。按工具维度拆开看——某个工具失败率特别高,八成是 schema 设计问题,不是模型问题。
七、加分项与踩坑
说出来就加分的词
structured output、constrained decoding、additionalProperties: false、幂等 key、HITL、服务端二次校验。
别踩的坑
- 只讲 prompt 和 few-shot,不讲校验——一听就是没上线过;
- 用正则去解析模型输出的 JSON——模型多写一句解释就崩,这是最脆的方案;
- 认为 schema 写得越全越好——字段越多、描述越长,选错概率越高,要精简;
- 把参数幻觉和事实幻觉混为一谈——前者能工程化治理,后者不行,混着讲显得没想清楚。
八、面试前 30 秒版
「参数幻觉和事实幻觉要分开看:它不是知识问题,是约束问题,所以能用工程手段压到很低。 四层:第一层 schema 设计——枚举代替自由文本、字段精简、
additionalProperties: false禁掉编字段、能推算的参数全部推到工具内部算;第二层服务端校验——结构、约束、业务三关,永远不信任模型传来的值;第三层结构化回灌——把「哪个字段、期望什么、实际什么」喂回去让它改,限 2 次;第四层兜底——高风险操作强制人工确认,失败就明确说失败。 核心思路是:不指望模型不出错,而是让错误拦得住、改得回来。」
小结与下期预告
这题的锚点是约束:prompt 是软的,schema 和校验是硬的。参数幻觉之所以可治,正是因为它能被硬约束框住。
下一篇往回退一层,聊 Agent 的 Memory 到底怎么分层——以及为什么 RAG 不是记忆,这两件事混着讲是面试里最常见的减分点。
本系列默认以 OpenAI 兼容接口 + Python 为例,示例为教学用最小实现,生产代码需补充鉴权、并发与审计。