← 返回蜂巢洞察

如何使用Node.js和Google Gemini通过函数调用来构建一个人工智能代理

github.com/ziaongit/nodejs-gemini-agent 。 目录 功能调用机制的工作原理 我们正在构建什么 先决条件 项目设置 工具的定义 工具功能的实现 构建智能代理的循环机制 命令行入口点的设置 添加Express HTTP服务器 智能代理的测试 故障排除 接下来要构建什么 功能调用机制的工作原理 这里有一个让人感到惊讶的地方:Gemini并不会直接运行你的代码。它只会返回一个结构化对象,其中包含诸如“调用 get_weather 函数、将 city 设置为柏林”这样的指令。你的代码会接收到这些指令,然后执行相应的功能并将结果反馈回去。Gemini会检查这些结果是否

<去年,有一位客户要求我在他们的内部报告工具中添加一个对话界面。员工们可以输入问题,系统会从数据库中检索实时的答案。

<我在一天之内就完成了第一个版本的开发。单个问题的处理功能是正常的。但一周后,一位测试人员输入了这样一个问题:“柏林的天气怎么样?500欧元现在换算成美元是多少?”

<该模型调用了天气查询函数,给出了相应的答案,却完全忽略了问题的后半部分。

<这就是聊天机器人与智能代理之间的区别。聊天机器人依靠训练数据来工作,因此它的功能是有限的;而智能代理则没有这种限制——它可以调用外部工具,读取返回的结果,然后决定是否继续处理后续任务。

<大多数问题只需要通过一两次外部工具的调用就能得到解决;那些需要多步骤处理的问题,则需要更多的调用。一旦去掉这个“循环机制”,整个系统就会陷入混乱。

<本教程将向您展示如何利用Google Gemini的功能调用API和Node.js来构建这样的循环机制。您将会开发出一个能够调用真实外部工具的智能代理:可以使用Open-Meteo获取实时天气信息,使用frankfurter.app查询实时汇率,还可以使用数学计算函数来进行各种运算。这三项服务都是完全免费的。您唯一需要的API密钥是Gemini的API密钥,在Google AI Studio中,每天可以使用1,500次这个API密钥。

<所有相关代码都托管在GitHub上:github.com/ziaongit/nodejs-gemini-agent

目录

功能调用机制的工作原理

<大多数关于大型语言模型的教程都会将功能调用的过程描述为:定义一个函数,然后模型就会调用它,就这样完成了。但这种描述方式忽略了真正关键的部分。

<实际上,模型并不会直接调用你定义的函数;它无法做到这一点。真正的过程更像是一种“协商”过程——你需要向模型提供信息,包括一系列工具的描述信息。每条描述信息都采用JSON格式,其中包含了函数名称、功能以及所需的参数。当用户提出请求时,Gemini会读取这些信息,然后决定使用哪种工具来满足用户的需要。

这里有一个让人感到惊讶的地方:Gemini并不会直接运行你的代码。它只会返回一个结构化对象,其中包含诸如“调用get_weather函数、将city设置为柏林”这样的指令。你的代码会接收到这些指令,然后执行相应的功能并将结果反馈回去。Gemini会检查这些结果是否足以构成完整的回答;如果不够,它就会请求使用其他工具来获取更多信息。

这种交互过程实际上是一个循环:

用户输入的消息
      │
      ▼
模型与相关工具的接口规范
      │
      ▼
响应结果:是否需要执行函数调用?
      │
   是         │                          否
      ▼                            ▼
执行相应的函数                   | 返回文本形式的答案
      │
      ▼
将结果反馈给模型             │
      └──── 形成循环     ────────────┘

这个循环会一直持续进行,直到模型认为它已经获得了足够的信息来给出完整的回答。正是这种机制使得Gemini能够依次调用多种工具:比如先查询天气信息,确认温度高于25摄氏度后,再去获取汇率数据作为补充。

