从零开始学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

相关推荐
TMT星球2 分钟前
康迪科技携核心电动产品亮相AIMExpo,渠道拓展再提速
人工智能·科技
kingmax542120083 分钟前
NOAI和IOAI竞赛学习路径
人工智能·学习·青少年编程
说私域4 分钟前
基于AI智能名片链动2+1模式服务预约小程序的旅拍消费需求激发路径研究
大数据·人工智能·小程序
一个会的不多的人13 分钟前
人工智能基础篇:概念性名词浅谈(第二十六讲)
人工智能·制造·量子计算·数字化转型
liu****14 分钟前
能源之星案例
人工智能·python·算法·机器学习·能源
程序员三藏18 分钟前
软件测试环境搭建及测试过程
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
摆烂咸鱼~18 分钟前
机器学习(13-2)
人工智能·机器学习
人工智能AI技术18 分钟前
从零复现马斯克开源X推荐算法
人工智能
CCPC不拿奖不改名21 分钟前
数据处理与分析:pandas基础+面试习题
开发语言·数据结构·python·面试·职场和发展·pandas