第七章:部署上线 - 让 Agent 服务千万人

前面六章,我们一直在本地"玩"Agent:写工具、接模型、搞多Agent协作。但说白了,一个Agent如果只能在你自己电脑上跑,那跟玩具没什么区别。

这一章,我们要把 Agent 推到线上去,让它真正为用户提供服务。


本章你将学到

  • ADK-Go 提供的 4 种部署方式,以及它们各自适合什么场景
  • 如何把 Agent 包装成 REST API,对接前端、移动端、第三方系统
  • 如何用 Web UI 快速演示你的 Agent
  • 如何用 Console 模式 在命令行里交互式调试
  • 如何通过 A2A 协议 实现 Agent 之间的远程通信
  • 如何集成 MCP 工具,接入外部工具生态
  • 如何同时部署 多个 Agent
  • 生产环境下的 最佳实践

部署方式概览

ADK-Go 提供了 4 种方式来运行你的 Agent,每种方式适合不同的场景:

方式 关键词 适合场景 生产就绪
REST API api 正式上线、对接前后端
Web UI webui 演示、开发调试
Console console 命令行调试、快速验证
A2A 协议 a2a Agent 之间互相调用

你可以根据自己的需要,选择一种或者组合使用。下面我们一个一个来看。


方式一:REST API(推荐生产环境)

这是最常见的部署方式。把你的 Agent 包装成一个 HTTP API,前端、移动端、其他后端服务都可以通过 HTTP 请求来跟 Agent 交互。

基本用法

最简单的方式是用 adkrest.NewHandler 创建一个标准的 http.Handler,然后挂到任何 HTTP 服务器上:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "time"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/server/adkrest"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    // 创建模型
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })
    if err != nil {
        log.Fatalf("创建模型失败: %v", err)
    }

    // 创建Agent
    myAgent, err := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "回答天气相关问题的Agent",
        Instruction: "你是一个天气助手,帮用户查询天气信息。",
        Tools: []tool.Tool{
            geminitool.GoogleSearch{},
        },
    })
    if err != nil {
        log.Fatalf("创建Agent失败: %v", err)
    }

    // 配置启动参数
    config := &launcher.Config{
        AgentLoader:    agent.NewSingleLoader(myAgent),
        SessionService: session.InMemoryService(),
    }

    // 创建REST API处理器
    // 第二个参数是SSE写超时时间,120秒足够大多数场景了
    apiHandler := adkrest.NewHandler(config, 120*time.Second)

    // 挂载到标准HTTP服务器
    mux := http.NewServeMux()
    mux.Handle("/api/", http.StripPrefix("/api", apiHandler))

    // 加个健康检查端点,方便监控和负载均衡器探活
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    // 启动服务
    log.Println("服务启动在 :8080")
    log.Println("API 地址: http://localhost:8080/api/")
    log.Println("健康检查: http://localhost:8080/health")

    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatalf("服务启动失败: %v", err)
    }
}

这段代码的核心就三步: 1. 创建 Agent 2. 用 adkrest.NewHandler 把它包装成 HTTP Handler 3. 挂到你喜欢的 HTTP 服务器上

注意 adkrest.NewHandler 返回的是标准的 http.Handler 接口,所以你可以用 Go 标准库的 http.ServeMux,也可以用你熟悉的第三方路由框架。

兼容任何 HTTP 框架

因为返回的是标准 http.Handler,你可以随意搭配:

用 gorilla/mux:

import "github.com/gorilla/mux"

router := mux.NewRouter()
router.PathPrefix("/api/").Handler(
    http.StripPrefix("/api", apiHandler),
)
http.ListenAndServe(":8080", router)

用 chi:

import "github.com/go-chi/chi/v5"

r := chi.NewRouter()
r.Mount("/api", apiHandler)
http.ListenAndServe(":8080", r)

用 gin:

import "github.com/gin-gonic/gin"

r := gin.Default()
r.Any("/api/*path", gin.WrapH(
    http.StripPrefix("/api", apiHandler),
))
r.Run(":8080")

选你项目已经在用的框架就行,ADK 不绑定任何特定框架。

