7.agent

1. Agent 和 Model 的区别

在使用 LangChain 时,modelagent 都可以使用 invoke 方法,但它们在定位、功能和底层执行逻辑上有本质区别:

  • Model(模型)
    • 定位:"大脑",即大模型本身(如 GPT-4、DeepSeek 等)。
    • 特点:纯文本生成,只做一问一答,没有联网、执行代码等外部扩展能力。
    • 调用过程model.invoke("你好") 发起一次网络请求,获取文本回复后立刻结束,速度极快。返回通常是单一的 AIMessage
    • 适用场景:简单的翻译、总结、闲聊等无需外部资料的任务。
    • 创建配置:仅包含大模型提供商的配置信息(如 API Key,Base URL,模型名称等)。
  • Agent(智能体)
    • 定位 :"带团队打仗的项目经理",即 大模型(大脑) + 工具(手脚) + 思考逻辑(工作流)
    • 特点:LangChain 将思考规划、工具调用、结果观察(如 ReAct 模式)以及错误重试机制封装在了底层。
    • 调用过程agent.invoke(...) 背后可能会自主发起多次网络请求(思考 -> 拦截并调工具 -> 观察返回结果 -> 再思考),速度视任务复杂度而定。返回结果通常是一个包含完整对话轨迹的状态数据结构。
    • 适用场景:需要搜索网页、查询数据库、计算复杂数据等高度依赖外部信息的复杂任务。
    • 创建配置 :在创建时,不仅需要传入 model 作为大脑,还需要传入 tools 数组来赋予其执行特定动作的能力。

2. Agent 的基本用法

在使用 Agent 之前,我们需要准备好大模型实例。在后续所有示例中,我们默认使用以下配置的模型作为基础:

python 复制代码
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from rich import print as rprint

load_dotenv(override=True)

# 1. 准备大模型实例
model = init_chat_model(
    model="gpt-5.4-mini",
    model_provider="openai",
    api_key=os.getenv("CLOSEAI_API_KEY"),
    base_url=os.getenv("CLOSEAI_BASE_URL")
)

2.1 直接调用 Agent(不绑定工具)

如果不给 Agent 绑定工具,它的能力类似于直接调用大模型,但返回的数据结构是一套完整的图状态(State)。

python 复制代码
# 2. 创建 Agent,仅传入 model
agent = create_agent(
    model=model
)

# 3. 调用 Agent
response = agent.invoke({
    "messages": [
        {"role": "system", "content": "你是一个精通数学的老师,擅长以通俗易懂的方式讲解数学问题"},
        {"role": "user", "content": "100 + 20 * 3 = ?"}
    ]
})

rprint(response)

2.2 给 Agent 绑定自定义工具

我们可以通过 @tool 装饰器将普通的 Python 函数转换为 LangChain 可识别的工具,然后传递给 Agent。

python 复制代码
from langchain_core.tools import tool

