Agent

如果只看最小本质,可以把 Agent 理解成:Agent = 模型 + 工具集 + 运行循环 + 当前状态

  • 模型(Model):负责理解用户目标、分析当前局面、决定下一步。
  • 工具集(Tools):负责执行动作,例如查天气、查库存、调用 API、搜索文档、访问数据库。
  • 运行循环(Loop):负责让模型不是只回答一次,而是可以“想一步、做一步、看结果、再决定下一步”。
  • 状态(State):负责保存本轮对话、工具返回、中间结果,有时也包含短期记忆。

不是所有 Agent 都必须同时具备“长期记忆、复杂规划、多智能体协作”。

对很多入门场景来说,一个最简单的 Agent 只要能:1. 读懂用户目标;2. 知道什么时候调工具;3. 能根据工具结果继续判断;4. 最后给出答案。就已经是 Agent 了。

常见工作方式:ReAct = Reason + Act

即先推理再行动。

这是 Agent 最经典的一种运行循环:

  1. Thought / Reason(思考):当前要做什么,先想一步。
  2. Action(行动):如果需要外部能力,就调用某个工具。
  3. Observation(观察):拿到工具返回结果。
  4. 继续循环或结束:如果还没完成,就继续下一轮;如果信息足够,就给出最终答案。

其他常见的 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)

这个问题天然要求两步:

  1. 先搜索产品,找出哪个最热门
  2. 再根据搜索结果,决定去查哪个产品的库存

这里最关键的地方不是工具本身,而是:第二步依赖第一步的结果。
这就是 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 什么时候用什么能力、按什么顺序做 决策与编排层

真实项目怎么配合

  1. 用户提出一个目标
  2. Agent 先判断该怎么做
  3. 如果缺知识,就先走 RAG
  4. 如果缺动作能力,就通过 Function Calling 去调 Tool
  5. 这些 Tool 可能是本地写的,也可能来自 MCP
  6. Agent 再根据返回结果继续判断,直到最终完成任务(需设好迭代上限与超时,避免死循环)

 

posted @ 2026-05-09 10:05  幻影之舞  阅读(31)  评论(0)    收藏  举报