← 返回上一页

第6章: Agent 实战入门:让模型学会“用工具做事”

让大模型学会“用工具”与“自己思考”

📦 环境准备(仅需执行一次)

text
python -m pip install -U "langchain==1.2.10" "langchain-community==0.3.31" "langchain-core==1.2.13" dashscope duckduckgo-search wikipedia requests

本章所有示例均使用上述固定版本,确保代码可复制运行。后续代码块不再重复安装命令。

🧭 学习路线导航

1️⃣ 入门示例:为什么需要工具
2️⃣ 什么是 Agent(核心机制)
3️⃣ Tools/Toolkits 清单(工具资产)
4️⃣ create_agent 工程化(可上线)
5️⃣ Agent 类型概览 + 案例

1️⃣ ⏰ 入门示例:让 AI 学会"看时间"(为什么需要工具)

我们先用一个最简单的例子来理解为什么一定要有工具:让 AI 回答"现在几点了?"

问题:普通 LLM 无法回答实时问题

python
from langchain_community.llms import Tongyi
import os

api_key = os.getenv("DASHSCOPE_API_KEY")
model = Tongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# 直接问 LLM
response = model.invoke("现在几点了?")
print(response)

# 输出类似:
# "抱歉,我无法获取实时信息,请查看您的设备时间..."

❌ 没有工具时,大模型的典型弊端

  • 拿不到实时信息:时间、天气、库存、最新公告等都无法凭空得知。
  • 算不准:字数统计、复杂数学、表格统计等纯靠生成很容易出错。
  • 读不到你的数据:本地文件、数据库、内部系统都需要“工具/API”接入。

解决方案:给 Agent 加一个"时间工具"

python
import os
from datetime import datetime
import logging

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


@tool
def get_current_time(query: str = "") -> str:
    """当用户询问现在的时间、日期时,使用此工具获取当前时间。"""
    now = datetime.now()
    return now.strftime("%Y年%m月%d日 %H:%M:%S")


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

# 建议开启基础日志配置(便于看到工具内日志/以及 middleware 日志)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_agent(
    llm,
    tools=[get_current_time],
    system_prompt="你是一个企业级助手。需要获取实时信息时请调用工具,并直接给最终答案。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "现在几点了?")]})
print("最终答案:", state["messages"][-1].content)

✅ 有了工具之后,Agent 做了什么?

  • 识别意图:判断用户在问时间。
  • 调用工具:执行 get_current_time 获取系统当前时间。
  • 组织输出:把工具结果转成自然语言并回复。

2️⃣ 🤖 什么是 Agent?

在 LangChain 中,Agent 可以理解为一个“会思考的调度员”: 它基于大模型的推理能力,自动决定什么时候调用哪个工具、如何组合工具, 并在多轮对话中持续记住上下文。

Agent 的四个核心组成

  • LLM:负责“思考”和生成文本(本章使用通义千问 qwen-turbo)。
  • Tools:一组可以调用的工具,例如:计算器、Python REPL、CSV 分析等。
  • Memory:对话记忆,用来存储历史消息,让 Agent 具备上下文意识。
  • Agent 逻辑:一套提示词 + 决策流程,告诉 LLM 何时、如何使用工具。

🧠 什么是 Skills?(很多 Agent 框架都会用到)

Skills 可以理解为:把一段“多步骤能力”封装成可复用模块。 它通常不是一次性的工具调用,而是一段可复用的工作流(可能包含多次工具调用 + 状态处理 + 结果格式化)。

和 Tool 的区别:Tool 更像“原子动作”(一次 API/一次函数调用);Skill 更像“组合动作”(把多个原子动作串成稳定能力)。
和 Agent 的区别:Agent 负责“决策与调度”(何时用什么);Skill 负责“把某类任务做稳”(具体怎么做、怎么兜底)。
一个直观例子
“网页总结”可以是一个 Skill: 先调用搜索/抓取工具拿到网页内容 → 再抽取要点 → 最后按固定结构输出摘要与引用。 你把它封装成 summarize_webpage(url) 之后,Agent 只需要在合适时机调用这个 Skill,就能稳定产出结果。

🔗 典型工作流程

text
用户问题 → Agent 分析意图
         ↓
   判断是否需要工具
     ↙           ↘
 直接回答      选择合适工具
                    ↓
             调用工具获取结果
                    ↓
             整理结果并回复用户
                    ↓
             写入对话记忆(Memory)

🧩 ReAct 机制(Reason + Act):Agent 为什么能“会做事”?

你在很多资料里看到的“React”通常指的是 ReActReason(推理) + Act(行动)。 其核心不是让模型“想得更深”,而是让模型在运行时形成一个可循环的闭环: 先做下一步决策调用工具拿到外部事实再根据事实继续决策

🔄 ReAct 循环流程图
用户问题 User Query Reason 选工具 + 生成参数 Act 调用工具执行 Observe 拿到结果 需要更多信息吗? 是:继续循环;否:输出最终答案 是 → 继续 否 → 输出
  • Reason:模型判断"是否需要工具 / 用哪个工具 / 参数是什么"。
  • Act:实际执行工具(例如:查时间、查库、请求 HTTP、读文件)。
  • Observe:把工具返回值作为"新证据"放回上下文。
  • Loop:重复上述过程,直到得到最终答案。

本章所有案例都用 create_agent + @tool + messages 来实现这种 ReAct 式闭环。

🧠 你可以把 Agent 理解成什么?(一个更工程化的视角)

Agent 不是“更聪明的模型”,而是一个运行时系统: 它把模型当作“决策引擎”,把工具当作“可执行能力”,把对话状态当作“上下文存储”, 最终形成一个可循环执行的流程:思考 → 调用工具 → 观察 → 再思考 → 输出

  • LLM(决策):判断“下一步做什么”。
  • Tools(执行):把“想法”落地成可执行动作(读文件/查库/算数/跑代码)。
  • State(状态):保存多轮消息、工具结果、中间结论,让系统可继续往下走。
  • Policy(规则):system prompt + 约束,决定安全边界、工具调用原则与输出风格。

⚠️ Agent 常见“踩坑点”(从入门到实战必须理解)

  • 工具参数 Schema 不匹配:工具函数参数名/类型不合理,会导致模型无法正确构造调用参数。
  • 工具“描述”不清晰:没有 docstring / description,模型不知道何时调用、如何调用。
  • 工具返回值不稳定:返回 JSON/文本混用、字段变化,会让模型难以总结结果。
  • 把工具当“黑盒 API”:工具需要做输入校验、错误兜底、超时与限流,否则一旦失败就会拖垮对话体验。
  • 多轮上下文丢失:Agent 的“记忆”不是魔法,本质是你把历史消息持续传入(或使用持久化 checkpoint)。

🔌 MCP 协议:Agent 工具调用的"万能接口"

🤔 什么是 MCP?

MCP(Model Context Protocol,模型上下文协议)是由 Anthropic(Claude 的开发公司)推出的开放标准协议, 旨在统一 AI 应用(包括 Agent)与各种数据源、工具之间的连接方式

简单理解:就像 USB 接口统一了电脑外设的连接方式,MCP 统一了 Agent 访问工具和数据的方式。 无论是本地文件、数据库、API 还是云服务,都可以通过 MCP 协议让 Agent 轻松调用。

❌ 传统 @tool 方式的痛点

  • 每个工具需要单独用 @tool 装饰器定义
  • 不同 Agent 框架需要重复开发工具
  • 工具代码与 Agent 代码耦合
  • 权限管理分散在各个工具函数中
  • 工具复用困难,维护成本高

✅ MCP 的优势

  • 一次配置,所有 Agent 工具通用
  • 标准化接口,开发简单
  • 工具与 Agent 解耦,易于维护
  • 统一的安全和权限控制
  • 社区共享,生态丰富(100+ 开源工具)

🏗️ MCP 架构:三层结构

第1层:Agent 应用层(MCP Clients) Claude Desktop 官方 Agent 内置 MCP 支持 Cursor / Windsurf AI 编程 Agent 支持 MCP 插件 自定义 Agent LangChain/LlamaIndex 集成 MCP SDK 更多 Agent... 任何支持 MCP 的 Agent 框架 第2层:MCP 协议层(标准化通信) 统一的 JSON-RPC 协议 + 标准化资源/工具/提示词接口 支持:stdio(本地)、HTTP/SSE(远程)、WebSocket(实时) 第3层:MCP 服务器层(MCP Servers - 工具/数据源适配器) 📁 文件系统 本地文件读写 PDF/Word/TXT 🗄️ 数据库 SQL 查询 MySQL/PostgreSQL 🌐 Web API REST/GraphQL GitHub/Notion ☁️ 云服务 云存储/SaaS Google Drive/S3 实际工具/数据源:本地文件、企业数据库、第三方 API、云端文档...

⚙️ MCP 工作原理(以 Agent 调用工具为例)

1️⃣
Agent 决策

"需要读取文件"

2️⃣
MCP 协议转换

转为标准 JSON-RPC 调用

3️⃣
MCP Server 执行

读取文件并格式化

4️⃣
返回结果

Agent 获得文件内容

🎯 MCP 在 Agent 中的实际应用

📚 场景1:企业知识库 Agent

通过 MCP 连接公司的 Confluence、Notion、SharePoint,Agent 可以直接搜索和引用企业文档, 无需为每个数据源单独写 @tool 函数。

💻 场景2:代码审查 Agent

MCP 连接 GitHub/GitLab,Agent 可以读取代码仓库、查看提交历史、分析代码结构, 自动完成代码审查和重构建议。

📊 场景3:数据分析 Agent

MCP 连接数据库(MySQL/PostgreSQL),Agent 可以执行 SQL 查询、生成报表、 分析业务数据,无需导出 CSV 或编写数据库工具函数。

🔄 场景4:实时同步 Agent

MCP 连接 Google Drive/Dropbox,Agent 可以实时读取最新版本的文档, 确保分析和回答基于最新数据,无需手动同步。

🚀 快速上手:5分钟为 Agent 配置 MCP

