第七章:部署上线 - 让 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 种部署方式:
- REST API - 生产环境首选,用
adkrest.NewHandler创建标准 HTTP Handler,兼容任何 Go HTTP 框架 - Web UI - 开发演示利器,用
web api webui一键启动可视化界面 - Console - 最轻量的调试方式,在终端里直接跟 Agent 聊天
- A2A - Agent 之间的远程通信协议,适合微服务架构
我们还学了:
- MCP 工具集成 - 接入外部工具生态,支持本地和远程 MCP 服务器
- 多 Agent 服务 - 用 NewMultiLoader 在一个服务里部署多个 Agent
- 生产环境最佳实践 - 持久化会话、健康检查、超时配置、优雅关闭、日志监控
到这里,你的 Agent 已经可以真正服务用户了。从开发到上线,整个链路都打通了。
动手练习
练习:部署 Agent 为 REST API 并用 curl 测试
目标:把你的 Agent 部署为 REST API,然后用 curl 完成一次完整的对话流程。
步骤:
- 创建一个新项目目录:
mkdir my-agent-api && cd my-agent-api
go mod init my-agent-api
- 创建
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)
}
}
- 安装依赖并启动:
export GOOGLE_API_KEY="你的API密钥"
go mod tidy
go run main.go
- 在另一个终端里,用 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 ""
- 试试流式模式(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 不仅能跑,还跑得稳。