REST API 端点详解

挂载好之后,ADK 会自动注册一系列 API 端点。假设你把 API 挂在了 /api 路径下:

应用管理

方法 路径 说明
GET /api/list-apps 列出所有已注册的 Agent 应用

这个端点在你用 MultiLoader 加载多个 Agent 的时候特别有用,可以查看当前有哪些 Agent 可用。

会话管理

方法 路径 说明
POST /api/apps/{app}/users/{user}/sessions 创建新会话
POST /api/apps/{app}/users/{user}/sessions/{session} 用指定ID创建会话
GET /api/apps/{app}/users/{user}/sessions 列出用户的所有会话
GET /api/apps/{app}/users/{user}/sessions/{session} 获取指定会话详情
DELETE /api/apps/{app}/users/{user}/sessions/{session} 删除指定会话

这里的 {app} 对应 Agent 的 Name,{user} 是你自己定义的用户标识,{session} 是会话 ID。

Agent 运行

方法 路径 说明
POST /api/run 运行 Agent(同步,等全部结果返回)
POST /api/run_sse 运行 Agent(SSE 流式返回)

这两个是最核心的端点。区别就是 /run 会等 Agent 完全处理完再返回所有结果,而 /run_sse 用 Server-Sent Events 实时推送中间结果,体验更好。

文件管理

方法 路径 说明
GET /api/apps/{app}/users/{user}/sessions/{session}/artifacts 列出会话中的所有文件
GET /api/apps/{app}/users/{user}/sessions/{session}/artifacts/{name} 获取指定文件
GET /api/apps/{app}/users/{user}/sessions/{session}/artifacts/{name}/versions/{ver} 获取文件的指定版本
DELETE /api/apps/{app}/users/{user}/sessions/{session}/artifacts/{name} 删除指定文件

调试

方法 路径 说明
GET /api/debug/trace/{event_id} 获取事件的调用链追踪
GET /api/apps/{app}/users/{user}/sessions/{session}/events/{event_id}/graph 获取事件的处理图

用 curl 测试你的 API

服务启动后,你可以用 curl 来走一遍完整的流程:

第一步:查看可用的 Agent

curl http://localhost:8080/api/list-apps

返回类似:

["weather_agent"]

第二步:创建一个会话

curl -X POST http://localhost:8080/api/apps/weather_agent/users/user123/sessions

返回的 JSON 里会包含一个 session ID,记下来后面要用。假设返回的 ID 是 abc-123

第三步:跟 Agent 对话

curl -X POST http://localhost:8080/api/run \
  -H "Content-Type: application/json" \
  -d '{
    "appName": "weather_agent",
    "userId": "user123",
    "sessionId": "abc-123",
    "newMessage": {
      "role": "user",
      "parts": [{"text": "北京今天天气怎么样?"}]
    }
  }'

如果你想用流式返回,把 /run 换成 /run_sse,再加上 "streaming": true

curl -X POST http://localhost:8080/api/run_sse \
  -H "Content-Type: application/json" \
  -d '{
    "appName": "weather_agent",
    "userId": "user123",
    "sessionId": "abc-123",
    "newMessage": {
      "role": "user",
      "parts": [{"text": "北京今天天气怎么样?"}]
    },
    "streaming": true
  }'

SSE 模式下,你会看到数据一行一行地推过来,每行以 data: 开头,后面跟着 JSON 格式的事件数据。

第四步:查看会话历史

curl http://localhost:8080/api/apps/weather_agent/users/user123/sessions/abc-123

第五步:健康检查

curl http://localhost:8080/health
# 返回: OK

方式二:Web UI - 内置的可视化界面

如果你想快速给别人演示你的 Agent,或者在开发过程中方便地调试,ADK-Go 自带了一个 Web UI。

使用 Launcher 启动

