Hello World Agent:从零构建最小可运行单元并理解其工程本质

上一章我们了解了 Agent 的三要素:Model、Tool、Instruction。本章将把这些概念转化为可执行的代码。我不仅会逐行拆解官方 Quickstart 示例,更会揭示每一行代码背后的设计决策——为什么这样写?在生产环境中会遇到什么问题?如何改进?这是你从"看懂示例"到"能写生产代码"的关键一跃。

完整的 Hello World Agent 代码

以下代码在官方示例基础上进行了工程化增强:增加了配置校验、结构化日志、优雅的错误处理和可观测性埋点。

package main

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

	"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"
	"google.golang.org/genai"
)

func main() {
	// 使用带超时的 context,避免程序挂死
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()

	// 配置加载与校验
	cfg, err := loadConfig()
	if err != nil {
		log.Fatalf("[FATAL] Configuration error: %v", err)
	}

	log.Printf("[INFO] Starting agent with model: %s", cfg.ModelName)

	// 初始化模型客户端
	model, err := gemini.NewModel(ctx, cfg.ModelName, &genai.ClientConfig{
		APIKey: cfg.APIKey,
	})
	if err != nil {
		log.Fatalf("[FATAL] Failed to initialize Gemini model: %v", err)
	}

	// 构建 Agent
	timeAgent, err := llmagent.New(llmagent.Config{
		Name:         "hello_time_agent",
		Model:        model,
		Description:  "A helpful assistant that tells the current time in any city using Google Search.",
		Instruction:  buildSystemInstruction(),
		Tools:        []tool.Tool{geminitool.GoogleSearch{}},
	})
	if err != nil {
		log.Fatalf("[FATAL] Failed to create agent: %v", err)
	}

	log.Printf("[INFO] Agent '%s' initialized successfully", timeAgent.Name())

	// 配置启动器
	config := &launcher.Config{
		AgentLoader: agent.NewSingleLoader(timeAgent),
	}

	// 执行
	l := full.NewLauncher()
	if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
		log.Fatalf("[FATAL] Execution failed: %v\n\nUsage: %s", err, l.CommandLineSyntax())
	}
}

// Config 保存应用配置
type Config struct {
	APIKey    string
	ModelName string
}

// loadConfig 从环境变量加载配置,并进行严格校验
func loadConfig() (*Config, error) {
	apiKey := os.Getenv("GOOGLE_API_KEY")
	if apiKey == "" {
		return nil, fmt.Errorf("GOOGLE_API_KEY environment variable is not set\n" +
			"Please run: export GOOGLE_API_KEY=your-api-key")
	}

	// 简单的 Key 格式校验(Gemini Key 通常是 39 字符的字母数字混合)
	if len(apiKey) < 20 {
		return nil, fmt.Errorf("GOOGLE_API_KEY appears to be invalid (too short)")
	}

	modelName := os.Getenv("GEMINI_MODEL")
	if modelName == "" {
		modelName = "gemini-2.0-flash"
		log.Printf("[WARN] GEMINI_MODEL not set, using default: %s", modelName)
	}

	return &Config{
		APIKey:    apiKey,
		ModelName: modelName,
	}, nil
}

// buildSystemInstruction 构建系统提示词
// 生产环境中,复杂的提示词建议从文件或配置中心加载,便于热更新
func buildSystemInstruction() string {
	return `You are a precise and helpful time assistant. Your capabilities:

1. When asked about the current time in a city, use the Google Search tool to find accurate information.
2. Always specify the timezone (e.g., CST, EST, UTC) in your response.
3. If the city name is ambiguous (e.g., "Springfield" exists in multiple countries), ask for clarification.
4. Respond in the same language as the user's query.
5. If search fails, honestly report that you cannot determine the time.`
}

代码逐行深度拆解

第一部分:Import 与包选择策略

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

	"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"
	"google.golang.org/genai"
)

包职责详解:

包路径职责生产注意事项
adk/agentAgent 抽象定义和加载器接口NewSingleLoader 适用于单 Agent 场景;多 Agent 场景需使用 NewMultiLoader
adk/agent/llmagent基于 LLM 的 Agent 实现这是目前最成熟的实现,未来可能有其他类型(如规则引擎 Agent)
adk/cmd/launcher启动器框架负责解析命令行参数、初始化运行环境
adk/cmd/launcher/full全功能启动器同时包含 CLI 和 Web 能力,开发调试方便;生产可只引入需要的子包
adk/model/geminiGemini 模型适配器目前 ADK Go 主要支持 Gemini;未来可能支持 Claude、GPT 等
adk/tool/geminitoolGoogle 官方工具集GoogleSearch 是最常用的工具;注意它受 API 配额限制
genaiGoogle AI 通用客户端ClientConfig 包含超时、重试、代理等底层网络配置

依赖管理建议:ADK Go 的模块划分很细,建议只引入实际使用的包。不必要的 import 会增加编译时间和二进制体积。

