LangGraph 智能体中 LLM 节点与工具的协作深度解析

在 LangGraph 智能体中,LLM 节点(通常是 ChatOpenAI 或其他支持函数调用的模型)并不是“魔法般”地知道工具的。它的“知识”和“能力”来源于以下几个关键组成部分和协作模式:

  1. 工具定义 (Tool Definition): LLM 理解工具的基础。
  2. 提示工程 (Prompt Engineering): LLM 接收到的指令和上下文。
  3. 输出解析 (Output Parsing): LLM 输出被结构化理解。
  4. 图结构与条件路由 (Graph Structure & Conditional Routing): 决定工作流走向。
  5. 状态管理 (State Management): 上下文的传递与更新。

1. 工具定义:LLM 的“工具箱说明书”

大模型能够使用工具的前提是,这些工具必须以它能理解的格式被“呈现”给它。目前主流的大模型,尤其是 OpenAI 的函数调用(Function Calling)能力,以及 LangChain 的 RunnableTools,都基于JSON Schema来定义工具。

1.1 JSON Schema:工具的“身份证”和“使用说明”

当你定义一个工具时,你需要提供:

  • 名称 (name): 工具的唯一标识符,LLM 会在生成函数调用时使用这个名字。
  • 描述 (description): 最关键的部分!这是 LLM 理解工具用途的自然语言描述。描述越清晰、越准确、越能体现工具的调用场景,LLM 越能正确选择和使用。
  • 参数 (parameters): 使用 JSON Schema 定义工具函数所需的输入参数。这告诉 LLM 调用这个工具需要提供哪些参数,每个参数的类型是什么,是否必须,以及它的作用(参数的 description 也非常重要)。

我们定义一个简单的搜索工具来进一步理解

from langchain_core.tools import tool

# 使用 @tool 装饰器可以方便地定义工具
# LangChain 会自动根据函数签名生成 JSON Schema
@tool
def search_web(query: str) -> str:
    """
    使用此工具执行互联网搜索以获取最新信息。
    输入参数 'query' 是一个字符串,表示要搜索的内容。
    """
    print(f"--- Calling Tool: search_web with query: '{query}' ---")
    # 实际场景中会调用真实的搜索引擎API
    return f"Search result for '{query}': According to the latest data (2025-06-15), quantum computing is progressing rapidly."

@tool
def get_current_weather(location: str) -> str:
    """
    获取指定城市当前的实时天气情况。
    输入参数 'location' 是一个字符串,表示要查询天气的城市名称,例如 '北京', 'New York'。
    """
    print(f"--- Calling Tool: get_current_weather for location: '{location}' ---")
    # 实际场景中会调用天气API
    return f"Weather in {location}: Sunny, 25°C."

# 将工具列表传递给 LLM
tools = [search_web, get_current_weather]

2. 提示工程:告诉 LLM “你有哪些工具”和“何时使用它们”

LLM 知道工具列表后,还需要通过提示 (Prompt) 来接收这些工具的定义,并被明确指示其角色和使用工具的策略。

2.1 LLM 绑定工具:将工具定义嵌入模型

在 LangChain 中,LLM 如何“接收”这些工具定义呢?通过 bind_tools() 方法。

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

# 初始化支持函数调用的 LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 将工具绑定到 LLM 上。
# 这会将工具的 JSON Schema 定义注入到 LLM 的上下文中,
# 使得 LLM 在生成响应时能识别这些工具并按需生成函数调用。
# tools 是一个list,放了一系列工具
llm_with_tools = llm.bind_tools(tools)

背后机制 (Mermaid 图解):

转化
注入
LLM 看到工具定义
Python Tool定义
LangChain Tool摘要
JSON Schema格式
LLM.bind_tools()
LLM's Context / System Prompt
LLM Model

llm_with_tools 被调用时,LangChain 会在内部处理,将 tools 列表中每个工具的 JSON Schema 表示添加到发送给 LLM 的请求中(通常在系统消息或特殊参数中)。LLM 收到这些信息后,就知道有哪些工具可用,以及如何构造它们的调用。

2.2 LLM 的“思考”过程与输出格式

