跳转至

tRPC-Agent-Go A2A 集成指南

概述

tRPC-Agent-Go 提供了完整的 A2A (Agent-to-Agent) 解决方案,包含两个核心组件:

  • A2A Server: 将本地 Agent 暴露为 A2A 服务,供其他 Agent 调用
  • A2A Agent: 调用远程 A2A 服务的客户端代理,像使用本地 Agent 一样使用远程 Agent

核心能力

  • 零协议感知: 开发者只需关注 Agent 的业务逻辑,无需了解 A2A 协议细节
  • 自动适配: 框架自动将 Agent 信息转换为 A2A AgentCard
  • 消息转换: 自动处理 A2A 协议消息与 Agent 消息格式的转换

A2A Server:暴露 Agent 为服务

概念介绍

A2A Server 是 tRPC-Agent-Go 提供的服务端组件,用于将任何本地 Agent 快速转换为符合 A2A 协议的网络服务。

核心特性

  • 一键转换: 通过简单配置将 Agent 暴露为 A2A 服务
  • 自动协议适配: 自动处理 A2A 协议与 Agent 接口的转换
  • AgentCard 生成: 自动生成服务发现所需的 AgentCard
  • 流式支持: 支持流式和非流式两种响应模式

Agent 到 A2A 的自动转换

tRPC-Agent-Go 通过 server/a2a 包实现了从 Agent 到 A2A 服务的无缝转换:

func New(opts ...Option) (*a2a.A2AServer, error) {}

AgentCard 自动生成

框架会自动提取 Agent 的元数据(名称、描述、工具等),生成符合 A2A 协议的 AgentCard,包括: - Agent 基本信息(名称、描述、URL) - 能力声明(是否支持流式) - 技能列表(基于 Agent 的工具自动生成)

消息协议转换

框架内置 messageProcessor 实现 A2A 协议消息与 Agent 消息格式的双向转换,用户无需关心消息格式转换的细节。

A2A Server 快速开始

使用 A2A Server 暴露 Agent 服务

只需几行代码,就可以将任意 Agent 转换为 A2A 服务:

基础示例:创建 A2A Server

package main

import (
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/model/openai"
    a2aserver "trpc.group/trpc-go/trpc-agent-go/server/a2a"
)

func main() {
    // 1. 创建一个普通的 Agent
    model := openai.New("gpt-4o-mini")
    agent := llmagent.New("MyAgent",
        llmagent.WithModel(model),
        llmagent.WithDescription("一个智能助手"),
    )

    // 2. 一键转换为 A2A 服务
    server, _ := a2aserver.New(
        a2aserver.WithHost("localhost:8080"),
        a2aserver.WithAgent(agent), // 传入任意 Agent
    )

    // 3. 启动服务,即可接受 A2A 请求
    server.Start(":8080")
}

直接使用 A2A 协议客户端调用

import (
    "trpc.group/trpc-go/trpc-a2a-go/client"
    "trpc.group/trpc-go/trpc-a2a-go/protocol"
)

func main() {
    // 连接到 A2A 服务
    client, _ := client.NewA2AClient("http://localhost:8080/")

    // 发送消息给 Agent
    message := protocol.NewMessage(
        protocol.MessageRoleUser,
        []protocol.Part{protocol.NewTextPart("你好,请帮我分析这段代码")},
    )

    // Agent 会自动处理并返回结果
    response, _ := client.SendMessage(context.Background(),
        protocol.SendMessageParams{Message: message})
}

A2AAgent:调用远程 A2A 服务

与 A2A Server 相对应,tRPC-Agent-Go 还提供了 A2AAgent,用于调用远程的 A2A 服务,实现 Agent 间的通信。

概念介绍

A2AAgent 是一个特殊的 Agent 实现,它不直接处理用户请求,而是将请求转发给远程的 A2A 服务。从使用者角度看,A2AAgent 就像一个普通的 Agent,但实际上它是远程 Agent 的本地代理。

简单理解: - A2A Server: 我有一个 Agent,想让别人调用 → 暴露为 A2A 服务 - A2AAgent: 我想调用别人的 Agent → 通过 A2AAgent 代理调用

核心特性

  • 透明代理: 像使用本地 Agent 一样使用远程 Agent
  • 自动发现: 通过 AgentCard 自动发现远程 Agent 的能力
  • 协议转换: 自动处理本地消息格式与 A2A 协议的转换
  • 流式支持: 支持流式和非流式两种通信模式
  • 状态传递: 支持将本地状态传递给远程 Agent
  • 错误处理: 完善的错误处理和重试机制

