LangChain Agents 代理
Agent 是 LangChain 里最强大也最容易被滥用的功能。
先说清楚它和普通 Chain 的根本区别:Chain 的执行路径是你预先定义的,Agent 的执行路径是 LLM 在运行时自己决定的。你给它一堆工具(搜索、计算、查数据库),它自己判断现在该用哪个、用几次、用什么参数。
这带来了极大的灵活性,但也带来了三个代价:慢(每次工具调用都是一次 LLM 请求)、贵(多次 API 调用)、不可预测(LLM 有时会走奇怪的路径)。
Agent 不是万能的。如果你的流程可以提前定义好步骤,用 Chain 或 LangGraph 比 Agent 更稳定、更便宜、更容易调试。我的经验是,很多人在不需要 Agent 的地方用了 Agent,然后抱怨"为什么这么慢、这么贵、结果不稳定"——其实换成普通 Chain 就解决了。
Agent 的工作流程
用户输入
│
▼
LLM 分析:我需要用什么工具?
│
▼
调用工具 1(如搜索天气)
│
▼
LLM 分析:结果够了吗?还需要其他信息?
│
▼
调用工具 2(如计算)
│
▼
LLM 生成最终答案
│
▼
返回用户
每个"调用工具 → 分析结果"都是一次 LLM API 请求。一次用户请求可能触发 3-5 次甚至更多的 LLM 调用。
快速开始
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
# 1. 定义工具
@tool
def get_weather(city: str) -> str:
"""获取指定城市当前天气。
当用户问天气相关问题时使用。
返回天气描述和温度信息。
"""
weather_data = {
"北京": "晴,25°C,空气质量良好",
"上海": "多云,28°C,微风",
"广州": "小雨,32°C,湿度高",
}
return weather_data.get(city, f"暂无 {city} 天气数据")
@tool
def calculate(expression: str) -> str:
"""执行数学计算。
支持加减乘除等基本运算。expression 是合法的数学表达式,如 '123 * 456'。
"""
try:
result = eval(expression)
return str(result)
except Exception as e:
return f"计算出错:{e}"
# 2. 创建 Prompt(必须有 agent_scratchpad 占位符)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手,可以使用工具来回答问题。"),
("placeholder", "{chat_history}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"), # Agent 的中间思考过程放这里
])
# 3. 组装
llm = ChatOpenAI(model="gpt-4o") # Agent 用 gpt-4o,不要用 mini,工具调用准确率差很多
tools = [get_weather, calculate]
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 开发时开着,能看到每次工具调用的细节
max_iterations=5, # 必须设上限,防止无限循环
max_execution_time=30, # 超时保险
)
# 4. 运行
result = agent_executor.invoke({
"input": "北京天气怎么样?另外帮我算一下 123 × 456"
})
print(result["output"])
工具定义:这里最容易搞砸
工具的 docstring 不是给你看的,是给 LLM 看的。LLM 读 docstring 来决定要不要用这个工具、怎么用。这是 Agent 开发里最被低估的技能——同样的工具,写好 docstring 和写烂 docstring,Agent 的行为差异非常大。
❌ 写法差(含糊,LLM 不知道何时用):
@tool
def search(query: str) -> str:
"""搜索信息"""
pass
✅ 写法好(明确说明适用场景和参数格式):
@tool
def search_web(query: str) -> str:
"""搜索网络上的实时信息,用于获取最新新闻、近期事件或需要实时数据的问题。
适用场景:
- 最新的新闻、事件
- 需要实时数据的问题(股价、天气、比赛结果)
- 验证某个事实是否正确
不适用于:
- 需要深度分析的问题(直接用你的知识回答更好)
- 历史事件(训练数据里有)
Args:
query: 搜索关键词,尽量精确,英文效果更好
Returns:
搜索结果摘要
"""
pass
使用 StructuredTool(多参数工具)
当工具需要多个参数,用 Pydantic 定义输入结构更清晰:
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
class EmailInput(BaseModel):
to: str = Field(description="收件人邮箱地址")
subject: str = Field(description="邮件主题")
body: str = Field(description="邮件正文,纯文本格式")
def send_email_func(to: str, subject: str, body: str) -> str:
# 实际发送逻辑
return f"邮件已发送给 {to}"
send_email = StructuredTool.from_function(
func=send_email_func,
name="send_email",
description="发送电子邮件给指定收件人。确认用户明确要求发送邮件时才调用。",
args_schema=EmailInput,
)
用哪种 Agent 类型
LangChain 提供了好几种 Agent,推荐很明确:
| 类型 | 创建函数 | 推荐度 | 说明 |
|---|---|---|---|
| Tool Calling Agent | create_tool_calling_agent | ⭐⭐⭐⭐⭐ | 首选,模型原生支持 function calling |
| ReAct Agent | create_react_agent | ⭐⭐⭐ | 兼容性好,但比 Tool Calling 慢 |
| OpenAI Functions | create_openai_functions_agent | ⭐⭐ | 已被 Tool Calling 取代 |
用 create_tool_calling_agent,其他的不需要研究了。
AgentExecutor 的关键参数
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 开发时开,生产环境关掉
max_iterations=5, # 最大工具调用轮次(必须设!)
max_execution_time=60, # 整体超时秒数
handle_parsing_errors=True, # 工具输出解析失败时不崩溃
return_intermediate_steps=True, # 返回每步工具调用记录(调试用)
)
max_iterations 是必须设的。我见过没设这个值的 Agent 在某次 LLM 返回异常时进入无限调用循环,Token 消耗一直跑到 API 额度耗尽才停。5-10 次通常够用,超过的话往往是 Agent 陷入了循环,应该结束并报错。
带记忆的 Agent
给 Agent 加上多轮对话记忆:
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
agent_with_history = RunnableWithMessageHistory(
agent_executor,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
)
config = {"configurable": {"session_id": "user-123"}}
agent_with_history.invoke({"input": "我叫小明"}, config=config)
agent_with_history.invoke({"input": "我叫什么名字?"}, config=config) # 记得你叫小明
实用示例:研究助手
一个能搜索网络并保存笔记的研究 Agent:
from langchain_community.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
@tool
def take_notes(content: str) -> str:
"""保存研究发现到笔记文件。
当发现重要信息需要记录时调用。content 是笔记内容。
"""
with open("research_notes.txt", "a", encoding="utf-8") as f:
f.write(content + "\n\n---\n\n")
return "笔记已保存"
@tool
def read_notes() -> str:
"""读取之前保存的研究笔记。
当需要回顾已记录的信息时调用。
"""
try:
with open("research_notes.txt", "r", encoding="utf-8") as f:
return f.read() or "暂无笔记"
except FileNotFoundError:
return "暂无笔记"
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个研究助手。系统性地进行研究:
1. 搜索相关信息
2. 记录重要发现(不要重复记录已有内容)
3. 整理成清晰的总结
做事要有条理,不要重复同样的搜索。"""),
("placeholder", "{chat_history}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent_executor = AgentExecutor(
agent=create_tool_calling_agent(
ChatOpenAI(model="gpt-4o"),
[search, take_notes, read_notes],
prompt
),
tools=[search, take_notes, read_notes],
max_iterations=8,
verbose=True,
)
流式输出(生产环境)
在 Web 应用里实时展示 Agent 的思考过程:
async for event in agent_executor.astream_events(
{"input": "分析一下 2024 年 AI 行业发展趋势"},
version="v1",
):
if event["event"] == "on_chat_model_stream":
content = event["data"]["chunk"].content
if content:
print(content, end="", flush=True)
elif event["event"] == "on_tool_start":
print(f"\n[调用工具: {event['name']}]")
常见问题
Agent 反复调用同一个工具:大概率是工具返回结果没有给 LLM 足够的信息去推进,或者 System Prompt 没有说清楚何时停止。在 System Prompt 里加"已经有足够信息时直接给出答案,不要继续搜索"。
工具调用参数格式错误:通常是 docstring 写得不清楚,LLM 对参数格式有误解。把参数的格式要求写进 docstring 里(如"日期格式 YYYY-MM-DD")。
Agent 比预期慢很多:检查 max_iterations 实际触发了多少次。如果经常跑到 4-5 次才完成,可能是任务适合用 Chain 而不是 Agent,或者 System Prompt 需要引导 LLM 更直接地解决问题。
安全隐患:涉及文件操作、代码执行、发送邮件等不可逆操作的工具,考虑在工具里加确认逻辑,或者用 LangGraph 的 Human-in-the-Loop 让人工确认后再执行。
小结
- Agent 让 LLM 自主选择工具,比 Chain 灵活,但也更慢、更贵、更难预测——不要把 Agent 当默认方案。
- 工具的 docstring 是写给 LLM 看的,说清楚何时用、不适合什么场景、参数格式——这是 Agent 开发的核心技能。
- 始终设置
max_iterations,没有上限的 Agent 在异常情况下会无限循环消耗 API 额度。 - 2024 年起用
create_tool_calling_agent,比 ReAct 准确,比旧的 OpenAI Functions 接口更通用。 - 生产环境的复杂 Agent 逻辑(循环、条件分支、人工审核)考虑迁移到 LangGraph,控制和可观察性更好。
下一步:LangGraph 进阶 — 用 Graph 结构管理 Agent 的状态和工作流,支持断点恢复和人工审核
相关参考:Tool use 官方文档 | AgentExecutor