上手A2A实战(上):从零搭建你的第一个A2A智能体

1,590 阅读9分钟

在前面三篇文章中,我们已经了解了A2A协议的基本概念、核心组件以及它与MCP的协同关系。今天,我们将进入实战环节,带领大家从零开始搭建一个A2A兼容的智能体。本篇文章将集中在环境配置和基础组件构建上,手把手带你入门A2A开发。

准备工作:搭建开发环境

开始开发A2A智能体前,我们需要准备好开发环境。这个过程并不复杂,只需遵循以下几个步骤:

1. 安装Python

A2A SDK使用Python开发,因此首先确保您的系统已安装Python 3.8或更高版本:

# 检查Python版本
python --version

如果需要安装或升级Python,可以访问Python官网下载最新版本。

2. 创建虚拟环境

为了避免依赖冲突,建议为A2A项目创建独立的虚拟环境:

# 创建项目目录
mkdir my-a2a-agent
cd my-a2a-agent

# 创建虚拟环境
python -m venv venv

# 激活虚拟环境
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate

3. 安装A2A SDK

激活虚拟环境后,使用pip安装A2A SDK:

# 确保pip是最新版本
pip install --upgrade pip

# 安装A2A SDK和其他工具
pip install a2a-sdk pytest black

# 保存依赖
pip freeze > requirements.txt

4. 创建项目结构

一个典型的A2A项目结构如下:

my-a2a-agent/
├── README.md
├── requirements.txt
├── main.py          # 入口文件
├── agent/
│   ├── __init__.py
│   ├── card.py      # Agent Card定义
│   ├── executor.py  # Agent执行器
│   └── skills/      # Agent技能目录
│       ├── __init__.py
│       └── hello.py # 示例技能
└── tests/           # 测试目录
    ├── __init__.py
    └── test_skills.py

让我们创建这个结构:

mkdir -p agent/skills tests
touch README.md main.py
touch agent/__init__.py agent/card.py agent/executor.py
touch agent/skills/__init__.py agent/skills/hello.py
touch tests/__init__.py tests/test_skills.py

第一步:定义基础技能

作为入门示例,我们将创建一个简单的"Hello World"智能体,它能够问候用户并进行简单的计算。

编辑agent/skills/hello.py

from typing import Dict, List, Any, Optional
from a2a.types import AgentSkill

# 定义问候技能
def greet(name: str, language: str = "en") -> str:
    """
    向用户发送问候
    
    Args:
        name: 用户名
        language: 语言代码(默认英语)
        
    Returns:
        个性化问候语
    """
    greetings = {
        "en": f"Hello, {name}! Welcome to A2A!",
        "zh": f"你好,{name}!欢迎使用A2A!",
        "es": f"¡Hola, {name}! ¡Bienvenido a A2A!",
        "fr": f"Bonjour, {name}! Bienvenue à A2A!"
    }
    
    return greetings.get(language, greetings["en"])

# 定义计算技能
def calculate(operation: str, a: float, b: float) -> Dict[str, Any]:
    """
    执行基本数学运算
    
    Args:
        operation: 运算类型(add、subtract、multiply、divide)
        a: 第一个数字
        b: 第二个数字
        
    Returns:
        包含运算结果的字典
    """
    result = None
    if operation == "add":
        result = a + b
    elif operation == "subtract":
        result = a - b
    elif operation == "multiply":
        result = a * b
    elif operation == "divide":
        if b == 0:
            return {
                "error": "除数不能为零",
                "success": False
            }
        result = a / b
    else:
        return {
            "error": f"不支持的运算: {operation}",
            "success": False
        }
    
    return {
        "operation": operation,
        "a": a,
        "b": b,
        "result": result,
        "success": True
    }

# 注册A2A技能
greet_skill = AgentSkill(
    id="greet",
    name="问候用户",
    description="向用户发送个性化问候",
    function=greet,
    examples=["向张三发送中文问候", "用英语问候李四"]
)

calculate_skill = AgentSkill(
    id="calculate",
    name="基础计算器",
    description="执行基本数学运算",
    function=calculate,
    examples=["计算 5 加 3", "将 10 除以 2"]
)

# 导出技能列表
available_skills = [greet_skill, calculate_skill]

这里我们定义了两个基本技能:

  1. greet:根据指定的语言向用户发送问候语
  2. calculate:执行四则运算,并返回结构化结果

每个技能都有详细的文档字符串,解释其参数和返回值,这是良好实践的一部分。

第二步:创建Agent Card

接下来,我们需要为智能体创建一个"名片",向外部声明其身份和能力。

编辑agent/card.py

from a2a.types import (
    AgentCard,
    AgentCapabilities,
    AgentAuthentication,
    AgentSkill
)
from agent.skills.hello import available_skills