# 1. 定义一个自定义工具
@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气。"""
    # 模拟真实的天气 API 调用
    return f"{city}天气晴朗,温度是15℃"

# 2. 创建 Agent,将模型和自定义工具绑定
agent = create_agent(
    model=model,
    tools=[get_weather] # 传入工具列表
)

# 3. 调用 Agent
response = agent.invoke({
    "messages": [
        {"role": "user", "content": "请问北京天气怎么样?"}
    ]
})

rprint(response) # 模型会自动决定去调用 get_weather 工具

2.3 给 Agent 绑定 LangChain 内置工具

LangChain 官方社区提供了非常多开箱即用的工具,例如 TavilySearch,可以轻松让 Agent 具备联网搜索的能力。

python 复制代码
from langchain_tavily import TavilySearch

# 1. 实例化内置的联网搜索工具
web_search = TavilySearch(
    max_results=2,
    tavily_api_key=os.getenv("TAVILY_API_KEY"),
)

# 2. 创建 Agent,绑定内置工具
agent = create_agent(
    model=model,
    tools=[web_search]
)

# 3. 调用 Agent
response = agent.invoke({
    "messages": [
        {"role": "user", "content": "请帮我查询2024年诺贝尔物理学奖得主是谁?"}
    ]
})

rprint(response) # 模型会自动使用搜索引擎工具获取最新信息

2.4 给 Agent 绑定多个工具

Agent 可以同时持有多个不同的工具。模型在遇到复杂问题时,会根据 Prompt 和工具的描述,自主决策使用哪个工具,甚至会组合调用多个工具来分步解决问题。

python 复制代码
# 1. 准备一个工具组合列表
tools = [get_weather, web_search]

# 2. 创建 Agent,同时赋予多个工具
agent = create_agent(
    model=model,
    tools=tools
)

# 3. 调用 Agent
response = agent.invoke({
    "messages": [
        {"role": "user", "content": "北京天气如何?另外,帮我搜一下最新的科技头条新闻。"}
    ]
})

rprint(response) # 模型会先后调用天气工具和搜索工具,最后总结回答

3. Agent 的高级用法

3.1 设置 Agent 的名称 (name)

在创建 Agent 时,可以通过 name 参数为其命名。这在多 Agent 协作或调试时非常有用,能在日志中清晰区分当前执行任务的 Agent。

python 复制代码
agent = create_agent(
    model=model,
    name="agent01" # 为 Agent 命名
)

3.2 设置系统提示词与提示词模板

3.2.1 使用 system_prompt 快捷参数

create_agent 提供了一个快捷参数 system_prompt,可以直接为其赋予"人设":

python 复制代码
agent = create_agent(
    model=model,
    system_prompt="你是一名多才多艺的智能助手,可以调用工具帮助用户解决问题。"
)

3.2.2 结合 ChatPromptTemplate 动态注入变量 (Model 与 Agent 对比)

对于复杂的场景,我们需要使用 ChatPromptTemplate 动态生成提示词。ModelAgent 都可以完美结合模板使用,但由于底层的输入要求不同,写法上略有差异:

✅ 对于裸 Model (大脑直接接收提示词): Model 能够直接接收并理解 PromptValue 对象,所以我们可以分步直接传给它:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个精通【{subject}】的智能助手。"),
    ("user", "我的问题是:{question}")
])

# 1. 填入变量,生成提示词对象 (PromptValue)
rendered_prompt = prompt.invoke({
    "subject": "物理学", 
    "question": "相对论是什么?"
})

# 2. 直接把提示词对象传给 Model
response = model.invoke(rendered_prompt)

✅ 对于 Agent (项目经理需要固定格式的状态字典): Agent 本质上是一个状态机(比如 LangGraph),它强制要求输入的数据必须是带有 "messages" 键的字典。因此我们需要手动分步进行格式转换

python 复制代码
# 1. 填入变量,生成提示词对象
rendered_prompt = prompt.invoke({
    "subject": "物理学", 
    "question": "2024年诺贝尔奖得主是谁?"
})

# 2. 把提示词对象转换成标准的"消息列表"
messages_list = rendered_prompt.to_messages()

# 3. 包装成带有 "messages" 键的字典,最后交给 Agent 执行
response = agent.invoke({
    "messages": messages_list
})

4. Agent 进阶:结构化输出的策略

控制工具函数的输入(即如何约束工具传参),对于 Model 和 Agent 是一模一样的:都是靠 Pydantic、函数注释(docstring)和类型提示。

但在控制最终的结构化输出 上,Agent 因为底层占用了工具调用,不能像裸 Model 一样简单粗暴地绑定 .with_structured_output()。在最新的 LangChain 中,官方为 Agent 提供了 4 种结构化输出策略 (response_format)

  1. ProviderStrategy (提供商策略): 直接利用底层大模型自带的结构化输出能力。LangChain 在底层自动处理了与 Agent 运行逻辑的兼容问题。
  2. ToolStrategy (工具策略) ------ 经典原理官方化: 这就是我们在底层逻辑中所说的**"瞒天过海(把输出伪装成工具)"大法**!底层自动把你传的 Pydantic 模型变成一个最终工具(Final Answer Tool),并强制 Agent 在最后一步调用它来生成格式化数据。
  3. AutoStrategy (自动策略) ------ 最推荐 : LangChain 会智能判断当前使用的模型:如果模型自身结构化能力强,就用 ProviderStrategy;如果不兼容,就自动降级用 ToolStrategy
  4. TypeStrategy (类型策略) : 直接传入 Pydantic 类,但官方不推荐此用法。

配置示例

python 复制代码
from langchain_core.messages import SystemMessage
from pydantic import BaseModel, Field
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import (
    ProviderStrategy, 
    ToolStrategy, 
    AutoStrategy
)
from langchain.tools import tool

# 1. 定义日常业务工具
@tool
def search_customer_database(query: str) -> str:
    """在客户数据库中搜索信息"""
    if "张三" in query:
        return "客户记录:张三,VIP客户,最近购买日期:2026-01-15,累计消费:$15,000"
    return "无记录"

@tool
def send_email(customer: str) -> str:
    """发送感谢邮件"""
    return f"已向 {customer} 发送感谢邮件"

# 2. 定义我们最终想要的结构化数据 (Pydantic Schema)
class CustomerAnalysis(BaseModel):
    """客户分析报告"""
    customer_name: str = Field(None, description="客户姓名")
    customer_tier: Literal["潜在客户", "普通客户", "VIP客户", "流失风险"] = Field("潜在客户", description="客户等级")
    recent_activity: str = Field(None, description="最近活动")
    send_email: bool = Field(False, description="是否已发送感谢邮件")

# 3. 创建 Agent
agent = create_agent(
    model=model,
    tools=[search_customer_database, send_email],
    # 强大的 System Prompt:指导 Agent 按既定 SOP 工作
    system_prompt=SystemMessage(content=
        "请分析指定客户的情况:\n"
        "1. 先搜索客户数据库了解最新情况\n"
        "2. 如果是VIP客户,则立刻调用发送感谢邮件工具\n"
        "3. 基于搜索结果生成最终的结构化分析报告"
    ),
    # 策略 1 (最推荐): 自动判断模型能力,智能选择策略
    response_format=AutoStrategy(CustomerAnalysis)
    
    # 策略 2 (手动指定): response_format=ToolStrategy(CustomerAnalysis)
    # 策略 3 (手动指定): response_format=ProviderStrategy(CustomerAnalysis)
    # 策略 4 (不推荐): response_format=CustomerAnalysis
)

response = agent.invoke({"messages": [{"role": "user", "content": "请分析客户张三"}]})
# 解析好的 Pydantic 对象会保存在 structured_response 字段中
print(response["structured_response"]) 

5. Agent 的错误处理机制 (handle_tool_error)

在给 Agent 绑定工具(或使用结构化输出)时,经常会遇到模型传参错误或工具内部报错的情况。我们可以通过为工具配置错误处理参数,让 Agent 拥有自动纠错的能力。

注意命名差异

  • 在普通工具(@tool)中,该参数名为 handle_tool_error
  • 在结构化输出的 ToolStrategy 等策略中,为了统一,该参数被命名为 handle_errors。 两者在底层的作用完全一致。

不论是普通工具里的 handle_tool_error,还是结构化输出里的 handle_errors,该参数都支持以下几种配置方式:

1. 设置为布尔值 (True/False)

  • True (默认行为) :当工具报错时,程序不会崩溃 。LangChain 会捕获真正的 Python 报错信息(如 ValueError),并将报错文本发还给大模型,让大模型自我反思并重试调用
  • False:遇到错误不拦截,直接让 Python 程序抛出异常并终止。

代码示例(结构化输出场景):

python 复制代码
from pydantic import BaseModel, Field
from typing import Union
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
# 假设 model 已经初始化
# model = init_chat_model(...)

class ContactInfo(BaseModel):
    """个人联系信息"""
    name: str = Field(description="姓名")
    email: str = Field(description="电子邮箱")

class EventDetails(BaseModel):
    """活动详情"""
    event_name: str = Field(description="活动名称")
    date: str = Field(description="活动日期")

agent = create_agent(
    model=model,
    response_format=ToolStrategy(
        Union[ContactInfo, EventDetails],
        tool_message_content="提取完成!",
        handle_errors=True  # 核心:设置为 True 以开启自动重试
    )
)

result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "请提取以下文本中内容:姓名:张三,电子邮箱:zhang3@atguigu.com,活动名称:公司年会,活动日期:2026-07-15"
    }]
})
print(result["structured_response"])

2. 设置为固定字符串 (推荐)

这是实战中非常实用的技巧,主要适用于以下场景:

  • 防止机密泄露:隐藏真实的内部代码报错(如 SQL 语法错误、IP 地址),只给大模型看安全的提示语。
  • 防止大模型被绕晕:屏蔽几十行的复杂 Traceback,用简明扼要的人话告诉模型哪里错了。
  • 自定义纠错引导:精准纠正模型的坏习惯。

代码示例(普通工具场景):

python 复制代码
from langchain.tools import tool
from langchain.agents import create_agent
# 假设 model 已经初始化
# model = init_chat_model(...)

# 核心:使用 fixed string 向大模型隐藏真实的 ValueError,并精准引导纠错
@tool(handle_tool_error="参数错误!订单号必须是纯数字,不要带字母或特殊字符!请检查后重试!")
def get_order_status(order_id: int) -> str:
    """查询订单状态。必须传入纯数字的订单号。"""
    # 模拟业务逻辑:如果大模型提取了字母,类型不匹配会抛出 ValueError 或 TypeError。
    # 因为配置了 handle_tool_error,程序不会崩溃,而是把上述报错信息替换为固定字符串发回给模型。
    if int(order_id) == 12345:
        return "订单 12345 状态:已发货"
    return "订单不存在"

agent = create_agent(
    model=model,
    tools=[get_order_status]
)

# 故意给一段带有干扰的文本
response = agent.invoke({
    "messages": [{"role": "user", "content": "帮我查一下订单状态,订单号好像是 O-12345。"}]
})
# 大模型第一次很可能会把 "O-12345" 当作参数传进去导致报错。
# 随后它会收到固定字符串提示,从而自我纠正提取出正确的 12345 再试一次。
print(response["messages"][-1].content)

3. 设置为指定异常类型

例如 handle_tool_error=ValueError。这意味着只有发生 ValueError 时才让模型重试,如果发生断网(NetworkError)等其他类型的错误,程序该崩溃还是崩溃。

4. 设置为自定义错误处理函数

支持传入一个自定义的 Python 函数,接收 Exception 对象,返回给模型一段特定的错误提示字符串:

python 复制代码
def my_custom_handler(error: Exception) -> str:
    # 可以在这里记录日志、发报警
    return f"模型老兄,你犯了 {type(error)} 类型的错误,仔细检查一下哦!"

@tool(handle_tool_error=my_custom_handler)
def some_tool(param: str) -> str:
    pass

核心结论 :所有放进 Agent tools 列表里的东西(包括你自己写的普通函数、官方的网络搜索插件、伪装成工具的结构化输出模板),本质上都是 BaseTool。只要是 BaseTool,就都具备这个定制错误提示并让模型"自我重试纠错"的能力。


6. Agent 的流式输出 (Streaming)

在实际业务中(特别是网页对话框),Agent 执行任务往往需要耗费较长时间。为了避免前端长时间"假死"无响应,我们需要使用 agent.stream() 来实时输出 Agent 的思考和执行进度。

在最新的 LangChain/LangGraph 架构中,stream_mode 支持多达 7 种模式,满足从前端展示到后端调试的各种需求。

6.1 常用流式输出模式梳理

  1. messages 模式 (最核心,打字机效果的唯一选择)
    • 只要大模型(LLM)一开始"思考",就会一个 Token 接着一个 Token 地将文字数据块(Chunk)推送出来。
    • 重点 :它不仅流式打印"最终作答的文本",还会流式打印大模型决定调用工具时"生成的内部参数格式(如 JSON)"。也就是大模型生成的所有内容,都会像打字机一样呈现
    • 适用场景:实现类似 ChatGPT 前端的实时对话打字特效。
  2. values 模式 (全局快照)
    • Agent 的工作流每走完一个节点(比如大脑思考结束,或者工具执行完毕),就会把当前整个 Agent 的完整历史状态(所有的历史消息列表)全量打印一次。
    • 适用场景:直观查看每一步后的全局大局观。
  3. updates 模式 (增量补丁 - 开发者最爱)
    • 触发时机与 values 完全一样,但它极其精简,只打印当前这一步操作带来的增量变化(例如刚刚新增的那一条消息)
    • 适用场景:开发者用来清晰监听节点进度,提取最新执行结果的黄金模式。
  4. checkpoints 模式 (持久化存档)
    • 当给 Agent 挂载了"记忆盘"(Saver)后,每次游戏存档(生成检查点快照)时才会触发。
    • 适用场景:后端专属,用于 Human-in-the-loop(人工审批)、断点续传(读档)、分布式追踪等高级系统架构设计。

(注:除此之外还包含 tasksdebugcustom 模式,一般仅用于底层极限调试或定制极其细粒度的进度条推送)

6.2 代码示例 (messages 模式)

下面展示如何捕获 messages 模式下的流式 Token,实现最经典的打字机效果:

python 复制代码
# 假设 agent 已创建,并配备了某些工具
# agent = create_agent(model=model, tools=[...])

# 调用 stream,并指定 stream_mode="messages"
stream = agent.stream(
    {"messages": [{"role": "user", "content": "请查一下北京天气,然后根据天气写一首诗"}]},
    stream_mode="messages"
)

# 遍历流式产生的数据块 (chunk)
# 这里的 chunk 包含了 (message_chunk, metadata) 两个元素
for chunk in stream:
    # 取出真正的内容片段
    msg_chunk = chunk[0]
    
    # 1. 打印普通的文本内容 (比如大模型正在作诗)
    if msg_chunk.content:
        print(msg_chunk.content, end="", flush=True)
        
    # 2. 如果你想监听大模型准备调什么工具,可以打印这段底层的 tool_calls 字典流
    elif msg_chunk.tool_call_chunks:
        print(f"\\n[系统提示:大模型正在疯狂敲代码准备调工具 {msg_chunk.tool_call_chunks[0]['name']}...]\\n")

7. 终极实战:融会贯通的"多功能智能助手"

学完前面的所有高级概念后,我们可以把它们全部拼装在一起,打造一个极具工程水准的 Agent。

这个终极例子完美融合了以下 6 大核心概念

  1. 定制 Agent 名称 (name="xxx"),方便日志调试。
  2. 使用提示词模板 (ChatPromptTemplate),实现动态变量注入和系统人设分离。
  3. 标准传参调用 :先用 prompt.invoke() 渲染变量生成消息,再直接传给 Agent,拒绝晦涩难懂的 LCEL 语法。
  4. 带有错误处理的工具 (@tool(handle_tool_error=...)),利用固定字符串精准引导纠错。
  5. 结构化输出 (AutoStrategy + handle_errors=True),强制约束最终输出并自带重试光环。
  6. 流式输出 (stream_mode="messages"),实现丝滑的打字机前端效果。

完整代码示例

python 复制代码
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
from langchain.agents import create_agent
from langchain.agents.structured_output import AutoStrategy
from pydantic import BaseModel, Field

load_dotenv(override=True)

# ================= 1. 初始化模型 =================
model = init_chat_model(
    model="gpt-4o-mini",
    model_provider="openai",
    api_key=os.getenv("CLOSEAI_API_KEY"),
    base_url=os.getenv("CLOSEAI_BASE_URL")
)

# ================= 2. 定义业务工具 (带精准容错机制) =================
@tool(handle_tool_error="参数错误!天气工具只能接受纯中文城市名,请不要带英文或拼音,重试!")
def get_weather(city: str) -> str:
    """查询国内城市的天气状况"""
    # 模拟业务容错拦截:如果大模型提取了 "Beijing" 而非 "北京",抛出异常交由外层纠错
    import re
    if re.search(r'[a-zA-Z]', city):
        raise ValueError("Invalid City Name")
    return f"{city}的天气是:晴朗,25度"

@tool(handle_tool_error="表达式错误!请检查你的数学公式格式是否支持 eval。")
def calculate_math(expression: str) -> str:
    """计算数学表达式的值,如 '2 + 2'"""
    return str(eval(expression))

# ================= 3. 定义结构化输出模型 (Pydantic) =================
class FinalResponse(BaseModel):
    """智能助手的最终格式化回答"""
    thought_process: str = Field(description="详细的推理和思考过程")
    final_answer: str = Field(description="直接给用户的最终答案")
    confidence_score: int = Field(description="对答案的自信度打分(0-100)")

# ================= 4. 构建 Agent 引擎 =================
# 将多个特性融为一体:命名、挂载工具、设置强约束的结构化策略并开启自动重试
ultimate_agent = create_agent(
    model=model,
    tools=[get_weather, calculate_math],
    name="SuperAssistant",
    response_format=AutoStrategy(
        schema=FinalResponse, 
        handle_errors=True  # 强力兜底:格式不对自己重做
    )
)

# ================= 5. 定义动态提示词模板 =================
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个全能的智能助理,名叫 {bot_name}。请尽力解答用户的问题。"),
    ("user", "{user_input}")
])

# ================= 6. 渲染提示词模板 =================
print("正在连线 SuperAssistant...")

# 先用模板填入动态变量,生成最终的消息列表
messages = prompt.invoke({
    "bot_name": "贾维斯", 
    "user_input": "Beijing现在的天气怎么样?帮我把它的温度乘以 5 告诉我结果。"
}).to_messages()

# ================= 7. 触发流式运行 (打字机效果) =================
# 把消息列表放进字典的 "messages" 字段,直接传给 agent 开启流式监听
stream = ultimate_agent.stream(
    {"messages": messages},
    stream_mode="messages"
)

# 实时捕获并打印 Token 块
for chunk in stream:
    msg = chunk[0]
    
    # 如果大模型在思考时生成了普通文本内容
    if msg.content:
        print(msg.content, end="", flush=True)
        
    # 如果大模型决定调用工具,把动作打印出来
    elif msg.tool_call_chunks:
        print(f"\\n[动作] -> 准备调用工具: {msg.tool_call_chunks[0]['name']} ...")
相关推荐
一个处女座的程序猿10 小时前
Agent之Skill:dashi-ppt-skill的简介、安装和使用方法、案例应用之详细攻略
powerpoint·agent·ppt-skill
AINative软件工程10 小时前
40% 的 Multi-Agent 项目在生产挂掉:任务分发、结果聚合与级联失败防护的工程实践
openai·agent
一个处女座的程序猿10 小时前
Agent之Skill:prompt-master的简介、安装和使用方法、案例应用之详细攻略
agent·skills·prompt-master
锅总的程序人生17 小时前
重Harness已死:强模型自己就能把活干漂亮
ai·大模型·agent·speccoding·harness·技能链
To_OC20 小时前
跑了个最简 RAG demo 后,我终于搞懂向量检索那堆分数到底啥意思
人工智能·llm·agent
思考着亮1 天前
6.结构化输出
agent
思考着亮1 天前
5.tools
agent
要开心吖ZSH1 天前
AI医疗分诊与健康咨询助手agent开发——(4-2)从零到一:我给AI医疗分诊助手加了个“知识库大脑“,终于搞懂了RAG是什么
java·docker·agent·医疗·rag
smartfish_liu1 天前
LLM-笨笨的agent 闭环沙盒任务测试: 把{private}/LLM/story/I_am_back.md发表到博客网
agent