新版 LangChain Agent 入门:从 `bind_tools + while` 到 `create_agent`

如果你是在 LangChain 早期版本开始学习 Agent 的,那么你大概率写过类似这样的代码:

py 复制代码
model_with_tools = model.bind_tools(tools)

messages = [
    HumanMessage("帮我查一下上海天气")
]

while True:
    response = model_with_tools.invoke(messages)

    messages.append(response)

    if not response.tool_calls:
        break

    for tool_call in response.tool_calls:
        tool = tools_by_name[tool_call["name"]]

        result = tool.invoke(
            tool_call["args"]
        )

        messages.append(
            ToolMessage(
                content=str(result),
                tool_call_id=tool_call["id"],
            )
        )

这其实已经是一个完整的 Agent Loop。

它的核心逻辑非常简单:

markdown 复制代码
用户
 ↓
LLM
 ↓
是否需要 Tool?
 ├── 不需要 → 最终回答
 └── 需要
       ↓
      Tool
       ↓
  ToolMessage
       ↓
      LLM
       ↓
  再次判断

但是到了新版 LangChain,这套写法发生了非常明显的变化。

现在推荐使用:

py 复制代码
from langchain.agents import create_agent

例如:

py 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

from app.tools import tools


model = ChatOpenAI(
    model="gpt-5"
)


agent = create_agent(
    model=model,
    tools=tools,
    system_prompt="""
你是一名专业的软件开发助手。

你可以自主调用工具完成任务。
"""
)

调用:

py 复制代码
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "上海天气怎么样?"
            }
        ]
    }
)

表面上看,我们之前写的一大堆代码突然消失了:

python 复制代码
bind_tools
while True
tool_calls
ToolMessage

但这些东西真的没了吗?

并没有。

它们只是被放进了 create_agent 底层的 Agent Runtime。


一、create_agent 到底替我们做了什么?

可以把它近似理解成:

py 复制代码
while True:
    response = model.invoke(messages)

    messages.append(response)

    if not response.tool_calls:
        break

    tool_results = execute_tools(
        response.tool_calls
    )

    messages.extend(tool_results)

也就是说:

复制代码
create_agent

解决的不是一个新的 Agent 原理,而是:

把以前手写的 Agent Loop 标准化了。

底层仍然是:

复制代码
Model
 ↓
Tool
 ↓
Model
 ↓
Tool
 ↓
Model

只不过现在这个循环是由 LangGraph Runtime 驱动的。

你可以把它理解成:

ini 复制代码
以前:

Agent = while 循环


现在:

Agent = LangGraph 状态图

大致对应:

yaml 复制代码
        Model
          │
          ▼
    有 tool_calls?
      /        \
    yes        no
     ↓          ↓
   Tools       END
     │
     └────────→ Model

所以,如果你之前学习过 bind_tools + while,那部分知识不仅没有过时,反而是理解新版 Agent 最重要的基础。


二、第一个真正的 Tool

新版 LangChain 仍然使用:

css 复制代码
@tool

定义工具。

例如计算器:

py 复制代码
from typing import Literal

from langchain.tools import tool


@tool
def calculator(
    a: float,
    b: float,
    operator: Literal[
        "add",
        "subtract",
        "multiply",
        "divide",
    ],
) -> float:
    """
    执行两个数字之间的数学运算。
    """

    match operator:
        case "add":
            return a + b

        case "subtract":
            return a - b

        case "multiply":
            return a * b

        case "divide":
            if b == 0:
                raise ValueError(
                    "除数不能为 0"
                )

            return a / b

    raise ValueError(
        f"不支持的运算:{operator}"
    )

然后交给 Agent:

py 复制代码
agent = create_agent(
    model=model,
    tools=[
        calculator
    ]
)

用户:

复制代码
123 * 456 等于多少?

模型可能不会直接回答,而是生成:

py 复制代码
AIMessage

