鸿蒙智能体开发实战:6.A2A 模式开发接口

前言

在上一篇文章中,我们完成了项目的初始化和基础架构搭建。本文将深入介绍 A2A 模式的核心接口开发,包括 JSON-RPC 2.0 协议的路由设计和实现。

一、协议架构概览

在开始接口开发之前,先回顾A2A模式的整体协议架构:

A2A协议采用三层架构设计:

  1. 传输层:基于HTTP/HTTPS,统一使用POST方法
  2. 协议层:JSON-RPC 2.0格式,标准化请求/响应
  3. 业务层:各RPC方法实现具体的业务逻辑

核心设计原则:

  • 无状态传输:服务器不维护长连接,通过Session机制管理状态
  • 流式优先:所有耗时操作支持SSE流式输出
  • 统一入口:所有方法通过单一端点路由

二、JSON-RPC 2.0 协议回顾

2.1 请求格式

JSON-RPC 是一种轻量级的远程过程调用协议,其请求格式如下:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": "request-123",
  "method": "message/stream",
  "params": {
    "key": "value"
  }
}

2.2 响应格式

响应格式:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": "request-123",
  "result": {
    "data": "response data"
  }
}

2.3 JSON-RPC 错误码规范

JSON-RPC 2.0 定义了标准的错误码范围:

错误码 含义 说明
-32700 解析错误 服务端收到无效的JSON
-32600 无效请求 发送的JSON不是有效的请求对象
-32601 方法不存在 请求的方法不存在
-32602 无效参数 方法参数无效
-32603 内部错误 服务端内部错误
-32000~-32099 服务端错误 服务端自定义错误

三、统一端点设计

A2A 协议采用统一端点设计,所有 RPC 方法都通过 /agent/message 端点处理:

复制代码
POST /agent/message
Headers:
  Content-Type: application/json
  agent-session-id: <session-id>

四、完整的路由实现

4.1 消息分发器

python 复制代码
# routes/agent_routes.py
from fastapi import APIRouter, Request, Header
from fastapi.responses import JSONResponse, StreamingResponse
import uuid
import json
from typing import Optional, Dict, Any

router = APIRouter(prefix="/agent", tags=["agent"])

# 共享状态存储
agent_sessions: Dict[str, Dict[str, Any]] = {}
conversation_contexts: Dict[str, list] = {}
task_states: Dict[str, Dict[str, Any]] = {}

@router.post("/message")
async def handle_agent_message(
    request: Request,
    agent_session_id: Optional[str] = Header(None, alias="agent-session-id"),
    x_request_id: Optional[str] = Header(None, alias="X-Request-ID"),
):
    """统一的 Agent 消息处理接口"""
    request_id = x_request_id or str(uuid.uuid4())

    # 解析请求体
    try:
        body = await request.json()
    except json.JSONDecodeError:
        return JSONResponse(
            status_code=400,
            content={"jsonrpc": "2.0", "id": "unknown", "error": {"code": 400, "message": "Invalid JSON"}}
        )

    jsonrpc_request = JsonRpcRequest(**body)
    method = jsonrpc_request.method

    # 路由分发
    if method == "initialize":
        return await handle_initialize(jsonrpc_request, request_id)
    elif method == "notifications/initialized":
        return await handle_initialized(jsonrpc_request, agent_session_id, request_id)
    elif method == "message/stream":
        return await handle_message_stream(jsonrpc_request, agent_session_id, request_id)
    elif method == "tasks/cancel":
        return await handle_tasks_cancel(jsonrpc_request, agent_session_id, request_id)
    elif method == "clearContext":
        return await handle_clear_context(jsonrpc_request, agent_session_id, request_id)
    else:
        return JSONResponse(
            status_code=400,
            content=JsonRpcResponse(
                jsonrpc="2.0",
                id=jsonrpc_request.id,
                error={"code": -32601, "message": f"Method not found: {method}"}
            ).model_dump()
        )

4.2 Initialize 方法

初始化会话,获取 agentSessionId

python 复制代码
async def handle_initialize(
    request: JsonRpcRequest,
    request_id: str,
) -> JSONResponse:
    """处理 initialize 方法"""
    from datetime import datetime, timedelta

    session_id = uuid.uuid4().hex
    session_ttl = 7 * 24 * 60 * 60  # 7 天

    # 存储会话信息
    agent_sessions[session_id] = {
        "created_at": datetime.utcnow(),
        "expires_at": datetime.utcnow() + timedelta(seconds=session_ttl),
        "status": "initialized",
    }

    response = JsonRpcResponse(
        jsonrpc="2.0",
        id=request.id,
        result={
            "version": "1.0",
            "agentSessionId": session_id,
            "agentSessionTtl": session_ttl,
        }
    )

    return JSONResponse(content=response.model_dump())

