整合gemini函数调用功能实现智能代理

这是一个非常棒的例子,它完美地展示了现代大语言模型(LLM)最强大的能力之一:函数调用(Function Calling)或工具使用(Tool Use)

我们来详细拆解一下这个过程,特别是它如何以及为何会涉及多次对 Gemini API 的请求。

核心思想:从“聊天”到“智能代理”

首先要理解,这段代码构建的不仅仅是一个简单的问答机器人。它构建了一个智能代理(Intelligent Agent)。你可以把它想象成一个工作流程:

  • 你 (User): 提出一个复杂的需求。

  • 你的代码 (Manager): 扮演“项目经理”的角色。它不直接干活,但它知道手下有哪些专家(工具)。

  • Gemini AI (Assistant): 扮演一个“聪明的助理”。它能理解你的需求,但它自己不会查价格或画图。当它发现需要这些专业技能时,它会向“项目经理”提出请求。

  • get_price, get_news, draw_chart (Tools): 扮演“领域专家”。它们是真正执行具体任务的函数。

这个流程的核心就是**“对话”——不仅是用户和AI的对话,更是你的代码和AI之间的多轮对话**。


API 请求次数分析

你的代码与 Gemini API 的交互次数取决于用户问题的复杂性。

场景一:简单问题(仅需 1 次 API 请求)

假设用户问了一个不需要使用任何工具的简单问题:

用户: "你好,你叫什么名字?"

流程如下:

  1. getGeminiChatAnswer 函数被调用,question 是 "你好,你叫什么名字?"。

  2. 代码构建 contents,并将问题和 tools(工具菜单)一起打包。

  3. 第1次 API 请求: 代码向 Gemini 发送请求,问:“对于这个问题‘你好,你叫什么名字?’,结合你拥有的这些工具,你应该如何回答?”

  4. API 响应: Gemini 分析后发现,回答这个问题根本不需要任何工具。于是它直接生成文本回复,例如 "我是一个由 Google 训练的大型语言模型。"

  5. 代码收到响应,检查 part.text 字段,发现里面有文本。

  6. 代码直接 return part.text;,函数执行结束。

总计:1 次 API 请求。


场景二:复杂问题(涉及多次 API 请求)

现在,我们来看一个更复杂的、需要调用工具的问题,这将触发多次API请求。

用户: "帮我查一下螺纹钢的价格,并画出它的日K线图。"

这个需求需要调用两个工具:get_pricedraw_chart

整个工作流程(代码与AI的“对话”)如下:

第1轮:用户向AI提问

  1. 第1次 API 请求 (代码 -> Gemini):

    • 你的代码将用户的请求("查螺纹钢价格并画日K线图")和它拥有的工具列表(get_price, get_news, draw_chart)一起发送给 Gemini。

    • 这相当于在问AI:“嘿,用户有这个需求,看看你的工具箱,你觉得第一步该做什么?”

  2. 第1次 API 响应 (Gemini -> 代码):

    • Gemini 分析后,决定第一步是获取价格。它不会返回文本,而是返回一个 functionCall 对象,类似这样:

      
      { "functionCall": { "name": "get_price", "args": { "symbol": "rb" } } }
      
    • 这相当于AI在对你的代码说:“好的,经理。请帮我调用 get_price 工具,参数是 symbol: 'rb'。”

第2轮:代码执行工具,并向AI汇报结果

  1. 本地函数执行 (在你的服务器上):

    • 你的代码接收到这个 functionCall 请求。

    • 它在 if (part.functionCall) 分支里,调用本地的 get_price("rb") 函数。

    • 假设函数执行后返回了结果:"螺纹钢最新价是3650元,今日涨跌幅为-0.5%..."

  2. 第2次 API 请求 (代码 -> Gemini):

    • 你的代码不会把这个结果直接给用户。它需要回去向AI“汇报”。

    • 它将上一步AI的调用请求 (functionCall) 和刚刚得到的函数结果 (functionResponse) 一起打包,再次发送给 Gemini。

    • 这相当于在对AI说:“助理,你要的价格数据我拿到了,给你:‘螺纹钢最新价是3650元...’。现在,基于这个新信息和用户的原始问题,下一步你打算做什么?”

  3. 第2次 API 响应 (Gemini -> 代码):

    • Gemini 收到价格信息后,回顾用户的原始问题,发现还有一个任务没完成:“画出它的日K线图”。

    • 于是,它再次返回一个 functionCall 对象:

      
      { "functionCall": { "name": "draw_chart", "args": { "symbol": "rb", "period": "daily" } } }
      
    • 这相当于AI说:“谢谢经理。现在请再帮我调用 draw_chart 工具,参数是 symbol: 'rb'period: 'daily'。”

第3轮:代码执行第二个工具,并最终形成答案

  1. 本地函数执行 (在你的服务器上):

    • 你的代码再次进入 if (part.functionCall) 分支,调用本地的 draw_chart(env, "rb", "daily") 函数。

    • 假设函数返回一个图表图片的URL:"https://charts.example.com/rb_daily.png"

  2. 第3次 API 请求 (代码 -> Gemini):

    • 代码再次向AI“汇报”,把第二个工具的执行结果发给 Gemini。

    • 这相当于说:“助理,你要的K线图也准备好了,链接是 https://...。现在所有信息都齐了,请你总结一下,给用户一个最终的、完整的答复。”

  3. 第3次 API 响应 (Gemini -> 代码):

    • 此时,Gemini 拥有了所有它需要的信息(价格文本和图表链接)。

    • 它不再需要调用任何工具,于是生成最终的、用户友好的文本回复。这次响应的 part 里将包含 text 字段:

      
      { "text": "好的,螺纹钢(rb)的最新价格是3650元,今日涨跌幅为-0.5%。这是它的日K线图:https://charts.example.com/rb_daily.png" }
      
  4. 流程结束:

    • 你的代码收到这个包含 text 的响应,进入 else if (part.text) 分支。

    • 它将这段文本返回给最终用户,while 循环结束。

总计:3 次 API 请求。

总结

  • while 循环 是实现这个多轮“代码-AI”对话的关键。它允许AI一步一步地思考和收集信息,直到它认为信息足够了,才生成最终答案。

  • contents 数组 就像是这次任务的“会议纪要”,它记录了整个对话历史,包括用户的原始问题、AI的每次工具调用请求以及每次工具调用的结果。这保证了AI在后续步骤中不会“忘记”之前发生了什么。

  • API请求次数 = 1 (初始问题) + N (N次工具调用)。每次AI需要一个工具时,就会增加一次API往返。