tool_calls:
[
    {
        "name": "calculator",
        "args": {
            "a": 123,
            "b": 456,
            "operator": "multiply"
        }
    }
]

Agent Runtime 执行:

py 复制代码
calculator(
    a=123,
    b=456,
    operator="multiply",
)

得到:

复制代码
56088

然后形成:

复制代码
ToolMessage

再次送给模型。

最终:

ini 复制代码
123 × 456 = 56088

三、为什么 @tool 很重要?

你写:

python 复制代码
@tool
def calculator(
    a: float,
    b: float,
    operator: str
):
    """
    执行数学运算
    """

LangChain 会把 Python Function 转成模型可以理解的工具 Schema。

大概类似:

py 复制代码
{
  "name": "calculator",
  "description": "执行数学运算",
  "parameters": {
    "type": "object",
    "properties": {
      "a": {
        "type": "number"
      },
      "b": {
        "type": "number"
      },
      "operator": {
        "type": "string"
      }
    }
  }
}

然后一起发给模型。

所以:

typescript 复制代码
Python Function
      ↓
     @tool
      ↓
 JSON Schema
      ↓
     LLM

模型不是通过:

arduino 复制代码
if "计算" in message:

来决定调用工具。

而是根据:

diff 复制代码
Tool Name
+
Tool Description
+
Tool Schema
+
用户上下文

自主判断。

因此:

Tool Description 本质上也是 Prompt Engineering。

例如:

py 复制代码
@tool
def query_order(order_id: str):
    """处理订单"""

描述很差。

更好的写法:

py 复制代码
@tool
def query_order(order_id: str):
    """
    根据订单 ID 查询订单状态。

    当用户询问订单是否发货、
    当前订单状态或物流状态时使用。
    """

模型选择工具的准确率通常会更高。


四、为什么推荐使用 Literal 和 Pydantic Schema?

假设:

arduino 复制代码
operator: str

模型理论上可能传:

json 复制代码
{
  "operator": "*"
}

但你的程序只支持:

复制代码
multiply

所以最好写:

py 复制代码
operator: Literal[
    "add",
    "subtract",
    "multiply",
    "divide",
]

这样生成的 Schema 会告诉模型:

复制代码
你只能从这几个值中选择

对于复杂 Tool,还可以使用 Pydantic:

py 复制代码
from pydantic import BaseModel, Field


class CalculatorInput(BaseModel):
    a: float = Field(
        description="第一个数字"
    )

    b: float = Field(
        description="第二个数字"
    )

然后:

py 复制代码
@tool(
    args_schema=CalculatorInput
)
def calculator(...):
    ...

所以 Tool 参数定义最好尽量:

diff 复制代码
明确类型
+
明确范围
+
明确描述

五、一次 agent.invoke() 不等于一次 LLM 请求

这是非常重要的一点。

例如:

arduino 复制代码
agent.invoke(...)

看起来只是调用了一次函数。

实际上可能发生:

sql 复制代码
LLM 请求 1
 ↓
Tool Call
 ↓
Tool 执行
 ↓
LLM 请求 2
 ↓
最终答案

如果任务复杂:

sql 复制代码
LLM
 ↓
Search Tool
 ↓
LLM
 ↓
Database Tool
 ↓
LLM
 ↓
Calculator
 ↓
LLM

所以:

复制代码
一次 Agent Invocation

可能包含:

diff 复制代码
N 次 LLM
+
N 次 Tool

这也解释了为什么 Agent:

复制代码
成本更高
延迟更高
行为更复杂

相比普通聊天模型,需要更多工程治理。


六、多个 Tool 可以同时调用

假设用户:

复制代码
查询上海天气,
告诉我北京时间,
再计算 123 * 456。

模型可能一次生成:

css 复制代码
tool_calls:

get_weather(...)
get_current_time(...)
calculator(...)

大致形成:

css 复制代码
               Model
                 │
        ┌────────┼────────┐
        ↓        ↓        ↓
    Weather    Time   Calculator
        │        │        │
        └────────┼────────┘
                 ↓
            ToolMessage
                 ↓
               Model