最方便的方式是用 full.NewLauncher(),它把所有启动模式都集成好了:

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    // 创建模型和Agent(省略了错误处理,实际代码要加上)
    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    myAgent, _ := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "天气查询Agent",
        Instruction: "你是一个天气助手。",
        Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    })

    config := &launcher.Config{
        AgentLoader: agent.NewSingleLoader(myAgent),
    }

    // 用 "web" 模式启动,同时启用 api 和 webui 两个子服务
    l := full.NewLauncher()
    if err := l.Execute(ctx, config, []string{"web", "api", "webui"}); err != nil {
        log.Fatalf("启动失败: %v\n\n%s", err, l.CommandLineSyntax())
    }
}

启动之后,打开浏览器访问 http://localhost:8080/ui/,你就能看到一个漂亮的聊天界面,可以直接跟你的 Agent 对话。

命令行参数

Web 模式支持一些有用的参数:

# 指定端口
go run main.go web -port 3000 api webui

# 调整超时时间
go run main.go web -write-timeout 30s -read-timeout 30s api webui

# 启用 OpenTelemetry 遥测数据导出到 GCP
go run main.go web -otel_to_cloud api webui

适用场景

Web UI 模式非常适合: - 给产品经理、老板做演示 - 开发过程中快速验证 Agent 行为 - 团队内部体验和测试

但是,不要把它用在生产环境。Web UI 主要是为了开发和演示设计的,没有针对高并发、安全性做优化。生产环境请用 REST API 方式。


方式三:Console - 命令行交互

最轻量的方式,直接在终端里跟 Agent 聊天。特别适合开发阶段快速调试。

基本用法

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    myAgent, _ := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "天气查询Agent",
        Instruction: "你是一个天气助手。",
        Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    })

    config := &launcher.Config{
        AgentLoader: agent.NewSingleLoader(myAgent),
    }

    // 用 "console" 模式启动
    l := full.NewLauncher()
    if err := l.Execute(ctx, config, []string{"console"}); err != nil {
        log.Fatalf("启动失败: %v\n\n%s", err, l.CommandLineSyntax())
    }
}

运行之后,你会看到这样的交互界面:

User -> 北京今天天气怎么样?

Agent -> 根据最新信息,北京今天晴转多云,气温 15-25 ...

User -> 那上海呢?

Agent -> 上海今天阴天有小雨...

User -> ( Ctrl+C 退出)

流式模式选择

Console 模式支持两种流式方式:

# 默认是 SSE 模式,Agent 一边思考一边输出
go run main.go console -streaming_mode sse

# 也可以关掉流式,等Agent完全想好再一次性输出
go run main.go console -streaming_mode none

SSE 模式体验更好,你能看到 Agent 的回答逐渐出现,就像 ChatGPT 那种打字效果。

Console 模式的工作原理

Console 模式其实就是帮你做了几件事: 1. 自动创建一个内存中的会话(InMemoryService) 2. 读取你在终端输入的文字 3. 把文字发给 Agent 的 Runner 4. 把 Agent 的回答打印出来 5. 循环往复,直到你按 Ctrl+C

很简单,但在开发阶段真的很好用。不用启动服务器、不用 curl,直接对话就行。


方式四:A2A 协议 - Agent 之间的远程通信

这是一个比较高级的场景:你有多个 Agent 分布在不同的服务上,它们需要互相调用。ADK-Go 支持 A2A(Agent-to-Agent)协议来实现这种跨服务的 Agent 通信。

什么是 A2A 协议

A2A 是 Google 主导的一个开放协议,专门用于 Agent 之间的通信。你可以把它理解成 Agent 世界的"HTTP": - 每个 Agent 有一个 Agent Card,描述自己的能力(类似 API 文档) - Agent Card 放在 /.well-known/agent.json 路径下(类似 robots.txt) - 通信使用 JSON-RPC 协议 - 支持 流式响应

服务端:暴露 Agent

用 Launcher 的 a2a 模式,一行代码就能把你的 Agent 暴露为 A2A 服务:

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    weatherAgent, _ := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "提供天气查询服务的Agent",
        Instruction: "你是一个天气助手,帮用户查询天气信息。",
        Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    })

    config := &launcher.Config{
        AgentLoader: agent.NewSingleLoader(weatherAgent),
    }

    // 启动 A2A 服务(同时也可以加上 api)
    l := full.NewLauncher()
    if err := l.Execute(ctx, config, []string{"web", "a2a"}); err != nil {
        log.Fatalf("启动失败: %v", err)
    }
}

