如何使用Node.js和Google Gemini通过函数调用来构建一个人工智能代理
github.com/ziaongit/nodejs-gemini-agent 。 目录 功能调用机制的工作原理 我们正在构建什么 先决条件 项目设置 工具的定义 工具功能的实现 构建智能代理的循环机制 命令行入口点的设置 添加Express HTTP服务器 智能代理的测试 故障排除 接下来要构建什么 功能调用机制的工作原理 这里有一个让人感到惊讶的地方:Gemini并不会直接运行你的代码。它只会返回一个结构化对象,其中包含诸如“调用 get_weather 函数、将 city 设置为柏林”这样的指令。你的代码会接收到这些指令,然后执行相应的功能并将结果反馈回去。Gemini会检查这些结果是否
目录
功能调用机制的工作原理
<大多数关于大型语言模型的教程都会将功能调用的过程描述为:定义一个函数,然后模型就会调用它,就这样完成了。但这种描述方式忽略了真正关键的部分。 <实际上,模型并不会直接调用你定义的函数;它无法做到这一点。真正的过程更像是一种“协商”过程——你需要向模型提供信息,包括一系列工具的描述信息。每条描述信息都采用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_Weather和get_weather是两个不同的函数。
8. 查询汇率时返回“未找到相应汇率”的错误信息
你提供的货币代码不被frankfurter.app支持。该API目前支持大约30种主要货币。请访问frankfurter.app查看支持的货币代码列表。
接下来应该开发什么
这里介绍的这三个工具其实只是基础功能。无论你添加多少个工具,代理循环的工作原理都是相同的。
数据库查询工具:通过编写search_products函数来查询PostgreSQL数据库中的数据,这样就可以让代理成为产品信息查询助手。只需将这个函数指向你的产品目录,它就能自动回答关于产品库存、价格和规格等问题,而你完全不需要编写任何路由逻辑。
写入操作工具:通过添加get_*系列函数,可以让代理具备写入数据的功能。再加入create_ticket或send_notification等功能,代理就能执行诸如提交支持请求、触发工作流程或更新记录等操作了。在开发这类工具时,一定要仔细考虑“哪些查询操作在执行前需要用户确认”这一因素。
会话间的数据共享:目前,每次调用model.startChat()都会创建一个新的对话会话。但在开始对话时传入一个history数组,模型就能记住之前的对话内容。你可以将这个历史记录存储在PostgreSQL或Redis数据库中,并以用户ID作为键进行索引,这样代理就可以在不同会话之间保持上下文信息的连续性。
本文的完整源代码托管在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点出现故障;而对于那些只是想得到答案的用户来说,系统展示的
阅读全文