1. 安装 Claude Desktop(官方支持最完善)
2. 配置 MCP Server(编辑 claude_desktop_config.json
3. 重启 Claude,Agent 即可通过自然语言调用 MCP 工具

💡 提示:MCP 社区已有 100+ 开源 Server,涵盖文件系统、GitHub、Google Drive、Slack、数据库等常见工具。 访问 MCP Servers 仓库 查看完整列表。

💡 核心要点

  • MCP 是标准协议,不是某个公司的专有技术,任何 Agent 框架都可以支持
  • 一次配置,处处可用,配置好的 MCP Server 可以被所有支持 MCP 的 Agent 使用
  • 工具与 Agent 解耦,MCP Server 独立运行,Agent 代码更简洁
  • 安全可控,MCP Server 运行在本地或私有服务器,数据不会泄露给第三方
  • 生态丰富,社区持续贡献新的 MCP Server,覆盖越来越多的工具和数据源

📌 最小可运行心智模型:create_agent + tools + messages

在本章(固定版本 langchain==1.2.10)里,我们采用最稳定、最好复制的方式: 用 create_agent 创建一个 Agent,然后通过 messages 传入对话历史。 你只要记住两句话:工具用 @tool 定义多轮对话就是复用 messages 列表

🌱 例子 A:最小多工具路由(时间 + 字数),让 Agent 自己选工具

python
import os
from datetime import datetime

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


@tool
def get_current_time(query: str = "") -> str:
    """当用户询问现在的时间、日期时,使用此工具获取当前时间。"""
    return datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")


@tool
def count_text_length(text: str) -> int:
    """计算文本长度(按字符数),当用户要求统计字数/长度时使用。"""
    return len(text)


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

# 开启基础日志配置(你会在控制台看到 middleware 层输出:mw.tools / mw.timing)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)
tools = [get_current_time, count_text_length]

agent = create_agent(
    llm,
    tools=tools,
    system_prompt=(
        "你是一个严谨的助手。"
        "当问题需要实时信息或精确计算时,请调用工具。"
        "输出只给最终答案,不要输出推理过程。"
    ),
    debug=True,
)

questions = [
    "现在几点了?",
    "'君不见黄河之水天上来' 这句话有多少个字?",
]

for q in questions:
    state = agent.invoke({"messages": [("human", q)]})
    print("\nQ:", q)
    print("A:", state["messages"][-1].content)

🧱 例子 B:工具输入校验 + 失败兜底(工程里更常见)

在生产环境里,工具一定要“可控”: 你不能把用户输入原样交给任意执行器(例如 PythonREPL)。下面给一个更通用的模板: 先做白名单校验,再执行;异常要转成可读错误信息

python
import os
import re
from datetime import datetime
from pathlib import Path

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


@tool
def safe_calculator(expr: str) -> str:
    """安全计算器:只允许数字、加减乘除、小数点与括号。"""
    if not re.fullmatch(r"[0-9\s\+\-\*\/\(\)\.]+", expr):
        return "非法表达式:只允许数字与 + - * / ( ) ."
    try:
        # 这里只是演示:简单场景可用;更严谨可用 ast 白名单解析(见本章 C1)
        value = eval(expr, {"__builtins__": {}}, {})
        return str(value)
    except Exception as e:
        return f"计算失败:{e}"


@tool
def safe_file_reader(path: str) -> str:
    """安全文件读取:只允许读取当前目录下的 .txt 文件,返回前 500 字符。"""
    import os
    from pathlib import Path
    
    # 输入校验:防止路径遍历攻击
    if ".." in path or "/" in path or "\\" in path:
        return "错误:不允许使用路径分隔符,只能读取当前目录下的文件"
    
    if not path.endswith(".txt"):
        return "错误:只允许读取 .txt 文件"
    
    file_path = Path(path)
    if not file_path.exists():
        return "错误:文件不存在"
    
    if not file_path.is_file():
        return "错误:不是文件"
    
    try:
        content = file_path.read_text(encoding="utf-8", errors="ignore")
        return content[:500] + ("..." if len(content) > 500 else "")
    except Exception as e:
        return f"读取失败:{e}"


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# 创建带安全校验的 Agent
agent = create_agent(
    model=llm,
    tools=[safe_calculator, safe_file_reader],
    system_prompt=(
        "你是一个严谨的计算和文件读取助手。"
        "1. 需要计算时调用 safe_calculator,需要读取文件时调用 safe_file_reader。"
        "2. 如果工具返回错误信息,请向用户解释具体的限制原因。"
        "3. 不要尝试绕过工具的安全限制。"
    ),
    name="SafeToolsAgent",
    debug=False,
)

# 创建一个测试文件
test_file = Path("demo.txt")
test_file.write_text("这是测试文件内容。\n包含多行文本,用于测试安全文件读取功能。", encoding="utf-8")

# 测试用例
test_cases = [
    ("计算 123 + 456", "正常计算"),
    ("计算 123/0", "除零错误处理"),
    ("计算 import os", "非法表达式拦截"),
    ("读取 demo.txt", "正常文件读取"),
    ("读取 ../etc/passwd", "路径遍历拦截"),
    ("读取 test.py", "文件类型拦截"),
]

print("=== 安全工具测试 ===")
for question, description in test_cases:
    print(f"\n[{description}]")
    print(f"Q: {question}")
    state = agent.invoke({"messages": [("human", question)]})
    print(f"A: {state['messages'][-1].content}")

# 清理测试文件
test_file.unlink() if test_file.exists() else None

print("\n=== Agent 配置信息 ===")
print(f"名称:{agent.name}")
print(f"工具数量:2(安全计算器 + 安全文件读取)")
print("特点:所有工具都有输入校验和异常处理")

3️⃣ 🧰 LangChain 自带常用 Tools / Toolkits 清单(建议先收藏)

下面是可以直接导入使用的常用工具名与导入路径。注意:不同工具分布在 langchain / langchain-community / langchain-experimental, 有的需要额外依赖或联网权限。你在企业落地时,通常会把这些工具封装成"内部工具层",统一做鉴权、审计与限流。

HTTP API 工具
人类介入(审批)
安全读文件
实时搜索
百科查询
Python 计算
SQL 查询
🔍 搜索 / 检索
联网
做什么:获取最新信息与事实背景,把网页/百科内容作为证据再总结。
典型场景:实时新闻、产品价格、技术文档查询、学术资料检索。
注意:建议做域名白名单、超时与频控,避免被恶意网站或高频率请求拖垮。
DuckDuckGoSearchRun
网页搜索
from langchain_community.tools import DuckDuckGoSearchRun
WikipediaQueryRun
维基百科查询
from langchain_community.tools import WikipediaQueryRun
ArxivQueryRun
学术论文检索
from langchain_community.tools import ArxivQueryRun
GoogleSearchAPIWrapper
Google 搜索
from langchain.utilities import GoogleSearchAPIWrapper
💻 代码执行
高危
做什么:精确计算、跑小段代码验证想法、调用系统命令。
典型场景:数学计算、数据清洗、算法验证、文件批量处理。
注意:务必沙箱隔离、allowlist、审计,不要在不可信输入场景开放。
PythonREPLTool
Python 执行
from langchain_experimental.tools import PythonREPLTool
ShellTool
Shell 命令
from langchain.tools import ShellTool
📊 表格 / CSV / SQL
数据
做什么:对结构化数据做统计、筛选、聚合(订单、报表、用户行为)。
典型场景:销售报表分析、用户行为统计、财务对账、数据清洗。
注意:SQL 工具要做最小权限与注入防护,CSV 大文件建议分块处理。
create_csv_agent
CSV 分析
from langchain_experimental.agents.agent_toolkits import create_csv_agent
create_python_agent
Python 分析
from langchain_experimental.agents.agent_toolkits import create_python_agent
SQLDatabaseTool
数据库查询
from langchain.tools import SQLDatabaseTool
📁 文件操作
高危
做什么:让 Agent 读写业务文件、导出报告、生成脚本等。
典型场景:日志分析、配置生成、数据导出、批量文件处理。
注意:强烈建议做路径白名单与最小权限,避免越权访问与误删。
ReadFileTool
读取文件
from langchain.tools import ReadFileTool
WriteFileTool
写入文件
from langchain.tools import WriteFileTool
DeleteFileTool
删除文件
from langchain.tools import DeleteFileTool
🌐 API / 网络
集成
做什么:把内部系统/第三方服务当作工具(订单、CRM、工单、支付、审批等)。
典型场景:调用内部 API、集成第三方服务、数据同步、业务流程自动化。
注意:必须处理鉴权、幂等、重试与脱敏,避免泄露敏感信息。
Tool.from_function
函数封装
from langchain.tools import Tool
🧑‍⚖️ 人类介入
生产推荐
做什么:让关键动作(写库、退款、发通知、执行脚本)在落地前人工确认。
典型场景:高危操作审批、敏感数据访问、重要业务决策、合规检查点。
注意:把确认点放在高危工具调用前或关键输出前,避免误操作。
human_in_the_loop
审批/确认
langchain.agents.middleware.human_in_the_loop
实战建议: Tool 描述要"像 API 文档一样写清楚",输入输出要稳定;高危工具(代码执行/写文件/外部 HTTP)要做权限隔离 + 审计

例子:HTTP API 工具(带域名白名单)

作用:把外部 HTTP 请求封装成 Tool,让 Agent 在需要事实信息时自行调用。 这里用域名白名单演示企业必备的安全策略:只允许访问指定域名(避免 SSRF、数据外泄)。 你运行后应能看到 Agent 调用 http_get,并对 https://httpbin.org/get 的返回字段进行总结。

python
import os
import time
import logging
import requests
from urllib.parse import urlparse

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


ALLOW_HOSTS = {"httpbin.org"}


@tool
def http_get(url: str) -> str:
    """发起 GET 请求(示例工具)。仅允许访问白名单域名,返回前 1000 个字符。"""
    host = urlparse(url).netloc
    if host not in ALLOW_HOSTS:
        return f"禁止访问域名:{host}"
    try:
        r = requests.get(url, timeout=10)
        r.raise_for_status()
        return r.text[:1000]
    except Exception as e:
        return f"请求失败:{e}"


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_agent(
    llm,
    tools=[http_get],
    system_prompt="你是一个工具型助手。需要外部信息时可以调用 http_get。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "帮我请求 https://httpbin.org/get 并总结返回里有哪些字段")]})
print(state["messages"][-1].content)

例子:人类介入(执行前人工确认)

作用:把“审批/确认点”放在高危 Tool 调用前(写库/退款/发通知/执行脚本)。 这个示例使用 langchain.agents.middleware.human_in_the_loop:当模型准备调用高危工具时会触发 interrupt, 需要你给出 approve / reject / edit 决策后才会继续执行。

🔄 人类介入流程图解
1️⃣ 正常流程:用户提问 → Agent 思考 → 准备调用高危工具 → 触发中断
2️⃣ 人工审核:显示工具名+参数 → 人工决策(approve/reject/edit)
3️⃣ 继续执行:根据决策继续或终止 → 返回最终结果
🎯 关键点:中断点由 interrupt_on 配置,只有指定工具会触发审批
🛠️ create_agent 参数配置表
参数 类型 取值范围 说明 使用案例
model 必填
ChatTongyi
通义千问,阿里云模型
ChatOpenAI
OpenAI GPT系列
ChatAnthropic
Claude系列
">支持 BaseChatModel 接口的任意模型
• 国内:ChatTongyi
• 海外:ChatOpenAI
tools 必填
List[BaseTool]
@tool装饰的函数列表
">Agent可调用的工具集合
[safe_calculator]
[http_get, file_read]
middleware 可选
HumanInTheLoopMiddleware
人工介入,高危操作审批
LoggingMiddleware
记录所有工具调用日志
TimingMiddleware
性能监控,记录执行时间
">中间件元组,控制工具调用行为
(hitl,) 单个
(hitl, logger) 多个
checkpointer 可选
MemorySaver()
内存保存,重启丢失
SqliteSaver
SQLite持久化,单机
PostgresSaver
PostgreSQL持久化,生产
">状态持久化,支持中断恢复
• 开发:MemorySaver()
• 单机:SqliteSaver("db.sqlite")
• 生产:PostgresSaver(conn_str)
debug 可选
True
打印详细执行过程
False
静默模式,仅输出结果
">调试模式,打印详细执行过程
• 开发:debug=True
• 生产:debug=False
system_prompt 可选
str
角色定义和行为规则
">定义Agent角色和行为规则
• "你是财务助手"
• "你是一个代码助手"
name 可选
str
实例名称标识
">实例名称,用于日志和调试
• "FinanceAgent"
• "CodeHelper"
🔧 调用时配置 (config)
thread_id
会话唯一标识,使用checkpointer时必填
{"configurable": {"thread_id": "demo_thread"}}
recursion_limit
防止无限循环,默认50
{"recursion_limit": 100}
tags
标签,用于监控和过滤
{"tags": ["production"]}
🔧 中间件详细参数配置
中间件 主要参数 参数说明 使用示例
HumanInTheLoopMiddleware
interrupt_on
Dict[str, bool]
interrupt_after
List[str]
interrupt_on:指定哪些工具调用前需要人工确认
interrupt_after:指定哪些工具调用后需要人工确认
interrupt_on={"send_notification": True, "delete_file": True}
interrupt_after=["payment_process"]
interrupt_on={"*": True} # 所有工具
LoggingMiddleware
logger_name
str
log_level
str
include_inputs
bool
logger_name:日志记录器名称
log_level:日志级别(DEBUG/INFO/WARNING/ERROR)
include_inputs:是否记录工具输入参数
logger_name="agent_tools"
log_level="INFO"
include_inputs=True
TimingMiddleware
threshold_ms
int
include_details
bool
logger_name
str
threshold_ms:超过此毫秒数的调用会被记录
include_details:是否记录详细调用信息
logger_name:性能日志记录器名称
threshold_ms=1000 # 1秒以上
include_details=True
logger_name="agent_performance"
💡 组合使用示例
middleware=(hitl, logger, timer)
同时启用人工介入、日志记录和性能监控,适合生产环境的关键业务Agent。
python
import os
import time
import logging

from langchain.agents import create_agent
from langchain.agents.middleware.human_in_the_loop import HumanInTheLoopMiddleware
from langchain.agents.middleware.types import AgentMiddleware, ToolCallRequest
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command


# 自定义 middleware(真拦截):在 tool 调用前/后记录日志与耗时
# 说明:你的 LangChain 版本是 1.2.x,此版本同步调用需要实现 wrap_tool_call(request, handler)
class ToolLoggingMiddleware(AgentMiddleware):
    def __init__(self, logger_name: str = "mw.tools", include_args: bool = True) -> None:
        super().__init__()
        self.logger = logging.getLogger(logger_name)
        self.include_args = include_args

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        tool_call = request.tool_call or {}
        name = tool_call.get("name")
        args = tool_call.get("args")

        # 使用 print 确保一定可见(比 logging 更直接)
        prefix = "[ToolLoggingMiddleware]"
        if self.include_args:
            print(f"{prefix} tool_call name={name} args={args}")
            self.logger.info("tool_call name=%s args=%s", name, args)
        else:
            print(f"{prefix} tool_call name={name}")
            self.logger.info("tool_call name=%s", name)

        result = handler(request)
        print(f"{prefix} tool_result name={name} type={type(result).__name__}")
        self.logger.info("tool_result name=%s type=%s", name, type(result).__name__)
        return result


class ToolTimingMiddleware(AgentMiddleware):
    def __init__(self, logger_name: str = "mw.timing", threshold_ms: int = 0) -> None:
        super().__init__()
        self.logger = logging.getLogger(logger_name)
        self.threshold_ms = threshold_ms

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        tool_call = request.tool_call or {}
        name = tool_call.get("name")

        start = time.perf_counter()
        result = handler(request)
        cost_ms = int((time.perf_counter() - start) * 1000)

        prefix = "[ToolTimingMiddleware]"
        if cost_ms >= self.threshold_ms:
            print(f"{prefix} tool_timing name={name} cost_ms={cost_ms}")
            self.logger.info("tool_timing name=%s cost_ms=%s", name, cost_ms)
        return result


@tool
def send_notification(user: str, message: str) -> str:
    """发送通知(高危示例工具)。执行前需要人工确认。"""
    # 这里用打印模拟真实发送(真实场景可替换为调用内部消息系统 API)
    print(f"[SENT] to={user} message={message}")
    return "发送成功"


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# 人类介入:对指定高危工具触发审批
hitl = HumanInTheLoopMiddleware(
    interrupt_on={
        "send_notification": True,
    }
)

# 可观测性(自定义 middleware 真拦截):
# - ToolLoggingMiddleware:记录每次 tool 调用的 name/args(工具真正执行时才会触发)
# - ToolTimingMiddleware:记录每次 tool 调用耗时(工具真正执行时才会触发)
# ⚠️ 注意:HITL 拦截时工具不会执行,middleware 也不会触发;
# 只有你输入 approve 后,工具真正执行时,才会看到 middleware 日志输出!
tool_logger = ToolLoggingMiddleware(logger_name="mw.tools", include_args=True)
tool_timer = ToolTimingMiddleware(logger_name="mw.timing", threshold_ms=0)

agent = create_agent(
    llm,
    tools=[send_notification],
    # middleware 链:建议顺序 = 可观测(日志/耗时) + 安全(HITL)
    middleware=(tool_logger, tool_timer, hitl),
    system_prompt=(
        "你是一个助理。遇到任何会影响用户或业务的动作(如发通知)必须调用 send_notification,"
        "并等待人工确认结果后再继续。"
    ),
    debug=True,
    checkpointer=MemorySaver(),
)

# ==============================
# 调用时配置(config)
# ==============================
# 1) configurable.thread_id:会话唯一标识(配合 checkpointer 才能做到“中断后继续/多轮复用”)
# 2) recursion_limit:防止 Agent 在工具调用/反思中无限循环(生产建议显式设置)
# 3) tags:用于可观测性平台/日志聚合(按环境、业务线、实验分组过滤)
run_config = {
    "configurable": {"thread_id": "demo_thread"},
    "recursion_limit": 100,
    "tags": ["production"],
}

state = agent.invoke({"messages": [("human", "给 alice 发送通知:订单已发货,并提醒她查收。")]}, config=run_config)

# 如果触发了人审:state 会包含 __interrupt__
if "__interrupt__" in state:
    req = state["__interrupt__"][0].value
    action_req = req["action_requests"][0]
    print("\n=== 需要人工确认 ===")
    print(action_req["description"])  # 展示工具名与参数

    decision = input("输入 approve / reject / edit: ").strip().lower()
    if decision == "approve":
        print("\n>>> 正在执行工具调用,你会看到 middleware 拦截输出...")
        cmd = Command(resume={"decisions": [{"type": "approve"}]})
    elif decision == "reject":
        cmd = Command(resume={"decisions": [{"type": "reject", "message": "人工拒绝"}]})
    elif decision == "edit":
        # 示例:把 message 改短一些;你也可以改 tool_name 或 args
        edited_action = {
            "name": action_req["name"],
            "args": {"user": action_req["args"]["user"], "message": "订单已发货,请查收"},
        }
        cmd = Command(resume={"decisions": [{"type": "edit", "edited_action": edited_action}]})
    else:
        cmd = Command(resume={"decisions": [{"type": "reject", "message": "未给出有效决策"}]})

    state = agent.invoke(cmd, config=run_config)

print(state["messages"][-1].content)

例子:安全读文件工具(带路径白名单)

python
import os
from pathlib import Path

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


BASE_DIR = Path(".").resolve()


@tool
def read_text_file(path: str) -> str:
    """读取文本文件(示例工具)。只允许读取当前目录及子目录的文件,返回前 2000 字符。"""
    p = (BASE_DIR / path).resolve()
    if not str(p).startswith(str(BASE_DIR)):
        return "禁止访问该路径"
    if not p.exists() or not p.is_file():
        return "文件不存在"
    try:
        content = p.read_text(encoding="utf-8", errors="ignore")
        return content[:2000]
    except Exception as e:
        return f"读取失败:{e}"


# 创建一个示例文件(让 Agent 能读到)
(Path(BASE_DIR) / "demo.txt").write_text("这是示例文件内容。\n第二行:Agent 可以安全读取。", encoding="utf-8")

api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_agent(
    llm,
    tools=[read_text_file],
    system_prompt="你是一个文件读取助手。需要读取文件内容时请调用 read_text_file。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "读取 demo.txt 文件内容并告诉我里面写了什么")]})
print(state["messages"][-1].content)
python
import os

from langchain.agents import create_agent
from langchain_community.chat_models import ChatTongyi
from langchain_community.tools import DuckDuckGoSearchRun


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

search = DuckDuckGoSearchRun()
agent = create_agent(
    llm,
    tools=[search],
    system_prompt="你是一个信息检索助手。需要最新信息时请调用 DuckDuckGoSearchRun,并给出简洁总结。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "帮我搜索:LangChain create_agent 是什么?用一句话总结")]})
print(state["messages"][-1].content)

例子:百科查询(WikipediaQueryRun)

python
import os

from langchain.agents import create_agent
from langchain_community.chat_models import ChatTongyi
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# 创建 Wikipedia 工具,需要提供 API wrapper
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
agent = create_agent(
    llm,
    tools=[wiki],
    system_prompt="你是一个百科助手。需要事实性背景时请调用 WikipediaQueryRun,并用 3 条要点回答。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "简要介绍一下 Transformer 模型")]})
print(state["messages"][-1].content)

例子:Python 计算(PythonREPLTool,高危能力示例)

python
import os

from langchain.agents import create_agent
from langchain_community.chat_models import ChatTongyi
from langchain_experimental.tools import PythonREPLTool


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

python_tool = PythonREPLTool()
agent = create_agent(
    llm,
    tools=[python_tool],
    system_prompt="你是一个计算助手。遇到精确计算请使用 PythonREPLTool。",
    debug=True,
)

state = agent.invoke({"messages": [("human", "用 python 计算 1~100 的和,并输出结果")]})
print(state["messages"][-1].content)

例子:SQL 查询(SQLite + 自定义 @tool,可离线运行)

python
import os
import sqlite3

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


@tool
def query_sqlite(sql: str) -> str:
    """执行 SQLite 查询并返回结果(示例工具:离线可运行)。仅支持 SELECT 查询。
    
    数据库表结构:
    - orders 表:id (INTEGER), user (TEXT), amount (REAL)
    - 示例数据:(1, "alice", 19.9), (2, "bob", 35.0), (3, "alice", 7.5)
    """
    # 安全检查:只允许 SELECT 查询
    sql_upper = sql.strip().upper()
    if not sql_upper.startswith('SELECT'):
        return "错误:只允许执行 SELECT 查询"
    
    conn = sqlite3.connect("demo.db")
    try:
        cur = conn.cursor()
        cur.execute(sql)
        rows = cur.fetchall()
        if not rows:
            return "查询结果为空"
        # 格式化输出
        result = ["查询结果:"]
        for row in rows:
            result.append(str(row))
        return "\n".join(result)
    except Exception as e:
        return f"查询错误:{str(e)}"
    finally:
        conn.close()


def init_db() -> None:
    conn = sqlite3.connect("demo.db")
    try:
        cur = conn.cursor()
        cur.execute("DROP TABLE IF EXISTS orders")
        cur.execute("CREATE TABLE orders (id INTEGER, user TEXT, amount REAL)")
        cur.executemany(
            "INSERT INTO orders VALUES (?, ?, ?)",
            [(1, "alice", 19.9), (2, "bob", 35.0), (3, "alice", 7.5)],
        )
        conn.commit()
    finally:
        conn.close()


init_db()

api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_agent(
    llm,
    tools=[query_sqlite],
    system_prompt="你是一个数据分析助手。需要精确统计时请调用 query_sqlite。",
    debug=True,
)

question = "orders 表里 alice 的总消费是多少?"
state = agent.invoke({"messages": [("human", question)]})
print(state["messages"][-1].content)

4️⃣ 🧱 create_agent 的参数与拓展点(能把 Demo 变成"可上线系统")

🎯 为什么需要这些参数?
• 从 Demo 到生产:Demo 只需要"能跑",生产需要"可靠、安全、可观测"
• 参数即策略:每个参数都是一种"控制手段",让 Agent 按你的规则运行
• 渐进式复杂:先必选参数跑通,再逐步加工程化参数提升稳定性
🔧 必选参数(最小可用)
  • model:决策引擎(ChatTongyi/OpenAI),负责"下一步做什么"的推理
  • tools:可执行能力列表(@tool 装饰的函数或预置工具类)
💡 最小示例
只有这两个参数就能跑通,适合快速验证想法
🎭 行为控制参数(让 Agent 按规则办事)
  • system_prompt:定义行为边界(工具调用原则、输出格式、拒答策略、安全约束)
  • 🛡️ 工程化参数(生产级稳定性)
    🔧 middleware(中间件链)
    作用:横切关注点,如限流、重试、脱敏、审计、监控
    顺序:按执行顺序排列,先限流再重试最后审计
    必要性:生产环境必备,防止系统过载和异常扩散
    🔥 常用中间件
    ModelCallLimitMiddleware:模型调用次数上限(防止死循环)
    ToolCallLimitMiddleware:工具调用次数上限(防止死循环)
    ModelRetryMiddleware:模型调用重试
    ToolRetryMiddleware:工具调用重试
    LoggingMiddleware:调用日志记录
    💾 checkpointer(状态持久化)
    作用:保存对话状态,支持中断恢复、多轮记忆
    场景:人工介入、长对话、系统重启后恢复
    必要性:有中断或长对话场景时必备
    🗄️ 存储选择
    MemorySaver():开发测试,重启丢失
    SqliteSaver("db.sqlite"):单机生产
    PostgresSaver(conn_str):分布式生产
    🔍 debug(调试模式)
    开发环境:debug=True,查看详细执行过程
    生产环境:debug=False,配合外部日志系统
    性能影响:debug=True 会增加 20-30% 延迟
    ⚙️ 推荐配置
    • 开发:debug=True + console 日志
    • 测试:debug=False + 结构化日志
    • 生产:debug=False + ELK/Prometheus
    ⚠️ 生产环境检查清单
    □ middleware 包含限流和重试
    □ checkpointer 使用持久化存储
    □ debug=False + 外部日志系统
    □ system_prompt 包含安全约束
    □ 配置监控和告警机制
    🚀 生产环境推荐组合
    🛡️ 安全优先
    system_prompt + middleware(限流) + interrupt_before(高危审批)
    🔧 稳定可靠
    middleware(重试) + checkpointer + name(监控标识)
    📊 可观测性
    name + debug=False + 外部日志 + 性能监控
    📈 渐进式升级路径
    1️⃣ MVP阶段:只使用必选参数,快速验证核心功能
    2️⃣ Beta阶段:加入行为控制参数,规范输出格式和边界
    3️⃣ 生产阶段:加入工程化参数,确保稳定性和安全性
    4️⃣ 企业阶段:结合外部监控、告警、审计系统

下面给一个生产级完整示例:包含所有常用参数,演示如何把 Demo 变成可上线的系统。

python
import os
from datetime import datetime

from langchain.agents import create_agent
from langchain.agents.middleware.model_call_limit import ModelCallLimitMiddleware
from langchain.agents.middleware.model_retry import ModelRetryMiddleware
from langchain.agents.middleware.tool_call_limit import ToolCallLimitMiddleware
from langchain.agents.middleware.tool_retry import ToolRetryMiddleware
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


@tool
def get_current_time(query: str = "") -> str:
    """当用户询问现在的时间、日期时,使用此工具获取当前时间。返回格式化的时间字符串。"""
    now = datetime.now()
    return now.strftime("%Y年%m月%d日 %H:%M:%S")


@tool
def calculate_expression(expr: str) -> str:
    """计算数学表达式。支持加减乘除和括号,返回计算结果。"""
    try:
        # 安全计算(仅允许数字和基本运算符)
        allowed_chars = set("0123456789+-*/(). ")
        if not all(c in allowed_chars for c in expr):
            return "错误:表达式包含非法字符"
        result = eval(expr)
        return f"计算结果:{result}"
    except Exception as e:
        return f"计算错误:{str(e)}"


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# 生产级 Agent 配置
agent = create_agent(
    # === 必选参数 ===
    model=llm,
    tools=[get_current_time, calculate_expression],
    
    # === 行为控制 ===
    system_prompt=(
        "你是一个专业的时间与计算助手。"
        "1. 严格遵循工具调用原则:需要时间信息时调用 get_current_time,需要计算时调用 calculate_expression。"
        "2. 输出格式要简洁明了,直接给出答案,不需要额外解释。"
        "3. 拒绝执行危险操作(如系统命令、文件删除等)。"
        "4. 遇到无法处理的问题,礼貌地说明限制。"
    ),
    name="TimeCalculatorAgent",  # Agent 名称
    
    # === 工程化参数 ===
    middleware=(
        # 防止死循环:限制最大调用次数
        ModelCallLimitMiddleware(run_limit=10),    # 每次运行最多 10 次模型调用
        ToolCallLimitMiddleware(run_limit=5),     # 每次运行最多 5 次工具调用
        
        # 容错机制:自动重试
        ModelRetryMiddleware(max_retries=2),       # 模型调用失败重试 2 次
        ToolRetryMiddleware(max_retries=1),        # 工具调用失败重试 1 次
    ),
    
    # 可观测性与运维
    debug=False,  # 生产环境关闭调试
)

# 测试调用
print("=== 测试时间查询 ===")
state1 = agent.invoke({"messages": [("human", "现在几点了?")]})
print(f"结果:{state1['messages'][-1].content}")

print("\n=== 测试数学计算 ===")
state2 = agent.invoke({"messages": [("human", "计算 123 + 456 * 2")]})
print(f"结果:{state2['messages'][-1].content}")

print("\n=== Agent 配置信息 ===")
print(f"名称:{agent.name}")
print(f"调试模式:{getattr(agent, 'debug', 'unknown')}")
print(f"Agent 创建成功,包含 {len([get_current_time, calculate_expression])} 个工具")
🚀 上线前检查清单
  • 安全检查:system_prompt 是否包含拒答策略?工具是否有权限控制?
  • 性能测试:限流参数是否合理?重试次数是否过多?
  • 监控配置:name 是否便于运维追踪?
  • 错误处理:所有工具是否有异常捕获?用户输入是否验证?
  • 日志集成:debug=False,是否接入外部日志系统?

5️⃣ 🤖 LangChain Agent 类型概览(结合案例)

🎯 Agent 选型指南:从通用到专用

LangChain 提供了多种 Agent 创建方式,从通用型专用型,满足不同场景需求。 理解这些差异能帮你选择最合适的方案,避免过度工程化或功能不足。

🔧 通用 Agent(推荐首选)
create_agent:最通用的 Agent 创建方式
  • 适用场景:大多数业务场景,需要灵活配置工具和行为
  • 核心优势:高度可定制,支持中间件、结构化输出、完整工程化能力
  • 使用方式:自定义 @tool + 系统提示 + 可选中间件
⚡ 专用 Agent(特定场景开箱即用)
🐍 代码执行类
  • create_python_agent:Python REPL 代码执行(计算/数据处理/快速原型)
  • 导入:from langchain_experimental.agents.agent_toolkits import create_python_agent
📊 数据分析类
  • create_csv_agent:CSV 文件分析(工具链封装好,适合快速上手)
  • create_pandas_dataframe_agent:Pandas DataFrame 分析
  • create_spark_dataframe_agent:Spark DataFrame(大数据场景)
  • 导入:from langchain_experimental.agents import create_*
🔍 检索/RAG 类
  • create_vectorstore_agent:单一 VectorStore 检索
  • create_vectorstore_router_agent:多 VectorStore 路由
  • create_conversational_retrieval_agent:对话式检索
  • 导入:from langchain_classic.agents import create_*
🎭 交互范式类
  • create_react_agent:ReAct 推理+行动范式
  • create_structured_chat_agent:结构化工具调用
  • create_tool_calling_agent:工具调用风格
  • create_json_chat_agent:JSON 输出风格
  • create_xml_agent:XML 输出风格
  • 导入:from langchain_classic.agents import create_*

💡 选型建议

  • 优先使用 create_agent:灵活性最高,支持完整工程化
  • 专用 Agent 适用场景:确定问题域且希望"开箱即用"时更省事
  • 权衡考虑:专用 Agent 减少样板代码,但可定制性弱于通用方案

📦 按类型理解:最小代码 + 关键点

下面每个类型都给一个“最小可跑”骨架,重点看入口函数输入/输出约定、以及最常踩的坑。 实战版本会在后面的案例二/三/四展开。

A) 通用工具 Agent:create_agent

适用场景:你有业务 Tool(查库/发通知/调用 HTTP/读文件…),想让模型“会用工具”。
关键点:工具用 @tool 暴露;对话输入统一走 messages;用 system_prompt 约束什么时候用工具。
对应案例:案例一(自定义工具)、案例四(工具箱总控)。
python
import os

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi
from requests.exceptions import ConnectionError


@tool
def add(a: int, b: int) -> int:
    """计算两个整数的和,用于精确的数学加法运算"""
    return a + b


api_key = os.getenv("DASHSCOPE_API_KEY")
llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_agent(
    model=llm,
    tools=[add],
    system_prompt="需要精确计算时必须调用 add 工具,直接给最终答案。",
    debug=True,
)

try:
    state = agent.invoke({"messages": [("human", "计算 12 + 30") ]})
    print("Agent 响应:", state)
    print("最终答案:", state["messages"][-1].content)
except ConnectionError as e:
    print("❌ 网络连接错误:", str(e))
    print("建议:检查网络连接或稍后重试")
except Exception as e:
    print("❌ 其他错误:", str(e))

B) 代码执行 Agent:create_python_agent(高危)

适用场景:数学/数据处理/快速原型,让模型“写代码并执行”。
关键点:输入走 input 字段;务必在隔离环境使用;生产要做 allowlist/HITL。
对应案例:案例二(Python REPL)。
🔥 create_python_agent vs create_agent + PythonREPLTool 的区别
create_python_agent(专用)
• 专门用于 Python 代码执行
• 内置优化过的 system_prompt
• 自动处理代码解析错误
• 返回值自动格式化为字符串
• 更适合复杂数学/数据处理
create_agent + Tool(通用)
• 通用 Agent 创建方式
• 需要手动配置所有参数
• 可以组合多种类型工具
• 更灵活但需要更多配置
• 适合多工具混合场景
python
import os
import logging

# 配置日志以便观察 Agent 执行过程
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

from langchain_experimental.agents.agent_toolkits import create_python_agent
from langchain_experimental.tools import PythonREPLTool
from langchain_community.llms import Tongyi


api_key = os.getenv("DASHSCOPE_API_KEY")
llm = Tongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

# ============ 方式一:create_python_agent(推荐用于纯 Python 场景)============
# 这是一个"专用 Agent 工厂",专门用于创建能执行 Python 代码的 Agent
# 内部已经封装了:
#   1. 适合代码生成的 system_prompt
#   2. 错误处理和重试逻辑
#   3. 结果格式化输出
python_agent = create_python_agent(
    # 核心参数
    llm=llm,                          # LLM 模型,负责生成 Python 代码
    tool=PythonREPLTool(),             # Python 执行工具,在沙箱中运行代码
    
    # 执行控制参数
    verbose=True,                      # 是否打印详细执行日志(开发时建议开启)
    
    # AgentExecutor 的额外配置(通过 agent_executor_kwargs 传递)
    agent_executor_kwargs={
        "handle_parsing_errors": True,  # 自动处理代码解析错误,失败时重试
        "max_iterations": 15,           # 最大迭代次数,防止无限循环
        # early_stopping_method: 达到最大迭代次数时的处理方式
        # - "generate" (默认): 调用 LLM 生成最终答案,基于已收集的信息给出一个总结性回答
        # - "force": 直接返回最后一次工具调用的结果,不做额外处理(可能不完整)
        # 建议:生产环境用 "generate",确保用户得到有意义的回复
        "early_stopping_method": "generate",
    },
)

# 调用方式:使用 "input" 字段传递用户问题
result = python_agent.invoke({"input": "用 Python 算一下第 10 个斐波那契数"})
print(f"结果类型: {type(result)}")  # dict
print(f"输出内容: {result['output']}")  # 最终答案字符串

# ============ 方式二:create_agent + PythonREPLTool(通用方式)============
# 对比:使用通用 create_agent 达到类似效果
# 区别:需要手动配置更多参数,但更灵活

from langchain.agents import create_agent

# 通用方式创建 Agent
# 注意:这里需要手动配置 system_prompt 来指导模型生成 Python 代码
general_agent = create_agent(
    model=llm,
    tools=[PythonREPLTool()],  # 同样可以传入 PythonREPLTool
    system_prompt=(
        "你是一个 Python 代码执行助手。"
        "当需要计算或数据处理时,请编写 Python 代码并执行。"
        "直接给出代码执行结果,不需要额外解释。"
    ),
    debug=True,
)

# 调用方式不同:使用 "messages" 字段
general_result = general_agent.invoke({
    "messages": [("human", "计算第 10 个斐波那契数")]
})
print(f"通用方式结果: {general_result['messages'][-1].content}")
📋 详细参数说明
create_python_agent 核心参数:
llm (BaseLanguageModel) - LLM 模型实例,负责生成 Python 代码
tool (BaseTool) - 必须是 PythonREPLTool 或其子类,用于执行代码
verbose (bool, 默认 False) - 是否打印详细执行日志
agent_executor_kwargs (dict, 可选) - 传递给 AgentExecutor 的配置,如错误处理、最大迭代次数等
PythonREPLTool 说明:
• 基于 Python REPL (Read-Eval-Print Loop) 实现
• 在本地 Python 环境中执行代码(⚠️ 有安全风险)
• 执行结果以字符串形式返回
• 支持多行代码和复杂计算
💡 使用建议
纯 Python 计算场景:优先使用 create_python_agent,配置简单且针对性强
多工具混合场景:使用 create_agent + PythonREPLTool,可与其他工具组合
生产环境:务必添加沙箱隔离、代码白名单、超时限制等安全措施

C) 表格/CSV Agent:create_csv_agent

适用场景:围绕一个 CSV/表格做自然语言分析(统计/筛选/分组)。
关键点:本质仍是“生成并执行 Pandas 代码”;可能需要安装 tabulate/pandas;属于高权限执行。
依赖安装pip install tabulate pandas(tabulate 用于 pandas 的 to_markdown() 方法)
对应案例:案例三(CSV 房价分析)、案例四(工具箱组合)。
python
import os

from langchain_experimental.agents.agent_toolkits import create_csv_agent
from langchain_community.chat_models import ChatTongyi


api_key = os.getenv("DASHSCOPE_API_KEY")
llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

agent = create_csv_agent(
    llm=llm,
    path="data/house_price.csv",        # CSV 文件路径
    verbose=True,                       # 打印详细执行日志
    agent_executor_kwargs={"handle_parsing_errors": True},
    allow_dangerous_code=True,          # 允许执行代码(⚠️ 安全风险)
)

result = agent.invoke({"input": "这份数据有多少行?"})
print(result["output"])
📋 create_csv_agent 详细参数说明
核心参数:
llm (BaseLanguageModel) - LLM 模型实例,负责生成 Pandas 分析代码
path (str) - CSV 文件的路径,支持本地文件或 URL
verbose (bool, 默认 False) - 是否打印详细执行日志,开发时建议开启
allow_dangerous_code (bool, 默认 False) - 是否允许执行 Pandas 代码,⚠️ 必须显式设为 True 才能运行
可选参数:
pandas_kwargs (dict, 可选) - 传递给 pandas.read_csv() 的参数,如 encoding="utf-8"
number_of_head_rows (int, 默认 5) - 加载 CSV 时读取的前几行数,用于向模型展示数据结构
prefix (str, 可选) - Prompt 前缀,用于覆盖默认的系统提示
suffix (str, 可选) - Prompt 后缀,用于在提示末尾添加额外指令
input_variables (list, 可选) - 自定义输入变量列表,用于高级 Prompt 定制
agent_executor_kwargs (dict, 可选) - 传递给 AgentExecutor 的配置,如 max_iterations、early_stopping_method 等
工作原理:
1. 使用 pandas.read_csv(path, **pandas_kwargs) 加载数据
2. 自动读取前 number_of_head_rows 行展示给 LLM,帮助理解数据结构
3. LLM 根据问题生成 Pandas 代码(如 df.describe()、df.groupby() 等)
4. 在沙箱中执行代码,返回结果给 LLM 生成自然语言回答

D) RAG/检索 Agent:create_retriever_tool + create_agent

适用场景:你已经有向量库(或多个向量库),希望 Agent 能"先检索再回答",必要时在多个库之间路由。
关键点:用 create_retriever_tool 把 retriever 包成标准 Tool,再交给 create_agent 统一调度。这是新版本推荐的工程化方式,比 Classic VectorStore Agent 更稳定。
依赖pip install chromadb langchain-community
python
import os

from langchain_community.chat_models import ChatTongyi
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.tools.retriever import create_retriever_tool
from langchain.agents import create_agent


api_key = os.getenv("DASHSCOPE_API_KEY")
llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)
embeddings = DashScopeEmbeddings(model="text-embedding-v1", dashscope_api_key=api_key)