当 LLM 接收到用户输入和工具定义后,它会在内部进行“思考”。这个思考过程在 LangChain 中通常通过 messages 列表来模拟,其中包含了对话历史和系统指令。LLM 的输出可能包含:

  1. 文本回复 (Text Response): 如果它认为不需要工具。
  2. 工具调用 (Tool Call): 如果它决定使用某个工具,它会按照 JSON Schema 定义的格式,输出一个结构化的工具调用请求(包含工具名称和参数)。
  3. 混合 (Mixed): 文本和工具调用同时出现(较少见,但可能)。

LLM 思考与输出流程 (Mermaid 图解):

有工具定义的内部推理
不需要工具
需要工具
包含
包含
用户输入 + 历史消息
LLM (llm_with_tools)
LLM输出
文本回复
工具调用请求 (JSON)
工具名
JSON 中的工具参数

3. 输出解析与工具执行:将 LLM 意图转化为实际行动

LLM 输出的工具调用请求只是一个“意图”。我们需要一个机制来解析这个意图,并执行对应的工具。

3.1 LangChain 的 ToolCalling 机制

LangChain 的 RunnableToolsToolExecutor 负责解析 LLM 的输出并实际调用工具。

from langgraph.prebuilt import ToolExecutor

# 创建一个工具执行器,它知道如何根据名称调用这些工具
tool_executor = ToolExecutor(tools)

# 代理的 LLM 调用节点(通常包含 LLM 和输出解析)
def call_llm_and_tools(state):
    messages = state['messages']
    response = llm_with_tools.invoke(messages) # LLM 生成包含工具调用的响应
    
    # LangChain 会自动检测 response 中是否包含 tool_calls
    if not response.tool_calls:
        # 如果没有工具调用,直接返回 LLM 的文本回复
        return {"messages": [response]}
    
    # 如果有工具调用,保存工具调用请求到状态中,准备下一步执行
    # LangGraph 的状态会自动合并,所以这里只需要返回需要更新的部分
    return {"messages": [response], "tool_calls": response.tool_calls}

# 代理的工具执行节点
async def execute_tools(state):
    tool_calls = state['tool_calls']
    tool_outputs = []
    
    # 遍历 LLM 生成的所有工具调用请求
    for tool_call in tool_calls:
        # 使用 ToolExecutor 来执行工具
        # LangChain 的 ToolExecutor 能够根据 tool_call.name 找到对应的工具并传递参数
        output = await tool_executor.ainvoke(tool_call) # 或者 .invoke()
        tool_outputs.append(output)
        
        # 将工具的输出作为 ToolMessage 添加回消息历史,供 LLM 下一轮参考
        state['messages'].append(ToolMessage(content=str(output), tool_call_id=tool_call.id))
        
    return {"tool_outputs": tool_outputs, "messages": state['messages']}

工具执行流程 (Mermaid 图解):

Extracts
Extracts
Calls Python Function based on Name
Returns
Wrapped as
LLM Output (Tool Call Request)
LangChain Tool Parsing
Tool Name
Tool Arguments
ToolExecutor
Actual Python Tool Function Execution
Tool Result (Raw Data)
LangChain ToolMessage

4. 图结构与条件路由:工作流的“交通指挥官”

LLM 知道“使用什么工具”,但 LangGraph 的图结构条件路由才真正决定了“何时使用”以及“接下来怎么办”。LLM 的输出仅仅是一个信号,这个信号通过条件函数被转换成图中的下一步决策

4.1 条件函数:基于 LLM 决策的路由
# 核心:根据 LLM 的输出决定下一步
def should_continue(state):
    messages = state['messages']
    last_message = messages[-1]

    # LLM 响应中是否包含工具调用?
    if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
        return "call_tool" # LLM 决定使用工具,跳转到工具执行节点
    else:
        return "end" # LLM 认为不需要工具,直接结束流程(或转到回复生成节点)

# LangGraph 的设置
from langgraph.graph import StateGraph, END

workflow = StateGraph(dict) # 简化状态为字典

workflow.add_node("llm_node", llm)
workflow.add_node("tool_node", execute_tools)

workflow.set_entry_point("llm_node")

# 根据 LLM 的输出(是否包含 tool_calls)进行条件路由
workflow.add_conditional_edges(
    "llm_node",           # 源节点
    should_continue,      # 条件函数:根据 LLM 响应决定下一步
    {
        "call_tool": "tool_node", # 如果 should_continue 返回 "call_tool",则去 tool_node
        "end": END                # 如果 should_continue 返回 "end",则结束
    }
)

