这是一个非常棒的例子,它完美地展示了现代大语言模型(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 请求)
假设用户问了一个不需要使用任何工具的简单问题:
用户: "你好,你叫什么名字?"
流程如下:
-
getGeminiChatAnswer函数被调用,question是 "你好,你叫什么名字?"。 -
代码构建
contents,并将问题和tools(工具菜单)一起打包。 -
第1次 API 请求: 代码向 Gemini 发送请求,问:“对于这个问题‘你好,你叫什么名字?’,结合你拥有的这些工具,你应该如何回答?”
-
API 响应: Gemini 分析后发现,回答这个问题根本不需要任何工具。于是它直接生成文本回复,例如
"我是一个由 Google 训练的大型语言模型。" -
代码收到响应,检查
part.text字段,发现里面有文本。 -
代码直接
return part.text;,函数执行结束。
总计:1 次 API 请求。
场景二:复杂问题(涉及多次 API 请求)
现在,我们来看一个更复杂的、需要调用工具的问题,这将触发多次API请求。
用户: "帮我查一下螺纹钢的价格,并画出它的日K线图。"
这个需求需要调用两个工具:get_price 和 draw_chart。
整个工作流程(代码与AI的“对话”)如下:
第1轮:用户向AI提问
-
第1次 API 请求 (代码 -> Gemini):
-
你的代码将用户的请求("查螺纹钢价格并画日K线图")和它拥有的工具列表(
get_price,get_news,draw_chart)一起发送给 Gemini。 -
这相当于在问AI:“嘿,用户有这个需求,看看你的工具箱,你觉得第一步该做什么?”
-
-
第1次 API 响应 (Gemini -> 代码):
-
Gemini 分析后,决定第一步是获取价格。它不会返回文本,而是返回一个
functionCall对象,类似这样:{ "functionCall": { "name": "get_price", "args": { "symbol": "rb" } } } -
这相当于AI在对你的代码说:“好的,经理。请帮我调用
get_price工具,参数是symbol: 'rb'。”
-
第2轮:代码执行工具,并向AI汇报结果
-
本地函数执行 (在你的服务器上):
-
你的代码接收到这个
functionCall请求。 -
它在
if (part.functionCall)分支里,调用本地的get_price("rb")函数。 -
假设函数执行后返回了结果:
"螺纹钢最新价是3650元,今日涨跌幅为-0.5%..."。
-
-
第2次 API 请求 (代码 -> Gemini):
-
你的代码不会把这个结果直接给用户。它需要回去向AI“汇报”。
-
它将上一步AI的调用请求 (
functionCall) 和刚刚得到的函数结果 (functionResponse) 一起打包,再次发送给 Gemini。 -
这相当于在对AI说:“助理,你要的价格数据我拿到了,给你:‘螺纹钢最新价是3650元...’。现在,基于这个新信息和用户的原始问题,下一步你打算做什么?”
-
-
第2次 API 响应 (Gemini -> 代码):
-
Gemini 收到价格信息后,回顾用户的原始问题,发现还有一个任务没完成:“画出它的日K线图”。
-
于是,它再次返回一个
functionCall对象:{ "functionCall": { "name": "draw_chart", "args": { "symbol": "rb", "period": "daily" } } } -
这相当于AI说:“谢谢经理。现在请再帮我调用
draw_chart工具,参数是symbol: 'rb'和period: 'daily'。”
-
第3轮:代码执行第二个工具,并最终形成答案
-
本地函数执行 (在你的服务器上):
-
你的代码再次进入
if (part.functionCall)分支,调用本地的draw_chart(env, "rb", "daily")函数。 -
假设函数返回一个图表图片的URL:
"https://charts.example.com/rb_daily.png"。
-
-
第3次 API 请求 (代码 -> Gemini):
-
代码再次向AI“汇报”,把第二个工具的执行结果发给 Gemini。
-
这相当于说:“助理,你要的K线图也准备好了,链接是
https://...。现在所有信息都齐了,请你总结一下,给用户一个最终的、完整的答复。”
-
-
第3次 API 响应 (Gemini -> 代码):
-
此时,Gemini 拥有了所有它需要的信息(价格文本和图表链接)。
-
它不再需要调用任何工具,于是生成最终的、用户友好的文本回复。这次响应的
part里将包含text字段:{ "text": "好的,螺纹钢(rb)的最新价格是3650元,今日涨跌幅为-0.5%。这是它的日K线图:https://charts.example.com/rb_daily.png" }
-
-
流程结束:
-
你的代码收到这个包含
text的响应,进入else if (part.text)分支。 -
它将这段文本返回给最终用户,
while循环结束。
-
总计:3 次 API 请求。
总结
-
while循环 是实现这个多轮“代码-AI”对话的关键。它允许AI一步一步地思考和收集信息,直到它认为信息足够了,才生成最终答案。 -
contents数组 就像是这次任务的“会议纪要”,它记录了整个对话历史,包括用户的原始问题、AI的每次工具调用请求以及每次工具调用的结果。这保证了AI在后续步骤中不会“忘记”之前发生了什么。 -
API请求次数 = 1 (初始问题) + N (N次工具调用)。每次AI需要一个工具时,就会增加一次API往返。