Function Calling 参数总出错?只修参数就行

Function Calling 参数总出错?只修参数就行

上线 Agent 之后最先崩掉的东西,通常不是模型不够聪明,而是工具参数校验。

模型返回 {"priority": "urgent"},你的 schema 里只有 low/normal/high;

模型把 retry 填成 true,你的下游要 int;

模型多塞了一个 foo,你的 create_ticket(**args) 直接抛 TypeError。

很多人的处理方式是:重发整轮对话让模型重写。这是最贵的做法------正文重写一遍,token 翻倍,而且模型很可能第二次还错在同一个地方。

正确的做法是:把校验错误当成工具的执行结果,回爌给模型,只让它修参数。

一、先写一个"会说人话"的校验器

python 复制代码
PY_TYPES = {"string": str, "integer": int, "array": list, "boolean": bool}


def validate(args, schema=SCHEMA):
    """返回错误列表;空列表代表参数合法,可以真正执行工具。"""
    errors = []
    if not isinstance(args, dict):
        return ["arguments 不是 JSON 对象"]
    props = schema["parameters"]["properties"]

    for key in schema["parameters"].get("required", []):
        if key not in args:
            errors.append(f"缺少必填字段: {key}")

    for key, val in args.items():
        spec = props.get(key)
        if spec is None:
            errors.append(f"未知字段: {key}(schema 未定义,应删除)")
            continue
        want = spec["type"]
        py_t = PY_TYPES.get(want)
        # bool 是 int 的子类,必须显式排除,否则 True 会被当成合法的 integer
        if want == "integer" and isinstance(val, bool):
            errors.append(f"{key} 类型应为 integer,实际是 boolean")
            continue
        if py_t and not isinstance(val, py_t):
            errors.append(f"{key} 类型应为 {want},实际是 {type(val).__name__}")
            continue
        if "enum" in spec and val not in spec["enum"]:
            errors.append(f"{key} 取值 {val!r} 不在允许值 {spec['enum']} 内")
        if want == "string" and len(val) < spec.get("minLength", 0):
            errors.append(f"{key} 长度不足 {spec['minLength']}")
        if want == "integer":
            if "minimum" in spec and val < spec["minimum"]:
                errors.append(f"{key} 小于最小值 {spec['minimum']}")
            if "maximum" in spec and val > spec["maximum"]:
                errors.append(f"{key} 超过最大值 {spec['maximum']}")
    return errors

这里面有一个特别隐蔽的坑 :isinstance(True, int) 在 Python 里是 True。

如果你只写 isinstance(val, int),那么模型把布尔值填进整型字段时校验会通过 ,

然后一路错到数据库层。所以 bool 必须单独挡一次。

二、把错误回灌给模型,只修参数

python 复制代码
def call_with_repair(model, max_attempt=3):
    attempt, errors, args = 0, None, None
    while attempt < max_attempt:
        args = model.repair(errors, args, attempt)      # 把上一轮的错误喂回去
        errors = validate(args)
        print(f"[第 {attempt + 1} 轮] 参数={json.dumps(args, ensure_ascii=False)}")
        if not errors:
            return run_tool(args)                        # 只有校验通过才真正执行
        for e in errors:
            print("     *", e)                            # 机器可读的错误,不是"参数不对"
        attempt += 1
    return {"ok": False, "reason": "超过最大重试次数"}

用一段模拟的"模型"跑一遍(第 1 轮故意给三种养型错误):

