深入浅出 LangChain Agent:从人工实现到框架原理
深入浅出 LangChain Agent:从人工实现到框架原理
引言
大语言模型(LLM)的函数调用能力让 AI 不再局限于文字对话,而是能够调用外部工具(如查询数据库、调用 API、执行计算),真正融入业务流程。基于这一能力,AI Agent 应运而生——它自主理解用户意图,规划步骤,调用工具,最终返回答案。
手动实现一个支持工具调用的 Agent 并不复杂,但需要处理多轮对话、工具执行、错误恢复等通用逻辑。LangChain 将这些重复劳动抽象为两个核心函数:create_tool_calling_agent 和 AgentExecutor,让开发者只需关注工具本身的实现。
本文将从一个手动实现的 Agent 出发,用它的工作流程来解释 LangChain 这两个函数的内部原理,并揭示编写顺序与调用顺序的关系,帮助读者真正掌握 Agent 的核心机制。
一、手动实现 Agent 的核心流程
我们先回顾一个手动实现的火车票查询 Agent 的主要步骤(代码已简化):
# 1. 定义工具函数
def check_tick(date, start, end):
# 调用 12306 API,返回余票信息
return result
def check_date():
return str(datetime.now().date())
# 2. 为模型描述工具(构造 tools 参数)
tools_desc = [
{
"type": "function",
"function": {
"name": "check_tick",
"description": "查询火车票",
"parameters": {...} # JSON Schema
}
},
# ... check_date 的描述
]
# 3. 第一轮调用,传入用户问题
messages = [system_msg, user_msg]
response = client.chat.completions.create(
model="...",
messages=messages,
tools=tools_desc
)
messages.append(response)
# 4. 循环处理工具调用
while response.tool_calls:
for tool_call in response.tool_calls:
args = json.loads(tool_call.function.arguments)
func = tool_map[tool_call.function.name]
result = func(**args)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"content": str(result)
})
response = client.chat.completions.create(...)
messages.append(response)
# 5. 输出最终答案
print(response.content)
这个流程清晰体现了 Agent 的核心思想:
- 模型决定何时调用工具(返回
tool_calls) - 开发者负责执行工具并返回结果
- 多轮对话直到模型直接回答
但手工实现需要自己管理对话历史、处理多个工具调用、应对异常等。LangChain 正是将这一通用流程封装起来。
二、LangChain 实现 Agent 的简洁代码
用 LangChain 实现同样的功能,代码浓缩为:
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
# 1. 定义工具(@tool 自动提取描述)
@tool
def check_tick(date: str, start: str, end: str) -> str:
"""查询火车票"""
# ... 实现 ...
return result
@tool
def check_date() -> str:
"""返回当前日期"""
return str(datetime.now().date())
# 2. 创建提示模板(预留 agent_scratchpad)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手。地址编码:北京 BJP,上海 SHH"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
# 3. 创建 Agent(配置阶段)
llm = get_llm() # LangChain 兼容的 LLM
tools = [check_tick, check_date]
agent = create_tool_calling_agent(llm, tools, prompt)
# 4. 创建执行器(管理循环)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 5. 执行查询(真正工作)
result = agent_executor.invoke({"input": "查今天北京到上海的票"})
print(result["output"])
短短几行,背后的原理是什么呢?让我们用人工实现的流程来一一对应解释。
三、create_tool_calling_agent 的原理(配置阶段)
这个函数对应人工实现中的步骤 2 和 3 的准备部分,它做了三件核心工作:
1. 将 Python 函数转换为模型可读的工具描述
人工实现中,我们需要手动编写 JSON Schema 来描述 check_tick 的参数和类型。而 @tool 装饰器会自动提取函数的名称、文档字符串、参数类型注解,生成完全相同的 JSON 描述。例如:
# 人工写的描述
{
"name": "check_tick",
"description": "查询火车票",
"parameters": {
"properties": {
"date": {"type": "string"},
"start": {"type": "string"},
"end": {"type": "string"}
},
"required": ["date", "start", "end"]
}
}
# @tool 自动生成的完全一致
2. 将工具描述注入到 Prompt 中
人工实现中,我们通过 tools 参数将描述传给模型。LangChain 也做同样的事,但它还会把工具描述以自然语言形式(或结构化形式)整合到系统提示中,让模型更清楚可用工具。同时,它会在 Prompt 中预留 {agent_scratchpad} 占位符,用于后续填充中间思考过程。
3. 返回一个配置好的 Runnable 对象
这个对象包含了 LLM 实例、工具映射、增强后的 Prompt 等信息。此时并未调用任何模型,也没有执行任何工具,仅仅是“磨刀”阶段。
用人工流程类比:create_tool_calling_agent 相当于我们写好了 tools_desc 和 system_msg,但还没发给模型。
四、AgentExecutor 的原理(执行阶段)
AgentExecutor 负责驱动多轮交互,对应人工实现中的步骤 3 到 5 的循环。它的核心是一个精心设计的 while 循环:
1. 第一轮:构造完整 Prompt 并调用模型
# 内部简化逻辑
def invoke(self, inputs):
scratchpad = "" # 初始为空
while True:
# 将 scratchpad 填入 prompt 的占位符
full_prompt = self.agent.prompt.format(
input=inputs["input"],
agent_scratchpad=scratchpad
)
# 调用 LLM
response = self.agent.llm.invoke(full_prompt)
# 如果模型直接回答(无工具调用)
if not response.tool_calls:
return response.content
# 否则,处理工具调用
for tool_call in response.tool_calls:
# 执行工具
tool_result = self.tools[tool_call.name].invoke(tool_call.args)
# 将这次调用及结果追加到 scratchpad
scratchpad += format_step(tool_call, tool_result)
# 继续循环
2. 工具执行与中间步骤记录
人工实现中,我们需要手动将工具结果以 role: "tool" 的消息追加到 messages 列表。LangChain 则用 agent_scratchpad 来存储这些中间步骤,格式通常是:
Thought: 我需要知道今天的日期。
Action: check_date
Observation: 2025-07-03
Thought: 现在有日期了,查询车票。
Action: check_tick
Action Input: {"date": "2025-07-03", "start": "BJP", "end": "SHH"}
Observation: G1 次 08:00 出发 ...
这个文本块会在下一轮调用时被填入 {agent_scratchpad},让模型“看到”自己之前的思考和外部反馈,从而做出更合理的下一步决策。
3. 循环终止条件
- 当模型返回的响应中没有
tool_calls时,视为得到最终答案,循环结束。 - 为防止无限循环,
AgentExecutor还支持max_iterations限制和早停机制。
4. 错误处理与可观测性
如果工具执行抛出异常,AgentExecutor 可以捕获并将错误信息格式化后加入 scratchpad,让模型尝试修正。设置 verbose=True 则会在控制台打印每一步的详细过程,便于调试。
用人工流程类比:AgentExecutor 相当于我们自己写的 while 循环,但它自动完成了消息拼接、工具调用、结果格式化、循环控制等所有琐碎工作。
五、编写顺序与调用顺序的关系
很多初学者会困惑:为什么先 create_tool_calling_agent 再 invoke?它们之间是什么关系?
编写顺序(代码书写顺序)
- 定义工具:
@tool装饰函数。 - 创建 Agent:
create_tool_calling_agent(llm, tools, prompt)。 - 创建执行器:
AgentExecutor(agent=agent, tools=tools)。 - 调用执行器:
agent_executor.invoke(input)。
这个顺序符合“先准备,后使用”的直觉。
调用顺序(运行时顺序)
- 第 2 步(create):只是配置,不涉及网络请求,瞬间完成。
- 第 4 步(invoke):才是真正的执行入口。此时:
AgentExecutor开始循环,每次循环内部会调用 LLM。- LLM 根据当前问题(及之前的
scratchpad)决定是否调用工具。 - 如果需要工具,
AgentExecutor就会调用你事先定义好的 Python 函数(check_tick等),并把结果写回scratchpad。 - 然后再次调用 LLM,直到最终答案。
关键:工具的选择是由问题驱动的,而不是预先指定的。例如,如果用户问“今天日期”,模型可能只调用 check_date;如果问“北京到上海的票”,模型会先调用 check_date 获取今天日期(如果需要),再调用 check_tick。这一切都发生在 invoke 内部。
问题驱动的本质
invoke 的参数 {"input": "..."} 是启动 Agent 的唯一入口。AgentExecutor 把输入传给 Agent,Agent 再驱动 LLM 思考。整个过程中,LLM 根据问题自主决定何时调用哪个工具。这正是 Agent 智能的体现。
六、总结:LangChain 封装的精髓
| 手工实现 | LangChain 封装 |
|---|---|
| 手动编写工具 JSON 描述 | @tool 自动提取 |
| 手动拼接系统提示与工具列表 | create_tool_calling_agent 完成 |
| 手动维护对话历史列表 | agent_scratchpad 自动记录 |
| 手动写 while 循环处理多次调用 | AgentExecutor 内置循环 |
| 手动处理工具执行异常 | 可配置错误处理,将异常反馈给模型 |
| 手动打印调试信息 | verbose=True 一键开启 |
LangChain 将 Agent 的通用流程抽象为两个互补的组件:create_tool_calling_agent 负责配置(告诉模型有什么工具),AgentExecutor 负责执行(驱动模型使用工具解决问题)。两者通过问题 input 串联,让开发者只需关注业务工具的实现,极大降低了开发门槛。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)