# === 1️⃣ 单库检索 Agent ===
docs = ["LangChain 是构建 LLM 应用的框架。", "Agent 是能调用工具的智能体。", "Tools 是 Agent 可以调用的外部函数。"]
vs = Chroma.from_texts(docs, embeddings)

# create_retriever_tool:把 retriever 包成标准 Tool
# 参数说明:
#   - retriever: BaseRetriever 实例,负责执行检索
#   - name: str,工具名称,Agent 通过此名称识别和调用工具
#   - description: str,工具描述,帮助 Agent 理解何时使用该工具
search_tool = create_retriever_tool(
    retriever=vs.as_retriever(search_kwargs={"k": 2}),  # k=2 表示返回最相关的2条结果
    name="search_langchain_docs",
    description="在 LangChain 知识库中搜索相关文档,回答关于 LangChain 的问题。",
)

agent1 = create_agent(
    model=llm,
    tools=[search_tool],
    system_prompt="你是一个知识库助手。需要回答问题时,先用 search_langchain_docs 检索相关文档,再基于检索结果回答。",
)
r1 = agent1.invoke({"messages": [("human", "LangChain 是什么?")]})
print("=== 单库检索 ===")
print(r1["messages"][-1].content)


# === 2️⃣ 多库路由 Agent ===
product_vs = Chroma.from_texts(["产品支持 Python 和 Java SDK。", "定价按 API 调用次数计费。"], embeddings)
faq_vs = Chroma.from_texts(["如何获取 API Key?请在控制台申请。", "支持 Python、Java、Node.js。"], embeddings)

