LangChain 工具机制深度解析:让 AI 不仅能说,还能做

38 阅读10分钟

从理解 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. 查询订单信息
  2. 查询物流信息
  3. 回答常见问题

步骤 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 会自动处理:

  1. 捕获错误
  2. 将错误信息返回给 LLM
  3. 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复杂的多步骤流程
精细控制手动特殊的业务逻辑

🎯 总结与建议

核心要点回顾

  1. Tool = Schema + Function

    • Schema 告诉 AI 工具是什么、怎么用
    • Function 实际执行任务
  2. 两种执行方式

    • 手动模式:完全控制,代码复杂
    • Agent 模式:自动处理,简洁高效
  3. 最佳实践

    • ✅ 优先使用 Agent 模式
    • ✅ 工具描述要清晰明确
    • ✅ 添加错误处理
    • ✅ 考虑安全性和权限

学习路径建议

第一步:理解 Tool 的概念
   ↓
第二步:学习如何定义 Tool
   ↓
第三步:使用 Agent 模式(推荐)
   ↓
第四步:理解手动模式(进阶)
   ↓
第五步:实战项目应用

💬 写在最后

理解 LangChain 的工具机制是构建强大 AI 应用的关键。记住:

Tools 让 AI 从"能说"变成"能做"
Agent 让工具调用从"手动"变成"自动"