第二部分:Context 的生命周期管理

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

这是生产代码与示例代码的关键差异之一。context.WithTimeout 确保整个 Agent 会话不会无限期挂起。在实际场景中,我们遇到过因为网络分区导致 Gemini API 连接挂死的情况——没有超时设置的程序会永远等待,goroutine 泄漏最终导致 OOM。

进阶技巧:对于流式响应(Stream)场景,应该为每个请求单独创建子 context:

reqCtx, reqCancel := context.WithTimeout(ctx, 30*time.Second)
defer reqCancel()
// 使用 reqCtx 进行单次模型调用

第三部分:模型初始化与配置

model, err := gemini.NewModel(ctx, cfg.ModelName, &genai.ClientConfig{
	APIKey: cfg.APIKey,
})

gemini.NewModel 是连接你的代码和 Google AI 基础设施的桥梁。它的第三个参数 ClientConfig 包含许多生产环境需要关注的选项:

&genai.ClientConfig{
	APIKey:    cfg.APIKey,
	Backend:   genai.BackendGoogleAI,  // 或 BackendVertexAI(企业级)
	HTTPOptions: &genai.HTTPOptions{
		Timeout: 30000,  // 毫秒
		Headers: map[string]string{
			"X-Custom-Header": "my-app-v1.0",
		},
	},
}

模型选择策略

模型延迟能力成本适用场景
gemini-2.0-flash日常对话、生产环境首选
gemini-2.0-pro复杂推理、代码生成、数学问题
gemini-2.0-flash-exp中高实验性功能测试,不建议生产使用

生产经验:我们在一个客服 Agent 项目中采用了"模型降级"策略——先用 Flash 处理 90% 的查询,当检测到复杂意图(通过简单的关键词匹配)时,再升级到 Pro。这样在保证体验的同时,将成本降低了 60%。

第四部分:Agent 配置与系统提示词工程

timeAgent, err := llmagent.New(llmagent.Config{
	Name:         "hello_time_agent",
	Model:        model,
	Description:  "A helpful assistant that tells the current time...",
	Instruction:  buildSystemInstruction(),
	Tools:        []tool.Tool{geminitool.GoogleSearch{}},
})

这是 Agent 的"灵魂"所在。每个字段都直接影响 Agent 的行为表现:

Name:不仅是标识,更是日志追踪、监控指标、分布式链路追踪中的关键标签。建议使用带命名空间的名称,如 team-a/time-agent/v1

Description:这个字段会被模型"阅读",帮助它理解自己在整个系统中的角色。在 Multi-Agent 系统中,Description 是 Agent 路由决策的重要依据。

Instruction(系统提示词):这是提示工程(Prompt Engineering)的主战场。一个好的系统提示词应该:

  1. 明确角色边界:告诉 Agent 它能做什么、不能做什么
  2. 规定输出格式:要求结构化输出时,给出示例
  3. 处理边界情况:明确错误时的行为(是猜测还是诚实承认不知道)
  4. 多语言支持:如果服务多语言用户,要求 Agent 用用户相同的语言回复

生产提示词示例(电商客服场景):

const systemInstruction = `You are a professional e-commerce customer service assistant.

## Your Capabilities
- Query order status using the OrderQuery tool
- Process returns using the ReturnTool
- Answer FAQs about shipping, payment, and products

## Rules
1. ALWAYS verify the user's identity before accessing order information
2. If you don't know the answer, transfer to human agent using EscalationTool
3. Never make up tracking numbers or delivery dates
4. Respond in the same language as the user

## Output Format
For order queries, respond with:
- Order ID
- Current status
- Estimated delivery (if available)`

第五部分:启动器与运行模式

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

l := full.NewLauncher()
if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
	log.Fatalf("Run failed: %v\n\nUsage: %s", err, l.CommandLineSyntax())
}

AgentLoader 是 ADK Go 运行时的核心抽象。它负责在程序启动时加载 Agent 实例。NewSingleLoader 是最简单的实现,适用于只有一个 Agent 的场景。

多 Agent 场景:当系统中有多个 Agent(如"天气 Agent"、“翻译 Agent”、“计算 Agent”),需要使用 NewMultiLoader

config := &launcher.Config{
	AgentLoader: agent.NewMultiLoader(
		weatherAgent,
		translateAgent,
		calcAgent,
	),
}

运行时会根据用户输入的意图,自动路由到最合适的 Agent。


运行 Agent

CLI 模式

source .env
go run agent.go

预期输出:

2026/05/29 14:32:01 [INFO] Starting agent with model: gemini-2.0-flash
2026/05/29 14:32:02 [INFO] Agent 'hello_time_agent' initialized successfully
[INFO] Agent "hello_time_agent" started
> What time is it in Shanghai?

Web 模式

go run agent.go web api webui