4.3 Notifications/Initialized 方法

通知服务器初始化完成:

python 复制代码
async def handle_initialized(
    request: JsonRpcRequest,
    agent_session_id: Optional[str],
    request_id: str,
) -> JSONResponse:
    """处理 notifications/initialized 方法"""

    if not agent_session_id or agent_session_id not in agent_sessions:
        return JSONResponse(
            status_code=401,
            content={"error": "Invalid session ID"}
        )

    # 更新会话状态为 active
    agent_sessions[agent_session_id]["status"] = "active"

    return JSONResponse(content={})

4.4 Message/Stream 方法

核心的流式消息处理方法,支持 SSE 输出:

python 复制代码
from fastapi.responses import StreamingResponse
import asyncio

async def handle_message_stream(
    request: JsonRpcRequest,
    agent_session_id: Optional[str],
    request_id: str,
) -> StreamingResponse:
    """处理 message/stream 方法,支持 SSE 流式输出"""

    params = request.params or {}
    task_id = params.get("id", str(uuid.uuid4()))
    session_id = params.get("sessionId", str(uuid.uuid4()))
    message = params.get("message", {})

    # 提取用户消息文本
    user_text = get_text_from_parts(message.get("parts", []))

    # 存储任务状态
    task_states[task_id] = {
        "sessionId": session_id,
        "status": "working",
    }

    async def generate_sse():
        """SSE 流式生成器"""
        try:
            # 步骤 1: 发送 submitted 状态
            yield create_sse_event({
                "taskId": task_id,
                "kind": "status-update",
                "status": {"state": "submitted"}
            })

            # 步骤 2: 处理用户请求(调用大模型)
            async for chunk in process_user_request(user_text, session_id):
                yield create_sse_event(chunk)

            # 步骤 3: 发送完成状态
            yield create_sse_event({
                "taskId": task_id,
                "kind": "status-update",
                "final": True,
                "status": {"state": "completed"}
            })

        except Exception as e:
            yield create_sse_event({
                "taskId": task_id,
                "kind": "status-update",
                "final": True,
                "status": {"state": "failed"},
                "error": str(e)
            })

    return StreamingResponse(
        generate_sse(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        }
    )

def get_text_from_parts(parts: list) -> str:
    """从 Message Parts 中提取文本内容"""
    texts = []
    for part in parts:
        if part.get("kind") == "text" and part.get("text"):
            texts.append(part.get("text"))
    return "\n".join(texts)

def create_sse_event(data: dict) -> str:
    """创建 SSE 事件字符串"""
    event = {
        "jsonrpc": "2.0",
        "id": task_id,
        "result": data,
        "error": {"code": 0, "message": "success"}
    }
    return f"data: {json.dumps(event, ensure_ascii=False)}\n\n"

4.5 Tasks/Cancel 方法

取消正在执行的任务:

python 复制代码
async def handle_tasks_cancel(
    request: JsonRpcRequest,
    agent_session_id: Optional[str],
    request_id: str,
) -> JSONResponse:
    """处理 tasks/cancel 方法"""

    params = request.params or {}
    task_id = params.get("id", request.id)

    if task_id in task_states:
        task_states[task_id]["status"] = "canceled"

    return JSONResponse(
        content=JsonRpcResponse(
            jsonrpc="2.0",
            id=request.id,
            result={
                "id": task_id,
                "status": {"state": "canceled"}
            }
        ).model_dump()
    )

4.6 ClearContext 方法

清理对话上下文:

python 复制代码
async def handle_clear_context(
    request: JsonRpcRequest,
    agent_session_id: Optional[str],
    request_id: str,
) -> JSONResponse:
    """处理 clearContext 方法"""

    params = request.params or {}
    session_id = params.get("sessionId", request.sessionId)

    if session_id and session_id in conversation_contexts:
        conversation_contexts[session_id] = []

    return JSONResponse(
        content=JsonRpcResponse(
            jsonrpc="2.0",
            id=request.id,
            result={"status": {"state": "cleared"}}
        ).model_dump()
    )

五、接口调用示例

5.1 初始化会话

bash 复制代码
curl -X POST http://localhost:8080/agent/message \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize"
  }'

响应:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "agentSessionId": "8f01f3d172cd4396a0e535ae8aec6687",
    "agentSessionTtl": 604800
  }
}