有一个细节在初次使用时很容易让人犯错:当Gemini在同一条响应消息中请求使用多个工具时,你必须同时运行所有这些工具,并将所有的结果汇总在一起返回。如果分别将每个工具的结果通过单独的消息发送回去,就会破坏模型对处理顺序的跟踪机制,从而导致不可靠的输出结果。

我们正在构建什么

在这个教程中,我们将构建一个拥有三种实用工具的人工智能代理:

  • get_weather — 通过Open-Meteo接口获取任意城市的当前天气信息(免费使用,无需API密钥)

  • calculate — 在JavaScript环境中安全地计算数学表达式

  • get_exchange_rate — 通过frankfurter.app获取实时货币汇率信息(免费使用,无需API密钥)

有两种方式可以运行这个代理:一种是使用readline命令行工具进行快速本地测试;另一种是将其作为Express HTTP接口集成到实际的应用程序中。

所使用的完整技术栈如下:

  • Node.js 20:运行环境(对于需要使用原生fetch函数的场景,最低要求为Node 18版本)

  • @google/generative-ai:官方的Gemini开发工具包

  • dotenv:用于加载环境变量

  • Express:用于构建HTTP服务器及API接口

  • Open-Meteo API:免费的天气与地理编码服务,无需密钥

  • frankfurter.app:免费的货币汇率查询服务,无需密钥

其整体架构如下:

┌─────────────────────────────────────────────────┐
│                   客户端                         │
│         CLI(readline界面)/ HTTP POST请求       │
└──────────────────────┬──────────────────────────┘
                       │  用户输入的消息
                       ▼
┌─────────────────────────────────────────────────┐
│               agent.js — 代理程序的核心逻辑        │
│                                                  │
│  1. 向Gemini发送请求及所需工具的接口规范     │
│  2. 接收Gemini的响应                     │
│  3. 检查是否需要执行函数调用             │
│      是         → 并行执行所有相关工具                 │
│            将所有结果汇总后返回                   │
│            返回步骤2                         │
│      否         → 直接返回最终答案                     │
└──────────────────────┬──────────────────────────┘
                       │  执行的具体工具                    │
                       ▼
┌─────────────────────────────────────────────────┐
│                  各个工具的实现代码                   │
│                                                  │
│  get_weather(city)                               │
│    └─► 使用geocoding-api.open-meteo.com服务     │
│        或 api.open-meteo.com                    │
│                                                  │
│  calculate(expression)                           │
│    └─► 在JavaScript环境中安全计算         │
│                                                  │
│  get_exchange_rate(from, to, amount?)            │
│    └─► 使用api.frankfurter.app服务     │
└─────────────────────────────────────────────────┘

先决条件

在开始之前,您需要满足以下要求:

  • Node.js 18或更高版本——运行node --version即可确认版本。

  • 需要从aistudio.google.com获取Gemini API密钥。这是免费的,无需使用信用卡。免费账户每天可发送1,500次请求。

  • 您还需要了解Node.js中async/await的语法及用法。基本上就是这些要求。

项目设置

mkdir nodejs-gemini-agent && cd nodejs-gemini-agent
npm init -y
npm install @google/generative-ai dotenv express
mkdir src

请添加一个.gitignore文件,因为您不希望.env文件被包含在代码仓库中:

node_modules/
.env

在项目根目录下创建一个.env文件,并填写以下内容:

GEMINI_API_KEY=您的API密钥
PORT=3000

# 可选:覆盖默认模型(gemini-2.0-flash)
# 如果达到免费账户的请求限制,请取消注释此行
# GEMINI_MODEL=gemini-2.0-flash-lite

项目结构如下:

nodejs-gemini-agent/
├── src/
│   ├── tools.js        ← 用于Gemini的工具脚本
│   ├── functions.js    ← 实际的功能实现代码
│   ├── agent.js        ← 负责处理用户请求的逻辑模块
│   ├── index.js        ← 命令行接口入口文件
│   └── server.js       ← Express HTTP服务器
├── .env
├── .env.example
├── .gitignore
└── package.json

定义工具脚本

Gemini无法直接读取您的代码,它完全是根据您提供的JSON格式来选择相应的工具脚本的。每个工具脚本都有一个名称、描述以及参数定义。

