兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
`getGeminiChatAnswer` 函数是 `ai.js` 文件中最为核心和复杂的函数,它实现了与 Google Gemini 模型的智能聊天功能,并深度集成了**多轮对话**和**函数调用(Function Calling)**能力。 下面我们来详细解析它的具体过程: ### `getGeminiChatAnswer` 函数的详细过程 **目标:** 根据用户的问题和历史对话,利用 Gemini 模型生成智能回复,并在需要时调用预定义的工具来获取信息或执行操作。 **输入:** * `question`: 用户当前的提问(字符串)。 * `history`: 之前的对话历史,一个数组,包含 `{ role: "user", parts: [...] }` 和 `{ role: "model", parts: [...] }` 形式的对象。 * `env`: 环境变量,包含 API Key 等配置。 **核心流程分解:** 1. **初始化与配置 (Setup and Initialization)** * **模型选择:** 定义两个 Gemini 模型的 URL: * `flashModelUrl`: `gemini-2.5-flash` (更快速、成本较低,作为备用模型) * `proModelUrl`: `gemini-2.5-pro` (更强大、能力更全面,作为首选模型) * **工具定义 (`tools`):** 这是一个关键部分,它向 Gemini 模型声明了AI可以使用的外部工具及其功能、描述和参数。 * `get_price`: 获取期货品种价格(参数:`name`)。 * `get_news`: 获取关键词新闻(参数:`keyword`)。 * `draw_chart`: 绘制K线图(参数:`symbol`, `period`)。 * 这些声明让AI知道在什么情况下可以调用这些工具,以及调用时需要提供哪些信息。 * **构建对话内容 (`contents`):** 这是一个数组,用于存储发送给Gemini API的完整对话历史。 * **系统提示 (System Prompt):** 初始的两条固定消息,用于设定AI的角色和行为: * `{ role: "user", parts: [{ text: "你是一个全能的AI助手..." }] }` * `{ role: "model", parts: [{ text: "好的,我已理解..." }] }` * 这有助于引导AI的回复风格和能力。 * **历史对话 (`history`):** 将传入的 `history` 数组展开并添加到 `contents` 中,确保AI了解之前的对话上下文。 * **当前问题 (`question`):** 将用户当前的 `question` 作为最后一条消息添加到 `contents` 中,角色为 `user`。 * **循环计数器 (`loopCount`):** 初始化为 `0`,用于限制多轮工具调用的最大次数(防止无限循环,这里设置为最多5次)。 2. **主循环:AI交互与工具调用 (Main Loop: AI Interaction & Tool Calling)** * 函数进入一个 `while (loopCount < 5)` 循环。这个循环是实现多轮工具调用的核心。 * **模型调用尝试:** * **首选 Pro 模型:** 尝试调用 `proModelUrl` 模型,将当前的 `contents` 和 `tools` 发送给API。 * **模型回退 (Fallback):** 如果 `proModelUrl` 调用失败,并且错误信息包含 "quota"(表示配额用尽),则会打印日志并尝试回退到 `flashModelUrl` 模型进行本次调用。 * 如果两种模型都失败,或者遇到其他非配额错误,则抛出错误或返回通用错误信息。 * **处理AI响应:** * **安全检查:** 检查 `data.candidates` 是否存在。如果不存在,可能因为内容被安全策略阻止,返回相应的错误信息。 * 获取第一个 `candidate` (AI的回复)。 * 检查 `candidate.content.parts` 是否为空,如果为空则返回错误。 3. **判断AI回复类型:文本回复还是函数调用 (Determine AI Response Type)** * **识别函数调用:** 检查 `candidate.content.parts` 中是否有任何部分包含 `functionCall` 属性。 * `functionCallParts = candidate.content.parts.filter(p => p.functionCall);` * **情景 A: AI 请求调用工具 (`functionCallParts.length > 0`)** * **记录AI的工具请求:** 将AI生成的包含工具调用请求的 `candidate.content` 对象添加到 `contents` 数组中。这很重要,因为它将AI的意图(调用工具)记录在对话历史中,以便后续AI能够理解。 * **执行工具:** * 使用 `Promise.all` 并行执行所有AI请求的工具调用(如果AI一次性请求了多个工具)。 * 对于每个 `functionCall`: * 提取 `name` (工具名称) 和 `args` (参数)。 * 根据 `name` 从 `availableTools` 对象中找到对应的实际函数。 * 使用 `switch` 语句调用具体的工具函数(`getPrice`, `getNews`, `drawChart`),并传入相应的参数。 * **错误处理:** 如果工具执行失败,捕获错误,并返回一个包含错误信息的 `functionResponse`。 * 将工具的执行结果封装成 `{ functionResponse: { name, response: { content: result } } }` 格式。 * **记录工具执行结果:** 将所有工具执行结果组成的数组,以 `role: "tool"` 的形式添加到 `contents` 数组中。 * **关键点:** 将工具的执行结果(例如,查询到的价格、新闻内容、图表URL)作为新的消息添加到对话历史中,并标记为 `role: "tool"`。这样,在下一次循环中,AI就能“看到”这些工具的输出,并基于这些信息生成最终的自然语言回复。 * **继续循环:** 循环继续,将更新后的 `contents` (包含AI的工具请求和工具的执行结果) 再次发送给 Gemini 模型,让AI根据这些结果生成最终的文本回复。 * **情景 B: AI 直接给出文本回复 (`functionCallParts.length === 0` 且 `candidate.content.parts[0]?.text` 存在)** * 这意味着AI已经完成了思考,或者不需要调用工具,直接给出了最终的自然语言回复。 * 提取 `finalText = candidate.content.parts[0].text`。 * 返回这个文本,并附注说明是哪个模型生成的(Pro 或 Flash)。 * **跳出循环:** 函数执行完毕,返回结果。 * **情景 C: 无法解析的AI回复 (Else)** * 如果AI的回复既不是函数调用也不是可解析的文本,则返回一个通用错误信息。 4. **循环终止 (Loop Termination)** * 如果 `loopCount` 达到最大值(5次)而AI仍未给出最终的文本回复(即一直在进行工具调用),则抛出一个错误,表示AI未能提供最终答案。 ### 总结过程流: 1. **用户提问** -> `getGeminiChatAnswer` 被调用。 2. **构建对话历史** (系统提示 + 历史对话 + 当前问题)。 3. **循环开始 (最多5次):** a. **调用 Gemini API** (优先 Pro,配额不足时回退 Flash),发送当前对话历史和可用工具声明。 b. **AI 回复:** i. **如果是工具调用请求:** 1. 将 AI 的工具请求添加到对话历史。 2. **执行工具** (例如,调用 `getPrice`、`getNews`、`drawChart`)。 3. 将工具的**执行结果**添加到对话历史 (角色为 `tool`)。 4. **循环继续**,将包含工具结果的完整历史再次发送给 AI。 ii. **如果是最终文本回复:** 1. 返回该文本。 2. **循环结束。** iii. **如果是其他异常:** 返回错误。 4. **如果循环次数用尽仍未得到文本回复:** 抛出错误。 这个详细过程展示了 `getGeminiChatAnswer` 如何通过巧妙地结合对话历史管理、模型回退和强大的函数调用机制,实现了一个高度智能和健壮的AI聊天机器人,使其能够理解复杂的用户意图并与外部系统进行交互。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章