让大模型学会“用工具”与“自己思考”
python -m pip install -U "langchain==1.2.10" "langchain-community==0.3.31" "langchain-core==1.2.13" dashscope duckduckgo-search wikipedia requests本章所有示例均使用上述固定版本,确保代码可复制运行。后续代码块不再重复安装命令。
我们先用一个最简单的例子来理解为什么一定要有工具:让 AI 回答"现在几点了?"
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)
# 输出类似:
# "抱歉,我无法获取实时信息,请查看您的设备时间..."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)get_current_time 获取系统当前时间。在 LangChain 中,Agent 可以理解为一个“会思考的调度员”: 它基于大模型的推理能力,自动决定什么时候调用哪个工具、如何组合工具, 并在多轮对话中持续记住上下文。
Skills 可以理解为:把一段“多步骤能力”封装成可复用模块。 它通常不是一次性的工具调用,而是一段可复用的工作流(可能包含多次工具调用 + 状态处理 + 结果格式化)。
summarize_webpage(url) 之后,Agent 只需要在合适时机调用这个 Skill,就能稳定产出结果。
用户问题 → Agent 分析意图
↓
判断是否需要工具
↙ ↘
直接回答 选择合适工具
↓
调用工具获取结果
↓
整理结果并回复用户
↓
写入对话记忆(Memory)你在很多资料里看到的“React”通常指的是 ReAct:Reason(推理) + Act(行动)。 其核心不是让模型“想得更深”,而是让模型在运行时形成一个可循环的闭环: 先做下一步决策 → 调用工具拿到外部事实 → 再根据事实继续决策。
本章所有案例都用 create_agent + @tool + messages 来实现这种 ReAct 式闭环。
Agent 不是“更聪明的模型”,而是一个运行时系统: 它把模型当作“决策引擎”,把工具当作“可执行能力”,把对话状态当作“上下文存储”, 最终形成一个可循环执行的流程:思考 → 调用工具 → 观察 → 再思考 → 输出。
MCP(Model Context Protocol,模型上下文协议)是由 Anthropic(Claude 的开发公司)推出的开放标准协议, 旨在统一 AI 应用(包括 Agent)与各种数据源、工具之间的连接方式。
简单理解:就像 USB 接口统一了电脑外设的连接方式,MCP 统一了 Agent 访问工具和数据的方式。 无论是本地文件、数据库、API 还是云服务,都可以通过 MCP 协议让 Agent 轻松调用。
"需要读取文件"
转为标准 JSON-RPC 调用
读取文件并格式化
Agent 获得文件内容
通过 MCP 连接公司的 Confluence、Notion、SharePoint,Agent 可以直接搜索和引用企业文档, 无需为每个数据源单独写 @tool 函数。
MCP 连接 GitHub/GitLab,Agent 可以读取代码仓库、查看提交历史、分析代码结构, 自动完成代码审查和重构建议。
MCP 连接数据库(MySQL/PostgreSQL),Agent 可以执行 SQL 查询、生成报表、 分析业务数据,无需导出 CSV 或编写数据库工具函数。
MCP 连接 Google Drive/Dropbox,Agent 可以实时读取最新版本的文档, 确保分析和回答基于最新数据,无需手动同步。
1. 安装 Claude Desktop(官方支持最完善)
2. 配置 MCP Server(编辑 claude_desktop_config.json)
3. 重启 Claude,Agent 即可通过自然语言调用 MCP 工具
💡 提示:MCP 社区已有 100+ 开源 Server,涵盖文件系统、GitHub、Google Drive、Slack、数据库等常见工具。 访问 MCP Servers 仓库 查看完整列表。
在本章(固定版本 langchain==1.2.10)里,我们采用最稳定、最好复制的方式:
用 create_agent 创建一个 Agent,然后通过 messages 传入对话历史。
你只要记住两句话:工具用 @tool 定义;多轮对话就是复用 messages 列表。
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)在生产环境里,工具一定要“可控”: 你不能把用户输入原样交给任意执行器(例如 PythonREPL)。下面给一个更通用的模板: 先做白名单校验,再执行;异常要转成可读错误信息。
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("特点:所有工具都有输入校验和异常处理")
下面是可以直接导入使用的常用工具名与导入路径。注意:不同工具分布在 langchain / langchain-community / langchain-experimental,
有的需要额外依赖或联网权限。你在企业落地时,通常会把这些工具封装成"内部工具层",统一做鉴权、审计与限流。
作用:把外部 HTTP 请求封装成 Tool,让 Agent 在需要事实信息时自行调用。
这里用域名白名单演示企业必备的安全策略:只允许访问指定域名(避免 SSRF、数据外泄)。
你运行后应能看到 Agent 调用 http_get,并对 https://httpbin.org/get 的返回字段进行总结。
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 决策后才会继续执行。
interrupt_on 配置,只有指定工具会触发审批{"configurable": {"thread_id": "demo_thread"}}{"recursion_limit": 100}{"tags": ["production"]}middleware=(hitl, logger, timer)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)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)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)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)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)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)ModelCallLimitMiddleware:模型调用次数上限(防止死循环)ToolCallLimitMiddleware:工具调用次数上限(防止死循环)ModelRetryMiddleware:模型调用重试ToolRetryMiddleware:工具调用重试LoggingMiddleware:调用日志记录MemorySaver():开发测试,重启丢失SqliteSaver("db.sqlite"):单机生产PostgresSaver(conn_str):分布式生产下面给一个生产级完整示例:包含所有常用参数,演示如何把 Demo 变成可上线的系统。
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])} 个工具")LangChain 提供了多种 Agent 创建方式,从通用型到专用型,满足不同场景需求。 理解这些差异能帮你选择最合适的方案,避免过度工程化或功能不足。
from langchain_experimental.agents.agent_toolkits import create_python_agentfrom langchain_experimental.agents import create_*from langchain_classic.agents import create_*from langchain_classic.agents import create_*create_agent:灵活性最高,支持完整工程化下面每个类型都给一个“最小可跑”骨架,重点看入口函数、输入/输出约定、以及最常踩的坑。 实战版本会在后面的案例二/三/四展开。
create_agent@tool 暴露;对话输入统一走 messages;用 system_prompt 约束什么时候用工具。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))create_python_agent(高危)input 字段;务必在隔离环境使用;生产要做 allowlist/HITL。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_csv_agenttabulate/pandas;属于高权限执行。pip install tabulate pandas(tabulate 用于 pandas 的 to_markdown() 方法)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_retriever_tool + create_agentcreate_retriever_tool 把 retriever 包成标准 Tool,再交给 create_agent 统一调度。这是新版本推荐的工程化方式,比 Classic VectorStore Agent 更稳定。pip install chromadb langchain-communityimport 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 + create_agent。create_react_agentfrom langgraph.prebuilt import create_react_agent,只支持 model 和 tools 两个主要参数from langchain.agents import create_agent(API 大幅简化)from langchain_classic.agents import AgentExecutormodel + tools,避免传递当前版本不支持的额外关键字参数(例如 state_modifier)。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_tool_calling_agent / create_structured_chat_agent(Classic)create_agent 不同,经典范式需要你显式配置 PromptTemplate,并配合 AgentExecutor 执行。ChatPromptTemplate,并配合 AgentExecutor(从 langchain_classic.agents 导入)执行。agent_scratchpad 在 Tool Calling 里是 MessagesPlaceholder,在 Structured Chat 里是字符串变量 {agent_scratchpad}。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 文本,需要解析,兼容性更好")
langchain.agents 已移除,必须用 from langchain_classic.agents import AgentExecutor。MessagesPlaceholder,Structured Chat 用字符串 {agent_scratchpad}。create_agent + system_prompt。下面通过具体案例,展示不同 Agent 类型的实际用法。建议按顺序学习: 自定义工具 → Python REPL → CSV 分析 → 多工具组合。
本案例展示如何使用 create_agent 创建自定义工具 Agent,这是最通用、最灵活的方式。 我们将创建一个文本字数计算工具,让 Agent 能够精确回答关于文本长度的问题。
接下来我们看一个具体的例子:让 Agent 正确计算一句诗的字数。
这个例子基于课件中的 01_agent_custom_tool.py。
from langchain_core.tools import BaseTool
class TextLengthTool(BaseTool):
"""文本字数计算工具"""
name: str = "文本字数计算工具"
description: str = "当你被要求计算文本的字数时,使用此工具"
def _run(self, text: str) -> int:
# 简单示例:按字符数量计算
return len(text)
tools = [TextLengthTool()]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,
)question = "'君不见黄河之水天上来奔流到海不复回',这句话的字数是多少?"
result = agent.invoke({"messages": [("human", question)]})
final_message = result["messages"][-1]
print("最终答案:", getattr(final_message, "content", final_message))文本字数计算工具。
有些问题适合通过“写一段 Python 代码”来解决,比如数学计算、数据分析等。
本例基于课件 02_agent_python_repl.py,展示 Python REPL Agent 的用法。
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"])
本例基于 03_agent_csv_analysis.py,使用 LangChain 的 CSV Agent
对 house_price.csv 进行自然语言分析。
如果你运行 CSV Agent 时看到:ImportError: Missing optional dependency 'tabulate',说明当前环境缺少可选依赖。
python -m pip install -U tabulate pandasfrom 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 个特征变量(面积、卧室数、地下室、装修状态等)。
df["price"].mean())。
最后,我们参考 04_tools_toolbox.py,把多个工具(Python Agent、CSV Agent、文本工具)
组合成一个 AI 工具箱,让 Agent 能根据问题类型自动选择工具。
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))tools 列表里添加工具,就能不断增强 Agent 的能力。B1. Agent 与普通 LLM 调用的核心差异是什么?
B2. Tools / Memory / Prompt 分别在 Agent 中承担什么职责?
C1. 【编程题】实现一个“安全计算器”工具:只允许加减乘除与括号,禁止执行任意 Python
要求:工具输入是字符串表达式;你需要做白名单校验,再用安全方式求值(例如 ast 解析)。
C2. 【编程题】给“时间工具 Agent”加上记忆:连续追问“那明天呢?”也能回答
提示:需要把历史消息保存下来,并在下一次调用时带上。
I1. 为什么 Python REPL / DataFrame Agent 被认为是高风险能力?怎么做安全治理?
I2. 生产里如何评估一个 Agent 是否“可靠”?有哪些常用指标?