使用场景

  1. 分布式 Agent 系统: 在微服务架构中调用其他服务的 Agent
  2. Agent 编排: 将多个专业 Agent 组合成复杂的工作流
  3. 跨团队协作: 调用其他团队提供的 Agent 服务

A2AAgent 快速开始

基本用法

package main

import (
    "context"
    "fmt"

    "trpc.group/trpc-go/trpc-agent-go/agent/a2aagent"
    "trpc.group/trpc-go/trpc-agent-go/runner"
    "trpc.group/trpc-go/trpc-agent-go/model"
    "trpc.group/trpc-go/trpc-agent-go/session/inmemory"
)

func main() {
    // 1. 创建 A2AAgent,指向远程 A2A 服务
    a2aAgent, err := a2aagent.New(
        a2aagent.WithAgentCardURL("http://localhost:8888"),
    )
    if err != nil {
        panic(err)
    }

    // 2. 像使用普通 Agent 一样使用
    sessionService := inmemory.NewSessionService()
    runner := runner.NewRunner("test", a2aAgent, 
        runner.WithSessionService(sessionService))

    // 3. 发送消息
    events, err := runner.Run(
        context.Background(),
        "user1",
        "session1", 
        model.NewUserMessage("请帮我讲个笑话"),
    )
    if err != nil {
        panic(err)
    }

    // 4. 处理响应
    for event := range events {
        if event.Response != nil && len(event.Response.Choices) > 0 {
            fmt.Print(event.Response.Choices[0].Message.Content)
        }
    }
}

高级配置

// 创建带有高级配置的 A2AAgent
a2aAgent, err := a2aagent.New(
    // 指定远程服务地址
    a2aagent.WithAgentCardURL("http://remote-agent:8888"),

    // 设置流式缓冲区大小
    a2aagent.WithStreamingChannelBufSize(2048),

    // 自定义协议转换
    a2aagent.WithCustomEventConverter(curtomEventConverter),

    a2aagent.WithCustomA2AConverter(cursomA2AConverter)

)

完整示例:A2A Server + A2AAgent 综合使用

以下是一个完整的示例,展示了如何在同一个程序中同时运行 A2A Server(暴露本地 Agent)和 A2AAgent(调用远程服务):

package main

import (
    "context"
    "fmt"
    "time"

    "trpc.group/trpc-go/trpc-agent-go/agent/a2aagent"
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/model/openai"
    "trpc.group/trpc-go/trpc-agent-go/runner"
    "trpc.group/trpc-go/trpc-agent-go/server/a2a"
    "trpc.group/trpc-go/trpc-agent-go/session/inmemory"
)

func main() {
    // 1. 创建并启动远程 Agent 服务
    remoteAgent := createRemoteAgent()
    startA2AServer(remoteAgent, "localhost:8888")

    time.Sleep(1 * time.Second) // 等待服务启动

    // 2. 创建 A2AAgent 连接到远程服务
    a2aAgent, err := a2aagent.New(
        a2aagent.WithAgentCardURL("http://localhost:8888"),
        a2aagent.WithTransferStateKey("user_context"),
    )
    if err != nil {
        panic(err)
    }

    // 3. 创建本地 Agent
    localAgent := createLocalAgent()

    // 4. 对比本地和远程 Agent 的响应
    compareAgents(localAgent, a2aAgent)
}

func createRemoteAgent() agent.Agent {
    model := openai.New("gpt-4o-mini")
    return llmagent.New("JokeAgent",
        llmagent.WithModel(model),
        llmagent.WithDescription("I am a joke-telling agent"),
        llmagent.WithInstruction("Always respond with a funny joke"),
    )
}

func createLocalAgent() agent.Agent {
    model := openai.New("gpt-4o-mini") 
    return llmagent.New("LocalAgent",
        llmagent.WithModel(model),
        llmagent.WithDescription("I am a local assistant"),
    )
}

func startA2AServer(agent agent.Agent, host string) {
    server, err := a2a.New(
        a2a.WithHost(host),
        a2a.WithAgent(agent, true), // 启用流式
    )
    if err != nil {
        panic(err)
    }

    go func() {
        server.Start(host)
    }()
}