# 多个检索工具,Agent 会根据问题内容自动选择
product_tool = create_retriever_tool(
    retriever=product_vs.as_retriever(search_kwargs={"k": 2}),
    name="search_product_docs",
    description="在产品文档中搜索,回答关于产品功能、SDK、定价的问题。",
)
faq_tool = create_retriever_tool(
    retriever=faq_vs.as_retriever(search_kwargs={"k": 2}),
    name="search_faq",
    description="在技术 FAQ 中搜索,回答关于 API Key、编程语言支持等技术问题。",
)

agent2 = create_agent(
    model=llm,
    tools=[product_tool, faq_tool],
    system_prompt="你是一个智能助手。根据问题类型选择合适的工具:产品/定价问题用 search_product_docs,技术/API 问题用 search_faq。",
)
r2 = agent2.invoke({"messages": [("human", "怎么获取 API Key?")]})
print("\n=== 多库路由 ===")
print(r2["messages"][-1].content)


# === 3️⃣ 对话式检索(多轮) ===
agent3 = create_agent(
    model=llm,
    tools=[search_tool],
    system_prompt="你是一个对话式知识库助手。能理解上下文,先检索再回答,支持多轮对话。",
)
messages = [("human", "Agent 能做什么?")]
r3 = agent3.invoke({"messages": messages})
print("\n=== 对话式检索(第1轮)===")
print(r3["messages"][-1].content)