启动后,A2A 服务默认运行在 http://localhost:8080,有两个关键路径: - /.well-known/agent.json - Agent Card,描述 Agent 的能力 - /a2a/invoke - JSON-RPC 调用端点

你可以用 curl 看看 Agent Card 长什么样:

curl http://localhost:8080/.well-known/agent.json

返回类似这样的 JSON:

{
  "name": "weather_agent",
  "description": "提供天气查询服务的Agent",
  "url": "http://localhost:8080/a2a/invoke",
  "preferredTransport": "jsonrpc",
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "capabilities": {
    "streaming": true
  }
}

客户端:调用远程 Agent

在另一个服务里,你可以用 remoteagent.NewA2A 来消费这个远程 Agent:

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/agent/remoteagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
)

func main() {
    ctx := context.Background()

    // 创建一个远程Agent代理
    // 只需要提供远程Agent的地址,ADK会自动发现Agent Card
    weatherRemote, err := remoteagent.NewA2A(remoteagent.A2AConfig{
        Name:            "远程天气Agent",
        AgentCardSource: "http://localhost:8080/.well-known/agent.json",
    })
    if err != nil {
        log.Fatalf("创建远程Agent失败: %v", err)
    }

    // 也可以直接提供 Agent Card 对象,跳过网络发现
    // weatherRemote, _ := remoteagent.NewA2A(remoteagent.A2AConfig{
    //     Name: "远程天气Agent",
    //     AgentCard: &a2a.AgentCard{
    //         Name: "weather_agent",
    //         URL:  "http://remote-server:8080/a2a/invoke",
    //     },
    // })

    // 创建一个"协调者"Agent,把远程Agent作为子Agent使用
    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    coordinator, _ := llmagent.New(llmagent.Config{
        Name:        "coordinator",
        Model:       model,
        Description: "协调多个Agent的总管",
        Instruction: "你是一个协调者,当用户问天气相关问题时,交给天气Agent处理。",
        SubAgents:   []agent.Agent{weatherRemote},
    })

    config := &launcher.Config{
        AgentLoader: agent.NewSingleLoader(coordinator),
    }

    l := full.NewLauncher()
    if err := l.Execute(ctx, config, os.Args[1:]); err != nil {
        log.Fatalf("启动失败: %v", err)
    }
}

这样做有什么好处呢?

  • 独立部署:每个 Agent 可以独立开发、测试、部署,互不干扰
  • 独立扩缩容:天气 Agent 请求量大就多开几个实例,不影响其他 Agent
  • 技术异构:A2A 是跨语言协议,你的 Go Agent 可以调用 Python 写的 Agent,反过来也行
  • 团队协作:不同团队负责不同的 Agent,通过 A2A 协议集成

手动搭建 A2A 服务端

如果你不想用 Launcher,也可以自己手动搭建 A2A 服务。这给你更多的控制权:

package main

import (
    "context"
    "log"
    "net/http"
    "os"

    "github.com/a2aproject/a2a-go/a2a"
    "github.com/a2aproject/a2a-go/a2asrv"
    "google.golang.org/genai"

    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/server/adka2a"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    myAgent, _ := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "天气查询Agent",
        Instruction: "你是一个天气助手。",
        Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    })

    // 构建 Agent Card
    agentCard := &a2a.AgentCard{
        Name:               myAgent.Name(),
        Description:        myAgent.Description(),
        URL:                "http://localhost:9090/invoke",
        PreferredTransport: a2a.TransportProtocolJSONRPC,
        DefaultInputModes:  []string{"text/plain"},
        DefaultOutputModes: []string{"text/plain"},
        Skills:             adka2a.BuildAgentSkills(myAgent),
        Capabilities:       a2a.AgentCapabilities{Streaming: true},
    }

    // 创建执行器
    executor := adka2a.NewExecutor(adka2a.ExecutorConfig{
        RunnerConfig: runner.Config{
            AppName:        myAgent.Name(),
            Agent:          myAgent,
            SessionService: session.InMemoryService(),
        },
    })

    // 创建请求处理器
    reqHandler := a2asrv.NewHandler(executor)

    // 注册路由
    mux := http.NewServeMux()
    mux.Handle("/.well-known/agent.json", a2asrv.NewStaticAgentCardHandler(agentCard))
    mux.Handle("/invoke", a2asrv.NewJSONRPCHandler(reqHandler))

    log.Println("A2A 服务启动在 :9090")
    http.ListenAndServe(":9090", mux)
}

