面试官问:「模型调工具总传错参数怎么办?」——参数幻觉的四层治理,别再只会加 prompt 了

0 阅读7分钟

面试官问:「模型调工具总传错参数怎么办?」——参数幻觉的四层治理,别再只会加 prompt 了

《AI Agent 面试 30 问》· 第 03 问 前两篇讲的是「该不该让模型决策」和「怎么组织决策」。这篇讲决策落地时最常炸的一环:模型说它要调工具,但参数是错的。

先分清两种幻觉

这是这道题最重要的分水岭,很多人一上来就答偏:

  • 事实幻觉(factual hallucination):模型编造不存在的信息。难治,只能靠 RAG、引用溯源、外部校验来兜。
  • 参数幻觉(argument hallucination):编造不存在的字段、类型写错、枚举值瞎填、该传 ID 却传了名字。这个好治——它本质是「格式 + 约束」问题,可以用工程手段压到很低。

面试时先说这一句,比直接背方案有效得多:「参数幻觉不是知识问题,是约束问题。」

四类成因,对号入座才能对症:

成因表现例子
schema 描述含糊 / 字段过多选错字段、张冠李戴同时有 user_id 和 account_id,模型填反
参数需要模型「推算」算错、编造让它算「三天后」的日期、把「北京」转成三字码
上下文里没有该信息幻觉填补成看似合理的值用户没说人数,模型自己填 2
解码自由度太高类型漂移"3" vs 3、"true" vs true、结果多包一层

一、真题还原

「你们线上有没有遇到模型调工具传错参数?怎么解决的?」

三层考察意图:

  1. 知不知道成因(「模型笨」不算答案);
  2. 有没有工程手段(schema、校验、重试、约束解码);
  3. 知不知道边界(哪些治不了、只能兜)。

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 为例,示例为教学用最小实现,生产代码需补充鉴权、并发与审计。