描述内容决定了请求路由的规则。Gemini会在接收到请求时读取这些描述,从而决定使用哪个工具脚本来处理用户的请求。因此,请重点关注如何调用这些函数,而不仅仅是它们具体能做什么。

// src/tools.js

const toolDefinitions = [
  {
    name: 'get_weather',
    description:
      '获取某城市的当前天气信息。返回摄氏温度、湿度百分比以及风速。当用户询问某个地点的“天气”、“温度”或“气候条件”时,可以使用此工具。'
    parameters: {
      type: 'OBJECT',
      properties: {
        city: {
          type: 'STRING',
          description: '城市名称,例如东京、伦敦、纽约等。'
        },
      },
      required: ['city'],
    },
  },
  {
    name: 'calculate',
    description:
      '计算数学表达式的结果并返回数值。适用于各种算术运算、百分比计算或用户要求的任何数值处理任务。切勿自行猜测计算结果,一定要使用此工具。'
    parameters: {
      type: 'OBJECT',
      properties: {
        expression: {
          type: 'STRING',
          description: '有效的数学表达式,例如“47.50 * 0.18”或“1500 / 12”。'
        },
      },
      required: ['expression'],
    },
  },
  {
    name: 'get_exchange_rate',
    description:
     > 获取两种货币之间的当前汇率,也可以用来转换特定金额。当用户询问货币兑换、汇率信息或外币的数值时,可以使用此工具。
    parameters: {
      type: 'OBJECT',
      properties: {
        from: {
          type: 'STRING',
          description: '源货币代码,例如USD、EUR、JPY、GBP等。'
        },
        to: {
          type: 'STRING',
          description: '目标货币代码,例如USD、EUR、JPY、GBP等。'
        },
        amount: {
          type: 'NUMBER',
          description: '需要转换的金额。此参数是可选的,如果没有提供,则默认为1。'
        },
      },
      required: ['from', 'to'],
    },
  },
];

module.exports = { toolDefinitions };

大多数情况下,模糊的描述就可以满足需求。但问题往往出现在一些细节上。“处理与货币相关的内容”这种描述在面对简单的问题时能够很好地发挥作用;然而一旦遇到含糊不清的情况,就会导致功能失效。“获取两种货币之间的当前汇率。当用户询问货币兑换相关问题时使用这个信息”这样的描述则比较可靠,因为这些额外的文字基本上不会带来任何负担,而修复那些设计不良的功能逻辑却需要花费更多的时间和精力。

实现工具功能

这些就是当模型请求执行时实际会被运行的函数。每个函数都会接收模型决定传递给它的参数,完成相应的处理工作,然后返回一个普通的JavaScript对象。

// functions.js文件

async function get_weather({ city }) {
  // Open-Meteo采用了两步处理流程:首先对城市进行地理编码,然后再获取天气信息。
  // 这两个API都是免费的,使用它们不需要任何密钥。
  const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
  const geoRes  = await fetch(geoUrl);
  const geoData = await geoRes.json();

  if (!geoData.results?.length) {
    return { error: `未找到该城市:${city}` };
  }

  const { latitude, longitude, name, country } = geoDataresults[0];

  const weatherUrl =
    `https://api.open-meteo.com/v1/forecast` +
    `?latitude=${latitude}&longitude=${longitude}` +
    `¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code`;

  const weatherRes  = await fetch(weatherUrl);
  const weatherData = await weatherRes.json();
  const current     = weatherData.current;

  return {
    city:        `${name}, ${country}`,
    temperature: `${current.temperature_2m}°C`,
    humidity:    `${current(relative_humidity_2m}%`,
    wind_speed:  `${current.wind_speed_10m} km/h`,
  };
}

function calculate({ expression }) {
  try {
    // 在执行评估之前,先去除所有非数字或基本运算符的内容。
    // 这里并不是一个完整的沙盒环境——如果用户的表达式来自不可信任的来源,在生产环境中应该使用像mathjs这样的数学解析库。
    const safe = expression.replace(/[^0-9+\-*/.() %]/g, '');
    if (!safe.trim()) return { error: '表达式无效或为空' };

    const result = Function('"use strict"; return (' + safe + ')')();
    return { expression, result };
  } catch {
    return { error: `无法评估该表达式:${expression}` };
  }
}

