深入浅出 LangChain Agent:从人工实现到框架原理

引言

大语言模型(LLM)的函数调用能力让 AI 不再局限于文字对话,而是能够调用外部工具(如查询数据库、调用 API、执行计算),真正融入业务流程。基于这一能力,AI Agent 应运而生——它自主理解用户意图,规划步骤,调用工具,最终返回答案。

手动实现一个支持工具调用的 Agent 并不复杂,但需要处理多轮对话、工具执行、错误恢复等通用逻辑。LangChain 将这些重复劳动抽象为两个核心函数:create_tool_calling_agentAgentExecutor,让开发者只需关注工具本身的实现。

本文将从一个手动实现的 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_descsystem_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_agentinvoke?它们之间是什么关系?

编写顺序(代码书写顺序)

  1. 定义工具@tool 装饰函数。
  2. 创建 Agentcreate_tool_calling_agent(llm, tools, prompt)
  3. 创建执行器AgentExecutor(agent=agent, tools=tools)
  4. 调用执行器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 串联,让开发者只需关注业务工具的实现,极大降低了开发门槛。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