从零开始学LangChain(二):LangChain的核心组件 - Agents

欢迎加入我的【ima知识库】从零开始学LangChain,第一时间收到更新!

Agents将大模型与工具结合起来,创建能够进行任务推理、决定使用哪种工具并迭代解决方案的系统。

create_agent 提供了一种适用于生产的Agent的实现方式。

在模型发出最终输出或达到迭代限制前,Agent会一直运行。

Core components(核心组件)

Model(模型)

模型是Agent的推理引擎。可以通过多种方式指定,支持静态和动态模型选择。

Static model(静态模型)

静态模型在创建代理时配置一次,并在整个执行过程中保持不变。这是最常见和最直接的方法。

python 复制代码
from langchain.agents import create_agent

agent = create_agent(
    "gpt-5",
    tools=tools
)

模型标识符字符串支持自动推理(例如,"gpt-5"将被推断为"openai:gpt-5")。参考查看模型标识符字符串映射的完整列表。

要对模型配置进行更多的控制,请使用供应商提供的工具包进行初始化模型实例。本次以 ChatDeepSeek 为例进行演示。其他可用的聊天模型,请参阅聊天模型

python 复制代码
from langchain.agents import create_agent
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

model = ChatDeepSeek(
    model="deepseek-chat",
    api_base="https://api.deepseek.com/v1",
    temperature=0.1,
    max_tokens=1000,
    timeout=30
    # ... (other params)
)
agent = create_agent(model)

在使用ChatDeepSeek时,需使用api_base参数(而非api_url)

模型实例使你能够完全控制配置。当你需要设置特定的参数时,比如 temperaturemax_tokenstimeoutsbase_url 和其他特定于提供程序的设置。请参考查看模型上可用的参数和方法。

Dynamic model(动态模型)

动态模型是在运行时根据当前状态和上下文选择的。这样能够适配复杂的路由逻辑,还能优化成本。

要使用动态模型,请使用 @wrap_model_call 装饰器,在发起请求的过程中修改模型参数。

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from dotenv import load_dotenv

load_dotenv()

basic_model = ChatDeepSeek(model="deepseek-chat")
advanced_model = ChatDeepSeek(model="deepseek-reasoner")

@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
    """根据对话的复杂度选择模型"""
    message_count = len(request.state["messages"])

    if message_count > 5:
        # 在长对话的时候使用高级模型
        model = advanced_model
    else:
        model = basic_model

    return handler(request.override(model=model))

agent = create_agent(
    model=basic_model,  # 默认使用基础模型
    middleware=[dynamic_model_selection]
)
result1 = agent.invoke({
    "messages": [{"role": "user", "content": "你叫什么名字?"}]
})
print(f"✅: {result1['messages'][-1].content}")
result2 = agent.invoke({
    "messages": [
        {"role": "user", "content": "\n1+1等于多少?"},
        {"role": "user", "content": "\n1+2等于多少?"},
        {"role": "user", "content": "\n1+3等于多少?"},
        {"role": "user", "content": "\n1+4等于多少?"},
        {"role": "user", "content": "\n1+5等于多少?"},
        {"role": "user", "content": "\n1+6等于多少?"}
    ]
})
print(f"✅: {result2['messages'][-1].content}")

⚠️ 使用结构化输出时不支持预绑定模型(通过bind_tools进行绑定的模型)。如果您需要具有结构化输出的动态模型选择,请确保传递给中间件的模型没有预绑定。
💡 更多模型配置的详细信息,参考Models。关于动态模型选择,参考Dynamic model in middleware,。

Tools(工具)

工具赋予agents采取行动的能力。agents通过以下方式超越了简单的纯模型工具绑定:

  • 有次序地调用多个工具(通过prompt触发)
  • 在适当的时候异步调用工具
  • 基于之前的结果的进行动态工具选择
  • 加入工具调用的重试逻辑和异常处理
  • 在跨工具调用时保持Agent状态的持久性

想要了解更多关于工具的信息,请查看工具

Defining Tools(定义工具)

将工具列表传递给Agent。

💡 工具可以被指定为普通的Python函数或协程。 tool decorator可用来自定义工具名称、描述、参数模型和其他属性。

python 复制代码
from langchain.tools import tool
from langchain.agents import create_agent
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

model = ChatDeepSeek(model="deepseek-chat")

@tool
def search(query: str) -> str:
    """查询信息"""
    return f"Results for: {query}"

@tool
def get_weather(location: str) -> str:
    """根据地点查询天气"""
    return f"{location}天气: 晴, 25℃"