async function get_exchange_rate({ from, to, amount = 1 }) {
  const url  = `https://api.frankfurter.app/latest?from=${from.toUpperCase()}&to=${to.toUpperCase()}`;
  const res  = await fetch(url);
  const data = await res.json();

  if (data.error) return { error: data.error };

  const rate      = data.rates[to.toUpperCase()];
  if (!rate) return { error: `未找到从${from}到${to}的汇率` };

  const converted = parseFloat((amount * rate).toFixed(4));

  return { from: from.toUpperCase(), to: to.toUpperCase(), rate, amount, converted };
}

module.exports = { get_weather, calculate, get_exchange_rate };

这里有几点值得注意的地方。

Open-Meteo在获取天气信息之前会先进行地理编码。直接将纬度和经度传递给天气数据接口,比使用城市名称字符串更为可靠,而且地理编码API也能很好地处理拼写错误。虽然需要执行两次请求操作,但这也是为了保证信息的准确性所必须付出的代价。

calculate函数在进行计算之前会删除所有非数字字符和运算符,这样可以降低代码被恶意利用的风险,但这种做法并不能真正实现“沙箱环境”。如果通过HTTP接口向匿名用户提供这个功能,那么应该使用像mathjs这样的数学解析库。

Frankfurter会在发送请求之前将货币代码转换为大写形式。用户可以输入“usd”或“Usd”,这两种格式都是可以被接受的。该API在接收数据时是区分大小写的。

构建智能交互循环

这个文件是其他所有功能的基础。整个智能交互循环仅由大约20行代码组成,其余部分主要是用于日志记录和错误处理。


// src/agent.js
const { GoogleGenerativeAI } = require('@google/generative-ai');
const { toolDefinitions } = require('./tools');
const { get_weather, calculate, get_exchange_rate } = require('./functions');

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// 将工具名称与对应的处理函数关联起来
const toolHandlers = { get_weather, calculate, get_exchange_rate };

async function runAgent(userMessage) {
  const model = genAI.getGenerativeModel({
    model: process.env.GEMINI_MODEL || 'gemini-2.0-flash',
    tools: [{ functionDeclarations: toolDefinitions }],
  });

  const chat = model.startChat();

  console.log `\n用户输入: ${userMessage}`);
  console.log('---');

  let response = await chat.sendMessage(userMessage);
  let iterations = 0;
  const MAX_ITERATIONS = 10; // 为防止无限循环而设置的最大迭代次数

  // 智能交互循环
  while (iterations < MAXIterationCount) {
    iterations++;
    const calls = response.response.functionCalls();

    // 如果没有需要使用的工具,直接返回结果
    if (!calls || calls.length === 0) break;

    // 运行所有请求的工具,并收集结果
    const toolResults = await Promise.allSettled(
      calls.map(async (call) => {
        console.log(`正在调用工具: ${call.name}(${JSON.stringify(call.args)})`);
        const handler = toolHandlers[call.name];

        if (!handler) {
          return {
            functionResponse: {
              name:     call.name,
              response: { error: `未知工具: ${call.name}` },
            },
          };
        }

        try {
          const result = await handler(call.args);
          console.log(`工具执行结果: ${JSON.stringify(result)}`);
          return {
            functionResponse: {
              name:     call.name,
              response: result,
            },
          };
        } catch (err) {
          return {
            functionResponse: {
              name:     call.name,
              response: { error: err.message },
            },
          };
        }
      })
    );

    // 从结果中提取有效数据
    const parts = toolResults
      .filter(r => r.status === 'fulfilled')
      .map(r => r.value);

    // 将所有结果汇总成一条消息,发送给模型
    response = await chat.sendMessage(parts);
  }

  return response.response.text();
}

module.exports = { runAgent };