func compareAgents(localAgent, remoteAgent agent.Agent) {
    sessionService := inmemory.NewSessionService()

    localRunner := runner.NewRunner("local", localAgent,
        runner.WithSessionService(sessionService))
    remoteRunner := runner.NewRunner("remote", remoteAgent,
        runner.WithSessionService(sessionService))

    userMessage := "请帮我讲个笑话"

    // 调用本地 Agent
    fmt.Println("=== Local Agent Response ===")
    processAgent(localRunner, userMessage)

    // 调用远程 Agent (通过 A2AAgent)
    fmt.Println("\n=== Remote Agent Response (via A2AAgent) ===")
    processAgent(remoteRunner, userMessage)
}

func processAgent(runner runner.Runner, message string) {
    events, err := runner.Run(
        context.Background(),
        "user1",
        "session1",
        model.NewUserMessage(message),
        agent.WithRuntimeState(map[string]any{
            "user_context": "test_context",
        }),
    )
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    for event := range events {
        if event.Response != nil && len(event.Response.Choices) > 0 {
            content := event.Response.Choices[0].Message.Content
            if content == "" {
                content = event.Response.Choices[0].Delta.Content
            }
            if content != "" {
                fmt.Print(content)
            }
        }
    }
    fmt.Println()
}

AgentCard 自动发现

A2AAgent 支持通过标准的 AgentCard 发现机制自动获取远程 Agent 的信息:

// A2AAgent 会自动从以下路径获取 AgentCard
// http://remote-agent:8888/.well-known/agent.json

type AgentCard struct {
    Name         string                 `json:"name"`
    Description  string                 `json:"description"`
    URL          string                 `json:"url"`
    Capabilities AgentCardCapabilities  `json:"capabilities"`
}

type AgentCardCapabilities struct {
    Streaming *bool `json:"streaming,omitempty"`
}

状态传递

A2AAgent 支持将本地运行时状态传递给远程 Agent:

a2aAgent, _ := a2aagent.New(
    a2aagent.WithAgentCardURL("http://remote-agent:8888"),
    // 指定要传递的状态键
    a2aagent.WithTransferStateKey("user_id", "session_context", "preferences"),
)

// 运行时状态会通过 A2A 协议的 metadata 字段传递给远程 Agent
events, _ := runner.Run(ctx, userID, sessionID, message,
    agent.WithRuntimeState(map[string]any{
        "user_id":         "12345",
        "session_context": "shopping_cart",
        "preferences":     map[string]string{"language": "zh"},
    }),
)

自定义转换器

对于特殊需求,可以自定义消息和事件转换器:

// 自定义 A2A 消息转换器
type CustomA2AConverter struct{}

func (c *CustomA2AConverter) ConvertToA2AMessage(
    isStream bool, 
    agentName string, 
    invocation *agent.Invocation,
) (*protocol.Message, error) {
    // 自定义消息转换逻辑
    return &protocol.Message{
        MessageID: invocation.InvocationID,
        Role:      protocol.MessageRoleUser,
        Parts:     []protocol.Part{/* 自定义内容 */},
    }, nil
}

// 自定义事件转换器  
type CustomEventConverter struct{}

func (c *CustomEventConverter) ConvertToEvent(
    result protocol.MessageResult,
    agentName string,
    invocation *agent.Invocation,
) (*event.Event, error) {
    // 自定义事件转换逻辑
    return event.New(invocation.InvocationID, agentName), nil
}

// 使用自定义转换器
a2aAgent, _ := a2aagent.New(
    a2aagent.WithAgentCardURL("http://remote-agent:8888"),
    a2aagent.WithA2AMessageConverter(&CustomA2AConverter{}),
    a2aagent.WithEventConverter(&CustomEventConverter{}),
)

总结:A2A Server vs A2AAgent

组件 职责 使用场景 核心功能
A2A Server 服务提供者 将本地 Agent 暴露给其他系统调用 • 协议转换
• AgentCard 生成
• 消息路由
• 流式支持
A2AAgent 服务消费者 调用远程 A2A 服务 • 透明代理
• 自动发现
• 状态传递
• 协议适配

典型架构模式

1
2
3
4
5
6
7
8
┌─────────────┐ A2A protocol  ┌───────────────┐
│   Client    │──────────────→│ A2A Server    |
│ (A2AAgent)  │               │ (local Agent) │
└─────────────┘               └───────────────┘
      ↑                              ↑
      │                              │
   调用远程                       暴露本地
   Agent服务                     Agent服务

通过 A2A Server 和 A2AAgent 的配合使用,可以比较方便的构建的远程的 Agent 系统。