agent = create_agent(model, tools=[search, get_weather])
result = agent.invoke({
    "messages": [{"role": "user", "content": "武汉的天气怎么样?"}]
})
print(f"✅: {result['messages'][-1].content}")

如果提供了一个空的工具列表,代理将由一个没有工具调用能力的LLM节点组成。

Tool error handling(工具的异常处理)

如果要自定义工具的异常处理,请使用@wrap_tool_call装饰器来创建中间件:

python 复制代码
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

model = ChatDeepSeek(model="deepseek-chat", max_retries=0)

@tool
def query_airindex(city: str) -> str:
    """计算空气指数"""
    pm25 = 1000
    airindex = pm25 / 0
    return airindex

@wrap_tool_call
def handle_tool_errors(request, handler):
    """自定义工具执行过程中的异常消息"""
    try:
        return handler(request)
    except Exception as e:
        print(f"❌: {str(e)}")
        return ToolMessage(
            content=f"Tool error: 请检查输入并重试。 ({str(e)})",
            tool_call_id=request.tool_call["id"]
        )

agent = create_agent(
    model=model,
    tools = [query_airindex],
    middleware=[handle_tool_errors]
)
result = agent.invoke({
    "messages": [{"role": "user", "content": "查询一下武汉的空气指数"}]
})
print(f"✅: {result['messages'][-2].content}")

当工具执行失败时,Agent将返回带有自定义异常消息的ToolMessage

python 复制代码
[
    ...
    ToolMessage(
        content="Tool error: 请检查输入并重试。 (division by zero)",
        tool_call_id="..."
    ),
    ...
]

Tool use in the ReAct loop

Agent遵循ReAct(" Reasoning(推理)+ Acting(执行)")规则,在简短的推理步骤与目标工具调用之间交替进行,并将结果观察结果提供给后续决策,直到它们能够提供最终答案。

ReaAct loop示例:

Prompt: 找到当前最流行的无线耳机并验证可用性。

ini 复制代码
================================ Human Message =================================

找到现在最流行的无线耳机,看看是否有货
  • Reasoning(推理):"流行度是时间敏感的,我需要使用已经提供的搜索工具。"
  • Acting(执行): 调用 search_products("无线耳机")
yaml 复制代码
================================== Ai Message ==================================
Tool Calls:
  search_products (call_abc123)
 Call ID: call_abc123
  Args:
    query: wireless headphones
ini 复制代码
================================= Tool Message =================================

Found 5 products matching "wireless headphones". Top 5 results: WH-1000XM5, ...

后面还会继续重复上述Reasoning和Acting步骤,直到Agent得到最终答案(这里不继续演示)。

💡 更多关于工具信息,请参考 Tools

System prompt(系统提示词)

你可以通过提供提示词来塑造Agent处理任务的方式。系统提示词通常是以字符串的形式提供:

python 复制代码
agent = create_agent(
    model,
    tools,
    system_prompt="你是一个有用的助手。回答要简洁准确。"
)

如果没有提供系统提示词,Agent将直接从消息中推断其任务。

系统提示词参数可以是 strSystemMessage。使用 SystemMessage 可以让你更好地控制提示词的结构,这对于特定大模型供应商的功能很有用,比如Anthropic的提示词缓存:

python 复制代码
from langchain.agents import create_agent
from langchain.messages import SystemMessage, HumanMessage

literary_agent = create_agent(
    model="anthropic:claude-sonnet-4-5",
    system_prompt=SystemMessage(
        content=[
            {
                "type": "text",
                "text": "You are an AI assistant tasked with analyzing literary works.",
            },
            {
                "type": "text",
                "text": "<the entire contents of 'Pride and Prejudice'>",
                "cache_control": {"type": "ephemeral"}
            }
        ]
    )
)

result = literary_agent.invoke(
    {"messages": [HumanMessage("Analyze the major themes in 'Pride and Prejudice'.")]}
)

cache_control 字段的 {"type": "ephemeral"} 告诉Anthropic缓存内容块,减少重复使用相同系统提示词的请求的延迟和成本。

Dynamic system prompt(动态系统提示词)

对于需要根据运行时上下文或Agent状态修改系统提示词这种更高级的用法,可以使用middleware(中间件)。

通过 @dynamic_prompt 装饰器创建中间件,实现根据模型请求生成系统提示词:

python 复制代码
from typing import TypedDict

from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest

from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

class Context(TypedDict):
    user_role: str

@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
    """根据用户角色来生成系统提示词"""
    user_role = request.runtime.context.get("user_role", "user")
    base_prompt = "你是一个有用的助手。"

    if user_role == "专家":
        return f"{base_prompt} 给出详细的专业解释。"
    elif user_role == "小白":
        return f"{base_prompt} 简单地解释概念,避免术语。"

    return base_prompt