def create_agent_card(
    base_url: str = "http://localhost:8000",
    name: str = "hello-world-agent",
    version: str = "1.0.0"
) -> AgentCard:
    """
    创建Agent Card
    
    Args:
        base_url: 智能体服务的基础URL
        name: 智能体名称
        version: 智能体版本
        
    Returns:
        配置好的AgentCard对象
    """
    
    # 定义智能体能力
    capabilities = AgentCapabilities(
        streaming=True,
        pushNotifications=False,
        stateTransitionHistory=True
    )
    
    # 定义认证需求(这里设置为不需要认证,仅用于演示)
    authentication = AgentAuthentication(
        schemes=[]  # 空列表表示不需要认证
    )
    
    # 创建Agent Card
    card = AgentCard(
        name=name,
        description="一个入门级A2A智能体示例,提供问候和计算功能",
        url=base_url,
        version=version,
        capabilities=capabilities,
        authentication=authentication,
        defaultInputModes=["text/plain"],
        defaultOutputModes=["text/plain", "application/json"],
        skills=available_skills
    )
    
    return card

Agent Card定义了智能体的核心属性:

  • 基本信息(名称、描述、版本)
  • 服务URL
  • 技术能力(如是否支持流式响应)
  • 认证需求
  • 默认输入/输出格式
  • 提供的技能列表

创建标准化的Agent Card是智能体被发现和使用的关键。

第三步:实现Agent执行器

Agent执行器是智能体的"大脑",负责接收请求、执行相应技能并返回结果。

编辑agent/executor.py

from typing import Dict, Any, Optional, List
from a2a.types import Task, TaskStatus, Message, TextPart, DataPart
from agent.skills.hello import greet, calculate

