LLM到AgentRAG——AI知识点概述 第六章:Function Call函数调用
Function Call——函数调用以我们的RAG系统为例整个RAG流程大概是这样的用户提出问题→问题拆分→检索分块→生成答案→比标注来源在基础场景中已经很完善了能够给出符盖对应知识点的回答但是用户的问题有时候并不只是查询文档并生成回答这么简单。EX用户“帮我查询12345中订单”“帮我点杯奶茶”千问点单、小美等。RAG或许能够通过预训练知识/知识库文档查询到对应的知识/相关指南但是无法真正查询到用户相关的真实订单信息、无法真实执行下单操作。——需要查询/点单相关API。通过Function Call 我们能够拓宽RAG系统边界、让Agent执行能力落地。查询知识库到其他功能传统应用能力边界即使是RAG这种应用所有的数据来源都是写死的预训练知识/知识库分块的再组装。企业场景下/特定实际应用场景下用户需求实际上不止查询文档查询实时业务数据查询业务/服务状态执行操作申报/发送信息等传统方案局限性最LOW的方案Prompt当中写死兜底回复最烂的方案用户体验是最差的同时也是最死板的。通过规则进行用户意图匹配调用接口例如用户询问的问题出现“年假”关键字我们就查询用户的年假if (userQuestion.contains(年假)) { int days hrSystem.getAnnualLeave(userId); return 您还剩 days 天年假; } if (userQuestion.contains(订单) userQuestion.contains(物流)) { String status logisticsSystem.getOrderStatus(orderId); return 订单物流状态 status; }方案问题规则是写不完的维护成本很高。这和我们为什么需要LLM的原因是一样的——每种不同的表达背后的意图是一样的难以靠规则进行匹配。每次新增一个工具我们就要修改代码增加规则当然你可以用责任链模式进行解耦但是规则匹配问题依旧存在需要更加灵活的方案模型判断使用什么工具什么时候需要使用传入什么参数。Function Call本质:模型输出调用意图模型并不具备直接调用程序当中方法函数的功能而是输出我们规定好的格式的文本例如JSON告诉程序“当前我认为需要调用什么函数对应的参数有什么”。真正执行函数/对应服务的还是我们的代码。流程定义好工具函数写好对应的参数描述参数名称、类型、描述、函数含义、描述将所有工具列表、用户问题拼接好之后发送给模型模型判断需要调用工具输出JSON统一称为tool_calls包含函数名、参数解析JSON组装为函数参数对象调用对应的函数得到结果执行结果、记忆等返回给大模型大模型生成最终答案EX第一轮发送工具列表、用户问题第一轮响应模型输出调用意图模型分析用户问题发现需要调用getUserAnnualLeave函数并给出对应参数{ tool_calls: [ { id: call_abc123, type: function, function: { name: getUserAnnualLeave, arguments: {\userId\: \12345\} } } ] }代码执行函数解析tool_calls执行函数拿到结果{ remainingDays: 5, totalDays: 10, usedDays: 5 }第二轮返回结果、记忆等给模型请求包含第一轮的用户问题第一轮的模型响应带有tool_calls)函数返回执行结果您还剩 5 天年假总共 10 天已使用 5 天。通过Function Call将工具执行需求判断交给大模型函数具体执行留给项目代码LLM工具调用决策OpenAI Function Call协议详解工具格式将工具列表以JSON数组格式发给模型每个工具定义格式{ type: function, function: { name: getUserAnnualLeave, description: 查询用户的年假余额包括总天数、已使用天数、剩余天数, parameters: { type: object, properties: { userId: { type: string, description: 用户 ID } }, required: [userId] } } }字段说明类型type固定为functionfunction.name函数名称后续function calls当中返回这个名字function.description:函数功能性描述模型判断是否调用工具的依据function.parameters:函数当中参数定义function.parameters的JSON Schema格式parameters使用JSON Schema格式定义参数常用字段type参数类型常用“object”标识参数是一个对象properties对象属性每个属性有对应的type类型和description描述required必填参数列表EX{ type: object, properties: { userId: { type: string, description: 用户 ID }, year: { type: integer, description: 查询的年份默认为当前年份 } }, required: [userId] }含义这个函数当中有两个参数userId和yearuserId是必填的类型为字符串year是可选的类型为整数请求格式工具列表发给模型完整请求JSON实例{ model: Qwen/Qwen2.5-7B-Instruct, messages: [ { role: user, content: 我还剩几天年假 } ], tools: [ { type: function, function: { name: getUserAnnualLeave, description: 查询用户的年假余额, parameters: { type: object, properties: { userId: { type: string, description: 用户 ID } }, required: [userId] } } } ], tool_choice: auto }字段说明model模型名称messages对话历史格式和普通Chat API一样tools工具列表tool_choice控制模型是否调用工具可选值auto模型自己判断是否需要调用工具默认值大部分场景够用让模型自己判断none:不调用工具只生成文本回答required必须调用不能只生成文本回答{type: function, function: {name: getUserAnnualLeave}}指定调用某个工具响应格式模型输出tool_calls模型判断需要使用工具响应格式如下{ choices: [ { message: { role: assistant, content: null, tool_calls: [ { id: call_abc123, type: function, function: { name: getUserAnnualLeave, arguments: {\userId\: \12345\} } } ] }, finish_reason: tool_calls } ] }关键字段message.tool_calls模型调用数组可能包含多个工具调用可能一次调用多个工具tool_calls[0].id:调用ID第二轮请求时需要带上对应id用于确定这次返回结果属于哪次调用填补之前特定请求的空白虽然LLM的api本身并不存储我们的上下文记忆但是这是协议的强制要求并且实际上我们通过id用于区分多个历史记录tool_calls[0].function.name:需要调用的函数名tool_calls[0].functionl.arguments:函数参数名JSON字符串请注意需要自己进行解析finish_reason:值为tool_calls因为模型输出的是工具调用而不是正常结束对应reason为stop注意message.content为null因为模型没有生成文本回答而是输出工具调用。再次请求函数执行结果返回给模型执行完函数之后需要将整个函数执行结果以及完整对话历史返回给api用于生成最终答案{ model: Qwen/Qwen2.5-7B-Instruct, messages: [//第一次调用内容 { role: user, content: 我还剩几天年假 }, { role: assistant, content: null, tool_calls: [ { id: call_abc123,//可以看到这里和下面的tool_call_id是对应的 type: function, function: { name: getUserAnnualLeave, arguments: {\userId\: \12345\} } } ] }, { //函数对应返回结果 role: tool, tool_call_id: call_abc123, content: {\remainingDays\: 5, \totalDays\: 10, \usedDays\: 5} } ] }关键第一条消息用户原始问题第二条消息第一轮模型响应带tool_calls),role是assistant第三条消息函数执行结果、role是tooltool_call_id需要和第二条消息当中的对应content是函数对应的返回值模型基于上面信息返回答案{ choices: [ { message: { role: assistant, content: 您还剩 5 天年假总共 10 天已使用 5 天。 }, finish_reason: stop } ] }注这次的final_reason是stop标识模型生成最终答案。存在问题Function Call解决了让模型调用工具的问题但是带来了新的问题工具定义维护成本高跨语言、跨系统集成复杂权限和安全控制需要自己实现可观测性不足开发者无法清晰看到、理解或者追踪系统内部发生了什么、中间的工具调用过程需要完备的日志记录等手段