坑0:我天真地以为Function Calling很简单

三个月前,我接了一个项目:给一个内部客服系统加上Function Calling,让用户可以查询订单、退换货、修改地址。我看了文档,觉得很简单——定义几个函数,让模型调用就行了。

三个月后,系统终于稳定了。但中间踩了无数坑。以下是我踩过的10个最大的坑,希望能帮你少走弯路。

坑1:模型调用了一个不存在的函数

这是最经典的坑。你定义了get_order_status函数,但模型自作主张调用了cancel_order(你没有定义这个函数)。当你的代码试图执行这个不存在的函数时,整个流程就崩溃了。

解决方案:不要信任模型输出的函数名。在调用前,始终验证函数名是否在你定义的函数列表中。如果模型调用了不存在的函数,返回一个友好的错误信息,让模型重新选择。

坑2:参数类型不匹配

模型给一个需要整数的参数传了字符串"3",给一个需要日期格式的参数传了"明天"。你的JSON解析器会直接崩溃。

解决方案:在你的函数实现中做好参数校验和类型转换。不要假设模型传的参数一定是正确的。

坑3:模型在不需要调用函数时调用了函数

用户说"你好",模型调用了get_order_status,传了一个空参数。这让用户困惑,也让系统浪费了资源。

解决方案:在system prompt中明确告诉模型"只在用户明确需要执行操作时调用函数"。也可以设置一个"置信度阈值"——如果模型不确信应该调用函数,就不要调用。

坑4:模型在需要调用函数时不调用

用户说"我上周买的那个东西怎么还没到",模型应该调用get_order_status,但它没有——它开始编造一些通用的安慰话术。

解决方案:在system prompt中给模型明确的指引——“当用户询问订单、物流、售后等具体问题时,你必须调用相应的函数获取真实信息,不要编造。”

坑5:函数调用结果太大,超出了上下文窗口

你调用了一个函数,返回了10000条订单记录。当你想把这个结果传回给模型时,发现超出了上下文窗口限制。

解决方案:在函数层面做好数据过滤和截断。只返回最相关的信息,而不是全量数据。如果结果确实很大,使用分页或多轮交互。

坑6:多轮函数调用中的状态丢失

模型先调用了get_order_list获取订单列表,然后想调用get_order_detail查看某个订单详情。但在第二轮调用中,它"忘记"了第一轮获取的订单ID。

解决方案:在系统中维护一个会话状态,记录已获取的信息。在prompt中保留关键的历史信息。

坑7:函数定义的描述不够清晰

你的函数描述写了"查询订单信息",但模型不知道这个函数可以查询哪些信息、需要什么参数、返回什么格式。结果模型要么不调用,要么传错参数。

解决方案:花时间写好函数描述。包括:这个函数做什么、什么情况下应该调用、每个参数的含义和格式、返回值的格式。一个好的函数描述应该像一份微型的API文档。

坑8:并行调用导致的竞态条件

模型同时调用了get_order_status和get_user_info,但get_user_info依赖于get_order_status返回的user_id。你的代码并行执行了两个调用,结果get_user_info因为缺少user_id而失败。

解决方案:如果函数之间有依赖关系,设计为串行调用。在prompt中引导模型"先调用函数A获取信息,再根据结果调用函数B"。

坑9:安全和权限问题

模型可以调用delete_order函数,但你不希望任何用户都能删除订单。某用户通过精心设计的prompt,让模型代为调用了delete_order。

解决方案:在函数执行层面做好权限控制,不要依赖模型来"判断"用户是否有权限。敏感操作(删除、修改、支付)需要额外的用户确认。

坑10:函数调用的成本

每次函数调用都是一次额外的API请求,都会产生费用。如果你的应用有大量用户,函数调用的成本可能会超出预期。

解决方案:设计好函数调用的策略。对常见问题做缓存,减少不必要的函数调用。使用更便宜的模型做函数调用判断,只用强模型做最终回复。

总结

Function Calling是一个强大的工具,但它不是魔法。它需要仔细的设计、充分的测试和持续的优化。如果你准备在项目中引入Function Calling,请记住这10个坑——它们可能帮你省下三个月的时间。