MCP 工具集成

MCP(Model Context Protocol)是另一个重要的协议,用来让 Agent 接入外部工具。跟 A2A 不同,MCP 关注的是工具和数据,而不是 Agent 之间的对话。

本地 MCP 服务器

你可以在进程内创建一个 MCP 服务器,注册自定义工具,然后让 Agent 使用:

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/modelcontextprotocol/go-sdk/mcp"
    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/mcptoolset"
)

// 定义工具的输入和输出结构
type WeatherInput struct {
    City string `json:"city" jsonschema:"城市名称"`
}

type WeatherOutput struct {
    Summary string `json:"weather_summary" jsonschema:"天气概况"`
}

// 工具的实现函数
func GetWeather(ctx context.Context, req *mcp.CallToolRequest, input WeatherInput) (*mcp.CallToolResult, WeatherOutput, error) {
    return nil, WeatherOutput{
        Summary: fmt.Sprintf("%s今天天气晴朗,温度适宜。", input.City),
    }, nil
}

func main() {
    ctx := context.Background()

    // 创建内存中的MCP传输通道
    clientTransport, serverTransport := mcp.NewInMemoryTransports()

    // 创建并启动MCP服务器
    server := mcp.NewServer(
        &mcp.Implementation{Name: "weather_server", Version: "v1.0.0"},
        nil,
    )

    // 注册工具到MCP服务器
    mcp.AddTool(server, &mcp.Tool{
        Name:        "get_weather",
        Description: "查询指定城市的天气",
    }, GetWeather)

    // 连接MCP服务器
    _, err := server.Connect(ctx, serverTransport, nil)
    if err != nil {
        log.Fatalf("MCP服务器连接失败: %v", err)
    }

    // 创建MCP工具集
    mcpTools, err := mcptoolset.New(mcptoolset.Config{
        Transport: clientTransport,
    })
    if err != nil {
        log.Fatalf("创建MCP工具集失败: %v", err)
    }

    // 创建Agent,使用MCP工具集
    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    myAgent, _ := llmagent.New(llmagent.Config{
        Name:        "helper_agent",
        Model:       model,
        Description: "多功能助手Agent",
        Instruction: "你是一个助手,可以帮用户查询天气。",
        Toolsets:    []tool.Toolset{mcpTools}, // 注意这里用的是 Toolsets,不是 Tools
    })

    config := &launcher.Config{
        AgentLoader: agent.NewSingleLoader(myAgent),
    }

    l := full.NewLauncher()
    if err := l.Execute(ctx, config, os.Args[1:]); err != nil {
        log.Fatalf("启动失败: %v", err)
    }
}

注意 llmagent.Config 有两个字段可以放工具: - Tools - 直接放 tool.Tool 类型的工具 - Toolsets - 放 tool.Toolset 类型的工具集,MCP 工具集就是这种

远程 MCP 服务器

你也可以接入远程的 MCP 服务器。比如 GitHub 官方提供了一个 MCP 服务器,你可以用它来操作 GitHub:

import (
    "github.com/modelcontextprotocol/go-sdk/mcp"
    "golang.org/x/oauth2"
    "google.golang.org/adk/tool/mcptoolset"
)

// 用 OAuth2 认证的 HTTP 客户端连接 GitHub MCP
ts := oauth2.StaticTokenSource(
    &oauth2.Token{AccessToken: os.Getenv("GITHUB_PAT")},
)

transport := &mcp.StreamableClientTransport{
    Endpoint:   "https://api.githubcopilot.com/mcp/",
    HTTPClient: oauth2.NewClient(ctx, ts),
}

