跳至正文

Covonaut 的 A2A 协议支持 ​

a2a 实现 Agent2Agent 协议,使 Covonaut 与其他兼容 Agent 互操作。

功能 ​

  • Agent Card 发现:/.well-known/agent-card.json,旧 agent.json 别名。
  • A2A 1.0 的 11 个标准 JSON-RPC 方法,包括推送配置和扩展卡片。
  • client.V1() 的类型化请求、响应和流。
  • 声明安全要求,并支持认证、版本及必需扩展的协商。
  • 旧 tasks/* 和 Go API 兼容。
  • 管理任务生命周期,支持同步响应和 SSE,传输文本、文件及结构化数据,并支持 Webhook 和远程 Agent 交接。

架构 ​

types 定义 Card、Task、Message、Part 和 Artifact;server 提供基于 HTTP 的 JSON-RPC 服务;client 调用远程 Agent;handoff 将远程调用接入本地 Agent 交接机制。

快速开始 ​

1. 将 Agent 暴露为服务 ​

配置模型和密钥后,可以运行以下程序。Agent Card 描述服务的能力和技能,DefaultAgentHandler 负责将请求交给 Agent 执行。

go
package main

import (
    "log"
    "os"

    "github.com/covoyage/covonaut/agentcore"
    "github.com/covoyage/covonaut/a2a"
    "github.com/covoyage/covonaut/provider/chatcompat"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    // Create your agent
    agent, err := agentcore.NewWithError(agentcore.Config{
        ModelConfig: agentcore.ModelConfig{
            Name:     "weather-agent",
            Model:    os.Getenv("AGENT_MODEL"),
            Provider: chatcompat.New(chatcompat.Config{APIKey: os.Getenv("OPENAI_API_KEY")}),
        },
        SystemPrompt: "You are a weather assistant.",
    })
    if err != nil {
        return err
    }
    defer agent.Close()

    // Define your agent card
    card := a2a.AgentCard{
        Name:        "weather-agent",
        Description: "Provides weather information for any location",
        URL:         "http://localhost:8080",
        Version:     "1.0.0",
        Capabilities: a2a.AgentCapabilities{
            Streaming: true,
        },
        Skills: []a2a.AgentSkill{
            {
                ID:          "get-weather",
                Name:        "Get Weather",
                Description: "Get current weather for a location",
                Tags:        []string{"weather", "forecast"},
            },
        },
    }

    // Create A2A server
    handler := a2a.NewDefaultAgentHandler(card, agent, agent.Config())
    server := a2a.NewServer(handler)

    log.Println("A2A server listening on :8080")
    return server.ListenAndServe(":8080")
}

生产环境还需处理认证、超时和资源释放。无模型本地联调可运行服务与客户端示例。

2. 调用远程 A2A 1.0 Agent ​

先获取远程服务的 Agent Card,再通过标准客户端发送请求。响应可能直接包含消息,也可能返回需要继续跟踪的任务状态。

go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "time"

    "github.com/covoyage/covonaut/a2a"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()
    client := a2a.NewClient("http://remote-agent.example.com")

    // Discover agent capabilities
    card, err := client.GetAgentCard(ctx)
    if err != nil {
        return err
    }
    fmt.Printf("Connected to: %s\n", card.Name)

    // Use the standards-based client facade.
    response, err := client.V1().SendMessage(ctx, a2a.V1SendMessageRequest{
        Message: a2a.V1Message{
            MessageID: "message-123",
            Role:      "ROLE_USER",
            Parts:     []a2a.V1Part{a2a.NewV1TextPart("What's the weather in Tokyo?")},
        },
    })
    if err != nil {
        return err
    }

    if response.Message != nil {
        fmt.Printf("Direct response: %+v\n", response.Message)
    } else if response.Task != nil {
        fmt.Printf("Task state: %s\n", response.Task.Status.State)
    } else {
        return errors.New("response contains neither a message nor a task")
    }
    return nil
}

3. 订阅流式任务更新 ​

片段应放在返回错误的函数中,并使用设有超时的 context。收到 EOF 表示流已结束,其他错误需要返回给调用方。使用结束后应关闭流,编译这段代码时还需导入 errors 和 io。

go
stream, err := client.V1().SendStreamingMessage(ctx, a2a.V1SendMessageRequest{
    Message: a2a.V1Message{
        MessageID: "message-456",
        Role:      "ROLE_USER",
        Parts:     []a2a.V1Part{a2a.NewV1TextPart("Write a long story")},
    },
})
if err != nil {
    return err
}
defer stream.Close()

for {
    ev, err := stream.Recv()
    if errors.Is(err, io.EOF) {
        break
    }
    if err != nil {
        return err
    }
    if ev.Task != nil {
        fmt.Printf("Initial state: %s\n", ev.Task.Status.State)
    }
    if ev.StatusUpdate != nil {
        fmt.Printf("State: %s\n", ev.StatusUpdate.Status.State)
    }
}

4. 接入交接系统 ​

调用方应检查创建错误,并在使用结束后关闭 Agent。注册远程目标后,模型可调用 transfer_to_math-expert 等工具委托任务。

go
import (
    "github.com/covoyage/covonaut/agentcore"
    "github.com/covoyage/covonaut/a2a"
)

func setupAgent(model string, provider agentcore.Provider) (*agentcore.Agent, error) {
    // Create remote handoff extension
    remoteAgents := []a2a.RemoteHandoffConfig{
        {
            Name:        "math-expert",
            Description: "Expert in mathematics and calculations",
            URL:         "http://math-agent.example.com",
        },
        {
            Name:        "code-reviewer",
            Description: "Reviews code and suggests improvements",
            URL:         "http://code-agent.example.com",
        },
    }

    ext := a2a.NewRemoteHandoffExtension(remoteAgents)

    return agentcore.NewWithError(agentcore.Config{
        ModelConfig: agentcore.ModelConfig{
            Name: "coordinator", Model: model, Provider: provider,
        },
        SystemPrompt: "You coordinate tasks between specialized agents.",
        Extensions:   []agentcore.Extension{ext},
    })
}

5. 带回调的适配器 ​

回调可处理输入、输出和状态变化;记录日志时避免泄漏用户内容。

go
adapter := a2a.NewAgentAdapter(card, agent, cfg, &a2a.AdapterCallbacks{
    BeforeRun: func(ctx context.Context, taskID, input string) (string, error) {
        log.Printf("Processing task %s", taskID)
        return input, nil
    },
    AfterRun: func(ctx context.Context, taskID, output string, err error) (*a2a.Task, error) {
        if err != nil {
            log.Printf("Task %s failed: %v", taskID, err)
        } else {
            log.Printf("Task %s completed", taskID)
        }
        return nil, nil // use default result
    },
    OnStatusChange: func(taskID string, state a2a.TaskState) {
        log.Printf("Task %s status changed to %s", taskID, state)
    },
})

server := a2a.NewServer(adapter)

API 参考 ​

服务端点 ​

方法端点说明
GET/.well-known/agent-card.json1.0 发现
GET/.well-known/agent.json旧发现
POST/JSON-RPC

A2A 1.0 JSON-RPC 方法 ​

方法说明
SendMessage发送/继续消息
SendStreamingMessage标准 JSON-RPC SSE
GetTask任务状态,支持 historyLength
ListTasks游标分页和字段控制
CancelTask取消任务
SubscribeToTask订阅未终止任务
CreateTaskPushNotificationConfig创建 webhook
GetTaskPushNotificationConfig获取配置
ListTaskPushNotificationConfigs列出配置
DeleteTaskPushNotificationConfig删除配置
GetExtendedAgentCard认证后的扩展 Card

请求可以通过 A2A-Version: 1.0 使用 1.0 协议。不填写版本或指定 0.3 时保留旧行为,其他版本会返回 VersionNotSupportedError。WithA2AExtensions 配置必需扩展;WithExtendedAgentCard 和 WithAuth 用于配置需要认证才能访问的扩展 Agent Card。

旧 JSON-RPC 方法 ​

方法参数说明
tasks/sendSendTaskRequest同步任务
tasks/sendSubscribeSendTaskRequestSSE 任务
tasks/getGetTaskRequest获取状态
tasks/cancelCancelTaskRequest取消
tasks/pushNotification/setSetPushNotificationRequest设置 webhook
tasks/pushNotification/get{id}获取 webhook

Part 类型 ​

文本、结构化数据、Base64 文件和 URI 文件使用不同构造器:

go
// Text
textPart := a2a.NewTextPart("Hello world")

// Structured data
dataPart := a2a.NewDataPart(map[string]any{"key": "value"})

// File (base64)
inlineFilePart := a2a.NewFilePartBytes("report.pdf", "application/pdf", base64Data)

// File (URI)
remoteFilePart := a2a.NewFilePartURI("image.png", "image/png", "http://example.com/image.png")

任务状态 ​

任务提交后处于 submitted,开始执行后进入 working,成功结束时进入 completed。需要更多输入时进入 input-required,补充输入后可以继续;任务也可能以 canceled 或 failed 结束。

测试 ​

sh
go test ./a2a/ -v

参考 ​

基于 MIT 许可证发布。