MAX_ITERATIONS这个限制并非无端设置。如果某个工具不断返回错误,而模型又持续尝试执行操作,就可能会导致循环现象的发生。对于任何实际的查询来说,10次迭代已经足够了;而对于那些需要使用多个工具来处理的复杂问题,通常也需要两三次尝试才能得到结果。

Promise.allSettled会并行执行所有被请求的工具,而不是按顺序执行它们。当模型在同一条响应中同时请求天气信息和汇率数据时,这些数据会同时被获取。如果某个工具出现了故障,其他工具的执行也不会因此受到影响。

进行日志记录是有意为之的。在构建和测试代理程序时,实时观察各种工具的调用情况可以帮助判断路由机制是否正常工作。在实际生产环境中,这些日志会被发送到结构化日志系统中,而不是直接显示在控制台上,但所记录的信息是相同的。

命令行接口入口点

require('dotenv').config()这条代码会首先被执行,这样你的API密钥就能在其他任何操作之前被加载出来。之后,程序就会进入标准的readline循环:你输入的每一条指令都会被传递给runAgent函数,处理结果会被打印出来,然后程序会等待用户输入下一个指令。

// src/index.js
require('dotenv').config();
const readline = require('readline');
const { runAgent } = require('./agent');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

function ask(prompt) {
  return new Promise(resolve => rl.question(prompt, resolve));
}

async function main() {
  console.log('Gemini Agent — 请输入你的问题,或输入“exit”来退出程序\n');

  while (true) {
    const input = await ask('你输入的内容: ');
    if (input.toLowerCase() === 'exit') break;
    if (!input.trim()) continue;

    try {
      const answer = await runAgent(input);
      console.log(`\n代理程序的回答:${answer}\n`);
    } catch (err) {
      console.error(`错误发生:${err.message}`);
    }
  }

  rl.close();
}

main();

添加Express HTTP服务器

命令行接口在测试阶段非常有用,但要想将代理程序集成到应用程序中,就需要一个HTTP端点。

// src/server.js
require('dotenv').config();
const express = require('express');
const { runAgent } = require('./agent');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.post('/agent', async (req, res) => {
  const { message } = req.body;

  if (!message || typeof message !== 'string') {
    return res.status(400).json({ error: '必须输入字符串类型的消息' });
  }

  try {
    const answer = await runAgent(message);
    res.json({ answer });
  } catch (err) {
    console.error('[代理程序错误]', err.message);
    res.status(500).json({ error: '代理程序无法处理该请求' });
  }
});

app.listen(PORT, () => {
  console.log(`代理服务器正在运行,地址为http://localhost:${PORT}`);
});

启动服务器的方法如下:

node src/server.js

操作步骤如下:

curl -X POST http://localhost:3000/agent \
  -H "Content-Type: application/json" \
  -d '{"message": "巴黎的天气怎么样?"}'

响应结果如下:

{
  "answer": "法国巴黎当前的天气为19摄氏度,湿度为65%,风速为12公里/小时。"
}

POST请求的请求体是一个简单的{ "message": ... }对象,响应结果也是一个简单的{ "answer": ... }字符串,其余的处理工作由代理程序完成。

测试代理程序

启动命令行界面:

node src/index.js

You:提示符是由index.js文件中的readline模块生成的。User:提示以及---分隔符是在每次程序运行时由agent.js记录下来的。这些日志记录的内容与在“代理循环”章节中讨论的内容是一样的。

使用单个工具查询天气信息:

You: 东京的天气怎么样?

User: 东京的天气怎么样?
---
调用的工具:get_weather({"city":"Tokyo"})
工具返回的结果:{"city":"Tokyo, JP","temperature":"31°C","humidity":"72%","wind_speed":"8 km/h"}

Agent: 日本东京当前的天气为31摄氏度,湿度为72%,风速为8公里/小时。

使用两个工具进行链式操作:

You: 东京的天气怎么样?如果温度高于20摄氏度,请将10,000日元兑换成欧元。