复制代码
    [第 1 轮] 参数={"title": "bug", "priority": "urgent", "foo": 1}
  -> 校验失败,回灌给模型:
     * title 长度不足 5
     * priority 取值 'urgent' 不在允许值 ['low', 'normal', 'high'] 内
     * 未知字段: foo(schema 未暂定,应删除)
    [一第 2 轮] 参数={"title": "登录页 500", "priority": "urgent", "retry": true}
  -> 校验失败,回灌给模型:
     * priority 取值 'urgent' 不在允许值 ['low', 'normal', 'high'] 内
     * retry 类型应为 integer,实际是 boolean

       [一第 3 轮] 参数={"title": "登录页 500", "priority": "high", "retry": 2}
  -> 校验通过
    [扮行工儷] create_ticket({"title": "更新确认", "priority": "high", "retry": 2})
{'ok': True, 'id': 'T-1024'}

三轮收教。我注意模型每轮只义报错指出的那几处,荃文(对话厅史)一个字都没重写------这是 token 成本能降下来的关键。

三、五个高频坑

坑 1:错误信息不够具体。

回猄 "参数不正确",模型只能矎匟;回猄 "priority 取值 'urgent' 不在允许值 'low','normal','high' 内",模型一次就能改对。校验信息要写成机器可读的,而不是给人看的。

坐 2:没挡 bool / 数字字符串。

isinstance(True, int) is True,以及模型爱远回 "3" 而不是 3。要么严格拒绝,要么在边界层做显式转换(转换失败的报错同样要具体)。

坑 3:没有重试上限。

模型可能在同一处反复失败。max_attempt=3 是我的经验值,超了就降级(改成让用户确认参数,或走默认值),不要无限循环烧 token。

坐 4:不知道工具是否幂等,就重试。

参数校验在执行前 ,所以"校验失败 → 重试"是安全的。但如果工具已经执行、只是返回结果解析失败,再重试就会产生重复订单/重复工单。*区分"执行前重试"和"执行后重试",后者必须带幂等键。�

坑 5:把 schema 写得过于宽松。

没有 enum、没有 minLength、没有 required,等于把校验工作全推给下游业务代码。schema 是你唯一能免费拿到的约束,写严一点。

四、生产环境建议

  • 用 pydantic / jsonschema 替代手写校验器,但保留"错误信息可回灌"这一层------库抛出的异常太啰嗦,直接喂给模型反而费 token;
  • 把 validate() 的调用放在工具包装层(decorator)里,业务函数只关心合法参数;
  • 记录每次修复的轮次和错误系型,一周后你会看到模型的系统性弱点(比如总在枚举值上出错,那就是 schema 描述没写清楚);
  • 参数校验通过 ≠ 业务校验通过。业务规则(库存、权限)仍然要在工兴内部再挡一层。

小结

  • Function Calling 的稳定性,靠的是校验器 + 错误回灌,不是换更贵的模型;
  • 错误信息要具体到"哪个字段、期望什么、实际什么";
  • 只修参数,不重写正文,token 成本立刻降一个量级。

文中的完整脚本和参数表我整理在自己的笔记站(道满PythonAI):https://www.daomanpy.com/llm/agent-development-practice

相关推荐
Android系统攻城狮1 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_prepare_read调用流程与实战(七十四)
linux·运维·服务器·gstreamer音视频·音视频进阶
GHN19878882 小时前
开源小工具按一下空格,什么都看见了
windows
我科绝伦(Huanhuan Zhou)2 小时前
Linux 通用压力测试脚本合集:磁盘 + 网络
linux·网络·压力测试
霸道流氓气质2 小时前
RAG 可观测性与生产监控实战:四层遥测模型、五大仪表盘与 OpenTelemetry 全链路追踪的 Java 生产级实现
java·人工智能·python
ch6234 小时前
《AI 辅助编程实战:从零跑通一个 Excel 报表生成脚本的踩坑与复盘》
人工智能·python
Soneswy4 小时前
Linux系统篇--重定向与文件操作命令
linux
有毒的教程4 小时前
Linux底层存储路由配
linux·运维·网络
围炉聊科技4 小时前
Windows-MCP 拆解与实测:AI 操作 Windows 这件事——智能体基建系列
windows·llm·dom
import_random4 小时前
[python]conda和pip(区别和联系2)
python