# 第2轮:携带历史继续追问
messages.append(("ai", r3["messages"][-1].content))
messages.append(("human", "那 Tools 呢?"))
r4 = agent3.invoke({"messages": messages})
print("\n=== 对话式检索(第2轮)===")
print(r4["messages"][-1].content)
📋 详细参数说明
create_retriever_tool 参数:
retriever (BaseRetriever) - 检索器实例,如 vs.as_retriever()
name (str) - 工具名称,Agent 通过此名称识别和调用
description (str) - 工具描述,⚠️ 非常重要!决定 Agent 何时使用该工具
document_separator (str, 可选) - 文档分隔符,默认 "\n\n"
vs.as_retriever() 常用参数:
search_kwargs (dict) - 检索参数,如 {"k": 2} 表示返回前2条最相关结果
search_type (str) - 检索类型,可选 "similarity"(相似度)、"mmr"(最大边际相关性)
filters (dict, 可选) - 元数据过滤条件,如 {"source": "product"}
create_agent 核心参数:
model (BaseChatModel) - 聊天模型实例
tools (list) - 工具列表,可包含多个检索工具或其他工具
system_prompt (str) - 系统提示,指导 Agent 如何使用工具
debug (bool, 默认 False) - 是否开启调试模式
max_iterations (int, 可选) - 最大迭代次数
max_execution_time (float, 可选) - 最大执行时间(秒)
踩坑
1) Classic VectorStore Agent 底层依赖 langchain.chains 已移除:新版本请用 create_retriever_tool + create_agent
2) Embedding 不一致:向量维度或模型变了会导致检索质量骤降。
3) 多库路由靠 Tool description:description 写清楚,模型才能正确选择工具。
4) 对话式检索:多轮时把历史 messages 传入,否则模型无法理解上下文。

