从理解 Tools 到掌握 Agent 的完整指南(JavaScript/TypeScript)
🚀 引言:AI 为什么需要"工具"?
想象一下,如果你有一个超级聪明的助手,但 TA 只能说话,不能做事——不能查天气、不能搜索网络、不能操作数据库。这样的助手再聪明,也只是"纸上谈兵"。
这就是 LLM(大语言模型)面临的问题: 它们擅长理解和生成文本,但无法直接与外部世界交互。
LangChain 的 Tools(工具)机制就是为了解决这个问题——让 AI 模型能够:
- 🌤️ 查询实时天气
- 🔍 搜索互联网
- 💾 查询数据库
- 🧮 执行计算
- 📧 发送邮件
- ... 做任何你能编程实现的事情
本文将深入浅出地讲解 LangChain 的工具机制,带你从理解概念到实际应用。
📚 第一部分:理解 Tools(工具)
什么是 Tool?
在 LangChain 中,Tool 就是 AI 可以调用的函数,由两部分组成:
Tool = Schema(说明书) + Function(实际功能)
1.1 Schema:工具的"说明书"
Schema 就像产品说明书,告诉 AI:
- 这个工具叫什么? (Name)
- 这个工具能做什么? (Description)
- 需要什么参数? (Arguments)
类比理解:
📱 工具:手机的计算器 App
📋 Schema:
- 名称:calculator
- 描述:计算数学表达式
- 参数:expression(要计算的表达式)
代码示例:
// Schema 定义(使用 Zod)
const schema = z.object({
city: z.string().describe('城市名称,例如:北京、上海'),
unit: z.enum(['celsius', 'fahrenheit']).optional().describe('温度单位'),
});
1.2 Function:工具的"实际功能"
Function 是真正执行任务的代码,通常是一个异步函数。
代码示例:
// Function 实现
async function getWeather(city: string, unit: string = 'celsius') {
const response = await fetch(
`https://api.weather.com/v1/weather?city=${city}&unit=${unit}`
);
const data = await response.json();
return data;
}
1.3 完整的 Tool 定义
将 Schema 和 Function 组合起来:
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
// 定义一个完整的天气查询工具
export const weatherTool = tool(
// Function:实际功能
async ({ city, unit }: { city: string; unit?: 'celsius' | 'fahrenheit' }) => {
const response = await fetch(
`https://api.weather.com/v1/weather?city=${city}&unit=${
unit || 'celsius'
}`
);
const data = await response.json();
return JSON.stringify(data);
},
// Schema:说明书
{
name: 'get_weather',
description: '获取指定城市的天气信息,包括温度、天气状况和湿度',
schema: z.object({
city: z.string().describe('城市名称'),
unit: z.enum(['celsius', 'fahrenheit']).optional().default('celsius'),
}),
}
);
🔄 第二部分:工具的执行方式
有了工具后,如何让 AI 使用它们呢?LangChain 提供了两种方式:
方式对比
| 特性 | 方式 1:手动处理 | 方式 2:Agent 自动 |
|---|---|---|
| 复杂度 | 🔴 高 | 🟢 低 |
| 控制力 | 🟢 完全控制 | 🟡 有限控制 |
| 代码量 | 🔴 多 | 🟢 少 |
| 推荐度 | 特殊场景 | ✅ 大多数场景 |
2.1 方式一:手动处理工具循环(直接使用模型)
适用场景: 需要完全控制工具执行逻辑的情况。
工作流程:
用户输入
↓
调用 LLM,绑定工具
↓
LLM 返回:需要调用工具 X
↓
【你手动执行】工具 X
↓
【你手动返回】结果给 LLM
↓
LLM 基于结果继续思考
↓
可能需要再次调用工具...
↓
最终返回答案
代码示例:
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, ToolMessage } from '@langchain/core/messages';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const llmWithTools = llm.bindTools([weatherTool, databaseTool]);
async function manualToolHandling(userInput: string) {
const messages = [new HumanMessage(userInput)];
// 手动循环处理
for (let i = 0; i < 10; i++) {
// 1. 调用模型
const response = await llmWithTools.invoke(messages);
messages.push(response);
// 2. 检查是否需要调用工具
if (!response.tool_calls || response.tool_calls.length === 0) {
// 没有工具调用,返回最终答案
return response.content;
}
// 3. 手动执行每个工具
for (const toolCall of response.tool_calls) {
const tool = tools.find(t => t.name === toolCall.name);
const result = await tool.invoke(toolCall.args);
// 4. 手动将结果返回给模型
messages.push(
new ToolMessage({
content: JSON.stringify(result),
tool_call_id: toolCall.id,
})
);
}
// 5. 继续循环...
}
return '达到最大迭代次数';
}
// 使用
const answer = await manualToolHandling('今天北京天气如何?');
console.log(answer);
特点:
- ✅ 完全控制执行流程
- ✅ 可以自定义错误处理
- ✅ 可以添加自定义逻辑
- ❌ 代码复杂
- ❌ 需要自己管理循环
- ❌ 容易出错
2.2 方式二:Agent 自动处理(推荐)
适用场景: 大多数应用场景(80% 的情况)。
工作流程:
用户输入
↓
Agent 接收
↓
【Agent 自动处理所有工具调用】
- 决定调用哪个工具
- 执行工具
- 获取结果
- 继续思考
- 重复直到完成
↓
返回最终答案
代码示例:
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createOpenAIFunctionsAgent } from 'langchain/agents';
import {
ChatPromptTemplate,
MessagesPlaceholder,
} from '@langchain/core/prompts';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const tools = [weatherTool, databaseTool, webSearchTool];
// 1. 定义提示词模板
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个有用的助手,可以使用工具来回答问题。'],
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad'),
]);
// 2. 创建 Agent
const agent = await createOpenAIFunctionsAgent({
llm,
tools,
prompt,
});
// 3. 创建 Agent Executor(自动处理工具循环)
const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true, // 显示执行过程
maxIterations: 15,
});
// 4. 使用(一行搞定!)
const result = await agentExecutor.invoke({
input: '今天北京天气如何?如果适合出门,帮我搜索附近的餐厅。',
});
console.log(result.output);
特点:
- ✅ 代码简洁
- ✅ 自动处理循环
- ✅ 内置错误处理
- ✅ 自动管理状态
- ✅ 生产级稳定性
- ❌ 控制力相对有限
🎯 第三部分:实战示例
3.1 场景:智能客服助手
需求: 构建一个客服助手,能够:
- 查询订单信息
- 查询物流信息
- 回答常见问题
步骤 1:定义工具
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
// 工具 1:查询订单
const orderQueryTool = tool(
async ({ orderId }: { orderId: string }) => {
const order = await db.query('SELECT * FROM orders WHERE id = ?', [
orderId,
]);
return JSON.stringify(order);
},
{
name: 'query_order',
description: '查询订单详细信息,包括订单状态、金额、商品等',
schema: z.object({
orderId: z.string().describe('订单号'),
}),
}
);
// 工具 2:查询物流
const shippingTool = tool(
async ({ orderId }: { orderId: string }) => {
const shipping = await logistics.track(orderId);
return JSON.stringify(shipping);
},
{
name: 'track_shipping',
description: '查询订单的物流信息和配送进度',
schema: z.object({
orderId: z.string().describe('订单号'),
}),
}
);
// 工具 3:搜索FAQ
const faqTool = tool(
async ({ question }: { question: string }) => {
const answer = await faqDatabase.search(question);
return answer;
},
{
name: 'search_faq',
description: '在常见问题库中搜索答案',
schema: z.object({
question: z.string().describe('用户的问题'),
}),
}
);
步骤 2:创建 Agent
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createOpenAIFunctionsAgent } from 'langchain/agents';
import {
ChatPromptTemplate,
MessagesPlaceholder,
} from '@langchain/core/prompts';
const llm = new ChatOpenAI({
modelName: 'gpt-4',
temperature: 0, // 客服场景需要确定性
});
const tools = [orderQueryTool, shippingTool, faqTool];
const prompt = ChatPromptTemplate.fromMessages([
[
'system',
`你是一个专业的客服助手。
工作原则:
1. 友好、专业、耐心
2. 使用工具获取准确信息
3. 如果无法解决问题,引导用户联系人工客服
`,
],
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad'),
]);
const agent = await createOpenAIFunctionsAgent({ llm, tools, prompt });
const customerServiceAgent = new AgentExecutor({
agent,
tools,
verbose: true,
maxIterations: 10,
});
// 导出供使用
export { customerServiceAgent };
步骤 3:使用
// 场景 1:查询订单
const result1 = await customerServiceAgent.invoke({
input: '你好,我想查询订单 #12345 的信息',
});
// Agent 会自动调用 query_order 工具
// 场景 2:多步骤处理
const result2 = await customerServiceAgent.invoke({
input: '订单 #12345 什么时候能到?另外怎么申请退货?',
});
// Agent 会自动:
// 1. 调用 track_shipping 查物流
// 2. 调用 search_faq 查退货政策
// 3. 综合信息给出回答
3.2 场景:数据分析助手
需求: 自然语言查询数据库
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
// SQL 查询工具
const sqlQueryTool = tool(
async ({ query }: { query: string }) => {
// 注意:实际应用中需要 SQL 注入防护
const results = await database.execute(query);
return JSON.stringify(results);
},
{
name: 'execute_sql',
description: '执行 SQL 查询语句,返回查询结果',
schema: z.object({
query: z
.string()
.describe('SQL 查询语句,例如:SELECT COUNT(*) FROM users'),
}),
}
);
// 数据可视化工具
const chartTool = tool(
async ({ data, type }: { data: any[]; type: string }) => {
const chartUrl = await chartService.generate(data, type);
return chartUrl;
},
{
name: 'create_chart',
description: '根据数据生成图表',
schema: z.object({
data: z.array(z.any()).describe('要可视化的数据'),
type: z.enum(['bar', 'line', 'pie']).describe('图表类型'),
}),
}
);
const tools = [sqlQueryTool, chartTool];
// 创建数据分析 Agent
const dataAnalyst = new AgentExecutor({
agent: await createOpenAIFunctionsAgent({
llm: new ChatOpenAI({ modelName: 'gpt-4' }),
tools,
prompt: ChatPromptTemplate.fromMessages([
['system', '你是一个数据分析师,擅长 SQL 和数据可视化。'],
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad'),
]),
}),
tools,
});
// 使用
const result = await dataAnalyst.invoke({
input: '统计过去30天每天的订单数量,并生成折线图',
});
// Agent 会:
// 1. 执行 SQL 查询
// 2. 调用 chartTool 生成图表
// 3. 返回结果和图表链接
💡 第四部分:最佳实践
4.1 工具设计原则
✅ 好的工具设计
// ✅ 描述清晰、参数明确
const goodTool = tool(
async ({ city, date }: { city: string; date?: string }) => {
// ...
},
{
name: 'get_weather',
description: '获取指定城市和日期的天气信息,包括温度、湿度、风速等',
schema: z.object({
city: z.string().describe('城市名称,例如:北京、上海、纽约'),
date: z
.string()
.optional()
.describe('日期,格式:YYYY-MM-DD,默认为今天'),
}),
}
);
❌ 不好的工具设计
// ❌ 描述模糊、参数不清
const badTool = tool(
async ({ params }: { params: any }) => {
// ...
},
{
name: 'tool1', // 名称不清晰
description: '获取信息', // 太模糊
schema: z.object({
params: z.any(), // 参数不明确
}),
}
);
4.2 错误处理
const robustTool = tool(
async ({ city }: { city: string }) => {
try {
const response = await fetch(
`https://api.weather.com/v1/weather?city=${city}`
);
if (!response.ok) {
return `错误:无法获取天气信息(HTTP ${response.status})`;
}
const data = await response.json();
return JSON.stringify(data);
} catch (error) {
// 友好的错误消息
return `抱歉,获取 ${city} 的天气信息时出错:${error.message}`;
}
},
{
name: 'get_weather',
description: '获取天气信息',
schema: z.object({
city: z.string().describe('城市名称'),
}),
}
);
4.3 性能优化
// 使用并发执行多个工具
import { RunnableSequence } from '@langchain/core/runnables';
// 如果需要同时调用多个工具,Agent 会自动并发处理
const agentExecutor = new AgentExecutor({
agent,
tools: [tool1, tool2, tool3],
maxConcurrency: 3, // 最多同时执行 3 个工具
});
4.4 安全性考虑
// 对敏感操作添加确认机制
const deleteTool = tool(
async ({ id, confirm }: { id: string; confirm: boolean }) => {
if (!confirm) {
return '请确认删除操作(设置 confirm 为 true)';
}
// 执行删除
await database.delete(id);
return '删除成功';
},
{
name: 'delete_record',
description: '删除记录(危险操作,需要确认)',
schema: z.object({
id: z.string().describe('要删除的记录 ID'),
confirm: z.boolean().describe('确认删除,必须设置为 true'),
}),
}
);
🎓 第五部分:常见问题
Q1: 什么时候用手动模式,什么时候用 Agent?
答:
- ✅ 99% 的情况用 Agent:代码简洁、稳定、易维护
- ⚠️ 特殊情况用手动模式:
- 需要非常精细的控制逻辑
- 特殊的错误处理需求
- 学习理解底层原理
Q2: Agent 会无限循环吗?
答: 不会。Agent 有内置的保护机制:
const agentExecutor = new AgentExecutor({
agent,
tools,
maxIterations: 15, // 最大迭代次数限制
});
Q3: 如何调试 Agent?
答: 使用 verbose 模式:
const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true, // 打印详细执行过程
returnIntermediateSteps: true, // 返回中间步骤
});
const result = await agentExecutor.invoke({ input: '...' });
console.log(result.intermediateSteps); // 查看每一步的执行
Q4: 工具调用失败怎么办?
答: Agent 会自动处理:
- 捕获错误
- 将错误信息返回给 LLM
- LLM 可以尝试其他方案或告知用户
// 工具内部的错误会被 Agent 捕获
const tool = tool(
async ({ city }) => {
if (!city) {
throw new Error("城市名称不能为空");
}
// Agent 会捕获这个错误并告知 LLM
},
{ name: "get_weather", ... }
);
Q5: 如何限制工具的使用权限?
答: 在工具内部添加权限检查:
const restrictedTool = tool(
async ({ userId, action }: { userId: string; action: string }) => {
// 权限检查
const hasPermission = await checkPermission(userId, action);
if (!hasPermission) {
return "权限不足,无法执行此操作";
}
// 执行操作
return await performAction(action);
},
{ name: "restricted_action", ... }
);
📊 第六部分:对比总结
手动模式 vs Agent 模式
| 维度 | 手动模式 | Agent 模式 |
|---|---|---|
| 代码量 | 50-100 行 | 10-20 行 |
| 复杂度 | 🔴 高 | 🟢 低 |
| 灵活性 | 🟢 完全控制 | 🟡 有限控制 |
| 维护性 | 🔴 难维护 | 🟢 易维护 |
| 错误处理 | 手动实现 | 自动处理 |
| 适用场景 | 特殊需求 | 通用场景 |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
典型应用场景
| 应用场景 | 推荐方式 | 原因 |
|---|---|---|
| 客服机器人 | Agent | 需要多步骤交互 |
| 数据查询 | Agent | 自动处理 SQL 和结果 |
| 内容生成 | Agent | 可能需要搜索和验证 |
| 工作流自动化 | Agent | 复杂的多步骤流程 |
| 精细控制 | 手动 | 特殊的业务逻辑 |
🎯 总结与建议
核心要点回顾
-
Tool = Schema + Function
- Schema 告诉 AI 工具是什么、怎么用
- Function 实际执行任务
-
两种执行方式
- 手动模式:完全控制,代码复杂
- Agent 模式:自动处理,简洁高效
-
最佳实践
- ✅ 优先使用 Agent 模式
- ✅ 工具描述要清晰明确
- ✅ 添加错误处理
- ✅ 考虑安全性和权限
学习路径建议
第一步:理解 Tool 的概念
↓
第二步:学习如何定义 Tool
↓
第三步:使用 Agent 模式(推荐)
↓
第四步:理解手动模式(进阶)
↓
第五步:实战项目应用
💬 写在最后
理解 LangChain 的工具机制是构建强大 AI 应用的关键。记住:
Tools 让 AI 从"能说"变成"能做"
Agent 让工具调用从"手动"变成"自动"