从零实现 GeekAgent —— Day2 工具调用循环
内容提要
本文介绍从零实现GeekAgent的第二天,核心是打通工具调用循环:模型通过工具清单主动调用函数(如获取时间),系统执行并回传结果,循环直至模型给出最终回答。设计上工具抽象为对象,结果统一为字符串,代码约268行,验证了多轮记忆和工具调用,为后续接入Shell等真实工具奠定基础。
延伸解读
工具调用循环的核心价值
本文通过一个简单的 get_current_time 工具,展示了 Agent 与 Chatbot 的本质区别:模型能够主动调用外部工具获取信息,而不是仅凭训练数据猜测。这种“模型下单、系统执行、结果回传”的循环机制,是构建更复杂 Agent 能力的基础。理解这一循环,有助于把握 Agent 架构中模型与工具交互的关键模式。
设计约定降低复杂度
作者通过三个约定简化了工具调用循环的实现:工具抽象为对象、历史记录统一管理、结果统一为字符串。这些约定使得新增工具只需向数组添加对象,无需修改循环逻辑。这种模块化设计思路,对于构建可扩展的 Agent 系统具有参考价值,能够有效降低后续接入更多工具时的维护成本。
模型自主决策与描述质量
文章强调,工具调用完全由模型自主判断,代码中不存在关键词匹配逻辑。这意味着工具的描述和参数 Schema 质量直接影响模型调用的准确性。开发者需要精心设计工具描述,确保模型能够理解何时使用该工具,以及如何正确传递参数。这提醒我们,在构建 Agent 时,工具定义的质量与模型能力同等重要。
流式处理与容错细节
实现中处理了流式响应下工具调用碎片化的问题,以及部分模型不返回 tool_call_id 的兼容性处理。这些细节对于实际开发至关重要,因为流式接口是常见场景,而不同模型的行为差异可能导致请求失败。文章通过具体代码展示了如何稳健地处理这些边界情况,为读者提供了实用的实现参考。
Q&A
什么是工具调用循环?
工具调用循环是指模型在需要外部信息时,通过工具清单调用函数(如获取时间),系统执行该函数并将结果回传给模型,模型再基于结果继续生成回答,如此循环,直到模型给出最终答案。
为什么需要让模型调用工具?
因为模型本身无法获取实时信息或执行外部操作,如查询当前时间。通过工具调用,模型可以获取自己没有的信息或执行动作,从而从聊天机器人升级为智能体(Agent)。
工具在代码中是如何抽象的?
工具被抽象为一个对象,包含名称(name)、描述(description)、参数JSON Schema(parameters)和运行函数(run)。模型只看到清单中声明的工具,run函数是实际执行的部分。
模型是如何决定是否调用工具的?
模型根据工具的描述和参数Schema自行判断。如果问题涉及实时信息(如当前时间),模型会输出一个JSON格式的工具调用请求,而不是直接回答。代码中没有关键词匹配逻辑。
工具调用的结果是如何回传给模型的?
工具执行的结果会被转换为字符串,并以role为'tool'的消息添加到对话历史中,同时通过tool_call_id与之前的assistant消息关联。然后模型会基于这个结果继续生成回答。
在流式响应中,工具调用是如何处理的?
流式响应中,工具调用的id、函数名和参数可能被拆分成多个片段,通过索引(index)进行聚合。代码中会按索引累积这些片段,直到获得完整的工具调用信息。
如果模型不返回tool_call_id怎么办?
如果模型不返回tool_call_id,代码会为每个工具调用生成一个稳定的ID(如call_0),以确保后续tool消息能够正确关联,避免请求报错。
Day 2 实现了哪些功能?
Day 2 实现了工具调用循环,包括一个获取当前时间的工具(get_current_time),模型可以主动调用它,系统执行并回传结果,最终模型给出基于工具结果的回答。代码共268行。