class HelloWorldAgentExecutor:
    """Hello World智能体执行器"""
    
    def __init__(self):
        """初始化执行器"""
        self.skills = {
            "greet": self._execute_greet,
            "calculate": self._execute_calculate
        }
    
    async def execute_task(self, task: Task) -> Task:
        """
        执行任务
        
        Args:
            task: 输入任务对象
            
        Returns:
            更新后的任务对象
        """
        # 获取用户消息
        user_message = task.message
        if not user_message or user_message.role != "user":
            task.status = TaskStatus.FAILED
            task.message = Message(
                role="agent",
                parts=[TextPart(text="无效的用户消息")]
            )
            return task
        
        # 分析用户意图(简化版)
        intent = self._analyze_intent(user_message)
        
        # 根据意图执行相应技能
        if intent["skill"] in self.skills:
            result = await self.skills[intent["skill"]](intent["params"])
            
            # 创建响应消息
            if isinstance(result, str):
                response_part = TextPart(text=result)
            else:
                response_part = DataPart(data=result)
            
            # 更新任务
            task.status = TaskStatus.COMPLETED
            task.message = Message(
                role="agent",
                parts=[response_part]
            )
        else:
            # 未识别的技能
            task.status = TaskStatus.FAILED
            task.message = Message(
                role="agent",
                parts=[TextPart(text=f"未找到技能: {intent['skill']}")]
            )
        
        return task
    
    def _analyze_intent(self, message: Message) -> Dict[str, Any]:
        """
        简化的意图分析
        实际应用中,这里可能会调用LLM或其他复杂的NLP服务
        
        本示例中,我们假设消息中直接包含了技能名称和参数
        格式:{"skill": "技能名称", "params": {...参数...}}
        """
        if len(message.parts) == 0:
            return {"skill": "unknown", "params": {}}
        
        # 尝试从消息中提取JSON数据
        for part in message.parts:
            if part.type == "data":
                data = part.data
                if isinstance(data, dict) and "skill" in data:
                    return data
        
        # 简单文本处理(仅示例用)
        text = ""
        for part in message.parts:
            if part.type == "text":
                text = part.text
                break
        
        if "问候" in text or "hello" in text.lower() or "greet" in text.lower():
            # 简单解析:"问候张三"或"用中文问候张三"
            parts = text.split()
            name = parts[-1] if len(parts) > 1 else "Guest"
            language = "zh" if "中文" in text else "en"
            return {"skill": "greet", "params": {"name": name, "language": language}}
        
        if any(op in text for op in ["计算", "加", "减", "乘", "除", "calculate"]):
            # 非常简化的解析,实际应用中应使用更复杂的NLP
            operation = "add"  # 默认加法
            a, b = 0, 0
            
            if "加" in text or "+" in text:
                operation = "add"
            elif "减" in text or "-" in text:
                operation = "subtract"
            elif "乘" in text or "*" in text:
                operation = "multiply"
            elif "除" in text or "/" in text:
                operation = "divide"
            
            # 非常简化的数字提取
            import re
            numbers = re.findall(r'\d+', text)
            if len(numbers) >= 2:
                a = float(numbers[0])
                b = float(numbers[1])
            
            return {"skill": "calculate", "params": {"operation": operation, "a": a, "b": b}}
        
        # 默认响应
        return {"skill": "greet", "params": {"name": "Guest"}}
    
    async def _execute_greet(self, params: Dict[str, Any]) -> str:
        """执行问候技能"""
        name = params.get("name", "Guest")
        language = params.get("language", "en")
        return greet(name, language)
    
    async def _execute_calculate(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """执行计算技能"""
        operation = params.get("operation", "add")
        a = params.get("a", 0)
        b = params.get("b", 0)
        return calculate(operation, a, b)

这个执行器包含三个主要组件:

  1. 意图分析:解析用户请求,确定需要使用哪个技能以及相应的参数
  2. 技能执行:调用相应的技能函数,处理参数和结果
  3. 响应生成:根据技能执行结果,生成格式化的响应

在实际应用中,意图分析部分可能会更加复杂,通常会借助大型语言模型(LLM)来理解用户意图。

第四步:创建入口文件

最后,我们需要创建一个入口文件,将所有组件连接起来并启动服务器。

编辑main.py

import asyncio
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore

from agent.card import create_agent_card
from agent.executor import HelloWorldAgentExecutor

# 配置参数
HOST = "127.0.0.1"
PORT = 8000
BASE_URL = f"http://{HOST}:{PORT}"

async def main():
    """主函数"""
    print(f"启动 Hello World A2A 智能体 ({BASE_URL})...")
    
    # 创建Agent Card
    agent_card = create_agent_card(base_url=BASE_URL)
    print(f"已创建Agent Card: {agent_card.name} (v{agent_card.version})")
    
    # 创建Agent执行器
    executor = HelloWorldAgentExecutor()
    print(f"已初始化执行器,支持技能: {', '.join(executor.skills.keys())}")
    
    # 创建任务存储
    task_store = InMemoryTaskStore()
    
    # 创建请求处理器
    request_handler = DefaultRequestHandler(
        task_executor=executor.execute_task,
        task_store=task_store,
        agent_card=agent_card
    )
    
    # 创建A2A应用
    app = A2AStarletteApplication(
        request_handler=request_handler,
        agent_card=agent_card
    )
    
    # 启动服务器
    config = uvicorn.Config(
        app=app,
        host=HOST,
        port=PORT,
        log_level="info"
    )
    server = uvicorn.Server(config)
    await server.serve()

if __name__ == "__main__":
    # 运行主函数
    asyncio.run(main())

入口文件完成了以下工作:

  1. 创建Agent Card
  2. 初始化Agent执行器
  3. 设置内存任务存储
  4. 配置请求处理器
  5. 创建A2A服务器应用
  6. 启动HTTP服务器

运行你的第一个A2A智能体

完成上述所有步骤后,就可以运行智能体了:

# 确保虚拟环境已激活
python main.py

如果一切正常,你应该会看到类似下面的输出:

启动 Hello World A2A 智能体 (http://127.0.0.1:8000)...
已创建Agent Card: hello-world-agent (v1.0.0)
已初始化执行器,支持技能: greet, calculate
INFO:     Started server process [12345]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

现在,你的A2A智能体已经在本地运行了!它提供以下端点:

  • /.well-known/agent.json:Agent Card,描述智能体的能力
  • /tasks/send:发送同步任务请求
  • /tasks/sendSubscribe:发送支持流式处理的任务请求
  • /tasks/get:获取任务状态

验证你的Agent Card

你可以通过浏览器访问 http://127.0.0.1:8000/.well-known/agent.json 来查看你的Agent Card。它应该显示一个包含智能体能力和技能信息的JSON对象。

小结与展望

恭喜!你已经成功从零开始创建了一个A2A兼容的智能体,它包含了标准的Agent Card、执行器和基本技能。虽然这个示例相对简单,但它包含了A2A协议的核心组件,可以作为你构建更复杂智能体的基础。

在下一篇文章《上手A2A实战(下):启动服务并与你的智能体"聊聊天"》中,我们将探讨如何:

  1. 通过API与智能体进行交互
  2. 实现流式响应和多轮对话
  3. 调试和优化智能体性能
  4. 将智能体部署到生产环境

通过这两篇实战文章,你将掌握构建A2A兼容智能体的全套技能,为探索更高级的智能体协作场景打下坚实基础。


下一篇预告《上手A2A实战(下):启动服务并与你的智能体"聊聊天"》