mcpTools, err := mcptoolset.New(mcptoolset.Config{
    Transport: transport,
})
if err != nil {
    log.Fatalf("连接GitHub MCP失败: %v", err)
}

这样你的 Agent 就能调用 GitHub 的各种 API 了,比如创建 Issue、查看 PR、管理仓库等等。

MCP vs A2A 的区别

这两个协议经常被搞混,简单总结一下:

MCP A2A
关注点 工具和数据 Agent 之间的对话
使用场景 Agent 调用外部工具 Agent 之间互相合作
类比 给 Agent 装上"手" 让 Agent 互相"说话"
协议 JSON-RPC JSON-RPC

一个 Agent 可以同时使用 MCP 工具和 A2A 远程 Agent,它们不冲突。


多 Agent 服务

前面的例子都是部署单个 Agent。如果你有多个 Agent,想在同一个服务里提供,可以用 NewMultiLoader

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/geminitool"
)

func main() {
    ctx := context.Background()

    model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })

    // 创建多个Agent
    weatherAgent, _ := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "天气查询Agent",
        Instruction: "你是天气助手。",
        Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    })

    codeAgent, _ := llmagent.New(llmagent.Config{
        Name:        "code_agent",
        Model:       model,
        Description: "编程助手Agent",
        Instruction: "你是一个编程助手,帮用户写代码、解答编程问题。",
    })

    translateAgent, _ := llmagent.New(llmagent.Config{
        Name:        "translate_agent",
        Model:       model,
        Description: "翻译Agent",
        Instruction: "你是一个翻译助手,帮用户在不同语言之间翻译。",
    })

    // 用 NewMultiLoader 加载多个Agent
    // 第一个参数是"根Agent"(默认Agent),后面的是附加Agent
    agentLoader, err := agent.NewMultiLoader(weatherAgent, codeAgent, translateAgent)
    if err != nil {
        log.Fatalf("创建MultiLoader失败: %v", err)
    }

    config := &launcher.Config{
        AgentLoader: agentLoader,
    }

    l := full.NewLauncher()
    if err := l.Execute(ctx, config, os.Args[1:]); err != nil {
        log.Fatalf("启动失败: %v", err)
    }
}

使用 MultiLoader 后:

  • GET /api/list-apps 会返回所有 Agent 的名字:["weather_agent", "code_agent", "translate_agent"]
  • 调用 /api/run 时,通过 appName 字段指定你要用哪个 Agent
  • 每个 Agent 有自己独立的会话空间

注意 NewMultiLoader 的签名:

func NewMultiLoader(root Agent, agents ...Agent) (Loader, error)

第一个参数是根 Agent(默认的那个),后面可以跟任意数量的其他 Agent。所有 Agent 的 Name 必须唯一,重复了会报错。


生产环境建议

前面说了这么多部署方式,你肯定在想:真正上线的时候还需要注意什么?这里给几条实战建议。

1. 使用持久化的 SessionService

前面的例子都用的 session.InMemoryService(),数据全在内存里,服务一重启就没了。生产环境你需要把会话数据存到数据库里。

ADK-Go 提供了基于数据库的 SessionService:

import "google.golang.org/adk/session/database"

// 使用数据库存储会话
// 具体的数据库配置取决于你的环境
dbSessionService := database.NewService(db)

config := &launcher.Config{
    AgentLoader:    agent.NewSingleLoader(myAgent),
    SessionService: dbSessionService, // 用数据库替代内存
}

如果你使用 Google Cloud,还可以用 Vertex AI 提供的托管会话服务:

import "google.golang.org/adk/session/vertexai"

// 使用 Vertex AI 的托管会话服务
// 数据自动持久化,不用操心数据库
vertexSessionService := vertexai.NewService(cfg)

config := &launcher.Config{
    AgentLoader:    agent.NewSingleLoader(myAgent),
    SessionService: vertexSessionService,
}

2. 添加健康检查

负载均衡器(如 Nginx、K8s Ingress)需要知道你的服务是否健康。加一个健康检查端点是基本操作:

mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
    // 可以在这里检查数据库连接、模型服务等
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
})