model = ChatDeepSeek(model="deepseek-chat")

agent = create_agent(
    model=model,
    middleware=[user_role_prompt],
    context_schema=Context
)

# 系统提示词会根据上下文动态产生
result1 = agent.invoke(
    {"messages": [{"role": "user", "content": "解释一下机器学习"}]},
    context={"user_role": "专家"}
)
print(f"✅ 专家: {result1['messages'][-1].content}")
result2 = agent.invoke(
    {"messages": [{"role": "user", "content": "解释一下机器学习"}]},
    context={"user_role": "小白"}
)
print(f"✅ 小白: {result2['messages'][-1].content}")

Invocation(调用)

您可以通过更新Agent的 State 状态来调用它。所有agents在其状态中都包含一个 sequence of messages (消息序列),发送一条消息来调用Agent:

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

有关Agent的streaming steps或tokens,请参考 streaming

另外,Agent遵循LangGraph Graph API,并支持所有相关的方法,如 streaminvoke

Advanced concepts(高级概念)

Structured output(结构化输出)

在某些情况下,你可能希望Agent以特定格式返回输出。LangChain通过 response_format (响应格式)提供了结构化输出的策略。

ToolStrategy(工具策略)

ToolStrategy 使用人为调用工具来生成结构化的输出。这适用于任何支持工具调用的模型:

python 复制代码
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

# 结构化输出模型(必须定义):
# 方法1:Pydantic BaseModel - 官方推荐
class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str

# 方法2:dataclass - 极致性能,简单场景
# from dataclasses import dataclass
# @dataclass
# class ContactInfo:
#     name: str
#     email: str
#     phone: str | None = None

# 方法3:TypedDict - 纯字典场景,无验证需求
# from typing import TypedDict, NotRequired
# class ContactInfo(TypedDict):
#     name: str
#     email: str
#     phone: NotRequired[str]

model = ChatDeepSeek(model="deepseek-chat")

agent = create_agent(
    model=model,
    response_format=ToolStrategy(ContactInfo)
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "提取联系人信息: John Doe, john@example.com, (555) 123-4567"}]
})

print(result["structured_response"])
# ContactInfo(name='John Doe', email='john@example.com', phone='(555) 123-4567')

ProviderStrategy(供应商策略)

ProviderStrategy 使用模型供应商提供的native(原生)结构化输出。这更可靠,但只适用于支持原生结构化输出的供应商(例如:OpenAI,DeepSeek等国内大模型暂时不支持ProviderStrategy):

python 复制代码
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ProviderStrategy
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

# 结构化输出模型(必须定义):
class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str

model = ChatDeepSeek(model="deepseek-chat")

try:
    agent = create_agent(
        model=model,
        response_format=ProviderStrategy(ContactInfo)
    )
    result = agent.invoke({
        "messages": [{"role": "user", "content": "提取联系人信息: John Doe, john@example.com, (555) 123-4567"}]
    })
    print(result["structured_response"])
except Exception as e:
    print(f"   ❌ ProviderStrategy 失败: {type(e).__name__}: {str(e)[:100]}...")
# ❌ ProviderStrategy 失败: BadRequestError: Error code: 400

🚨 从 langchain 1.0 开始,必须显式地使用 ToolStrategyProviderStrategy

Memory

Agents通过消息state(状态)自动维护会话历史。还可以对agent进行配置,使用自定义状态模型,以便在会话期间记住额外的信息。

存储在状态中的信息可以看作是agent的 short-term memory 短期记忆:

自定义state(状态)模型必须将 AgentState 扩展为 TypedDict

自定义state(状态)的方式有两种:

  1. 通过 middleware (首选)
  2. 通过 state_schema on create_agent

Defining state via middleware(通过中间件定义状态)

当你的自定义state(状态)需要被特定的中间件hooks(钩子)和附加到该中间件上的tools(工具)访问时,使用中间件来定义自定义state(状态)。

python 复制代码
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from typing import Any
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv

load_dotenv()

model = ChatDeepSeek(model="deepseek-chat")

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 当前天气:晴天,25°C"
  
class CustomState(AgentState):
    """支持用户偏好的自定义状态"""
    user_preferences: dict[str, str]

class CustomMiddleware(AgentMiddleware):
    """根据用户偏好动态调整提示词和工具访问的自定义中间件"""
    state_schema = CustomState

    def before_model(self, state: CustomState, runtime) -> dict[str, Any] | None:
        print("============= before_model ==================")
        print(state.get("user_preferences"))
        
agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[CustomMiddleware()]
)