E) ReAct 推理+行动范式:create_react_agent

🧠 什么是 ReAct 范式?
ReAct = Reasoning(推理)+ Acting(行动)
这是 Agent 领域最经典的范式之一,由 Google 在 2022 年提出。核心思想是:让 LLM 交替进行"思考"和"行动",形成推理链。
与 Tool Calling 不同,ReAct 通过文本生成来完成思考和行动,不需要模型支持 function calling。
🔄 ReAct 执行流程
1️⃣ Thought(思考):模型分析问题,决定下一步行动
2️⃣ Action(行动):选择工具并生成调用参数
3️⃣ Observation(观察):获取工具执行结果
4️⃣ 循环:重复 Thought → Action → Observation 直到得出最终答案
⚠️ 版本兼容性注意:
当前 LangGraph 版本: from langgraph.prebuilt import create_react_agent,只支持 modeltools 两个主要参数
未来(LangGraph ≥ 1.0)迁移方向: from langchain.agents import create_agent(API 大幅简化)
AgentExecutor: from langchain_classic.agents import AgentExecutor
提示:本示例只使用 model + tools,避免传递当前版本不支持的额外关键字参数(例如 state_modifier)。
python
import os
import logging
from pprint import pprint

# 配置日志观察 ReAct 的推理过程
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi


api_key = os.getenv("DASHSCOPE_API_KEY")
# LangGraph 当前版本要求使用 model 参数名
model = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)


# ============ 工具定义 ============
@tool
def calculator(expression: str) -> str:
    """执行数学计算,支持加减乘除和括号。"""
    try:
        # 安全评估:只允许数字和运算符
        allowed = set("0123456789+-*/.() ")
        if not all(c in allowed for c in expression):
            return "错误:表达式包含非法字符"
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except Exception as e:
        return f"计算错误:{e}"


@tool
def search_weather(city: str) -> str:
    """查询指定城市的天气。"""
    weather_db = {
        "北京": "晴朗,25°C",
        "上海": "多云,22°C",
        "广州": "小雨,28°C",
        "深圳": "阴天,26°C"
    }
    return weather_db.get(city, f"未找到 {city} 的天气信息")


@tool
def get_current_date() -> str:
    """获取今天的日期。"""
    from datetime import datetime
    return datetime.now().strftime("%Y年%m月%d日")


tools = [calculator, search_weather, get_current_date]


# ============ 创建 ReAct Agent ============
# create_react_agent 关键参数说明(以当前 LangGraph 版本为准):
#   - model: 模型实例(注意:参数名是 model,而不是 llm)
#   - tools: 工具列表
#
# create_react_agent 返回的是一个可调用的 graph,本身就可以直接 invoke

react_agent = create_react_agent(
    model=model,
    tools=tools,
)


# ============ 执行示例 ============
questions = [
    "今天日期是多少?帮我计算 15 * 8 + 12 等于多少",
    "北京和上海的天气分别是怎样的?",
    "如果我有 100 元,买了 3 个每个 15 元的商品,还剩多少钱?"
]

for question in questions:
    print(f"\n{'='*60}")
    print(f"问题:{question}")
    print('='*60)

    # 直接调用由 create_react_agent 返回的 graph
    result = react_agent.invoke({
        "messages": [("human", question)]
    })

    print("\nAgent 原始输出 (原始结构):")
    pprint(result)

    # 尝试从返回结果中解析 ReAct 过程(Thought / Action / Observation)
    if isinstance(result, dict):
        messages = result.get("messages") or result.get("steps")
    else:
        messages = None

    if messages:
        print("\n--- ReAct 过程回放(按时间顺序)---")
        for idx, msg in enumerate(messages, start=1):
            role = getattr(msg, "type", getattr(msg, "role", ""))
            content = getattr(msg, "content", msg)
            print(f"\n[{idx}] role={role}")
            print(content)
    else:
        print("\n(未能自动解析出 messages 列表,请根据原始输出结构手动查看 Thought/Action/Observation)")
