深入理解A2A协议:从零搭建多Agent协作系统实战

目录

[一、为什么需要 A2A 协议](#一、为什么需要 A2A 协议)

[1.1 现状的痛点](#1.1 现状的痛点)

[1.2 用一个故事理解](#1.2 用一个故事理解)

[1.3 A2A 的设计目标](#1.3 A2A 的设计目标)

[二、A2A 协议核心概念详解](#二、A2A 协议核心概念详解)

[2.1 核心概念一览表](#2.1 核心概念一览表)

[2.2 任务生命周期(Task State Machine)](#2.2 任务生命周期(Task State Machine))

[2.3 通信流程图](#2.3 通信流程图)

三、系统架构设计

[3.1 整体架构](#3.1 整体架构)

[3.2 项目目录结构](#3.2 项目目录结构)

[3.3 技术栈](#3.3 技术栈)

[四、基础实战------模拟版多 Agent 协作](#四、基础实战——模拟版多 Agent 协作)

[4.1 环境准备与依赖安装](#4.1 环境准备与依赖安装)

[4.2 创建"天气专家"Agent](#4.2 创建"天气专家"Agent)

[4.3 创建"票务专家"Agent](#4.3 创建"票务专家"Agent)

[4.4 创建"总控Agent"------团队的大脑](#4.4 创建"总控Agent"——团队的大脑)

[4.5 运行与验证](#4.5 运行与验证)

[五、进阶实战------集成 LLM 实现真实业务](#五、进阶实战——集成 LLM 实现真实业务)

[5.1 整体预览](#5.1 整体预览)

[5.2 通用 LLM 配置](#5.2 通用 LLM 配置)

[5.3 天气专家进阶:Function Calling](#5.3 天气专家进阶:Function Calling)

[5.4 票务专家进阶:LangGraph 工作流](#5.4 票务专家进阶:LangGraph 工作流)

[5.5 总控Agent进阶:意图识别路由](#5.5 总控Agent进阶:意图识别路由)

[六、A2A 与 MCP 的关系辨析](#六、A2A 与 MCP 的关系辨析)

七、总结与展望

[7.1 核心收获](#7.1 核心收获)

[7.2 实际生产中的扩展方向](#7.2 实际生产中的扩展方向)


一、为什么需要 A2A 协议

1.1 现状的痛点

在当前的 AI Agent 生态中,每一个 Agent 都是"孤岛":

  • 能力单一:一个 Agent 通常只擅长一个领域(查天气、写代码、搜索信息......),没有"全能Agent"。
  • 接口各异:不同框架(LangChain、CrewAI、AutoGen、自研框架)构建的 Agent,通信方式千差万别,无法直接对话。
  • 协作困难:当一个任务需要多个 Agent 协同完成时,开发者必须手动编写胶水代码,将它们"硬编码"在一起。

1.2 用一个故事理解

想象你是一位忙碌的旅行规划师(我们称之为"主控Agent")。你的客户(用户)对你说:

"帮我规划一次北京到上海的出差,顺便看看那边天气怎么样。"

如果你单打独斗,你需要:

  1. 1.打开浏览器查天气。
  2. 2.打开 12306 订火车票。
  3. 3.把两件事的结果拼在一起告诉客户。

这非常繁琐,而且你不是万能的。但如果你有一个团队------"天气专家"和"票务专家"各司其职,你只需要发一封标准化的工作邮件 给他们,他们干完活再把结果填在交接单上发回来。

A2A 协议就是这套"标准化的工作邮件模板"和"任务交接单"。

1.3 A2A 的设计目标

Google 提出 A2A 协议时,明确了以下设计原则:

设计原则 说明
标准化 定义统一的消息格式、任务生命周期和错误处理机制
互操作性 不同框架、不同语言构建的 Agent 可以无缝通信
去中心化 没有单一的中央调度器,Agent 之间直接对等通信
可发现性 Agent 通过"名片"(AgentCard)自我暴露能力,其他 Agent 可以自动发现
异步优先 天然支持长时间运行的任务和人工介入场景

二、A2A 协议核心概念详解

在写代码之前,我们先把 A2A 协议中的核心概念彻底弄清楚。这些概念是后续所有代码的"地基"。

2.1 核心概念一览表

A2A 术语 类比 说明 代码中的体现
Agent 一个专家/员工 具有特定能力的 AI 实体,能接收任务并返回结果 A2AServer 的子类
AgentCard 专家的名片 描述 Agent 的名称、能力、地址等元信息,用于服务发现 AgentCard 对象
AgentSkill 名片上的技能标签 描述 Agent 擅长的具体技能,包含名称、描述和示例 AgentSkill 对象
Task 工作交接单 A2A 通信的核心载体,包含消息、状态、结果等完整信息 Task 对象
Message 交接单中的内容 用户或 Agent 发送的具体消息,包含角色和内容 Message 对象
Artifact 工作成果物 Agent 完成任务后输出的结果,附着在 Task 上 task.artifacts
TaskStatus 交接单的盖章状态 标记任务当前所处阶段:待处理、进行中、已完成、失败 TaskStatus / TaskState
AgentNetwork 专家通讯录 总控端维护的可调用 Agent 列表 AgentNetwork 对象

2.2 任务生命周期(Task State Machine)

A2A 协议中的任务有明确的状态流转:

复制代码
提交(SUBMITTED) → 进行中(WORKING) → 已完成(COMPLETED)
                  │                   ↘ 失败(FAILED)
                  ↘ 需要输入(INPUT_REQUIRED) → 进行中(WORKING) → ...
状态 含义
TaskState.SUBMITTED 任务已提交,等待处理
TaskState.WORKING Agent 正在处理该任务
TaskState.INPUT_REQUIRED 任务需要额外信息才能继续(如订票缺少出发城市)
TaskState.COMPLETED 任务已成功完成,结果在 artifacts
TaskState.FAILED 任务处理失败

2.3 通信流程图

复制代码
┌──────────────┐         A2A Protocol          ┌──────────────┐
│  用户 (User)  │                               │ WeatherAgent │
│              │                                │  (Server)    │
└──────┬───────┘                                └──────▲───────┘
       │                                               │
       │  ① 发送请求                                    │  ④ 返回 Task+Artifact
       ▼                                               │
┌──────────────┐     ② 发送 Task (HTTP/JSON-RPC)    ┌────┴─────────┐
│ Orchestrator │ ──────────────────────────────────▶│              │
│  (Client)    │                                    │  天气专家     │
│              │ ──────────────────────────────────▶│  票务专家     │
│              │     ③ 并发发送 Task                 │  ...更多专家  │
└──────┬───────┘                                    └──────────────┘
       │
       │  ⑤ 汇总结果返回用户
       ▼
┌──────────────┐
│  用户 (User)  │
└──────────────┘

三、系统架构设计

3.1 整体架构

本实战项目采用星型拓扑结构,以"总控Agent"为中心,协调多个专家Agent:

复制代码
                    ┌─────────────────┐
                    │     用户输入     │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │   总控Agent     │
                    │  (Orchestrator) │
                    │  - 意图识别     │
                    │  - 任务分发     │
                    │  - 结果汇总     │
                    └───┬─────────┬───┘
                        │         │
            ┌───────────▼──┐  ┌──▼───────────┐
            │ 天气专家Agent │  │  票务专家Agent │
            │  Port: 5008  │  │  Port: 5009  │
            │  - Function  │  │  - LangGraph │
            │    Calling   │  │    工作流     │
            └──────────────┘  └──────────────┘

3.2 项目目录结构

复制代码
a2a_travel_system/
├── common/
│   └── llm.py                      # 通用 LLM 配置
├── __001__weather_agent/
│   ├── __001__parse_weather.py      # 天气查询逻辑(LangChain Function Calling)
│   └── __002__weather_agent.py      # 天气专家 A2A Server
├── __002__ticket_agent/
│   ├── __001__ticket_workflow.py    # 订票逻辑(LangGraph 工作流)
│   └── __002__ticket_agent.py       # 票务专家 A2A Server
├── __003__orchestrator/
│   ├── __001__weather_ticket_intent.py  # 意图识别模块
│   └── __002__orchestrator.py           # 总控 Agent
├── .env                             # 环境变量(API Key 等)
└── requirements.txt

3.3 技术栈

组件 技术选型 作用
A2A 通信层 python-a2a 实现 Agent 间的标准化通信
LLM 调用 langchain-openai 通过 OpenAI 兼容接口调用大模型
工具调用 LangChain @tool + Function Calling 天气专家的技能实现
工作流编排 LangGraph 票务专家的多步骤推理
意图识别 LangChain + Pydantic Output Parser 总控Agent的智能路由

四、基础实战------模拟版多 Agent 协作

我们先用最简单的方式跑通整个 A2A 流程,然后再逐步升级。

4.1 环境准备与依赖安装

python 复制代码
# 创建虚拟环境(推荐)
python -m venv a2a_env
source a2a_env/bin/activate  # macOS/Linux
# a2a_env\Scripts\activate   # Windows

# 安装核心依赖
pip install python-a2a

# 进阶部分额外依赖
pip install langchain-openai langchain-core langgraph pydantic python-dotenv

注意python-a2a 是 Python 社区对 A2A 协议的实现库,它封装了 Server 启动、Client 通信、消息序列化等底层细节,让开发者可以聚焦于业务逻辑。

4.2 创建"天气专家"Agent

这个 Agent 专注于一件事:接收天气查询请求,返回天气信息

代码文件:weather_agent.py

python 复制代码
"""
天气专家 Agent - A2A Server
功能:接收天气查询请求,返回模拟天气数据。
"""

from python_a2a import (
    A2AServer, run_server,
    AgentCard, AgentSkill,
    TaskStatus, TaskState
)

# ========== 1. 定义 Agent 名片 ==========
agent_card = AgentCard(
    name="WeatherExpert",
    description="负责查询天气的专家,能够根据城市名称返回天气信息。",
    url="http://127.0.0.1:5008",
    skills=[
        AgentSkill(
            name="get_weather",
            description="根据城市查询当前天气状况,包括温度、天气类型等。",
            examples=["北京天气怎么样?", "查一下上海天气", "广州今天下雨吗?"]
        )
    ]
)


# ========== 2. 实现 Server 逻辑 ==========
class WeatherExpertServer(A2AServer):
    """
    天气专家服务端。
    继承 A2AServer,只需实现 handle_task 方法即可。
    """

    def __init__(self):
        super().__init__(agent_card=agent_card)

    def weather_service(self, text: str) -> str:
        """天气业务逻辑(模拟版)"""
        # 简单的关键词匹配(后续会用 LLM 替代)
        weather_data = {
            "北京": "北京今天晴空万里,温度 28°C,微风,适合出行!☀️",
            "上海": "上海今天多云转晴,温度 25°C,空气质量良好。🌤️",
            "广州": "广州今天有小雨,温度 22°C,出门记得带伞。🌧️",
            "深圳": "深圳今天阵雨,温度 26°C,湿度较高。⛈️",
            "成都": "成都今天阴天,温度 24°C,适合吃火锅。☁️",
            "杭州": "杭州今天多云,温度 25°C,西湖值得一去。🌥️",
        }

        for city, weather in weather_data.items():
            if city in text:
                return weather

        return "抱歉,暂不支持该城市的天气查询。请试试北京、上海、广州、深圳、成都、杭州。"

    def handle_task(self, task):
        """
        A2A 任务处理入口 ------ 这是 A2AServer 要求实现的核心方法。

        流程:
        1. 从 task.message 中提取用户文本
        2. 调用业务逻辑获取结果
        3. 将结果封装到 task.artifacts 中
        4. 更新 task.status 为 COMPLETED
        5. 返回 task
        """
        print("📨 [天气专家] 收到任务")

        # Step 1: 提取用户消息
        text = (task.message or {}).get("content", {}).get("text", "")
        print(f"   用户输入:{text}")

        # Step 2: 调用业务逻辑
        result = self.weather_service(text)

        # Step 3: 封装结果到 artifacts(工作成果物)
        task.artifacts = [{
            "parts": [{"type": "text", "text": result}]
        }]

        # Step 4: 更新任务状态为已完成
        task.status = TaskStatus(state=TaskState.COMPLETED)

        print(f"✅ [天气专家] 任务完成,返回:{result}")
        return task


# ========== 3. 启动服务 ==========
if __name__ == "__main__":
    server = WeatherExpertServer()
    print(f"✅ [天气专家] 启动成功,监听地址:{server.agent_card.url}")
    run_server(server, host="127.0.0.1", port=5008, debug=True)

启动方式:

复制代码
python weather_agent.py 

启动成功后,你会看到类似日志:

python 复制代码
✅ [天气专家] 启动成功,监听地址:http://127.0.0.1:5008
 * Running on http://127.0.0.1:5008

单独测试天气专家:

python 复制代码
"""测试天气专家 Agent(需先启动 weather_agent.py)"""
import asyncio
import uuid
from python_a2a import A2AClient, Message, MessageRole, Task, TextContent

WEATHER_AGENT_URL = "http://127.0.0.1:5008"

async def test_weather_agent(text: str = "北京天气怎么样?"):
    # 构建消息
    message = Message(
        role=MessageRole.USER,
        content=TextContent(text=text)
    )
    # 构建任务
    task = Task(
        id=f"test-{uuid.uuid4()}",
        message=message.to_dict()
    )

    # 发送任务并等待结果
    client = A2AClient(WEATHER_AGENT_URL)
    result_task = await client.send_task_async(task)

    # 提取结果
    reply = result_task.artifacts[0]["parts"][0]["text"]
    print(f"请求:{text}")
    print(f"回复:{reply}")

if __name__ == "__main__":
    asyncio.run(test_weather_agent())

预期输出:

python 复制代码
请求:北京天气怎么样?
回复:北京今天晴空万里,温度 28°C,微风,适合出行!☀️

4.3 创建"票务专家"Agent

与天气专家同理,票务专家专注于火车票预订

代码文件:ticket_agent.py

python 复制代码
"""
票务专家 Agent - A2A Server
功能:接收订票请求,返回模拟的订票结果。
"""

from python_a2a import (
    A2AServer, run_server,
    AgentCard, AgentSkill,
    TaskStatus, TaskState
)

# ========== 1. 定义 Agent 名片 ==========
agent_card = AgentCard(
    name="TicketExpert",
    description="负责预订火车票的专家,能够根据出发地和目的地预订车票。",
    url="http://127.0.0.1:5009",
    skills=[
        AgentSkill(
            name="book_train_ticket",
            description="根据出发地和目的地预订火车票。",
            examples=[
                "帮我订北京到上海的火车票",
                "买张去广州的票",
                "订一张后天从深圳到成都的高铁"
            ]
        )
    ]
)


# ========== 2. 实现 Server 逻辑 ==========
class TicketExpertServer(A2AServer):

    def __init__(self):
        super().__init__(agent_card=agent_card)

    def ticket_service(self, text: str) -> str:
        """订票业务逻辑(模拟版)"""
        # 预设线路
        routes = {
            ("北京", "上海"): "G101次列车,北京南站 → 上海虹桥站,08:00发车,12:28到达",
            ("上海", "北京"): "G102次列车,上海虹桥站 → 北京南站,09:00发车,13:28到达",
            ("北京", "广州"): "G71次列车,北京西站 → 广州南站,07:00发车,14:38到达",
            ("广州", "深圳"): "C7001次列车,广州南站 → 深圳北站,08:00发车,08:35到达",
        }

        for (start, end), detail in routes.items():
            if start in text and end in text:
                return f"✅ 已为您预订{detail},座位号:10车05A,请准时乘车!"

        return "❌ 抱歉,目前仅支持以下线路:北京⇄上海、北京→广州、广州→深圳。"

    def handle_task(self, task):
        print("📨 [票务专家] 收到任务")

        text = (task.message or {}).get("content", {}).get("text", "")
        print(f"   用户输入:{text}")

        result = self.ticket_service(text)

        task.artifacts = [{
            "parts": [{"type": "text", "text": result}]
        }]
        task.status = TaskStatus(state=TaskState.COMPLETED)

        print(f"✅ [票务专家] 任务完成,返回:{result}")
        return task


# ========== 3. 启动服务 ==========
if __name__ == "__main__":
    server = TicketExpertServer()
    print(f"✅ [票务专家] 启动成功,监听地址:{server.agent_card.url}")
    run_server(server, host="127.0.0.1", port=5009, debug=True)

4.4 创建"总控Agent"------团队的大脑

总控 Agent 是整个系统的指挥中心。它本身不生产任何业务能力,而是负责:

  1. 1.维护专家通讯录(AgentNetwork)
  2. 2.接收用户请求并分析意图
  3. 3.分发任务给对应的专家 Agent
  4. 4.汇总结果并回复用户

代码文件:orchestrator.py

python 复制代码
"""
总控 Agent(Orchestrator)
角色:A2A Client,负责接收用户请求、分发任务、汇总结果。
"""

import asyncio
import uuid
from python_a2a import (
    AgentNetwork, Task,
    Message, MessageRole, TextContent
)

# ========== 1. 专家通讯录 ==========
network = AgentNetwork(name="TravelTeam")
network.add("WeatherExpert", "http://127.0.0.1:5008")
network.add("TicketExpert", "http://127.0.0.1:5009")


class Orchestrator:
    """
    总控Agent - 旅行规划团队的大脑。

    职责:
    - 分析用户请求中的关键词,判断需要调用哪些专家
    - 并发分发任务(使用 asyncio.gather)
    - 汇总所有专家的结果,组装最终回复
    """

    def __init__(self, network):
        self.network = network

    def make_task(self, text: str) -> Task:
        """构建 A2A 标准任务对象"""
        message = Message(
            role=MessageRole.USER,
            content=TextContent(text=text)
        )
        return Task(
            id=f"task-{uuid.uuid4()}",
            message=message.to_dict()
        )

    async def call_agent(self, agent_name: str, text: str):
        """
        调用单个专家 Agent。

        Args:
            agent_name: 专家名称(对应通讯录中的 key)
            text: 用户原文

        Returns:
            (agent_name, result_text) 元组
        """
        print(f"📤 [总控Agent] 正在向【{agent_name}】发送任务...")

        client = self.network.get_agent(agent_name)
        task = self.make_task(text)
        result_task = await client.send_task_async(task)

        result = result_task.artifacts[0]["parts"][0]["text"]
        print(f"📥 [总控Agent] 收到【{agent_name}】的回复")
        return agent_name, result

    async def handle_user_request(self, text: str) -> str:
        """
        处理用户请求的主流程。

        策略(基础版用关键词匹配,进阶版用 LLM 意图识别):
        - 包含"天气" → 调用天气专家
        - 包含"订票"或"火车票" → 调用票务专家
        - 两者可能同时出现 → 并发调用
        """
        print("\n" + "=" * 60)
        print(f"👋 [总控Agent] 收到用户请求:{text}")

        # ---- 意图分析(基础版:关键词匹配) ----
        jobs = []

        if "天气" in text:
            print("🔍 [总控Agent] 分析意图 → 需要查询天气")
            jobs.append(self.call_agent("WeatherExpert", text))

        if "订票" in text or "火车票" in text:
            print("🔍 [总控Agent] 分析意图 → 需要预订火车票")
            jobs.append(self.call_agent("TicketExpert", text))

        if not jobs:
            return "抱歉,我目前只会查天气和订火车票。请说"查天气"或"订票"试试。"

        # ---- 并发执行所有任务 ----
        results = await asyncio.gather(*jobs, return_exceptions=True)

        # ---- 汇总结果 ----
        final_parts = []
        for item in results:
            if isinstance(item, Exception):
                final_parts.append(f"❌ 调用失败:{item}")
            else:
                agent_name, result = item
                final_parts.append(f"【{agent_name}】{result}")

        final_response = "\n\n".join(final_parts)

        print("\n✅ [总控Agent] 任务汇总完成")
        print("=" * 60)

        return final_response


async def main():
    # 打印通讯录
    print("✅ [总控Agent] 专家通讯录已加载:")
    for agent in network.list_agents():
        print(f"   📋 {agent['name']}: {agent['description']}")

    orchestrator = Orchestrator(network)

    print("\n" + "=" * 60)
    print("🚀 欢迎使用旅行规划总控Agent!")
    print("💡 我可以帮您:查天气、订火车票")
    print("📝 输入 'quit' 退出")
    print("=" * 60)

    while True:
        text = input("\n🧑 您:").strip()
        if not text:
            continue
        if text.lower() == "quit":
            print("👋 再见!")
            break

        result = await orchestrator.handle_user_request(text)
        print(f"\n🤖 助手:\n{result}")


if __name__ == "__main__":
    asyncio.run(main())

4.5 运行与验证

启动顺序很重要------先启动两个专家,再启动总控:

python 复制代码
# 终端 1:启动天气专家
python weather_agent.py

# 终端 2:启动票务专家
python ticket_agent.py

# 终端 3:启动总控 Agent
python orchestrator.py

场景一:查询天气

python 复制代码
🧑 您:帮我查一下上海的天气

============================================================
👋 [总控Agent] 收到用户请求:帮我查一下上海的天气
🔍 [总控Agent] 分析意图 → 需要查询天气
📤 [总控Agent] 正在向【WeatherExpert】发送任务...
📨 [天气专家] 收到任务
   用户输入:帮我查一下上海的天气
✅ [天气专家] 任务完成
📥 [总控Agent] 收到【WeatherExpert】的回复

✅ [总控Agent] 任务汇总完成
============================================================

🤖 助手:
【WeatherExpert】上海今天多云转晴,温度 25°C,空气质量良好。🌤️

场景二:复合任务(同时查天气 + 订票)

python 复制代码
🧑 您:我要去上海出差,帮我订票并看看天气如何?

============================================================
👋 [总控Agent] 收到用户请求:我要去上海出差,帮我订票并看看天气如何?
🔍 [总控Agent] 分析意图 → 需要查询天气
🔍 [总控Agent] 分析意图 → 需要预订火车票
📤 [总控Agent] 正在向【WeatherExpert】发送任务...
📤 [总控Agent] 正在向【TicketExpert】发送任务...
📨 [天气专家] 收到任务
📨 [票务专家] 收到任务
📥 [总控Agent] 收到【WeatherExpert】的回复
📥 [总控Agent] 收到【TicketExpert】的回复

✅ [总控Agent] 任务汇总完成
============================================================

🤖 助手:
【WeatherExpert】上海今天多云转晴,温度 25°C,空气质量良好。🌤️

【TicketExpert】✅ 已为您预订G101次列车,北京南站 → 上海虹桥站,08:00发车,12:28到达,座位号:10车05A,请准时乘车!

关键观察 :两个任务是通过 asyncio.gather并发执行的,这意味着查天气和订票是同时进行的,总耗时取决于最慢的那个,而非两者之和。这正是 A2A 协议"异步优先"设计理念的体现。


五、进阶实战------集成 LLM 实现真实业务

基础版用关键词匹配来判断意图、返回结果,逻辑过于简单。在进阶版中,我们引入**大语言模型(LLM)**来实现:

  • 天气专家:使用 LangChain Function Calling 自动调用天气工具
  • 票务专家:使用 LangGraph 工作流实现信息抽取 + 订票的多步骤推理
  • 总控 Agent:使用 LLM 意图识别替代关键词匹配

5.1 整体预览

进阶版的系统架构保持不变,但每个组件的"大脑"从硬编码逻辑升级为 LLM 驱动:

python 复制代码
┌────────────────────────────────────────────────────┐
│                    总控 Agent                        │
│   ┌──────────────────────────────────┐              │
│   │ LLM 意图识别                      │              │
│   │ "need_weather: true,              │              │
│   │  need_ticket: true"               │              │
│   └────────────┬─────────┬───────────┘              │
│                │         │                           │
│         ┌──────▼──┐  ┌───▼───────┐                  │
│         │天气专家  │  │票务专家    │                  │
│         │         │  │           │                  │
│         │LLM +    │  │LLM +      │                  │
│         │Function  │  │LangGraph  │                  │
│         │Calling   │  │工作流     │                  │
│         └─────────┘  └───────────┘                  │
└────────────────────────────────────────────────────┘

5.2 通用 LLM 配置

文件:common/llm.py

python 复制代码
"""
通用 LLM 配置模块
所有需要调用大模型的组件共用此配置,避免重复初始化。
支持 OpenAI 兼容接口(如 DeepSeek、通义千问、本地 Ollama 等)。
"""

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

# 加载 .env 文件(从项目根目录向上查找)
env_path = os.path.join(
    os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
    ".env"
)
load_dotenv(env_path)

MODEL_API_KEY = os.getenv("MODEL_API_KEY")
MODEL_BASE_URL = os.getenv("MODEL_BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")

my_llm = ChatOpenAI(
    api_key=MODEL_API_KEY,
    base_url=MODEL_BASE_URL,
    model=MODEL_NAME,
    temperature=0,  # 设为 0 保证意图识别和信息抽取的稳定性
)

if __name__ == "__main__":
    # 快速测试 LLM 连通性
    print(f"模型:{MODEL_NAME}")
    print(f"接口:{MODEL_BASE_URL}")
    print("测试输出:", end="")
    for chunk in my_llm.stream("你好,请用一句话介绍你自己。"):
        print(chunk.content, flush=True, end="")
    print()

.env 文件示例:

python 复制代码
MODEL_API_KEY=sk-your-api-key-here
MODEL_BASE_URL=https://api.openai.com/v1
MODEL_NAME=gpt-4o-mini

5.3 天气专家进阶:Function Calling

使用 LangChain 的 @tool 装饰器定义天气查询工具,然后通过 Function Calling 让 LLM 自动决定何时调用以及如何解析用户输入。

文件:__001__weather_agent/__001__parse_weather.py

python 复制代码
"""
天气查询逻辑 ------ 基于 LangChain Function Calling
LLM 会自动判断用户是否在问天气,并提取城市名称,调用对应工具。
"""

from langchain_core.tools import tool
from langchain_core.messages import HumanMessage

from common.llm import my_llm


# ========== 1. 定义工具 ==========
@tool
def get_weather(city: str) -> str:
    """根据城市名称返回该城市的当前天气情况。支持全国主要城市。"""

    # 模拟天气数据(实际项目中替换为真实 API,如和风天气、OpenWeatherMap)
    weather_db = {
        "北京": "晴,28°C,微风",
        "上海": "多云,22°C,东南风3级",
        "广州": "小雨,19°C,湿度85%",
        "深圳": "阵雨,26°C,体感温度29°C",
        "成都": "阴,24°C,空气质量良",
        "杭州": "多云,25°C,适合出行",
        "南京": "晴,27°C,紫外线较强",
        "武汉": "雷阵雨,23°C,请注意安全",
        "西安": "晴,30°C,高温预警",
        "重庆": "雾,21°C,能见度较低",
        "天津": "多云,26°C,海风较大",
        "苏州": "小雨,20°C,出门带伞",
        "长沙": "阴,22°C,湿度适中",
        "郑州": "晴,29°C,干燥",
        "青岛": "晴,24°C,海滨城市空气清新",
        "厦门": "多云,27°C,适合旅游",
        "昆明": "晴,23°C,四季如春",
        "大连": "多云,18°C,海风凉爽",
        "哈尔滨": "晴,15°C,早晚温差大",
        "乌鲁木齐": "晴,26°C,干燥少雨",
    }

    return weather_db.get(city, f"未找到 {city} 的天气信息,目前支持全国主要省会及一线城市。")


# ========== 2. 组装 Agent(LLM + Tools) ==========
tools = [get_weather]

# 绑定工具到 LLM,让模型具备 Function Calling 能力
llm_with_tools = my_llm.bind_tools(tools)


def parse_weather_text(user_input: str) -> str:
    """
    天气查询入口函数。

    流程:
    1. 将用户输入发送给绑定了工具的 LLM
    2. LLM 自动判断是否需要调用 get_weather 工具
    3. 如果需要,LLM 提取 city 参数并调用工具
    4. 将工具返回结果再次交给 LLM,生成自然语言回复
    """
    # 第一轮:LLM 分析是否需要调用工具
    response = llm_with_tools.invoke([HumanMessage(content=user_input)])

    # 如果 LLM 调用了工具
    if response.tool_calls:
        tool_results = []
        for tool_call in response.tool_calls:
            # 执行工具调用
            result = get_weather.invoke(tool_call["args"])
            tool_results.append(result)

        # 第二轮:将工具结果交给 LLM 生成最终回复
        from langchain_core.messages import ToolMessage
        messages = [
            HumanMessage(content=user_input),
            response,
            ToolMessage(content="\n".join(tool_results), tool_call_id=response.tool_calls[0]["id"])
        ]
        final_response = llm_with_tools.invoke(messages)
        return final_response.content

    # 如果 LLM 认为不需要调用工具,直接返回回复
    return response.content


if __name__ == "__main__":
    # 测试用例
    test_inputs = [
        "北京的天气怎么样呢?",
        "查一下上海今天天气如何",
        "广州下雨吗?",
        "你好,今天过得怎么样?",  # 非天气问题
    ]
    for inp in test_inputs:
        print(f"\n输入:{inp}")
        print(f"输出:{parse_weather_text(inp)}")
        print("-" * 40)

天气 Agent Server 不需要大改,只需替换 weather_service 方法的实现:

文件:__001__weather_agent/__002__weather_agent.py

python 复制代码
"""
天气专家 Agent(进阶版)
核心变化:weather_service 从硬编码逻辑替换为 LLM Function Calling。
"""

from python_a2a import (
    A2AServer, run_server,
    AgentCard, AgentSkill,
    TaskStatus, TaskState
)

from __001__weather_agent.__001__parse_weather import parse_weather_text

agent_card = AgentCard(
    name="WeatherExpert",
    description="负责查询天气的专家,能够根据城市名称返回实时天气信息。",
    url="http://127.0.0.1:5008",
    skills=[
        AgentSkill(
            name="get_weather",
            description="根据城市查询天气,支持全国主要城市。",
            examples=["北京天气怎么样?", "查一下上海天气", "广州今天下雨吗?"]
        )
    ]
)


class WeatherExpertServer(A2AServer):
    def __init__(self):
        super().__init__(agent_card=agent_card)

    def weather_service(self, text: str) -> str:
        """调用 LLM Function Calling 处理天气查询"""
        return parse_weather_text(text)

    def handle_task(self, task):
        print("📨 [天气专家] 收到任务")
        text = (task.message or {}).get("content", {}).get("text", "")
        print(f"   用户输入:{text}")

        result = self.weather_service(text)

        task.artifacts = [{
            "parts": [{"type": "text", "text": result}]
        }]
        task.status = TaskStatus(state=TaskState.COMPLETED)

        print(f"✅ [天气专家] 已返回:{result}")
        return task


if __name__ == "__main__":
    server = WeatherExpertServer()
    print(f"✅ [天气专家] 启动成功:{server.agent_card.url}")
    run_server(server, host="0.0.0.0", port=5008, debug=True)

5.4 票务专家进阶:LangGraph 工作流

票务专家的逻辑更复杂,需要:

  1. 1.信息抽取:从自然语言中提取出发城市、目的城市、出行时间
  2. 2.信息校验:检查是否有缺失字段,必要时提示用户补充
  3. 3.执行订票:组装订票结果

使用 LangGraph 构建一个两节点的线性工作流。

文件:__002__ticket_agent/__001__ticket_workflow.py

python 复制代码
"""
订票工作流 ------ 基于 LangGraph 的两步推理链

工作流:
  START → extract_city_node(信息抽取) → order_ticket_node(订票/补全提示) → END
"""

from typing import TypedDict

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langgraph.constants import END, START
from langgraph.graph import StateGraph
from pydantic import BaseModel, Field

from common.llm import my_llm


# ========== 1. 定义状态结构 ==========
class AgentState(TypedDict):
    """LangGraph 工作流的共享状态"""
    input: str              # 用户原始输入
    start_city: str         # 抽取出的出发城市
    end_city: str           # 抽取出的目的城市
    departure_time: str     # 抽取出的出行时间
    output: str             # 最终输出结果


# ========== 2. 定义信息抽取的 Pydantic Schema ==========
class TicketBookingExtract(BaseModel):
    """订票信息的结构化表示"""
    start_city: str = Field(
        default="",
        description="出发城市,如北京、上海;用户未提及或无法确定时填空字符串",
    )
    end_city: str = Field(
        default="",
        description="目的城市;用户未提及或无法确定时填空字符串",
    )
    departure_time: str = Field(
        default="",
        description="预定出行时间,保留用户原意,如 2026-05-20、明天上午、下周一;未提及或无法确定时填空字符串",
    )


# ========== 3. 构建信息抽取 Chain ==========
_extract_parser = PydanticOutputParser(pydantic_object=TicketBookingExtract)

_extract_prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "你是火车票预订信息抽取助手。从用户自然语言中提取出发城市、目的城市和出行/预定时间。"
        "城市使用标准中文地名(如'北京'而非'帝都'),不要编造用户未提及的信息。"
        "任一字段抽不出来时输出空字符串 \"\",不要用'未知''无'等占位词。"
    ),
    (
        "user",
        "请从以下内容抽取订票信息。\n"
        "{format_instructions}\n\n"
        "用户输入:{user_input}",
    ),
]).partial(format_instructions=_extract_parser.get_format_instructions())

_extract_chain = _extract_prompt | my_llm | _extract_parser


# 无效值集合,用于清洗 LLM 输出
_PLACEHOLDER_VALUES = frozenset(
    {"未知", "无", "未提及", "null", "none", "n/a", "na", "不详", "待定"}
)


def _clean_field(value: str) -> str:
    """清洗 LLM 输出中的无效占位值"""
    s = (value or "").strip()
    if not s or s.lower() in _PLACEHOLDER_VALUES:
        return ""
    return s


# ========== 4. LangGraph 节点函数 ==========
def extract_city_node(state: AgentState) -> AgentState:
    """节点1:使用 LLM + Pydantic Parser 从用户输入中抽取订票信息"""
    user_input = state.get("input", "")
    print(f"   [信息抽取] 正在解析用户输入:{user_input}")

    extracted = _extract_chain.invoke({"user_input": user_input})

    state["start_city"] = _clean_field(extracted.start_city)
    state["end_city"] = _clean_field(extracted.end_city)
    state["departure_time"] = _clean_field(extracted.departure_time)

    print(f"   [信息抽取] 结果 → 出发:{state['start_city'] or '未提取'},"
          f"目的:{state['end_city'] or '未提取'},"
          f"时间:{state['departure_time'] or '未提取'}")
    return state


def order_ticket_node(state: AgentState) -> AgentState:
    """节点2:校验信息完整性,执行订票或提示补充"""
    start_city = state.get("start_city", "")
    end_city = state.get("end_city", "")
    departure_time = state.get("departure_time", "")

    # 检查缺失字段
    missing = []
    if not start_city:
        missing.append("出发城市")
    if not end_city:
        missing.append("目的城市")
    if not departure_time:
        missing.append("出行时间")

    if missing:
        # 信息不完整,提示用户补充
        state["output"] = (
            f"订票信息不完整,还需要您补充以下信息:{'、'.join(missing)}。\n"
            f"请提供完整信息后重新下单。"
        )
    else:
        # 信息完整,执行订票(模拟)
        state["output"] = (
            f"✅ 已为您成功预订火车票!\n"
            f"   🚄 线路:{start_city} → {end_city}\n"
            f"   📅 时间:{departure_time}\n"
            f"   💺 座位:08车 06F(靠窗)\n"
            f"   💰 票价:二等座 ¥553.0"
        )

    print(f"   [订票节点] 输出:{state['output'][:50]}...")
    return state


# ========== 5. 构建 LangGraph ==========
def build_langgraph():
    """构建并编译 LangGraph 工作流"""
    graph_builder = StateGraph(AgentState)

    # 添加节点
    graph_builder.add_node("extract_city_node", extract_city_node)
    graph_builder.add_node("order_ticket_node", order_ticket_node)

    # 添加边
    graph_builder.add_edge(START, "extract_city_node")
    graph_builder.add_edge("extract_city_node", "order_ticket_node")
    graph_builder.add_edge("order_ticket_node", END)

    return graph_builder.compile()


# 编译工作流(模块加载时执行一次)
graph = build_langgraph()


def parse_ticket_text(text: str) -> str:
    """订票入口函数"""
    result = graph.invoke({"input": text})
    return result.get("output", "处理出错。")


if __name__ == "__main__":
    # 测试用例
    test_cases = [
        "帮我订一张后天从北京到上海的火车票",
        "我想买张去广州的票",                 # 缺少出发城市
        "从深圳到成都,下周一出发",             # 完整信息
    ]
    for case in test_cases:
        print(f"\n输入:{case}")
        print(f"输出:{parse_ticket_text(case)}")
        print("-" * 50)

票务 Agent Server:

文件:__002__ticket_agent/__002__ticket_agent.py

python 复制代码
"""
票务专家 Agent(进阶版)
核心变化:ticket_service 从硬编码替换为 LangGraph 工作流。
"""

from python_a2a import (
    A2AServer, run_server,
    AgentCard, AgentSkill,
    TaskStatus, TaskState
)

from __002__ticket_agent.__001__ticket_workflow import parse_ticket_text

agent_card = AgentCard(
    name="TicketExpert",
    description="负责预订火车票的专家,能够理解自然语言并自动提取订票信息。",
    url="http://127.0.0.1:5009",
    skills=[
        AgentSkill(
            name="book_train_ticket",
            description="根据出发地和目的地预订火车票,支持自然语言输入。",
            examples=[
                "帮我订北京到上海的火车票",
                "买张去广州的票",
                "订一张后天从深圳到成都的高铁"
            ]
        )
    ]
)


class TicketExpertServer(A2AServer):
    def __init__(self):
        super().__init__(agent_card=agent_card)

    def ticket_service(self, text: str) -> str:
        """调用 LangGraph 工作流处理订票请求"""
        return parse_ticket_text(text)

    def handle_task(self, task):
        print("📨 [票务专家] 收到任务")
        text = (task.message or {}).get("content", {}).get("text", "")
        print(f"   用户输入:{text}")

        result = self.ticket_service(text)

        task.artifacts = [{
            "parts": [{"type": "text", "text": result}]
        }]
        task.status = TaskStatus(state=TaskState.COMPLETED)

        print(f"✅ [票务专家] 已返回:{result[:50]}...")
        return task


if __name__ == "__main__":
    server = TicketExpertServer()
    print(f"✅ [票务专家] 启动成功:{server.agent_card.url}")
    run_server(server, host="0.0.0.0", port=5009, debug=True)

5.5 总控Agent进阶:意图识别路由

进阶版的总控 Agent 不再使用关键词匹配,而是通过 LLM 进行结构化意图识别

文件:__003__orchestrator/__001__weather_ticket_intent.py

python 复制代码
"""
意图识别模块 ------ 使用 LLM + Pydantic 判断用户是否需要查天气或订票
"""

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

from common.llm import my_llm


class WeatherTicketIntent(BaseModel):
    """意图识别的结构化输出"""
    need_weather: bool = Field(
        description="用户是否需要查询某地天气、气温、是否下雨等气象信息"
    )
    need_ticket: bool = Field(
        description="用户是否需要预订或购买火车票、高铁票、机票等出行票务"
    )


# 构建意图识别 Chain
_intent_parser = PydanticOutputParser(pydantic_object=WeatherTicketIntent)

_intent_prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "你是旅行助手意图识别模块。根据用户的一句话,判断是否需要查天气、是否需要订票。\n"
        "规则:\n"
        "1. 只根据用户明确或合理隐含的需求判断,不要臆测\n"
        "2. 与天气、票务无关的闲聊,两项均为 false\n"
        "3. 同一句话里可能同时需要查天气和订票,此时对应字段均为 true\n"
        "4. '出差''旅行'等隐含出行需求时,need_ticket 可为 true\n"
        "5. 提到某个城市但没有明确出行意图时,不要设 need_ticket 为 true"
    ),
    (
        "user",
        "请判断以下用户输入的意图。\n"
        "{format_instructions}\n\n"
        "用户输入:{user_input}",
    ),
]).partial(format_instructions=_intent_parser.get_format_instructions())

_intent_chain = _intent_prompt | my_llm | _intent_parser


def detect_weather_ticket_intent(user_input: str) -> WeatherTicketIntent:
    """用 LLM 判断用户输入是否包含查天气或订票需求。"""
    text = (user_input or "").strip()
    if not text:
        return WeatherTicketIntent(need_weather=False, need_ticket=False)
    return _intent_chain.invoke({"user_input": text})


if __name__ == "__main__":
    # 测试意图识别
    test_samples = [
        "北京今天天气怎么样?",                        # weather=True, ticket=False
        "帮我订一张后天从北京到上海的火车票",            # weather=False, ticket=True
        "查一下广州天气,再订明天去深圳的票",            # weather=True, ticket=True
        "我要去上海出差,帮我订票并看看天气如何",        # weather=True, ticket=True
        "你好,介绍一下你自己",                          # weather=False, ticket=False
        "北京有什么好玩的?",                            # weather=False, ticket=False
    ]
    for s in test_samples:
        intent = detect_weather_ticket_intent(s)
        print(f"输入:{s}")
        print(f"  → 查天气={intent.need_weather},订票={intent.need_ticket}\n")

总控 Agent 主文件(进阶版):

文件:__003__orchestrator/__002__orchestrator.py

python 复制代码
"""
总控 Agent(进阶版)
核心变化:意图判断从关键词匹配升级为 LLM 结构化识别。
"""

import asyncio
import uuid
from python_a2a import (
    AgentNetwork, Task,
    Message, MessageRole, TextContent
)

from __003__orchestrator.__001__weather_ticket_intent import (
    detect_weather_ticket_intent,
)

# ========== 专家通讯录 ==========
network = AgentNetwork(name="TravelTeam")
network.add("WeatherExpert", "http://127.0.0.1:5008")
network.add("TicketExpert", "http://127.0.0.1:5009")


class Orchestrator:
    def __init__(self, network):
        self.network = network

    def make_task(self, text: str) -> Task:
        message = Message(
            role=MessageRole.USER,
            content=TextContent(text=text)
        )
        return Task(
            id=f"task-{uuid.uuid4()}",
            message=message.to_dict()
        )

    async def call_agent(self, agent_name: str, text: str):
        print(f"📤 [总控Agent] 正在调用【{agent_name}】")
        client = self.network.get_agent(agent_name)
        task = self.make_task(text)
        result_task = await client.send_task_async(task)
        result = result_task.artifacts[0]["parts"][0]["text"]
        print(f"📥 [总控Agent] 收到【{agent_name}】回复")
        return agent_name, result

    async def handle_user_request(self, text: str) -> str:
        print("\n" + "=" * 60)
        print(f"👋 [总控Agent] 收到用户请求:{text}")

        # ---- LLM 意图识别 ----
        intent = detect_weather_ticket_intent(text)
        print(f"🔍 [总控Agent] 意图识别结果:查天气={intent.need_weather},订票={intent.need_ticket}")

        jobs = []
        if intent.need_weather:
            jobs.append(self.call_agent("WeatherExpert", text))
        if intent.need_ticket:
            jobs.append(self.call_agent("TicketExpert", text))

        if not jobs:
            return "抱歉,我目前只会查天气和订火车票。您可以这样说:\n• "北京天气怎么样?"\n• "帮我订一张去上海的火车票""

        results = await asyncio.gather(*jobs, return_exceptions=True)

        final_parts = []
        for item in results:
            if isinstance(item, Exception):
                final_parts.append(f"❌ 调用失败:{item}")
            else:
                agent_name, result = item
                final_parts.append(f"【{agent_name}】\n{result}")

        final_response = "\n\n".join(final_parts)

        print("\n✅ [总控Agent] 汇总完成")
        print("=" * 60)
        return final_response


async def main():
    print("✅ [总控Agent] 通讯录已加载:")
    for agent in network.list_agents():
        print(f"   📋 {agent['name']}: {agent['description']}")

    orchestrator = Orchestrator(network)

    print("\n" + "=" * 60)
    print("🚀 欢迎使用旅行规划总控Agent(LLM 进阶版)!")
    print("💡 我可以帮您:查天气、订火车票")
    print("📝 输入 'quit' 退出")
    print("=" * 60)

    while True:
        text = input("\n🧑 您:").strip()
        if not text:
            continue
        if text.lower() == "quit":
            print("👋 再见!")
            break

        result = await orchestrator.handle_user_request(text)
        print(f"\n🤖 助手:\n{result}")


if __name__ == "__main__":
    asyncio.run(main())

进阶版运行效果:

python 复制代码
🧑 您:我要去上海出差,帮我看看天气顺便订个票

============================================================
👋 [总控Agent] 收到用户请求:我要去上海出差,帮我看看天气顺便订个票
🔍 [总控Agent] 意图识别结果:查天气=True,订票=True
📤 [总控Agent] 正在调用【WeatherExpert】
📤 [总控Agent] 正在调用【TicketExpert】
📨 [天气专家] 收到任务
📨 [票务专家] 收到任务
   [信息抽取] 正在解析用户输入:我要去上海出差...
   [信息抽取] 结果 → 出发:(未提及),目的:上海,时间:(未提及)
📥 [总控Agent] 收到【WeatherExpert】回复
📥 [总控Agent] 收到【TicketExpert】回复

✅ [总控Agent] 汇总完成
============================================================

🤖 助手:
【WeatherExpert】
上海今天多云,温度22°C,东南风3级,适合出行。

【TicketExpert】
订票信息不完整,还需要您补充以下信息:出发城市、出行时间。
请提供完整信息后重新下单。

注意看:LLM 驱动的票务专家能够智能识别出用户只说了"去上海"但没说"从哪出发"和"什么时间",会主动提示补充信息,而不是盲目返回一个错误结果。这就是 LLM 相比硬编码的巨大优势。


六、A2A 与 MCP 的关系辨析

在学习 A2A 的过程中,很多人会将它与 Anthropic 的 MCP(Model Context Protocol) 混淆。这里做一个清晰的辨析:

维度 MCP (Model Context Protocol) A2A (Agent-to-Agent)
提出者 Anthropic (2024年11月) Google (2025年4月)
解决的问题 Agent 如何调用工具和数据源 Agent 之间如何互相通信和协作
通信对象 Agent ↔ Tool(工具) Agent ↔ Agent(智能体)
类比 工人和工具箱之间的接口标准 工人与工人之间的协作流程
关注点 工具发现、数据注入、上下文管理 任务分发、状态流转、结果汇总
典型场景 LLM 调用数据库查询、调用搜索引擎 总控Agent调度天气Agent和票务Agent

两者是互补关系,而非竞争关系

  • 一个 Agent 可以通过 MCP 接入各种工具(数据库、API、文件系统)。
  • 多个 Agent 之间可以通过 A2A 互相发现、互相委托任务。
  • 在复杂系统中,MCP 负责"纵向"的工具调用,A2A 负责"横向"的智能体协作

七、总结与展望

7.1 核心收获

通过本教程的实战,我们掌握了以下关键知识:

概念层面:

  • A2A 是什么:一套让 AI Agent 之间标准化通信和协作的开放协议
  • 核心组件:Agent(工作者)、AgentCard(名片)、AgentSkill(技能标签)、Task(工单)、Artifact(成果物)
  • 通信流程:Client Agent 通过 AgentNetwork 发现 Server Agent → 发送 Task → 接收 Artifact → 汇总响应

工程层面:

  • 使用 python-a2a 快速搭建 A2A Server 和 Client
  • 使用 LangChain Function Calling 实现工具调用型 Agent
  • 使用 LangGraph 构建多步骤推理型 Agent
  • 使用 Pydantic + LLM 实现结构化意图识别

架构层面:

  • 星型拓扑的编排模式(Orchestrator 模式)
  • asyncio.gather 实现任务并发执行
  • 各 Agent 独立部署、独立扩展的微服务思想

7.2 实际生产中的扩展方向

方向 说明
接入真实 API 天气对接和风天气/高德天气,票务对接 12306 或携程开放平台
增加更多专家 酒店预订 Agent、景点推荐 Agent、行程日历 Agent 等
错误重试机制 为 Task 增加超时控制和重试策略
Agent 动态发现 通过服务注册中心(如 Consul)实现 Agent 的自动注册和发现
流式响应 利用 A2A 的 Streaming 能力,实时返回处理进度
多轮对话 利用 TaskState.INPUT_REQUIRED 实现任务中断-补充-继续的交互模式
安全认证 为 AgentCard 增加认证机制,防止未授权的 Agent 访问
相关推荐
yoyo_zzm6 小时前
四大编程技术对比:PHP、Java、Python与HTML
java·python·php
C137的本贾尼6 小时前
融会贯通:打造完整的 RAG 问答链
python·langchain
deephub6 小时前
构建一个可自我改进的多 Agent RAG 系统:架构、评估,以及带人工审核的 Prompt 反馈闭环
人工智能·python·大语言模型·rag
信竞星球_少儿编程题库6 小时前
2026年全国信息素养大赛算法应用主题赛 丝路新城 Python 模拟卷(三)
开发语言·python·算法
进击切图仔6 小时前
python 工程使用 .env getenv 安全加载环境变量(备忘)
chrome·python·安全
TechWayfarer7 小时前
出海APP本地化实战:基于IP归属地API的网关路由与多语言自动切换方案
网络·python·网络协议·tcp/ip
wj3055853787 小时前
课程 5:将官方 LTX-2.3 工作流改造成 GGUF 主模型工作流
python·cuda·comfyui
Muyuan19987 小时前
31.Cursor 初体验:用 AI Agent 给 PaperPilot 做一次最小工程重构
人工智能·python·重构·django·fastapi·faiss
范范@7 小时前
python基础-5大容器
开发语言·python