模型让你调一个 refund(order_id, amount) 退款函数,结果它给的 order_id 是它现编的,amount 比订单金额还大。这种"入参幻觉"我吃过亏,下面用问答的形式把我趟过的几个问题理一遍。


Q:模型真的会编造函数入参吗?有多严重?

会,而且比想象中频繁。我做过一个退款 Agent,统计了一周,大概每 100 次函数调用里有 6~8 次入参有问题。最常见三类:

  • 编造不存在的 ID(用户没提,模型自己造一个看起来很像的)

  • 数值越界(退款金额 > 订单金额、数量为负)

  • 枚举值乱填(状态字段它填了个枚举里根本没有的值)

光靠 prompt 里写"不要编造参数"压不住,能降但压不到零。


Q:那靠模型自己约束不行,怎么办?

核心就一句:把函数入参当成完全不可信的外部输入来校验。跟你校验前端表单提交是一个道理,谁信前端谁倒霉。

我在工具被真正执行之前,强制加一道校验关:

def validate_refund(order_id, amount):
    order = db.get_order(order_id)
    if order is None:
        return Err("order_id 不存在,请向用户确认订单号")
    if amount <= 0 or amount > order.paid_amount:
        return Err(f"退款金额非法,应在 0~{order.paid_amount} 之间")
    return Ok()

校验不过,不执行,把错误原因回吐给模型,让它重新组织。


Q:校验失败后,是直接报错还是让模型重试?

我倾向"带着错误信息让它重试一次"。把 order_id 不存在 这种具体原因塞回去,模型很多时候会反应过来"哦我没问用户要订单号",转头去问用户,而不是再编一个。

但重试要设上限,我设的是最多 2 次。见过模型连编三次不同的假 ID 死活不改的,再循环下去就是烧钱,到上限直接转人工。


Q:schema 层面能不能提前堵一部分?

能,而且性价比很高。在函数定义里就把约束写死:

{
  "name": "refund",
  "parameters": {
    "amount": {"type": "number", "minimum": 0},
    "reason": {"type": "string", "enum": ["质量", "物流", "其他"]}
  }
}

枚举和类型约束写进 schema 后,枚举值乱填这类直接少了一大半。不过 schema 管不了"业务级"约束(比如金额不能超订单),那种还得靠运行时校验。两层都得有。


Q:这套自己写很麻烦吗?

说实话校验逻辑本身不复杂,麻烦的是编排——什么时候校验、失败怎么回流给模型、重试几次。我后来是在一个零代码搭智能体的平台上配的:函数节点后面挂一个校验节点,校验节点的失败分支连回模型节点形成重试环,到次数上限走转人工分支。这套流程图拉出来一目了然,比埋在代码里的 if-else 好维护多了。


Q:有没有什么反直觉的坑?

有一个。我一开始把校验错误信息写得特别"机器",比如 ERR_AMOUNT_001,结果模型看不懂,重试还是错。后来改成人话——退款金额 200 超过了订单实付 150——模型一下就改对了。给模型看的错误信息要写成人话,别写错误码。 这跟给人看的日志正好相反。


最后补一句不相关的:模型那端我走的讯飞  MaaS,现成 API,没自己部署,省了不少运维心思。

你们 function calling 的入参校验做到哪一层了?只靠 schema 还是上了运行时兜底?评论区说说你们的幻觉率。

Logo

一站式 AI 云服务平台

更多推荐