📋 create_react_agent 详细参数说明
create_react_agent 参数:
llm (BaseLanguageModel) - LLM 模型实例,负责生成 Thought/Action
tools (Sequence[BaseTool]) - 工具列表,Agent 可以调用的工具
prompt (BasePromptTemplate) - Prompt 模板,必须包含以下占位符
• {tools} - 工具描述(会被自动替换)
• {tool_names} - 工具名称列表(会被自动替换)
• {input} - 用户输入问题
• {agent_scratchpad} - 存储中间步骤的 scratchpad
AgentExecutor 配置参数:
agent (BaseMultiActionAgent) - Agent 实例
tools (Sequence[BaseTool]) - 工具列表
verbose (bool, 默认 False) - 是否打印 Thought/Action/Observation 过程
max_iterations (int, 默认 15) - 最大迭代次数,防止无限循环
max_execution_time (float, 可选) - 最大执行时间(秒)
handle_parsing_errors (bool/str, 默认 False) - 如何处理解析错误,设为 True 自动处理,或设为特定错误提示字符串
💡 ReAct vs Tool Calling vs Structured Chat 对比
ReAct: 通过文本生成 Thought/Action/Observation,不需要模型支持 function calling,兼容性最好,但依赖 Prompt 工程
Tool Calling: 模型直接输出 tool_call 对象,需要模型支持 function calling,更可靠、更结构化
Structured Chat: 输出 JSON 格式的 action/action_input,需要模型能正确遵循输出格式,介于两者之间
踩坑提醒
1) Prompt 模板必须包含特定占位符:{tools}、{tool_names}、{input}、{agent_scratchpad} 缺一不可
2) 模型输出格式不稳定:ReAct 依赖模型正确生成 Thought/Action,弱模型可能格式混乱
3) Verbose 模式很重要:开发时务必开启 verbose=True,观察推理链是否正确
4) 迭代限制必须设置:防止模型陷入无限循环,建议 max_iterations=10~15

F) 经典范式 Agent:create_tool_calling_agent / create_structured_chat_agent(Classic)

🎯 什么是"经典范式 Agent"?
经典范式 Agent 是 LangChain 早期版本提供的专用工厂函数,让你能够显式选择 Agent 的思考-行动循环方式(范式)。
与新版通用的 create_agent 不同,经典范式需要你显式配置 PromptTemplate,并配合 AgentExecutor 执行。
🔧 Tool Calling 范式
核心机制:模型直接输出工具调用请求(函数调用格式)
工作流程
1️⃣ 模型接收用户问题
2️⃣ 判断需要工具 → 生成 tool_call 对象
3️⃣ 系统执行工具 → 返回 tool_result
4️⃣ 模型基于结果生成最终回答
适用场景:工具参数简单、需要多轮工具调用、现代模型(GPT-4/Qwen 等支持 function calling)
💬 Structured Chat 范式
核心机制:模型通过文本输出结构化 JSON 来描述行动
工作流程
1️⃣ 模型接收用户问题 + 系统提示(含输出格式)
2️⃣ 输出 JSON:{"action": "工具名", "action_input": "参数"}
3️⃣ 系统解析 JSON → 执行对应工具
4️⃣ 循环直到输出 {"action": "Final Answer"}
适用场景:需要精确控制输出格式、兼容不支持 function calling 的模型、教学演示
🔄 两种范式的执行流程对比
Tool Calling
用户输入
LLM → tool_call
执行工具
LLM → 最终回答
Structured Chat
用户输入
LLM → JSON
↓ 解析
执行工具
↓ 循环
直到 Final Answer
适用场景:你希望明确采用固定交互范式(Tool Calling / Structured Chat),或迁移旧项目。
关键点:Classic 工厂需要显式准备 ChatPromptTemplate,并配合 AgentExecutor(从 langchain_classic.agents 导入)执行。
注意agent_scratchpad 在 Tool Calling 里是 MessagesPlaceholder,在 Structured Chat 里是字符串变量 {agent_scratchpad}
python
import os
import re
import logging

# 配置日志:开启 DEBUG 级别可看到 Agent 内部执行细节
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("classic_agent_demo")

from langchain_core.tools import tool
from langchain_core.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    HumanMessagePromptTemplate,
    SystemMessagePromptTemplate,
)
from langchain_community.chat_models import ChatTongyi
from langchain_classic.agents import (
    create_tool_calling_agent,
    create_structured_chat_agent,
    AgentExecutor,  # 注意:从 langchain_classic 导入,不是 langchain
)


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")

llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)
logger.info(f"✅ LLM 初始化完成:model={llm.model_name}, temperature=0")


# ============ 工具定义 ============
@tool
def calculator(expression: str) -> str:
    """安全计算器,只允许数字与加减乘除括号。"""
    logger.info(f"🔧 [工具调用] calculator: expression={expression}")
    
    if not re.fullmatch(r"[0-9\s\+\-\*\/\(\)\.]+", expression):
        logger.warning(f"⚠️ [工具拒绝] 非法表达式: {expression}")
        return "非法表达式"
    
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        logger.info(f"✅ [工具结果] calculator: result={result}")
        return str(result)
    except Exception as e:
        logger.error(f"❌ [工具错误] calculator: {e}")
        return f"计算错误:{e}"


@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。"""
    logger.info(f"🔧 [工具调用] get_weather: city={city}")
    
    data = {"北京": "晴天 25°C", "上海": "多云 22°C", "广州": "雨天 28°C"}
    result = data.get(city, f"未找到 {city} 的天气")
    
    logger.info(f"✅ [工具结果] get_weather: {result}")
    return result


tools = [calculator, get_weather]
logger.info(f"📦 已注册 {len(tools)} 个工具: {[t.name for t in tools]}")


# ============ 1️⃣ Tool Calling Agent ============
# 说明:这是现代 LLM(GPT-4/Qwen 等)推荐的范式
# - 模型直接输出 tool_call 对象,而非文本 JSON
# - agent_scratchpad 用 MessagesPlaceholder(消息列表形式)
logger.info("\n" + "="*50)
logger.info("🔧 创建 Tool Calling Agent...")
logger.info("="*50)

tc_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个智能助手,可以使用工具来回答问题。"),
    ("human", "{input}"),
    # MessagesPlaceholder:Tool Calling 范式用消息列表存储思考过程
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

tc_agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=tc_prompt)
logger.info(f"✅ Tool Calling Agent 创建成功: type={type(tc_agent).__name__}")

# AgentExecutor:执行 Agent 的运行时环境
# - verbose=True:打印详细执行日志(可以看到每一步的思考过程)
# - handle_parsing_errors=True:自动处理解析错误
tc_executor = AgentExecutor(
    agent=tc_agent, 
    tools=tools, 
    verbose=True,  # 开启详细日志,你会看到 "Entering Agent Executor" 等输出
    handle_parsing_errors=True,
)
logger.info("✅ Tool Calling Executor 创建成功")

logger.info("\n🚀 执行 Tool Calling Agent: 计算 15 * 8 + 12")
print("\n" + "="*50)
print("🔧 Tool Calling Agent 执行中...")
print("="*50)

r1 = tc_executor.invoke({"input": "计算 15 * 8 + 12"})

print("\n" + "="*50)
print(f"✅ Tool Calling Agent 结果: {r1['output']}")
print("="*50)


# ============ 2️⃣ Structured Chat Agent ============
# 说明:这是早期 LLM 兼容的范式,通过文本输出结构化 JSON
# - 模型输出 {"action": "工具名", "action_input": "参数"} 格式的 JSON
# - agent_scratchpad 是字符串变量(不是 MessagesPlaceholder)
# - {tools} 和 {tool_names} 必须保留为模板变量,AgentExecutor 会自动注入
logger.info("\n" + "="*50)
logger.info("💬 创建 Structured Chat Agent...")
logger.info("="*50)

sc_system = """你是一个智能助手,可以使用工具来回答问题。

可用工具:
{tools}

工具名称:{tool_names}

使用 JSON 格式调用工具:
```json
{{
    "action": "工具名称",
    "action_input": "工具参数"
}}
```

