最近在重构项目时升级到了 LangChain v1.0.2,发现新版本的 createAgent() API 确实好用很多。之前写 Agent 需要自己手动处理工具调用的循环,现在一个函数调用就搞定了。今天就来聊聊 Agents 这个核心概念,以及 v1.0 到底改进了什么。
什么是 Agent?
简单来说,Agent 就是让大语言模型"动起来"的东西。它不仅仅是生成文本,还能调用工具、做决策、迭代解决问题。
用公式来表达就是:
ini
Agent = Language Model(语言模型) + Tools(工具) + Reasoning(推理能力)
想象一下,你问 ChatGPT:"北京的天气怎么样?"传统的模型只能告诉你它训练数据里的信息,或者告诉你它不知道。但有了 Agent,它就可以:
- 理解你的问题
- 决定调用天气查询工具
- 执行工具获取实时数据
- 基于结果生成回答
整个过程是自动的,你不需要手动去调用天气 API,也不需要把结果传回去。Agent 自己会处理这个循环。
为什么需要 Agent?
这个问题其实很简单。语言模型再聪明,它也只是"知道"训练数据里的东西。但现实世界的数据是动态的、实时的。你需要:
- 查询数据库获取最新订单状态
- 调用 API 获取实时天气
- 搜索代码库找到相关代码
- 执行代码验证结果
这些操作都需要"工具",而 Agent 就是那个能智能选择和使用工具的"大脑"。
v1.0 的改进:createAgent()
之前用 LangChain 0.3.x 的时候,创建 Agent 需要好几步:
typescript
import { AgentExecutor, createOpenAIFunctionsAgent } from 'langchain/agents';
import {
ChatPromptTemplate,
MessagesPlaceholder,
} from '@langchain/core/prompts';
// 先定义 prompt
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个有用的助手'],
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad'),
]);
// 创建 agent
const agent = await createOpenAIFunctionsAgent({
llm,
tools,
prompt,
});
// 再创建 executor
const executor = new AgentExecutor({
agent,
tools,
maxIterations: 15,
});
// 最后才能用
const result = await executor.invoke({ input: '问题' });
说实话,这套流程对新手来说有点复杂。而且每次都要写模板代码,容易出错。
现在 v1.0 的 createAgent() 就简单多了:
typescript
import { createAgent } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from 'langchain';
import { z } from 'zod';
// 定义工具
const weatherTool = tool(
async ({ city }: { city: string }) => {
return `天气:${city} 晴天,25°C`;
},
{
name: 'get_weather',
description: '获取指定城市的天气信息',
schema: z.object({
city: z.string().describe('城市名称'),
}),
}
);
// 创建模型
const llm = new ChatOpenAI({
modelName: 'gpt-4',
temperature: 0,
});
// 创建 Agent - 就这么简单!
const agent = createAgent({
llm,
tools: [weatherTool],
maxIterations: 15,
systemMessage: '你是一个有用的助手',
});
// 使用
const result = await agent.invoke({
input: '北京的天气如何?',
});
console.log(result.output);
看,代码量少了一半,而且更直观。这就是 v1.0 "opinionated" 设计理念的体现------提供最佳实践,减少配置负担。
Agent 是怎么工作的?
Agent 的核心是一个循环。理解这个循环机制很重要,它能帮你更好地使用 Agent。
Agent 执行循环详解
让我用图来展示 Agent 的工作流程:
yaml
┌─────────────────────────────────┐
│ 1. 接收用户输入 │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 2. LLM 分析任务并决定行动 │
│ - 是否需要调用工具? │
│ - 调用哪个工具? │
│ - 使用什么参数? │
└────────────┬────────────────────┘
↓
┌───┴───┐
│需要工具│
└───┬───┘
↓
┌─────────────────────────────────┐
│ 3. 执行选定的工具 │
│ - 调用工具函数 │
│ - 获取工具结果 │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 4. 将工具结果返回给 LLM │
│ - 创建 ToolMessage │
│ - 添加到消息历史 │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 5. LLM 基于结果继续推理 │
│ - 分析工具结果 │
│ - 决定下一步行动 │
└────────────┬────────────────────┘
↓
┌────────┴────────┐
│ 是否有最终答案? │
└────────┬────────┘
┌───┴───┐
No │ Yes │
└───┬─┘ └─┬───┘
│ │
↓ ↓
返回步骤2 返回最终答案
这个循环会自动重复,直到满足停止条件。整个过程你不需要写任何循环代码,Agent 自己会处理。
停止条件详解
Agent 会在以下情况下停止:
-
模型给出最终答案
- LLM 认为不需要更多工具了
- 直接返回最终答案
- 这是最理想的停止方式
-
达到迭代限制
typescriptconst agent = createAgent({ llm, tools, maxIterations: 15, // 最多执行 15 次迭代 });- 防止无限循环
- 保护机制,防止资源耗尽
- 如果达到限制还没完成,会返回当前状态
-
错误发生
- 工具执行失败
- 或其他错误导致停止
- Agent 会尝试错误恢复,但严重错误会停止
实际执行示例
让我用一个具体例子说明 Agent 的执行过程:
typescript
// 用户输入
const input = '查询北京的天气,如果温度高于25度,就搜索附近的游泳馆';
// Agent 执行过程(自动)
// 迭代 1:
// LLM 决策:需要调用 get_weather 工具
// 执行:get_weather({ city: '北京' })
// 结果:{ temperature: 28, condition: '晴天' }
// 迭代 2:
// LLM 分析:温度是28度,高于25度
// LLM 决策:需要调用 search_nearby 工具
// 执行:search_nearby({ query: '游泳馆', location: '北京' })
// 结果:[{ name: '...', distance: '...' }]
// 迭代 3:
// LLM 分析:已有足够信息
// LLM 决策:生成最终答案,不再调用工具
// 输出:'今天北京天气晴朗,温度28度,适合游泳。附近有以下游泳馆...'
// 停止:模型发出最终答案 ✅
看,Agent 会自动处理这整个流程,你只需要调用一次 invoke() 就行了。
深入理解:Agent 的核心组件
在写实际例子之前,我想先聊聊 Agent 的三个核心组件,这样你能更好地理解它是怎么工作的。
1. Language Model(语言模型)
这是 Agent 的"大脑",负责:
- 理解用户意图
- 决定使用哪个工具
- 分析工具结果
- 生成最终答案
typescript
const llm = new ChatOpenAI({
modelName: 'gpt-4',
temperature: 0, // 对于需要确定性的任务,设为 0
});
2. Tools(工具)
这是 Agent 的"双手",能让 Agent 执行实际操作:
typescript
// 工具的结构
const tool = tool(
async (input) => {
// 实际执行的代码
return result;
},
{
name: 'tool_name', // 工具名称
description: '工具描述', // 这很重要!
schema: z.object({...}), // 参数定义
}
);
3. Reasoning(推理能力)
这是 Agent 的"思考过程",体现在:
- 分析任务复杂度
- 规划执行步骤
- 决定工具调用顺序
- 整合多个工具的结果
这三个组件组合在一起,就形成了 Agent 的完整能力。用公式来表达就是:
ini
Agent = Language Model(大脑) + Tools(双手) + Reasoning(思考过程)
Agent vs 传统 LLM:什么时候用哪个?
理解 Agent 和传统 LLM 的区别,能帮你更好地判断什么时候用 Agent。
核心区别对比
| 特性 | 传统 LLM | Agent |
|---|---|---|
| 能力范围 | 只能生成文本 | 能调用工具、执行操作 |
| 数据来源 | 训练数据(静态) | 训练数据 + 实时工具数据 |
| 解决问题 | 一次性回答 | 迭代式解决 |
| 工具使用 | 不支持 | 自动选择和执行 |
| 适用场景 | 简单问答、文本生成 | 复杂任务、多步骤操作 |
什么时候用 Agent?
适合用 Agent 的场景:
- ✅ 需要查询实时数据(天气、订单、数据库)
- ✅ 需要执行多个步骤(查询 → 分析 → 生成报告)
- ✅ 需要调用外部工具(搜索、计算、API)
- ✅ 任务需要迭代优化(不断尝试直到找到答案)
不适合用 Agent 的场景:
- ❌ 简单的文本生成(翻译、摘要)
- ❌ 不需要实时数据的问答
- ❌ 成本敏感的场景(Agent 会多次调用 LLM)
- ❌ 确定性要求极高的场景(Agent 有一定不确定性)
举个例子:
typescript
// ❌ 不需要 Agent:简单的翻译
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const result = await llm.invoke('把 "Hello" 翻译成中文');
// 直接用 LLM 就够了
// ✅ 需要 Agent:查询实时数据并分析
const agent = createAgent({
llm,
tools: [weatherTool, calculatorTool],
});
const result = await agent.invoke(
'北京的天气如何?如果温度高于20度,计算一下换算成华氏度是多少'
);
// Agent 会自动调用工具、分析结果、生成答案
一个实际例子
让我写个完整的例子,展示 Agent 如何解决一个多步骤的问题:
typescript
import { createAgent } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from 'langchain';
import { z } from 'zod';
// 工具1:查询订单
const queryOrderTool = 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 trackShippingTool = tool(
async ({ orderId }: { orderId: string }) => {
const shipping = await logisticsAPI.track(orderId);
return JSON.stringify(shipping);
},
{
name: 'track_shipping',
description: '查询订单的物流信息和配送进度',
schema: z.object({
orderId: z.string().describe('订单号'),
}),
}
);
// 工具3:搜索FAQ
const searchFAQTool = tool(
async ({ question }: { question: string }) => {
const answer = await faqDatabase.search(question);
return answer;
},
{
name: 'search_faq',
description: '在常见问题库中搜索答案',
schema: z.object({
question: z.string().describe('用户的问题'),
}),
}
);
// 创建客服 Agent
const customerServiceAgent = createAgent({
llm: new ChatOpenAI({
modelName: 'gpt-4',
temperature: 0, // 客服需要确定性
}),
tools: [queryOrderTool, trackShippingTool, searchFAQTool],
maxIterations: 10,
systemMessage: `你是一个专业的客服助手。
工作原则:
1. 友好、专业、耐心
2. 使用工具获取准确信息
3. 如果无法解决问题,引导用户联系人工客服`,
});
// 使用
const result = await customerServiceAgent.invoke({
input: '你好,我想查询订单 #12345 的信息,还有什么时候能到?',
});
console.log(result.output);
当你运行这段代码时,Agent 会自动:
- 理解用户想查询订单和物流信息
- 调用
queryOrderTool获取订单详情 - 调用
trackShippingTool获取物流信息 - 整合信息生成友好的回答
整个过程你不需要写任何循环代码,Agent 自己会处理。
工具描述的重要性
这里有个很重要的点:工具的描述一定要清晰。Agent 是通过描述来理解工具的用途的,描述越清晰,Agent 选择工具就越准确。
好的描述:
typescript
{
name: 'get_weather',
description: '获取指定城市的天气信息,包括温度、湿度和天气状况',
// ...
}
不好的描述:
typescript
{
name: 'tool1',
description: '获取信息', // 太模糊了
// ...
}
如果描述不清楚,Agent 可能会选错工具,或者不知道该用哪个。这就像给一个员工分配任务,如果你不说清楚,他可能做错事。
迭代限制和错误处理
Agent 会自动处理错误,这很贴心。如果工具执行失败,Agent 会捕获错误信息,然后尝试其他方案。但你需要设置合理的迭代限制,防止无限循环。
typescript
const agent = createAgent({
llm,
tools,
maxIterations: 15, // 最多15次迭代
// ...
});
一般来说,10-15 次迭代足够解决大多数问题了。如果任务特别复杂,可以适当增加,但也要注意成本。
实际应用场景
我在项目中用 Agent 做了几个场景:
客服机器人:用户问订单状态,Agent 自动查询数据库和物流 API,然后生成回答。这比写一堆 if-else 判断要优雅多了。
数据分析助手:用户用自然语言问数据问题,Agent 自动生成 SQL、执行查询、可视化结果。不需要用户懂 SQL。
代码助手:搜索代码库、读取文件、执行测试、生成代码。Agent 可以自动完成多个步骤。
每个场景都大大简化了代码,而且用户体验更好。
和手动工具调用的对比
之前提到过,v0.3.x 需要手动处理工具调用循环。让我详细对比一下这两种方式的区别。
手动方式(v0.3.x)
typescript
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]);
const messages = [new HumanMessage('问题')];
let response = await llmWithTools.invoke(messages);
while (response.tool_calls?.length > 0) {
// 手动执行每个工具
const toolResults = await Promise.all(
response.tool_calls.map(tc =>
tools.find(t => t.name === tc.name).invoke(tc.args)
)
);
// 手动创建 ToolMessage
messages.push(response);
toolResults.forEach((result, i) => {
messages.push(
new ToolMessage({
content: result,
tool_call_id: response.tool_calls[i].id,
})
);
});
// 再次调用
response = await llmWithTools.invoke(messages);
}
手动方式的问题:
- ❌ 代码复杂,需要写循环逻辑
- ❌ 容易出错,消息历史管理麻烦
- ❌ 需要手动处理错误
- ❌ 没有迭代限制保护
- ❌ 每次都要写类似的模板代码
Agent 方式(v1.0)
typescript
import { createAgent } from 'langchain';
const agent = createAgent({
llm,
tools: [weatherTool, databaseTool],
maxIterations: 15,
});
const result = await agent.invoke({ input: '问题' });
Agent 方式的优势:
- ✅ 代码简洁,一行搞定
- ✅ 自动处理循环和消息历史
- ✅ 内置错误处理
- ✅ 自动迭代限制保护
- ✅ 生产就绪的实现
对比总结
| 维度 | 手动方式 | Agent 方式 |
|---|---|---|
| 代码量 | 20-30 行 | 5-10 行 |
| 复杂度 | 高 | 低 |
| 错误处理 | 手动实现 | 自动处理 |
| 迭代限制 | 手动管理 | 自动保护 |
| 维护性 | 难维护 | 易维护 |
| 适用场景 | 特殊需求 | 大多数场景 |
很明显,Agent 方式更简单、更可靠。除非你有特殊需求,否则建议直接用 createAgent()。
一些注意事项
使用 Agent 的时候,有几个点需要注意:
- 工具描述要清晰:这决定了 Agent 能否正确选择工具
- 设置合理的迭代限制:防止无限循环,也控制成本
- 错误处理:虽然 Agent 会自动处理,但工具内部最好也有错误处理
- 成本考虑:每次迭代都会调用 LLM,复杂的任务成本会比较高
总结
LangChain v1.0 的 createAgent() 确实是个很大的改进。代码更简洁,API 更直观,而且内置了最佳实践。对于需要多步骤交互的场景,Agent 是个很好的选择。
不过它也不是万能的。简单的任务可能直接用 LLM 就够了,不需要 Agent。而且 Agent 需要调用 LLM 多次,成本会比较高。所以要根据实际需求来选择。
总的来说,Agent 让 AI 应用开发变得更简单了。你可以专注于业务逻辑,而不用操心工具调用的循环处理。这就是框架的价值------让复杂的事情变简单。