启动后访问 http://localhost:8080/webui。Web 模式的优势在于:

  1. 会话保持:CLI 每次运行都是独立进程,Web 模式可以保持多轮对话上下文
  2. 可视化:可以观察 Agent 的思考过程(Thought → Action → Observation → Final Answer)
  3. 易分享:把 URL 发给同事,零配置即可体验

常见错误分析与深度排障

错误 1:Failed to create model: UNAUTHORIZED

根因层级分析

  1. 环境变量未加载GOOGLE_API_KEY 为空
  2. Key 格式错误:复制时多了空格或换行符
  3. Key 已失效:在 Google AI Studio 中被删除或过期
  4. 项目未启用 API:Google Cloud 项目中未启用 Gemini API

系统化排障脚本

#!/bin/bash
# check-env.sh - 环境检查脚本

echo "=== ADK Go 环境诊断 ==="

# 检查 Go 版本
echo -n "Go version: "
go version

# 检查 API Key
if [ -z "$GOOGLE_API_KEY" ]; then
    echo "❌ GOOGLE_API_KEY is not set"
    echo "   Fix: source .env or export GOOGLE_API_KEY=your-key"
else
    echo "✅ GOOGLE_API_KEY is set (length: ${#GOOGLE_API_KEY})"
fi

# 检查网络连通性
echo -n "Testing Google API connectivity... "
if curl -s --max-time 5 "https://generativelanguage.googleapis.com" > /dev/null; then
    echo "✅ OK"
else
    echo "❌ Failed (check your network or proxy settings)"
fi

# 检查代理
if [ -n "$HTTPS_PROXY" ]; then
    echo "ℹ️  HTTPS_PROXY is set to: $HTTPS_PROXY"
fi

错误 2:Failed to create model: PERMISSION_DENIED

根因:API Key 没有调用 Gemini API 的权限。

解决

  1. 访问 Google AI Studio
  2. 找到你的 Key,确认关联的项目已启用 “Generative Language API”
  3. 如果是新创建的 Google Cloud 项目,可能需要等待几分钟让权限生效

错误 3:google.golang.org/adk/agent/llmagent: module not found

根因:Go 模块缓存中没有 ADK Go 的依赖,或者 go.mod 未正确初始化。

解决

# 1. 确认 go.mod 存在
ls go.mod || go mod init my-agent

# 2. 清理缓存并重新下载
go clean -modcache
go mod tidy

# 3. 验证依赖是否下载成功
go list -m google.golang.org/adk

错误 4:Agent 运行后无响应或超时

根因分析

  1. 网络问题:无法连接到 Gemini API(中国大陆常见)
  2. 配额耗尽:API Key 的每日配额已用完
  3. 工具调用失败:Google Search 工具需要额外的 API 权限

诊断方法

// 在代码中添加更详细的日志
log.Printf("[DEBUG] Creating model with name: %s", cfg.ModelName)
model, err := gemini.NewModel(ctx, cfg.ModelName, config)
if err != nil {
	log.Printf("[ERROR] Model creation failed. "+
		"Model: %s, Error type: %T, Error: %v", cfg.ModelName, err, err)
	return err
}
log.Printf("[DEBUG] Model created successfully")

扩展练习与思考题

练习 1:加入自定义 Tool

尝试在 Tools 数组中加入更多工具:

Tools: []tool.Tool{
	geminitool.GoogleSearch{},
	geminitool.GoogleTranslate{},  // 翻译工具
	// 你的自定义工具
},

观察 Agent 如何根据用户意图自动选择合适的工具。这是理解 Agent “决策能力"的最佳方式。

练习 2:对比有 Tool 和无 Tool 的行为差异

Tools 改为空数组,重新运行。然后询问 “现在上海几点?”

观察要点

  • 有 Tool 时:Agent 会调用 Search 查询实时信息
  • 无 Tool 时:Agent 只能基于训练数据回答(可能给出错误时间)

这揭示了 Tool 的核心价值——弥补模型知识的时间边界。大语言模型的训练数据有截止日期,Tool 让它能够获取实时信息。

练习 3:修改系统提示词观察行为变化

尝试以下不同的 Instruction,观察 Agent 回答风格的差异:

  1. “You are a concise assistant. Answer in one sentence."(简洁模式)
  2. “You are a detailed assistant. Provide step-by-step reasoning."(详细模式)
  3. “You are a sarcastic assistant."(风格化模式——观察模型如何理解"讽刺”)

下一步

Agent 成功运行后,你已经建立了对 ADK Go 的完整感官认识。接下来,我们将深入对比 CLI 和 Web 两种运行方式——它们各自适合什么场景?在生产环境中应该如何选择?Web 模式的会话保持机制是如何实现的?这些都是构建生产级 Agent 系统必须理解的问题。

Agent 核心概念 | CLI vs Web 运行方式 →


想跟着学更多 Go ADK 实战?关注「全栈之巅-梦兽编程」公众号,每周更新 Go / AI 编程实战干货。

也欢迎了解 梦兽编程 AI 编程助手服务 ,帮你把 AI 编程工具用到生产环境。