User: 东京的天气怎么样?如果温度高于20摄氏度,请将10,000日元兑换成欧元。
---
调用的工具:get_weather({"city":"Tokyo"})
工具返回的结果:{"city":"Tokyo, JP","temperature":"31°C","humidity":"72%","wind_speed":"8 km/h"}

调用的工具:get_exchange_rate({"from":"JPY","to":"EUR","amount":10000})
工具返回的结果:{"from":"JPY","to":"EUR","rate":0.006,"amount":10000,"converted":60.0}

Agent: 东京当前的天气为31摄氏度,高于20摄氏度。按照当前汇率计算,10,000日元兑换成欧元约为60.00欧元。

注意观察发生了什么:模型首先调用了get_weather函数获取了天气信息,然后根据用户的问题应用了条件逻辑,最后调用了get_exchange_rate函数进行货币兑换。你并没有编写任何这些分支逻辑,模型完全是根据输入的信息自行完成这些操作的。

使用计算器功能:

You: 47.50美元的餐厅账单,18%的小费是多少?

User: 47.50美元的餐厅账单,18%的小费是多少?
---
调用的工具:calculate({"expression":"47.50 * 0.18"})
工具返回的结果:{"expression":"47.50 * 0.18","result":8.55}

Agent: 47.50美元的账单,18%的小费是8.55美元,因此总金额为56.05美元。

在一个查询中同时使用三个工具:

You>伦敦和柏林的天气怎么样?250英镑换算成欧元是多少?

User>伦敦和柏林的天气怎么样?250英镑换算成欧元是多少?
---
调用的工具:get_weather({"city":"London"})
调用的工具:get_weather({"city":"Berlin"})
调用的工具:get_exchange_rate({"from":"GBP","to":"EUR","amount":250})
工具返回的结果:{"city":"London, GB","temperature":"16°C","humidity":"78%","wind_speed":"20 km/h"}
工具返回的结果:{"city":"Berlin, DE","temperature":"22°C","humidity":"55%","wind_speed":"14 km/h"}
工具返回的结果:{"from":"GBP","to":"EUR","rate":1.17,"amount":250,"converted":292.5}

Agent>伦敦当前的天气为16摄氏度,湿度为78%,风速为20公里/小时;柏林的天气更暖和,温度为22摄氏度,湿度为55%,风速为14公里/小时。按照当前汇率计算,250英镑换算成欧元约为292.50欧元。

这三种工具都是并行运行的。之所以能这样,就是因为使用了`Promise.allSettled`这个函数。如果使用顺序循环,那么就会发出三次串行的网络请求;而并行执行的话,在大致相当于最慢的那次请求所需的时间内,就能得到相同的结果。

故障排除

以下是一些你可能会遇到的常见问题,以及相应的解决方法:

1. [404 未找到] 对于API版本v1beta而言,models/gemini-1.5-flash这个模型并不存在

这个模型名称已经过时了。谷歌会逐渐淘汰较旧的别名,因此请在`agent.js`文件中将该名称替换为`gemini-2.0-flash`。要查看你的API密钥实际上可以访问哪些模型,可以运行以下命令:

node -e "
const { GoogleGenerativeAI } = require('@google/generative-ai');
require('dotenv').config();
const g = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
g.listModels().then(r => r.models.forEach(m => console.log(m.name)));
"

2. [429 请求次数过多] 你已经超出了当前的请求限额

对于`gemini-2.0-flash`这个模型来说,免费使用套餐允许用户每天最多发送1,500次请求。一旦超过了这个限制,每次请求都会返回429错误代码,直到太平洋时间午夜时分限额才会重置。

错误信息中会直接指出是哪个限额被超出了。GenerateRequestsPerDayPerProjectPerModel-FreeTier表示你已经达到了每天的请求限额;GenerateRequestsPerMinutePerProjectPerModel-FreeTier则表示你超过了每分钟的请求限额。

对于每分钟的限额来说,错误信息中会包含一个`retryDelay`字段。你需要等待这个指定的时间后再尝试发送请求。而对于每天的限额来说,整个项目的所有模型都会共享同一个限额。

