Agent
如果只看最小本质,可以把 Agent 理解成:Agent = 模型 + 工具集 + 运行循环 + 当前状态。
- 模型(Model):负责理解用户目标、分析当前局面、决定下一步。
- 工具集(Tools):负责执行动作,例如查天气、查库存、调用 API、搜索文档、访问数据库。
- 运行循环(Loop):负责让模型不是只回答一次,而是可以“想一步、做一步、看结果、再决定下一步”。
- 状态(State):负责保存本轮对话、工具返回、中间结果,有时也包含短期记忆。
不是所有 Agent 都必须同时具备“长期记忆、复杂规划、多智能体协作”。
对很多入门场景来说,一个最简单的 Agent 只要能:1. 读懂用户目标;2. 知道什么时候调工具;3. 能根据工具结果继续判断;4. 最后给出答案。就已经是 Agent 了。
常见工作方式:ReAct = Reason + Act
即先推理再行动。
这是 Agent 最经典的一种运行循环:
- Thought / Reason(思考):当前要做什么,先想一步。
- Action(行动):如果需要外部能力,就调用某个工具。
- Observation(观察):拿到工具返回结果。
- 继续循环或结束:如果还没完成,就继续下一轮;如果信息足够,就给出最终答案。
其他常见的 Agent 形态还包括:
- Plan-and-Execute:先整体规划,再按计划逐步执行。
- Router / Supervisor:先判断该把任务交给哪个工具、哪个子 Agent、哪个流程。
- Workflow + Agent 混合:固定部分用工作流,遇到不确定节点时再让 Agent 决策。
- Multi-Agent / A2A:多个 Agent 分工协作,由一个主 Agent 或协调器统筹。
- Human-in-the-loop:关键步骤需要人工确认,Agent 不是全自动闭环。
所以更准确地说:
- ReAct 是 Agent 最经典的一种工作机制
- 不是 Agent 的唯一形态
- 本章先用 ReAct 入门,是因为它最容易把 Agent 的核心价值讲清楚
Agent创建方式
| 维度 | V0.3 / classic | V1.x / create_agent |
| 核心入口 | create_tool_calling_agent + AgentExecutor |
create_agent |
| 代码组织 | 手动拼更多组件 | 统一入口更简洁 |
| 学习价值 | 更容易看清内部结构 | 更接近当前官方主线 |
| 运行时心智 | Agent 决策,Executor 驱动循环 | graph runtime 驱动循环 |
| 更适合 | 理解原理、维护旧案例 | 新项目、快速搭建 |
v0.3案例
# 模型 + 工具 + 提示模板 → create_tool_calling_agent 得到 Agent → 用 AgentExecutor 执行,
#关键组件:
# ChatPromptTemplate 定义对话结构(含 `agent_scratchpad` 占位符)、AgentExecutor 驱动循环。
# `agent_scratchpad` 可以理解成 Agent 的“草稿区 / 中间步骤区”,没有它,classic 路线下的多步推理就很难成立。
# `AgentExecutor(verbose=True)` 很适合教学和排查,它相当于一个轻量级的执行日志窗口;新版教程里补充的
# `stream()` / LangSmith 则是更偏 1.x 和工程化的观察手段。
import json
import os
import httpx
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
from langchain_classic.agents import create_tool_calling_agent
from langchain_classic.agents import AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
@tool
def get_weather(loc):
"""
查询即时天气函数
:param loc: 必要参数,字符串类型,表示查询天气的城市名称;中国城市需用英文名,如 Beijing、Shanghai。
:return: OpenWeather API 返回的天气信息,JSON 序列化后的字符串。
"""
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": loc,
"appid": os.getenv("OPENWEATHER_API_KEY"),
"units": "metric",
"lang": "zh_cn",
}
response = httpx.get(url, params=params, timeout=30)
data = response.json()
print(json.dumps(data))
return json.dumps(data)
# 初始化大模型,用于理解用户问题并决定是否调用工具、如何组合结果
llm = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 定义 Agent 的对话结构:system 定角色,human 为用户输入,
# placeholder 供 Executor 填入中间推理与工具调用记录
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是天气助手,请根据用户的问题,给出相应的天气信息"),
("human", "{input}"),
(
"placeholder",
"{agent_scratchpad}",
), # V0.3 必备:Agent 的「草稿本」,记录多轮推理与工具输出
]
)
tools = [get_weather]
# 将 LLM、工具列表、提示模板组装成「可做工具调用决策」的 Agent(尚未执行)
agent = create_tool_calling_agent(llm, tools, prompt)
# AgentExecutor 负责循环:调用 Agent → 执行其选中的工具 →
# 把结果写回 agent_scratchpad → 再交给 Agent,直到结束
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 一次问题触发多工具调用(北京、上海天气)并聚合回答
result = agent_executor.invoke(
{"input": "请问今天北京和上海的天气怎么样,哪个城市更热?"}
)
print(result)
V1.X案例
# V1.0 与 V0.3 对比:不再手写 PromptTemplate、create_tool_calling_agent、AgentExecutor,改为
# create_agent(model, tools, system_prompt, response_format=...) 一步得到可调用的 Agent,
import os
import json
import httpx
from pathlib import Path
from typing_extensions import (
TypedDict,
) # Python < 3.12 下 Pydantic 要求用 typing_extensions.TypedDict
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# .env 在项目根目录,从任意子目录运行脚本时都从根目录加载
load_dotenv(Path(__file__).resolve().parent.parent.parent / ".env")
@tool
def get_weather(loc: str) -> str:
"""
查询即时天气函数
:param loc: 城市英文名,如 Beijing、Shanghai。
:return: OpenWeather API 返回的天气信息(JSON 字符串)。
"""
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": loc,
"appid": os.getenv("OPENWEATHER_API_KEY"),
"units": "metric",
"lang": "zh_cn",
}
response = httpx.get(url, params=params, timeout=30)
data = response.json()
return json.dumps(data, ensure_ascii=False)
# 定义结构化输出:Agent 最终回答会按此结构填充,便于代码中直接取字段
class WeatherCompareOutput(TypedDict):
beijing_temp: float
shanghai_temp: float
hotter_city: str
summary: str
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# V1.0 一步创建 Agent:模型、工具、系统提示、输出格式一次传入
# 如果后面还要扩展短期记忆或拦截控制,通常会继续给 create_agent 传 checkpointer / middleware
agent = create_agent(
model=model,
tools=[get_weather],
system_prompt=(
"你是天气助手。"
"当用户询问多个城市天气时,"
"你需要分别调用工具获取数据,并进行比较分析。"
),
response_format=WeatherCompareOutput,
)
result = agent.invoke({"input": "请问今天北京和上海的天气怎么样,哪个城市更热?"})
print(result)
print()
print(json.dumps(result["structured_response"], ensure_ascii=False, indent=2))
V1.X的核心参数
| 参数 | 是否常见 | 作用 |
| model | 必备 | 让谁来做推理与决策 |
| tools | 很常见 | 给 Agent 哪些可调用能力 |
| system_prompt | 很常见 | 约束 Agent 的角色、风格和工作规则 |
| response_format | 很常见 | 让最终结果更适合结构化输出 |
| checkpointer | 进阶但很重要 | 保存运行状态,可支撑短期记忆 |
| middleware | 进阶但很重要 | 在模型调用、工具调用等阶段插入自定义控制逻辑 |
CheckPoint | thread_id | 短期记忆
如果你给 create_agent 传入 checkpointer,底层运行时就可以把 Agent 的状态保存下来。这时 Agent 不再只是“当前这一轮问什么答什么”,而是可以在多轮调用之间保留上下文。
- checkpointer:状态保存器,负责把运行过程中的状态记下来
- thread_id:一次会话或一条对话线程的标识
- 短期记忆:在同一个
thread_id下,多轮消息和状态能够被延续使用
概括成一句话:checkpointer 决定“状态能不能存下来”,thread_id 决定“这些状态属于哪一条会话”
stream()
如果 Agent 要经历:模型判断、工具调用、工具返回、再次判断、最终输出。那么直接 invoke() 往往要等到最后才看到结果。而 stream() 的价值就在于:把中间进展实时暴露出来。
这对实际项目非常有用,因为它能帮助你:
- 看到 Agent 到底卡在模型还是工具上
- 看到是否发生了多轮工具调用
- 给前端提供更好的交互体验
- 调试“为什么这次 Agent 没按预期走”
所以这里的区别很直接:invoke() 更像等最终结果,stream() 更像看 Agent 执行过程。
LangSmith
Agent 的问题,往往不是“有没有报错”这么简单,而是:
- 为什么调了这个工具而不是那个
- 为什么多调了一轮
- 为什么结构化输出没按预期生成
- 为什么这一步耗时特别长
这类问题只看最后一句回答,通常是不够的。也是为什么官方文档会把 LangSmith 和 Agent 经常放在一起讲。可以把 LangSmith 先看作:用于追踪、调试、测试、评估 Agent 运行过程的可观测平台。
import os
import json
import httpx
from pathlib import Path
from typing_extensions import(
TypeDict
)
from dotenv import load_dotenv
from langchain.agent import creat_agent
from langchain_core.tools import tool
from langchain_openao import ChatOpenAI
# .env 在项目根目录,从任意子目录运行脚本时都从根目录加载
load_dotenv(Path(__file__).resolve().parent.parent.parent / ".env")
@tool
def get_weather(loc: str) -> str:
"""
查询即时天气函数
:param loc: 城市英文名,如 Beijing、Shanghai。
:return: OpenWeather API 返回的天气信息(JSON 字符串)。
"""
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": loc,
"appid": os.getenv("OPENWEATHER_API_KEY"),
"units": "metric",
"lang": "zh_cn"
}
response = httpx.get(url, params= params, timeout = 30)
data = response.json()
return json.dumps(data, ensure_ascii=False)
# 定义结构化输出:Agent 最终回答会按此结构填充,便于代码中直接取字段
class WeatherCompareOutput(TypedDict):
beijing_temp: float
shanghai_temp: float
hotter_city: str
summary: str
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# V1.0 一步创建 Agent:模型、工具、系统提示、输出格式一次传入
# 如果后面还要扩展短期记忆或拦截控制,通常会继续给 create_agent 传 checkpointer / middleware
agent = create_agent(
model = model,
tools = [get_weather],
systemprompt=(
"你是天气助手。"
"当用户询问多个城市天气时,"
"你需要分别调用工具获取数据,并进行比较分析。"
),
response_format= WeatherCompareOutput
)
# 调用 Agent,返回结果中包含 messages 与 structured_response(若指定了 response_format)
# 这里先用 invoke 看最终结果;如需观察中间步骤,可在工程里改为 stream()
result = agent.invoke({"input": "请问今天北京和上海的天气怎么样,哪个城市更热?"})
print(result)
print(json.dumps(result["structured_response"], ensure_ascii=False, indent=2))
# json.dumps() 是 Python json 模块的核心函数,用于将 Python 对象(如字典、列表)序列化为一个 JSON 格式的字符串
# ensure_ascii:默认为 True,会将所有非 ASCII 字符(如中文)转义为 \uXXXX 形式的 Unicode 编码。若希望直接输出原始字符,需设置为 False
# indent:用于美化输出,使生成的 JSON 字符串具有可读性强的缩进格式。通常设置为一个正整数(如 2 或 4)代表缩进空格数
# sort_keys:默认为 False。若设置为 True,则输出的 JSON 对象(字典)将按照键名的字母顺序进行排序
#
ReAct案例
# 本案例提供两个 Tool:
# search_products(按类别查产品)、
# check_inventory(查库存);
# Agent 自主决定调用顺序与次数(如先搜索再查库存),体现「多步、有条件」的决策能力
# 通过 `result["messages"]` 可追踪完整对话:
# AIMessage(含 `tool_calls`)、
# ToolMessage(工具输出)、
# 最终 AIMessage(文本回答)。这和新版教程里强调的“消息视角理解 Agent”是对应的
import os
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.tools import tool
from dotenv import load_dotenv
load_dotenv()
# 模拟产品数据库:类别 -> 产品列表(id、name、popularity、price)
PRODUCT_DATABASE = {
"无线耳机": [
{"id": "WH-1000XM5", "name": "索尼 WH-1000XM5", "popularity": 95, "price": 299},
{"id": "QC45", "name": "Bose QuietComfort 45", "popularity": 88, "price": 329},
{"id": "AIRMAX", "name": "苹果 AirPods Max", "popularity": 92, "price": 549},
{"id": "PXC550", "name": "森海塞尔 PXC 550", "popularity": 76, "price": 299},
{"id": "HT450", "name": "JBL Tune 760NC", "popularity": 82, "price": 99},
],
"游戏鼠标": [
{"id": "GPW", "name": "罗技 G Pro 无线", "popularity": 90, "price": 129},
{"id": "VIPER", "name": "雷蛇 Viper V2 Pro", "popularity": 87, "price": 149},
{"id": "DAV3", "name": "雷蛇 DeathAdder V3", "popularity": 85, "price": 119},
],
"笔记本电脑": [
{"id": "MBP14", "name": "MacBook Pro 14英寸", "popularity": 94, "price": 1999},
{"id": "XPS13", "name": "戴尔 XPS 13", "popularity": 89, "price": 1299},
{"id": "TPX1", "name": "ThinkPad X1 Carbon", "popularity": 86, "price": 1499},
],
}
# 模拟库存:产品 ID -> 库存数量与仓位
INVENTORY_DATABASE = {
"WH-1000XM5": {"stock": 10, "location": "仓库-A"},
"QC45": {"stock": 0, "location": "仓库-B"},
"AIRMAX": {"stock": 5, "location": "仓库-C"},
"PXC550": {"stock": 15, "location": "仓库-A"},
"HT450": {"stock": 25, "location": "仓库-B"},
"GPW": {"stock": 8, "location": "仓库-C"},
"VIPER": {"stock": 12, "location": "仓库-A"},
"DAV3": {"stock": 3, "location": "仓库-B"},
"MBP14": {"stock": 7, "location": "仓库-C"},
"XPS13": {"stock": 0, "location": "仓库-A"},
"TPX1": {"stock": 4, "location": "仓库-B"},
}
@tool
def search_products(query: str) ->str:
"""搜索产品并返回按受欢迎度排序的结果(Tool:能力封装,供 Agent 调用)"""
print(f"🔍 [工具调用] search_products('{query}')")
keyword_mapping = {
"无线耳机": ["无线耳机", "蓝牙耳机", "头戴式耳机", "耳机"],
"游戏鼠标": ["游戏鼠标", "电竞鼠标", "鼠标"],
"笔记本电脑": ["笔记本电脑", "笔记本", "手提电脑", "电脑"],
}
matched_category = None
for category, keywords in keyword_mapping.items():
if any(keyword in query for keyword in keywords):
matched_category = category
break
if matched_category and matched_category in PRODUCT_DATABASE:
products = PRODUCT_DATABASE[matched_category]
sorted_products = sorted(products, key=lambda x: x["popularity"], reverse=True)
result = f"找到 {len(sorted_products)} 个匹配 '{query}' 的产品:\n"
for i, product in enumerate(sorted_products, 1):
result += f"{i}. {product['name']} (ID: {product['id']}) - 受欢迎度: {product['popularity']}% - ¥{product['price']}\n"
return result
return "未找到匹配产品"
@tool
def check_invent(product_id: str) -> str:
"""检查特定产品的库存状态(Tool:能力封装)"""
if product_id in INVENTORY_DATABASE:
stock_info = INVENTORY_DATABASE[product_id]
status = "有库存" if stock_info["stock"] > 0 else "缺货"
return f"产品 {product_id}: {status} ({stock_info['stock']} 件库存) - 位置: {stock_info['location']}"
return f"未找到产品ID: {product_id}"
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 系统提示中明确 ReAct:先推理、再选工具、基于结果继续推理直至得到完整答案
# 这里是在“用 ReAct 作为最常见入门机制”,并不代表 Agent 只有这一种工作方式
agent = cerate_agent(
model.
tools = [search_products, check_inventory],
system_prompt="""你是电商助手,遵循ReAct模式:
1. 先推理用户需求
2. 选择合适的工具执行操作
3. 基于工具结果进行下一步推理
4. 重复直到获得完整答案
保持推理步骤简洁明了。""",
)
# 测试:一次问题可能触发多轮「推理 → 选工具 → 观察 → 再推理」
result1 = agent.invoke(
{
"messages": [
{"role": "user", "content": "查找当前最受欢迎的无线耳机并检查是否有库存"}
]
}
)
print("📊 最终结果:")
for msg in result1["messages"]:
if hasattr(msg, "content"):
print(f"{msg.__class__.__name__}: {msg.content}")
print("=" * 40)
这个问题天然要求两步:
- 先搜索产品,找出哪个最热门
- 再根据搜索结果,决定去查哪个产品的库存
这里最关键的地方不是工具本身,而是:第二步依赖第一步的结果。
这就是 Agent 价值所在。如果没有 Agent,你就要手工写死流程;有了 Agent,模型可以根据第一步返回内容,自主决定下一步调什么
A2A案例
# A2A = 多个专属 Agent 各司其职 + 一个总协调逻辑负责调度与汇总。本案例中机票 / 酒店 / 打车三个
# 子 Agent 分别只绑定一个 `@tool`,总协调按业务顺序依次 `invoke` 子链并整合结果,对应教程「5.3 A2A」
# 子 Agent 的实现方式是:`Prompt | llm.bind_tools([单个工具]) | output_parser`
# 本质上是“单一职责的 Runnable 子链”,而不是一个什么都做的大一统 Agent。
# 总协调部分使用 `RunnableLambda` 封装“按顺序调度多个子链 + 失败时兜底”的编排逻辑。
# 这更接近教程里强调的“分工 + 协调”,也是本案例最值得学习的地方。
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain.tools import tool
from dotenv import load_dotenv
load_dotenv()
# ===================== 大模型与输出解析 =====================
llm = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
output_parser = StrOutputParser()
# ===================== 模拟业务函数:用 @tool(名称, description=...) 封装,供子 Agent 绑定 =====================
@tool(
"CtripBookFlight",
description="预订机票的唯一工具,必须调用,参数是departure出发地、arrival目的地、date出行日期(格式2026-02-01)",
)
def ctrip_book_flight(departure: str, arrival: str, date: str) -> str:
"""携程订机票:固定返回测试结果"""
return f"【携程机票预订成功】\n出发地:{departure}\n目的地:{arrival}\n出行日期:{date}\n航班号:CA1885(北京首都T3→上海浦东T2)\n起飞时间:14:00\n降落时间:16:30\n座位:经济舱34A\n电子客票号:999-1234567890\n舱位等级:经济舱超级经济座"
@tool(
"MeituanBookHotel",
description="预订酒店的唯一工具,必须调用,参数是city城市、near_by附近地标、check_in入住日期、check_out离店日期",
)
def meituan_book_hotel(city: str, near_by: str, check_in: str, check_out: str) -> str:
"""美团订酒店:固定返回测试结果"""
return f"【美团酒店预订成功】\n城市:{city}\n位置:{near_by}附近\n入住日期:{check_in}\n离店日期:{check_out}\n酒店名称:上海浦东机场铂尔曼大酒店\n房型:豪华大床房(含双人自助早餐)\n房号:1508\n预订号:MT20260201001\n入住人:张三\n退房政策:入住后24小时内可免费取消"
@tool(
"DidiBookTaxi",
description="预约打车的唯一工具,必须调用,参数是start起点、end终点、time用车时间",
)
def didi_book_taxi(start: str, end: str, time: str) -> str:
"""滴滴打车:固定返回测试结果"""
return f"【滴滴打车预约成功】\n起点:{start}\n终点:{end}\n用车时间:{time}\n车型:滴滴快车(舒适型)\n司机姓名:王师傅\n车牌号:沪A12345\n司机电话:13800138000\n预估费用:35元(券后立减5元,实付30元)\n预计接驾时间:16:35\n车型空间:5座,可放2件24寸行李箱"
# ===================== 专属 Agent:每条子链只绑定一个工具,体现“单一职责” =====================
def create_ctrip_agent(llm):
llm_with_tools = llm.bind_tools([ctrip_book_flight])
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是专业的工具调用助手,只能调用CtripBookFlight工具,"
"调用格式必须正确,"
"直接传入参数:departure='北京', arrival='上海', date='2026-02-01',"
"调用后直接返回工具执行的完整字符串结果,不能有任何其他内容,不能留空!",
),
("human", "{input}"),
]
)
return prompt | llm_with_tools | output_parser
def create_meituan_agent(llm):
llm_with_tools = llm.bind_tools([meituan_book_hotel])
prompt = ChatPromptTemplate.from_messages(
[
("system",
"你是专业的工具调用助手,只能调用MeituanBookHotel工具,调用格式必须正确,"
"直接传入参数:city='上海', near_by='浦东机场', check_in='2026-02-01', "
"check_out='2026-02-02',调用后直接返回工具执行的完整字符串结果,"
"不能有任何其他内容,不能留空!",
),
("human", "{input}")
]
)
return prompt | llm_with_tools | output_parser
def create_didi_agent(llm):
llm_with_tools = llm.bind_tools([didi_book_taxi])
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是专业的工具调用助手,只能调用DidiBookTaxi工具,调用格式必须正确,"
"直接传入参数:start='上海浦东机场T2', end='上海浦东机场铂尔曼大酒店', "
"time='2026-02-01 16:40',调用后直接返回工具执行的完整字符串结果,"
"不能有任何其他内容,不能留空!",
),
("human", "{input}"),
]
)
return prompt | llm_with_tools | output_parser
# ===================== 总协调器:按业务顺序调用子链,必要时用 .func 兜底 =====================
def create_travel_coordinator_agent(llm, ctrip_chain, meituan_chain, didi_chain):
"""总协调:负责编排顺序,不负责再去“自由选工具”做 ReAct 式决策。"""
def a2a_search(input_dict):
print("🔍 开始执行A2A协作测试,依次调用各业务Agent...\n")
ctrip_func = (
ctrip_book_flight.func
) # StructuredTool 的 .func 为原始可调用函数,兜底时直接用
meituan_func = meituan_book_hotel.func # 获取美团工具原始函数
didi_func = didi_book_taxi.func
# 1. 携程Agent调用
print("1. 调用【携程机票Agent】>>>")
try:
ctrip_result = ctrip_chain.invoke({"input": "订机票"})
except:
ctrip_result = ""
if not ctrip_result.strip():
# .strip()用于移除字符串开头和结尾的指定字符(默认为空白字符),并返回一个新字符串而不修改原字符串
ctrip_result = ctrip_func("北京", "上海", "2026-02-01") # 替换为原始函数
print(f"✅ 携程测试结果:\n{ctrip_result}\n" + "-" * 80 + "\n")
# 2. 美团Agent调用
print("2. 调用【美团酒店Agent】>>>")
try:
meituan_result = meituan_chain.invoke({"input": "订酒店"})
except:
meituan_result = ""
if not meituan_result.strip():
meituan_result = meituan_func(
"上海", "浦东机场", "2026-02-01", "2026-02-02"
)
print(f"✅ 美团测试结果:\n{meituan_result}\n" + "-" * 80 + "\n")
# 3. 滴滴Agent调用
print("3. 调用【滴滴打车Agent】>>>")
try:
didi_result = didi_chain.invoke({"input": "预约打车"})
except:
didi_result = ""
if not didi_result.strip():
didi_result = didi_func(
"上海浦东机场T2", "上海浦东机场铂尔曼大酒店", "2026-02-01 16:40"
) # 替换为原始函数
print(f"✅ 滴滴测试结果:\n{didi_result}\n" + "-" * 80 + "\n")
# 整合最终报告
total_report = f"""
📋 【携程-美团-滴滴 A2A协作测试最终报告】
{('='*90)}
📌 测试状态:本地运行成功,所有Agent均返回完整结果(含兜底保障)
📌 协作流程:携程订机票 → 美团订酒店 → 滴滴打车(按业务顺序执行)
📌 测试环境:Python3.13 + LangChain1.0 + 通义千问qwen-plus + @tool装饰器(修复可调用问题)
{('='*90)}
【1. 携程机票预订结果】
{ctrip_result}
【2. 美团酒店预订结果】
{meituan_result}
【3. 滴滴打车预约结果】
{didi_result}
{('='*90)}
💡 测试结论:A2A协作逻辑正常,@tool装饰器集成成功,无报错!
"""
return tatal_report
# 封装为 Runnable,与子 Agent 链一致,可被 invoke
return RunnableLambda(
a2a_schedule
)
# ===================== 主程序:初始化子 Agent 与总协调,执行一次完整行程请求 =====================
if __name__ == "__main__":
try:
# 初始化各专属Agent
print("🔧 初始化携程/美团/滴滴专属Agent...")
ctrip_chain = create_ctrip_agent(llm)
meituan_chain = create_meituan_agent(llm)
didi_chain = create_didi_agent(llm)
print("✅ 所有Agent初始化完成!\n" + "=" * 90 + "\n")
# 初始化A2A总协调Agent
print("🔧 初始化A2A总协调Agent(调度核心)...")
coor_chain = create_travel_coordinator_agent(
llm, ctrip_chain, meituan_chain, didi_chain
)
print("✅ 总协调Agent初始化完成!\n" + "=" * 90 + "\n")
# 执行A2A协作核心测试
print("🚀 携程-美团-滴滴 A2A协作测试正式开始 🚀")
final_result = coor_chain.invoke(
{"input": "安排2026-02-01北京飞上海的完整行程"}
)
# 打印最终完整测试报告
print("\n" + "=" * 90)
print(final_result)
print("=" * 90)
except Exception as e:
print(f"❌ 全局运行异常:{type(e).__name__} - {str(e)[:100]}")
print(
"💡 快速排查:"
"1. 通义密钥是否正确 2. 网络能否访问阿里云 3. LangChain版本是否为1.0.0"
)
多智能体协作的重点不是 API 长什么样,而是“分工 + 协调”
Agent + MCP案例
# 从同目录的 mcp.json 加载 MCP 服务配置,使用 langchain_mcp_adapters 的 MultiServerMCPClient 连接多台
# MCP 服务器并获取工具列表,再交给 LangChain 的
# create_tool_calling_agent + AgentExecutor,形成「LLM + MCP 工具」的对话 Agent。这也是第 21 章里“外部工具接入 Agent”的代表案例。
# 流程:加载 mcp.json → 初始化 MultiServerMCPClient → 异步获取 MCP Tools → 创建 DeepSeek 模型与
# 提示模板 → 组装 Agent 与 AgentExecutor → 启动命令行聊天循环(输入 quit 退出)。
# 本案例重点展示“把 MCP Tools 交给 LangChain Agent”;Resources 和 Prompts 虽然也是 MCP 能力,但这里没有作为主线展开
import asyncio
import json
import os
from pathlib import Path
from loguru import logger
# 默认 mcp.json 路径(与本文件同目录)
_MCP_JSON_PATH = Path(__file__).resolve().parent / "mcp.json"
def load_servers(file_path: str | Path | None = None) -> dict:
"""
加载 MCP 服务器配置。
:param file_path: 配置文件路径,默认使用同目录下的 mcp.json
:return: 完整配置字典,如 {"mcpServers": {"weather": {...}, "fetch": {...}}}
这里读取的是“客户端如何连接服务”的约定配置,而不是协议本体。
"""
path = Path(file_path) if file_path else _MCP_JSON_PATH
if not path.exists():
logger.warning(f"未找到 mcp 配置文件: {path}")
return {"mcpServers": {}}
with open(path, "r", encoding="utf-8") as f:
config = json.load(f)
logger.info(
f"已加载 mcp 配置: {path},共 {len(config.get('mcpServers', {}))} 个服务"
)
return config
| 概念 | 它解决什么问题 | 一句话理解 |
| Tool | 系统有哪些可调用能力 | 能力层 |
| Function Calling | 模型怎么把“调工具”表达出来 | 调用机制 |
| RAG | 模型缺知识时怎么拿上下文 | 上下文增强 |
| MCP | 工具 / 资源 / Prompt 怎么标准化接入 | 连接协议连接协议 |
| Agent | 什么时候用什么能力、按什么顺序做 | 决策与编排层 |
真实项目怎么配合
- 用户提出一个目标
- Agent 先判断该怎么做
- 如果缺知识,就先走 RAG
- 如果缺动作能力,就通过 Function Calling 去调 Tool
- 这些 Tool 可能是本地写的,也可能来自 MCP
- Agent 再根据返回结果继续判断,直到最终完成任务(需设好迭代上限与超时,避免死循环)

浙公网安备 33010602011771号