# 工具执行完成后,通常会返回 LLM 节点进行总结或继续对话
workflow.add_edge("tool_node", "llm_node")

app = workflow.compile()

工作流走向(条件路由)Mermaid 图解:

LangGraph LLM-Tool Loop
LLM输出是否包括工具调用?
包括切发现该工具
工具输出信息
无工具调用
LLM 节点 - 思考/决策
条件函数: should_continue
工具执行节点
用户输入
最终回复 / END

工作流走向的理解:

  1. 初始化: 用户输入进入 llm_node
  2. LLM 决策: llm_node 调用 llm_with_tools.invoke()。LLM 根据其内部对工具的理解和用户输入,决定是否生成工具调用。
  3. 条件路由 (should_continue): should_continue函数被调用,它检查 llm_node 的输出。
    • 如果 LLM 决定调用工具: should_continue 返回 "call_tool"。图流转到 tool_node
    • 如果 LLM 决定不调用工具: should_continue 返回 "end"。图直接结束(或进入其他结束流程)。
  4. 工具执行: tool_node 接收状态中的 tool_calls,并通过 tool_executor 实际执行这些工具。工具的输出被封装成 ToolMessage 并添加到 messages 历史中。
  5. 循环回 LLM:tool_node 执行完毕后,通过 add_edge("tool_node", "llm_node"),流程再次回到 llm_node。这时,llm_node 再次接收包含最新工具输出的 messages 历史。LLM 可以根据工具的反馈进行下一步思考:
    • 总结工具结果并生成最终回复。
    • 发现工具结果不足,需要进行进一步的工具调用或修改策略。
    • 发现问题,进入错误处理流程。 这个循环会持续进行,直到 LLM 认为任务完成并不再生成工具调用。

5. 状态管理:上下文的“粘合剂”

在上述过程中,AgentState 扮演了至关重要的角色。它是所有节点共享和更新的唯一的、可变的数据结构

  • 输入: 每个节点在执行时,都会收到当前的 AgentState 作为输入。
  • 输出: 每个节点执行完毕后,返回一个字典,LangGraph 会将这个字典合并 (merge) 到当前的 AgentState 中。
    • 对于列表(如 messages),通常是追加(append)。
    • 对于字典(如 tool_calls, tool_outputs),通常是更新(update)。
  • 上下文维持: messages 列表在整个流程中不断积累,确保 LLM 始终拥有完整的对话历史和工具交互记录,这是它进行“思考”和“决策”的基础。

状态变化流 (Mermaid 图解):

AgentState Shared Context
Reads & Updates
Writes
Reads
Writes
Writes to
Reads
对话历史
LLM的工具调用意图
工具执行结果
其他自定义数据
LLM Node
Tool Node
Messages_or_ToolMessage
Conditional Function
Messages_or_ToolCalls

总结:LLM 智能体如何工作

  1. 工具注册: Python 工具函数通过装饰器或封装,生成 JSON Schema 定义。

  2. LLM 绑定: 这些 JSON Schema 定义被传递给 LLM,让 LLM “知道”有哪些工具,它们的用途和参数。

  3. LLM 决策: 当用户输入到达 LLM 节点时,LLM 根据其接收到的提示(包括工具定义)和历史消息,决定是直接回复还是调用工具。如果调用工具,它会生成一个结构化的工具调用请求。

  4. 输出解析与路由:

    LangGraph 的条件函数检查 LLM 的输出。

    • 如果发现工具调用请求,则将流程路由到工具执行节点
    • 如果没有工具调用,则流程可能结束或路由到其他回复生成节点。
  5. 工具执行: 工具执行节点解析 LLM 的工具调用请求,并通过 ToolExecutor 实际运行对应的 Python 函数。

  6. 结果反馈与循环: 工具的执行结果被封装成 ToolMessage 添加到共享状态的 messages 历史中。然后,流程通常会循环回到 LLM 节点。LLM 再次接收到包含最新工具输出的完整上下文,进行下一轮的“思考-行动-观察”循环,直到任务完成。

  7. 状态持久化: 在整个过程中,LangGraph 的 Checkpointer 可以将 AgentState 持久化,确保代理具有长期记忆,并且可以在中断后恢复。

你可能感兴趣的:(大模型,Agent,人工智能,python,深度学习,Agent,神经网络)