有三种解决办法:

  • 创建新项目(这是最快的解决方法):前往aistudio.google.com创建一个新项目,获取一个新的API密钥,然后将其添加到`.env`文件中。这样你就能立即使用新的限额了。

  • 启用计费功能:启用计费功能的项目可以享受更高的请求限额,同时仍然可以继续使用免费套餐。具体操作请在aistudio.google.com进行设置。

  • 等待限额重置:限额会在太平洋时间午夜时分自动重置。

由于`agent.js`文件是从`process.env.GEMINI_MODEL`这个环境变量中获取模型名称的,因此你也可以在不修改代码的情况下更换使用的模型。如果你想使用一个占用资源较少的模型,可以在`.env`文件中添加以下内容:

GEMINIMODEL=gemini-2.0-flash-lite

等限额重置后,再将这行代码从`.env`文件中删除,这样代理程序就会重新使用`gemini-2.0-flash`模型了。

3. 错误:GEMINI_API_KEY未设置

在绝大多数情况下,`require('dotenv').config()`这个代码要么被遗漏了,要么被放在了其他代码的后面。请将其移到`index.js`文件的顶部。同时,你的`.env`文件也必须位于项目的根目录下,并且其中必须包含真实的API密钥,而不能是像`your_api_key_here`这样的占位符。

4. GoogleGenerativeAIError: 400 INVALID_ARGUMENT

几乎总是由于工具模式定义有误所致。Gemini使用大写字母来表示类型,例如'OBJECT''STRING''NUMBER',而JSON Schema则使用小写字母。请检查你的parameters.type值是否正确。

5. 模型在未调用任何工具的情况下直接给出答案

描述信息过于模糊,或者用户提出的问题与模型能够处理的类型不匹配,因此模型无法正确处理该请求。请在描述中明确说明在什么情况下应该使用相应的工具。例如,添加“当用户询问关于X的问题时,请使用此工具”这样的说明,可以直接提高模型路由的准确性。

6. TypeError: fetch is not a function

Node 17及更低版本的JavaScript系统中没有内置的fetch函数,该函数是在Node 18版本中添加的。你可以运行node --version来检查自己使用的JavaScript版本是否支持fetch函数。

如果无法升级系统,可以通过npm install node-fetch来安装这个模块。任何使用fetch函数的代码文件,都需要在第一行添加const fetch = require('node-fetch')这一行代码。

7. 工具可以在独立环境中正常运行,但代理循环不会调用它

toolDefinitions中定义的工具名称,必须与toolHandlers中对应的键完全一致。在JavaScript中,字母的大小写是区分意义的:get_Weatherget_weather是两个不同的函数。

8. 查询汇率时返回“未找到相应汇率”的错误信息

你提供的货币代码不被frankfurter.app支持。该API目前支持大约30种主要货币。请访问frankfurter.app查看支持的货币代码列表。

接下来应该开发什么

这里介绍的这三个工具其实只是基础功能。无论你添加多少个工具,代理循环的工作原理都是相同的。

数据库查询工具:通过编写search_products函数来查询PostgreSQL数据库中的数据,这样就可以让代理成为产品信息查询助手。只需将这个函数指向你的产品目录,它就能自动回答关于产品库存、价格和规格等问题,而你完全不需要编写任何路由逻辑。

写入操作工具:通过添加get_*系列函数,可以让代理具备写入数据的功能。再加入create_ticketsend_notification等功能,代理就能执行诸如提交支持请求、触发工作流程或更新记录等操作了。在开发这类工具时,一定要仔细考虑“哪些查询操作在执行前需要用户确认”这一因素。

会话间的数据共享:目前,每次调用model.startChat()都会创建一个新的对话会话。但在开始对话时传入一个history数组,模型就能记住之前的对话内容。你可以将这个历史记录存储在PostgreSQL或Redis数据库中,并以用户ID作为键进行索引,这样代理就可以在不同会话之间保持上下文信息的连续性。