// 如果需要更详细的健康检查
mux.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
    // 检查所有依赖是否就绪
    // 比如数据库连接、模型API可用性等
    if !checkDependencies() {
        w.WriteHeader(http.StatusServiceUnavailable)
        w.Write([]byte("NOT READY"))
        return
    }
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("READY"))
})

3. 配置合理的超时时间

Agent 跟 LLM 交互可能需要比较长的时间,特别是复杂任务。你需要合理设置超时:

// 如果你用 Launcher 的 web 模式
go run main.go web \
    -write-timeout 120s \    // 写超时要足够长,因为LLM响应可能很慢
    -read-timeout 30s \      // 读超时不用太长,请求体一般不大
    -idle-timeout 120s \     // 空闲超时
    -shutdown-timeout 30s \  // 优雅关闭的等待时间
    api

如果你自己搭建 HTTP 服务器:

srv := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    WriteTimeout: 120 * time.Second, // LLM 响应可能很慢
    ReadTimeout:  30 * time.Second,
    IdleTimeout:  120 * time.Second,
}
srv.ListenAndServe()

4. 优雅关闭

不要直接 kill 掉服务,要给正在进行的请求一个完成的机会:

import (
    "context"
    "os"
    "os/signal"
    "syscall"
)

// 创建一个可以取消的 context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// 监听系统信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

srv := &http.Server{
    Addr:    ":8080",
    Handler: mux,
}

// 在 goroutine 里启动服务器
go func() {
    if err := srv.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatalf("服务异常: %v", err)
    }
}()

log.Println("服务已启动,按 Ctrl+C 优雅关闭")

// 等待关闭信号
<-sigChan
log.Println("收到关闭信号,正在优雅关闭...")

// 给30秒时间让正在处理的请求完成
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()

if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Fatalf("关闭失败: %v", err)
}

log.Println("服务已安全关闭")

好消息是,如果你用 Launcher 的 web 模式,它已经帮你处理好了优雅关闭逻辑。

5. 日志和监控

ADK-Go 支持 OpenTelemetry,你可以把遥测数据导出到各种监控系统:

// 在 Launcher 模式下,用 -otel_to_cloud 参数开启
go run main.go web -otel_to_cloud api

// 或者在代码中配置
import "google.golang.org/adk/telemetry"

config := &launcher.Config{
    AgentLoader:    agent.NewSingleLoader(myAgent),
    SessionService: sessionService,
    TelemetryOptions: []telemetry.Option{
        telemetry.WithSpanProcessors(yourSpanProcessor),
    },
}

6. 部署架构建议

对于简单的项目:

用户 --> Nginx/Traefik --> Go Agent 服务 --> LLM API

对于需要高可用的项目:

用户 --> 负载均衡器 --> Go Agent 服务 (多实例) --> LLM API
                                |
                                v
                           数据库 (Session 持久化)

对于微服务架构:

用户 --> API Gateway --> 协调者 Agent
                            |
                    +-------+-------+
                    |       |       |
                    v       v       v
                Agent A  Agent B  Agent C  (各自独立部署,通过 A2A 通信)

小结

这一章我们系统地学习了 ADK-Go 的 4 种部署方式:

  1. REST API - 生产环境首选,用 adkrest.NewHandler 创建标准 HTTP Handler,兼容任何 Go HTTP 框架
  2. Web UI - 开发演示利器,用 web api webui 一键启动可视化界面
  3. Console - 最轻量的调试方式,在终端里直接跟 Agent 聊天
  4. A2A - Agent 之间的远程通信协议,适合微服务架构

我们还学了: - MCP 工具集成 - 接入外部工具生态,支持本地和远程 MCP 服务器 - 多 Agent 服务 - 用 NewMultiLoader 在一个服务里部署多个 Agent - 生产环境最佳实践 - 持久化会话、健康检查、超时配置、优雅关闭、日志监控

到这里,你的 Agent 已经可以真正服务用户了。从开发到上线,整个链路都打通了。


动手练习

练习:部署 Agent 为 REST API 并用 curl 测试

目标:把你的 Agent 部署为 REST API,然后用 curl 完成一次完整的对话流程。