重要字段说明:

  1. agentSessionId:会话唯一标识,后续请求必须在Header中携带
  2. agentSessionTtl:会话有效期(秒),默认为7天

提示:建议在客户端实现Session续期逻辑,在过期前重新调用initialize获取新ID。

5.2 发送流式消息

bash 复制代码
curl -X POST http://localhost:8080/agent/message \
  -H "Content-Type: application/json" \
  -H "agent-session-id: 8f01f3d172cd4396a0e535ae8aec6687" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "message/stream",
    "params": {
      "id": "task-001",
      "sessionId": "session-001",
      "message": {
        "role": "user",
        "parts": [{
          "kind": "text",
          "text": "你好,请帮我介绍一下你自己"
        }]
      }
    }
  }'

六、接口认证与鉴权实现

6.1 认证方式对比

认证方式 适用场景 安全等级
API Key 内部测试
AK/SK 生产环境
OAuth 2.0 需要用户身份

6.2 API Key 校验中间件

python 复制代码
from fastapi import HTTPException, Header

async def api_key_auth(x_api_key: str = Header(...)):
    if x_api_key != API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API Key")

七、SSE 流式输出优化

7.1 心跳保持

python 复制代码
async def heartbeat():
    while True:
        await asyncio.sleep(15)
        yield ": ping\n\n"

7.2 流式输出控制

字段 推荐值 说明
Cache-Control no-cache 禁用缓存
Connection keep-alive 保持连接
X-Accel-Buffering no 禁用代理缓冲

八、接口测试用例

8.1 测试 initialize

bash 复制代码
curl -X POST http://localhost:8080/agent/message \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

8.2 测试 message/stream

bash 复制代码
curl -X POST http://localhost:8080/agent/message \
  -H "Content-Type: application/json" \
  -H "agent-session-id: $SESSION_ID" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "message/stream",
    "params": {...}
  }'

九、接口性能优化建议

  1. 使用异步数据库连接池管理会话
  2. 对大模型调用增加超时与重试
  3. 对频繁访问的上下文使用 Redis 缓存

9.1 超时配置示例

python 复制代码
import httpx

timeout = httpx.Timeout(10.0, connect=2.0)
client = httpx.AsyncClient(timeout=timeout)

小结

本文详细介绍了A2A模式的核心接口开发:

  1. 协议架构:三层架构设计,传输层/协议层/业务层解耦
  2. JSON-RPC 2.0:标准化请求响应格式和错误码规范
  3. 统一端点设计 :所有RPC方法通过 /agent/message 路由
  4. SSE流式输出:实时推送任务状态和结果
  5. 会话管理 :基于 agentSessionId 的完整状态跟踪
  6. 六个RPC方法:消息分发器、初始化、初始化完成、流式消息、取消任务、清理上下文

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
澹然疏离1 小时前
HarmonyOS应用《民族图鉴》开发第34篇:TTSEngineService——语音引擎封装与降级策略
华为·harmonyos
花开彼岸天~2 小时前
03 module.json5 配置文件全解:能力声明、权限申请与设备支持
华为·harmonyos·鸿蒙系统
ZZZMMM.zip3 小时前
基于鸿蒙HarmonyOS NEXT开发AI股票分析应用:智能投资新体验与鸿蒙Flutter框架跨端实践
人工智能·flutter·华为·harmonyos·鸿蒙
花开彼岸天~4 小时前
04 oh-package.json5 与依赖管理:ohpm 包管理器全解析
华为·harmonyos·鸿蒙系统
爸爸6196 小时前
06 项目目录结构与编码规范:三层架构与 DAO 模式实践
华为·架构·harmonyos·鸿蒙系统
不才难以繁此生7 小时前
HarmonyOS 端侧应用实战:中式美食如何把资源、路由、存储和视频拆菜串成一条工程链路
harmonyos·arkts·arkui·中式美食·端侧应用
爸爸6197 小时前
07 ArkTS 基础类型与接口定义:从 TypeScript 到鸿蒙的类型升级
javascript·华为·typescript·harmonyos·鸿蒙系统
绝世番茄7 小时前
鸿蒙 HarmonyOS ArkTS 音乐播放器界面布局深度学习指南
深度学习·华为·list·harmonyos·鸿蒙
不才难以繁此生8 小时前
HarmonyOS 实战|中式美食视频拆菜编辑面板:因子状态、草稿卡片与底部操作条
harmonyos·arkts·中式美食·arkui v2·视频拆菜