也就是说:

Tool Calling 并不意味着一次只能执行一个 Tool。

这也是后面为什么 Agent State 需要考虑:

复制代码
并行更新
Reducer
状态冲突

七、FastAPI 中怎么调用 Agent?

我们可以把 Agent 放进 FastAPI。

请求模型:

py 复制代码
from pydantic import BaseModel


class ChatRequest(BaseModel):
    message: str

Router:

py 复制代码
@router.post("/chat")
async def chat(
    request: ChatRequest
):
    result = await agent.ainvoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": request.message,
                }
            ]
        }
    )

    return {
        "answer":
            result["messages"][-1].content
    }

这里推荐:

csharp 复制代码
await agent.ainvoke()

而不是:

arduino 复制代码
agent.invoke()

因为 FastAPI Router 是:

python 复制代码
async def

Agent 又会调用外部 LLM API。

所以:

csharp 复制代码
FastAPI async
 ↓
Agent async
 ↓
HTTP async

整个链路更加合理。


八、新版 Agent 最重要的认知

学到这里,最好形成一个新的知识框架。

第一层:

复制代码
LLM Tool Calling

对应:

复制代码
bind_tools
tool_calls
ToolMessage

第二层:

复制代码
LangGraph Runtime

对应:

复制代码
State
Node
Edge
Conditional Edge
ToolNode

第三层:

复制代码
LangChain create_agent

对应:

复制代码
create_agent
Middleware
Runtime Context
Structured Output
Memory

也就是说:

复制代码
create_agent
      ↓
   LangGraph
      ↓
Tool Calling

新版 LangChain 并没有把旧知识推翻。

它只是把底层能力:

复制代码
Tool Calling

逐渐升级成了一个:

复制代码
可状态化
可扩展
可持久化
可中断
可流式

的 Agent Runtime。


九、总结

如果你以前写的是:

diff 复制代码
bind_tools
+
while
+
ToolMessage

现在可以升级成:

ini 复制代码
agent = create_agent(
    model=model,
    tools=tools,
)

但千万不要把 create_agent 当成黑盒。

你脑子里应该始终能把它展开成:

yaml 复制代码
用户
 ↓
Model
 ↓
tool_calls?
 ├── no → END
 │
 └── yes
      ↓
     Tool
      ↓
 ToolMessage
      ↓
     Model

这就是新版 LangChain Agent 最底层、也最稳定的一层认知。

下一篇我们继续进入真正发生巨大变化的部分:

State、Context、ToolRuntime 和 Middleware。

相关推荐
神秘的猪头1 小时前
新版 LangChain Agent 核心架构:State、Context、ToolRuntime 与 Middleware
langchain·llm·fastapi
神秘的猪头1 小时前
把新版 LangChain Agent 做成真正应用:Memory、Streaming、SSE 与 Structured Output
langchain·fastapi
染指11103 小时前
110.Agent-LangChain核心组件-Messages消息和提示词工程
人工智能·microsoft·langchain·agents
minhuan3 小时前
搭建本地人脸识别系统:解析FastAPI+InsightFace+FAISS全栈实现原理与优化方案26.6
fastapi·faiss·insightface·大模型应用·人脸识别模型·本地化人脸识别系统
tryCbest4 小时前
FastAPI中passlib包的作用
python·fastapi·passlib
zzzll11116 小时前
LangChain 1.3 新特性详解与实战指南
java·数据库·langchain
meilindehuzi_a6 小时前
LangChain.js + Milvus 向量长期记忆实战:对话写入、语义检索与 RAG 增强
javascript·langchain·milvus
ToTensor21 小时前
DataGen——合成数据生成器:把一句任务描述变成可校验的训练数据
langchain·agent
青 春 记 忆1 天前
零基础入门python66:FastAPI AI标题、摘要和标签
python·fastapi·后端开发