最终答案格式:
```json
{{
    "action": "Final Answer",
    "action_input": "你的最终回答"
}}
```"""

sc_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(sc_system),
    # 注意:Structured Chat 的 agent_scratchpad 是字符串,用 {agent_scratchpad} 嵌入
    HumanMessagePromptTemplate.from_template("{input}\n\n{agent_scratchpad}"),
])

sc_agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=sc_prompt)
logger.info(f"✅ Structured Chat Agent 创建成功: type={type(sc_agent).__name__}")

sc_executor = AgentExecutor(
    agent=sc_agent, 
    tools=tools, 
    verbose=True,  # 同样开启详细日志
    handle_parsing_errors=True,
)
logger.info("✅ Structured Chat Executor 创建成功")

logger.info("\n🚀 执行 Structured Chat Agent: 查询北京天气")
print("\n" + "="*50)
print("💬 Structured Chat Agent 执行中...")
print("="*50)

r2 = sc_executor.invoke({"input": "北京今天天气怎么样?"})

print("\n" + "="*50)
print(f"✅ Structured Chat Agent 结果: {r2['output']}")
print("="*50)


# ============ 执行总结 ============
logger.info("\n" + "="*50)
logger.info("📊 执行总结")
logger.info("="*50)
logger.info(f"Tool Calling Agent 输出: {r1['output']}")
logger.info(f"Structured Chat Agent 输出: {r2['output']}")
logger.info("\n💡 关键区别:")
logger.info("  - Tool Calling: 模型直接输出 tool_call 对象,更现代、更可靠")
logger.info("  - Structured Chat: 模型输出 JSON 文本,需要解析,兼容性更好")
踩坑
1) AgentExecutor 从 langchain_classic 导入:新版 langchain.agents 已移除,必须用 from langchain_classic.agents import AgentExecutor
2) agent_scratchpad 类型不同:Tool Calling 用 MessagesPlaceholder,Structured Chat 用字符串 {agent_scratchpad}
3) Structured Chat 的 {tools}/{tool_names}:必须保留为模板变量,不能预先渲染,AgentExecutor 会自动注入。
4) 工程建议:Classic 主要用于理解范式/迁移旧项目;新项目优先用 create_agent + system_prompt

🛠️ 实战案例:从通用到专用

下面通过具体案例,展示不同 Agent 类型的实际用法。建议按顺序学习: 自定义工具 → Python REPL → CSV 分析 → 多工具组合

🧰 案例一:自定义工具 Agent(文本字数计算)

🔧 使用通用 Agent:create_agent

本案例展示如何使用 create_agent 创建自定义工具 Agent,这是最通用、最灵活的方式。 我们将创建一个文本字数计算工具,让 Agent 能够精确回答关于文本长度的问题。

💡 学习要点:自定义 @tool 装饰器、工具描述的重要性、Agent 如何选择工具

接下来我们看一个具体的例子:让 Agent 正确计算一句诗的字数。
这个例子基于课件中的 01_agent_custom_tool.py

1. 定义自定义工具

python
from langchain_core.tools import BaseTool


class TextLengthTool(BaseTool):
    """文本字数计算工具"""
    name: str = "文本字数计算工具"
    description: str = "当你被要求计算文本的字数时,使用此工具"

    def _run(self, text: str) -> int:
        # 简单示例:按字符数量计算
        return len(text)


tools = [TextLengthTool()]

2. 创建 Agent(Structured Chat Agent)

python
import os

from langchain.agents import create_agent
from langchain_community.chat_models import ChatTongyi


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")


# 1️⃣ 初始化通义千问(Chat 模型)
llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)


# 2️⃣ 创建 Agent(LangChain 1.2.x:使用 create_agent)
agent = create_agent(
    llm,
    tools=tools,
    system_prompt="你是一个严谨的助手。需要精确计算字数时请调用工具,直接给最终结果。",
    debug=True,
)

3. 让 Agent 决定何时使用工具

python
question = "'君不见黄河之水天上来奔流到海不复回',这句话的字数是多少?"

result = agent.invoke({"messages": [("human", question)]})
final_message = result["messages"][-1]
print("最终答案:", getattr(final_message, "content", final_message))

💡 你可以从日志中看到什么?

  • Agent 先分析问题,判断需要精确计算字数 → 选择 文本字数计算工具
  • 调用工具,获得精确字数结果。
  • 再用自然语言包装结果,给出最终回答。

🐍 案例二:Python REPL Agent(自动写代码)

有些问题适合通过“写一段 Python 代码”来解决,比如数学计算、数据分析等。 本例基于课件 02_agent_python_repl.py,展示 Python REPL Agent 的用法。

python
from langchain_experimental.agents.agent_toolkits import create_python_agent
from langchain_experimental.tools import PythonREPLTool
from langchain_community.llms import Tongyi


api_key = os.getenv("DASHSCOPE_API_KEY")

llm = Tongyi(
    model_name="qwen-turbo",
    dashscope_api_key=api_key,
    temperature=0,
)

# 创建 Python Agent
python_agent = create_python_agent(
    llm=llm,
    tool=PythonREPLTool(),
    verbose=True,
    agent_executor_kwargs={"handle_parsing_errors": True},
)

question = "第12个斐波那契数列的数字是多少?"
result = python_agent.invoke({"input": question})

print("最终答案:", result["output"])

⚠️ 安全提示

  • Python REPL 工具可以执行任意代码,务必在本地、安全的实验环境中使用。
  • 不要在生产环境、包含敏感数据的服务器上直接开放此能力。
  • 对于高风险场景,可以改用“受限工具”(只暴露必要函数)。

📊 案例三:CSV Agent 分析房价数据

本例基于 03_agent_csv_analysis.py,使用 LangChain 的 CSV Agenthouse_price.csv 进行自然语言分析。

🔧 常见报错:缺少 tabulate

如果你运行 CSV Agent 时看到:ImportError: Missing optional dependency 'tabulate',说明当前环境缺少可选依赖。

text
python -m pip install -U tabulate pandas
python
from langchain_experimental.agents.agent_toolkits import create_csv_agent
from langchain_community.chat_models import ChatTongyi
import os


api_key = os.getenv("DASHSCOPE_API_KEY")
llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)

csv_file = "data/house_price.csv"  # 确保文件存在

agent = create_csv_agent(
    llm=llm,
    path=csv_file,
    verbose=True,
    agent_executor_kwargs={"handle_parsing_errors": True},
    allow_dangerous_code=True,  # 在安全环境中使用
)

question = "数据集里,所有房子的价格平均值是多少?用中文回答。"
result = agent.invoke({"input": question})

print("最终答案:", result["output"])

📥 示例数据下载

本案例使用的房价数据集已放置在项目的 data/ 目录下: 点击下载 house_price.csv

数据集包含 545 条房价记录,13 个特征变量(面积、卧室数、地下室、装修状态等)。

这个 Agent 背后发生了什么?

  • 自动加载 CSV → 转为 Pandas DataFrame。
  • LLM 根据问题自动生成 Pandas 代码(例如 df["price"].mean())。
  • 在 DataFrame 上执行代码,并把结果解释成自然语言返回。

🧱 案例四:多工具组合的 AI 工具箱

最后,我们参考 04_tools_toolbox.py,把多个工具(Python Agent、CSV Agent、文本工具) 组合成一个 AI 工具箱,让 Agent 能根据问题类型自动选择工具。

python
import os

from langchain.agents import create_agent
from langchain_core.tools import BaseTool, tool
from langchain_experimental.agents.agent_toolkits import create_csv_agent, create_python_agent
from langchain_experimental.tools import PythonREPLTool
from langchain_community.chat_models import ChatTongyi


api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("请先在环境变量中设置 DASHSCOPE_API_KEY")


base_llm = ChatTongyi(model_name="qwen-turbo", dashscope_api_key=api_key, temperature=0)


class TextLengthTool(BaseTool):
    name: str = "文本字数计算工具"
    description: str = "当你需要计算文本包含的字数时,使用此工具"

    def _run(self, text: str) -> int:
        return len(text)


# 1️⃣ 子 Agent:Python 代码执行
python_agent = create_python_agent(
    llm=base_llm,
    tool=PythonREPLTool(),
    verbose=True,
    agent_executor_kwargs={"handle_parsing_errors": True},
)


# 2️⃣ 子 Agent:CSV 分析(可选)
csv_file = "data/house_price.csv"
csv_agent = None
if os.path.exists(csv_file):
    csv_agent = create_csv_agent(
        llm=base_llm,
        path=csv_file,
        verbose=True,
        agent_executor_kwargs={"handle_parsing_errors": True},
        allow_dangerous_code=True,
    )


@tool
def python_code_tool(question: str) -> str:
    """执行 Python 代码来完成数学计算、算法等任务"""
    return python_agent.invoke({"input": question})["output"]


@tool
def csv_analysis_tool(question: str) -> str:
    """当你需要回答关于 house_price.csv 的问题时使用(需要本地存在 data/house_price.csv)"""
    if not csv_agent:
        return "CSV 文件不存在,请先准备 data/house_price.csv"
    return csv_agent.invoke({"input": question})["output"]


# 3️⃣ 组装工具箱(主 Agent 会自动决定用哪个工具)
tools = [python_code_tool, csv_analysis_tool, TextLengthTool()]


# 4️⃣ 创建主 Agent(LangChain 1.2.x:使用 create_agent)
agent = create_agent(
    base_llm,
    tools=tools,
    system_prompt="你是一个工具型助手。遇到需要计算/分析时请调用合适工具。",
    debug=True,
)


question = "house_price 数据集有多少行?同时帮我算一下第10个斐波那契数。"
result = agent.invoke({"messages": [("human", question)]})
final_message = result["messages"][-1]
print("最终答案:", getattr(final_message, "content", final_message))

🔗 工具箱的优势

  • 统一入口:用户只需对一个 Agent 说话,内部自动选择工具。
  • 组合能力:一个问题可以先用 CSV 工具,再用 Python 工具,完成复杂任务。
  • 易扩展:只要继续往 tools 列表里添加工具,就能不断增强 Agent 的能力。

📝 本章小结

  • 理解了 Agent 的四个核心组成:LLM、工具、记忆和决策逻辑。
  • 实现了一个使用自定义工具的 Agent,让大模型学会精确计算文本字数。
  • 了解了 Python REPL Agent 和 CSV Agent 的使用场景与安全注意事项。
  • 学会了如何把多个工具组合成一个 AI 工具箱,让 Agent 自动选择、组合工具。

📚 课后习题与常见面试题

🧩 基础题(理解概念)

B1. Agent 与普通 LLM 调用的核心差异是什么?

B2. Tools / Memory / Prompt 分别在 Agent 中承担什么职责?

💻 编程题(可直接运行)

C1. 【编程题】实现一个“安全计算器”工具:只允许加减乘除与括号,禁止执行任意 Python

要求:工具输入是字符串表达式;你需要做白名单校验,再用安全方式求值(例如 ast 解析)。

C2. 【编程题】给“时间工具 Agent”加上记忆:连续追问“那明天呢?”也能回答

提示:需要把历史消息保存下来,并在下一次调用时带上。

🎤 常见面试题 + 面试总结

I1. 为什么 Python REPL / DataFrame Agent 被认为是高风险能力?怎么做安全治理?

I2. 生产里如何评估一个 Agent 是否“可靠”?有哪些常用指标?

🧠 面试速记(背诵版)

  • Agent = LLM + Tools + Memory + Policy/Prompt:工具扩展能力边界,记忆提供上下文。
  • 关键工程点:工具签名稳定、错误可恢复(parsing errors)、可观测性(logs/traces)、成本控制。
  • 安全:代码执行/数据访问必须隔离与最小权限,优先白名单与审计。
  • 评估:成功率、工具选择正确率、步数、延迟、成本、安全事件。