步骤

  1. 创建一个新项目目录:
mkdir my-agent-api && cd my-agent-api
go mod init my-agent-api
  1. 创建 main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "time"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/server/adkrest"
    "google.golang.org/adk/session"
)

func main() {
    ctx := context.Background()

    // 创建模型
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })
    if err != nil {
        log.Fatalf("创建模型失败: %v", err)
    }

    // 创建一个简单的聊天Agent
    chatAgent, err := llmagent.New(llmagent.Config{
        Name:        "chat_agent",
        Model:       model,
        Description: "一个友好的聊天Agent",
        Instruction: "你是一个友好的中文聊天助手。回答要简洁有趣。",
    })
    if err != nil {
        log.Fatalf("创建Agent失败: %v", err)
    }

    // 配置
    config := &launcher.Config{
        AgentLoader:    agent.NewSingleLoader(chatAgent),
        SessionService: session.InMemoryService(),
    }

    // 创建REST API
    apiHandler := adkrest.NewHandler(config, 120*time.Second)

    mux := http.NewServeMux()
    mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    log.Println("Agent API 服务启动在 http://localhost:8080")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatalf("服务启动失败: %v", err)
    }
}
  1. 安装依赖并启动:
export GOOGLE_API_KEY="你的API密钥"
go mod tidy
go run main.go
  1. 在另一个终端里,用 curl 测试完整流程:
# 步骤1: 查看可用的Agent
echo "=== 查看可用的Agent ==="
curl -s http://localhost:8080/api/list-apps | jq .

# 步骤2: 创建会话
echo "=== 创建会话 ==="
SESSION=$(curl -s -X POST \
    http://localhost:8080/api/apps/chat_agent/users/test_user/sessions | jq -r '.id')
echo "会话ID: $SESSION"

# 步骤3: 发送消息
echo "=== 发送第一条消息 ==="
curl -s -X POST http://localhost:8080/api/run \
    -H "Content-Type: application/json" \
    -d "{
        \"appName\": \"chat_agent\",
        \"userId\": \"test_user\",
        \"sessionId\": \"$SESSION\",
        \"newMessage\": {
            \"role\": \"user\",
            \"parts\": [{\"text\": \"你好!给我讲个笑话吧\"}]
        }
    }" | jq .

# 步骤4: 继续对话(Agent会记住上下文)
echo "=== 发送第二条消息 ==="
curl -s -X POST http://localhost:8080/api/run \
    -H "Content-Type: application/json" \
    -d "{
        \"appName\": \"chat_agent\",
        \"userId\": \"test_user\",
        \"sessionId\": \"$SESSION\",
        \"newMessage\": {
            \"role\": \"user\",
            \"parts\": [{\"text\": \"再来一个更好笑的\"}]
        }
    }" | jq .

# 步骤5: 查看会话历史
echo "=== 查看会话历史 ==="
curl -s http://localhost:8080/api/apps/chat_agent/users/test_user/sessions/$SESSION | jq .

# 步骤6: 健康检查
echo "=== 健康检查 ==="
curl -s http://localhost:8080/health
echo ""
  1. 试试流式模式(SSE):
echo "=== SSE 流式对话 ==="
curl -N -X POST http://localhost:8080/api/run_sse \
    -H "Content-Type: application/json" \
    -d "{
        \"appName\": \"chat_agent\",
        \"userId\": \"test_user\",
        \"sessionId\": \"$SESSION\",
        \"newMessage\": {
            \"role\": \"user\",
            \"parts\": [{\"text\": \"用3句话介绍一下Go语言\"}]
        },
        \"streaming\": true
    }"

验证标准: - 健康检查返回 OK - 能成功创建会话并获得会话 ID - Agent 能正确回答问题 - 第二次对话 Agent 能记住上下文 - SSE 模式下能看到数据逐行推送

完成这个练习后,你就掌握了 ADK-Go Agent 部署的核心技能。接下来在你自己的项目中,只需要把 Agent 的逻辑替换成你的业务逻辑就行了。


下一章预告:我们会聊聊 Agent 的测试和调试技巧,让你的 Agent 不仅能跑,还跑得稳。