流式响应:对于那些在用户输入内容的同时就立即显示答案的界面而言,应将`chat.sendMessage`替换为`chat sendMessageStream`。工具调用流程本身保持不变,只有最终响应的传递方式会发生变化。 更换模型:在`getGenerativeModel`函数中使用的`model`参数是决定系统使用哪个Gemini版本的唯一因素。`gemini-2.0-flash-lite`版本体积更小、运行速度更快,适用于简单的查询任务;而对于需要执行复杂推理的任务,建议通过“故障排除”章节中的`listModels`脚本来查找最新的可用模型。所有Gemini模型的函数调用接口都是相同的,因此更换模型只需修改一行代码即可。 本文的完整源代码托管在GitHub上,地址为:github.com/ziaongit/nodejs-gemini-agent

相关文章

技术实践

如何使用LangSmith来追踪和监控人工智能代理的行为

在本教程中,我将向您展示如何使用LangSmith来追踪和监控本地的AI代理。我们会构建一个简单的本地AI代理,然后为其启用LangSmith追踪功能,这样我们就能通过Web界面查看模型调用情况、工具使用情况以及请求处理延迟等信息。 我们将使用LangChain v1、Ollama、Qwen以及Python这些工具。除了用于实现观测功能的组件外,所有操作都在您的本地机器上完成,因此代理本身不会产生任何与模型API相关的费用。 目录 背景知识 什么是可观测性与监控? 什么是LangSmith? 开发动机与架构设计 步骤1:安装Ollama并下载模型 步骤2:安装Python相关依赖库 步骤3:启

阅读全文
技术实践

如何利用提示工程与上下文工程来开发人工智能代理

在这个教程中,我将向您展示提示工程和上下文工程如何提升人工智能模型的性能。 我们将构建一个简单的本地模型,从基础输入开始,然后通过使用更合适的提示语和更丰富的上下文信息来改进它,这样您就能看到每一项改变对最终输出结果的影响。 我们将会使用LangChain v1、Ollama、Qwen以及Python。所有操作都在您的个人电脑上完成,因此您无需支付任何API费用。 目录 背景知识 什么是提示工程? 什么是上下文工程? 为什么提示工程和上下文工程对人工智能模型如此重要 动机与架构 步骤1:安装Ollama并下载模型 步骤2:安装Python相关依赖库 步骤3:编写代理代码 示例输出结果 提示语优

阅读全文
技术实践

如何使用ROS 2和YOLOv11构建实时物体检测与跟踪系统

如果你曾经尝试过构建一个能够真正感知周围环境、对其进行追踪并作出相应反应的机器人系统,你就会知道:真正的难点并不在于训练检测模型,而在于如何让这个模型在真实的机器人软件系统中实时稳定地运行——尤其是在遇到硬件限制或时间调度问题时,也能保证系统的正常运作。 在这个教程中,你将使用ROS 2和YOLOv11构建一个完整的实时物体检测与追踪系统。你会学习如何将模拟器中的摄像头数据发送到ROS 2系统中,在单独的线程中运行YOLO推理算法,利用ByteTrack技术实现跨帧的多物体追踪功能,以及如何将训练好的模型导出为ONNX格式,以便在性能有限的硬件上更快地执行推理任务。 通过学习这个教程,你不仅会

阅读全文
技术实践

如何利用Gemini构建人工智能功能:面向开发者的提示工程实用指南

大多数关于提示工程的教学教程都遵循相同的流程:安装SDK,输入API密钥,调用 generateContent 函数,然后打印输出结果。模型会生成一些看似合理的内容,之后教学教程也就结束了。 但当你真正尝试将这个系统投入实际使用时,才会发现其实真正的准备工作根本还没有开始。 “API返回的文本”与“让用户感到可信的实际功能”之间的差距,正是需要耗费大量精力去解决的地方。 这个差距中充满了各种棘手的问题:模型生成的内容听起来和其他聊天机器人没什么两样;它会编造用户从未说过的话;它返回的数据会被用Markdown格式包裹起来;系统会在凌晨2点出现故障;而对于那些只是想得到答案的用户来说,系统展示的

阅读全文