print("======== 🤖 开始调用... ==================")
result = agent.invoke({
    "messages": [{"role": "user", "content": "武汉的天气怎么样,有哪些地方值得游玩?"}],
    "user_preferences": {"style": "technical", "verbosity": "detailed"},
})
print(f"✅ 回答: {result['messages'][-1].content}")

Defining state via state_schema (通过 state_schema 定义状态)

使用 state_schema 这个参数作为定义状态的快捷键,这种方式只有在使用的tools(工具)的时候才能使用。

python 复制代码
from langchain.agents import AgentState
from langchain.agents import create_agent
from langchain_core.tools import tool
from dotenv import load_dotenv

load_dotenv()

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 当前天气:晴天,25°C"

class CustomState(AgentState):
    user_preferences: dict

agent = create_agent(
    model="deepseek-chat",
    tools=[get_weather],
    state_schema=CustomState
)

print("======== 🤖 开始调用... ==================")
result = agent.invoke({
    "messages": [{"role": "user", "content": "武汉的天气怎么样,有哪些地方值得游玩?"}],
    "user_preferences": {"style": "technical", "verbosity": "detailed"},
})
print(f"✅ 回答: {result['messages'][-1].content}")

⚠️ 从 langchain 1.0 开始,自定义状态模式模型是 TypedDict 类型。不再支持Pydantic models和dataclasses。详情请参考 v1 migration guide
⚠️ 定义自定义state(状态),使用 middleware 优于 state_schema ,这让你在概念上将状态扩展限定在相关中间件和工具的范围内。

state_schemacreate_agent 时仍然向后兼容。
💡 更多关于memory的信息,参考 Memory 。关于实现跨会话long-term memory的信息,参考 Long-term memory

Streaming(流)

我们已经看到了如何使用 invoke 调用Agent得到最终响应。如果Agent执行多个步骤,这可能需要一段时间。为了显示中间进程,我们可以在消息生成时将其流化。

python 复制代码
from langchain.agents import create_agent
from langchain_core.tools import tool
from dotenv import load_dotenv

load_dotenv()

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 当前天气:晴天,25°C"

agent = create_agent(model="deepseek-chat", tools=[get_weather])
for chunk in agent.stream({
    "messages": [{"role": "user", "content": "武汉的天气怎么样,给我一个穿衣建议"}]
}, stream_mode="values"):
    # Each chunk contains the full state at that point
    latest_message = chunk["messages"][-1]
    if latest_message.content:
        print(f"Agent: {latest_message.content}")
    elif latest_message.tool_calls:
        print(f"Calling tools: {[tc['name'] for tc in latest_message.tool_calls]}")

💡 更多关于streaming的细节,参考Streaming

Middleware(中间件)

Middleware 中间件为在不同的执行阶段定制Agent的行为提供了强大的扩展能力。你可以这样使用中间件:

  • 调用模型之前的处理Agent的状态。(例如:message trimming,context injection)
  • 修改或验证模型的响应(例如:guardrails,content filtering)
  • 使用自定义逻辑处理工具执行产生的异常
  • 实现基于状态或上下文的动态模型选择
  • 添加自定义日志、监控或分析

中间件无缝地集成到Agent的执行中,你可以在关键节点拦截和修改数据流,而无需更改核心Agent逻辑。

💡 关于中间件的完整文档(包括 @before_model , @after_model , @wrap_tool_call),请参考 Middleware

相关推荐
ZsTs1192 小时前
《2025 AI 自动化新高度:一套代码搞定 iOS、Android 双端,全平台 AutoGLM 部署实战》
前端·人工智能·全栈
Guheyunyi2 小时前
安全风险监测预警系统如何重塑企业安全防线
大数据·人工智能·科技·安全·信息可视化
GIS数据转换器2 小时前
空天地一体化边坡监测及安全预警系统
大数据·人工智能·安全·机器学习·3d·无人机
风送雨2 小时前
多模态RAG工程开发教程(上)
python·langchain
棒棒的皮皮2 小时前
【OpenCV】Python图像处理形态学之膨胀
图像处理·python·opencv·计算机视觉
Dev7z2 小时前
YOLO11 公共区域违法发传单检测系统设计与实现
人工智能·计算机视觉·目标跟踪
小草cys2 小时前
HarmonyOS Next调用高德api获取实时天气,api接口
开发语言·python·arkts·鸿蒙·harmony os
爬山算法2 小时前
Netty(25)Netty的序列化和反序列化机制是什么?
开发语言·python
王中阳Go2 小时前
06 Go Eino AI应用开发实战 | Eino 框架核心架构
人工智能·后端·go