梦兽编程
AI 套装

API Key 与 Model 选择:成本、能力与场景的权衡

详解 Google Gemini API Key 的获取与管理,以及如何根据场景选择合适的模型:flash/pro/experimental,及其对成本和能力的影响。

在 ADK Go 项目的全生命周期中,有两个决策会反复出现:API Key 如何管理才能既安全又便利?面对 Gemini 家族中 Flash、Pro、Experimental 等多个模型,如何在成本、延迟、能力之间找到最优平衡点?这两个问题看似简单,但在生产环境中,它们的答案直接影响着系统的安全性、可用性和运营成本。本章将分享我们在多个项目中的实战经验,包括一次因为 Key 管理不当导致的安全事件,以及通过模型动态路由将成本降低 60% 的优化案例。

Google Gemini API Key 全生命周期管理

获取 Key 的正确姿势

  1. 访问 Google AI Studio
  2. 使用 Google 账号登录(建议:使用团队共享的 Google Workspace 账号,便于权限管理和审计)
  3. 点击 “Create API Key”
  4. 选择或创建 Google Cloud 项目(关键决策点:配额和计费都以项目为维度,建议为生产、测试分别创建项目)
  5. 复制 Key(注意:Google 只显示一次,务必立即保存到安全位置)

项目隔离策略

环境项目命名配额策略访问控制
开发mycompany-ai-dev标准配额开发者群组
测试mycompany-ai-staging标准配额CI/CD 服务账号
生产mycompany-ai-prod申请提升配额受限服务账号

这种隔离的好处是:开发环境的误操作不会影响生产配额,生产环境的 Key 泄露范围也被限制在最小。

四级 Key 管理方案

根据项目阶段和团队规模,Key 管理应该逐步演进:

方案一:本地 .env 文件(个人开发/小团队)

# .env.example(提交到 git)
export GOOGLE_API_KEY="your-api-key-here"
export GEMINI_MODEL="gemini-2.0-flash"

# .env(不提交 git,每个开发者独立维护)
export GOOGLE_API_KEY="AIzaSy..."

团队协作流程

# 新成员 onboarding
cp .env.example .env
# 编辑 .env,填入自己的 API Key
vim .env
source .env && go run ./cmd/agent

方案二:环境变量注入(CI/CD 推荐)

在 GitHub Actions、GitLab CI 等流水线中,通过 Secrets 注入:

# .github/workflows/deploy.yml
name: Deploy Agent
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
          GEMINI_MODEL: ${{ vars.GEMINI_MODEL }}
        run: |
          go build -o agent ./cmd/agent
          ./agent

安全要点

  • 在 CI 日志中自动屏蔽 Secrets,防止泄露
  • 定期轮换 Key(建议每 90 天)
  • 为 CI 创建独立的 Service Account Key,权限最小化

方案三:Secret Manager(生产环境标准)

生产环境绝不应将 Key 存储在文件系统中。使用云厂商的 Secret Manager 是行业标准做法:

Google Cloud Secret Manager 集成

package config

import (
	"context"
	"fmt"

	secretmanager "cloud.google.com/go/secretmanager/apiv1"
	"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
)

// SecretManagerClient 封装 Secret Manager 操作
type SecretManagerClient struct {
	client *secretmanager.Client
	projectID string
}

// NewSecretManagerClient 创建客户端
func NewSecretManagerClient(ctx context.Context, projectID string) (*SecretManagerClient, error) {
	client, err := secretmanager.NewClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to create secretmanager client: %w", err)
	}
	return &SecretManagerClient{
		client:    client,
		projectID: projectID,
	}, nil
}

// GetSecret 获取指定版本的 Secret
func (s *SecretManagerClient) GetSecret(ctx context.Context, secretName string) (string, error) {
	req := &secretmanagerpb.AccessSecretVersionRequest{
		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/latest", s.projectID, secretName),
	}

	result, err := s.client.AccessSecretVersion(ctx, req)
	if err != nil {
		return "", fmt.Errorf("failed to access secret %s: %w", secretName, err)
	}

	return string(result.Payload.Data), nil
}

// Close 关闭客户端连接
func (s *SecretManagerClient) Close() error {
	return s.client.Close()
}

使用方式

func loadAPIKey(ctx context.Context) (string, error) {
	// 本地开发:从环境变量读取
	if key := os.Getenv("GOOGLE_API_KEY"); key != "" {
		return key, nil
	}

	// 生产环境:从 Secret Manager 读取
	sm, err := config.NewSecretManagerClient(ctx, "my-project-id")
	if err != nil {
		return "", err
	}
	defer sm.Close()

	return sm.GetSecret(ctx, "gemini-api-key")
}

方案四:Key Pool 与动态路由(高并发生产环境)

当单 Key 的配额成为瓶颈时,需要实现 Key Pool:

package config

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"google.golang.org/genai"
)

// KeyPool 管理多个 API Key,支持轮询和故障转移
type KeyPool struct {
	keys      []string
	current   uint64
	mu        sync.RWMutex
	failures  map[string]int       // 记录每个 Key 的失败次数
	lastUsed  map[string]time.Time // 记录最后使用时间
}

// NewKeyPool 从环境变量创建 Key Pool
func NewKeyPool() *KeyPool {
	keys := []string{}
	for i := 1; ; i++ {
		key := os.Getenv(fmt.Sprintf("GOOGLE_API_KEY_%d", i))
		if key == "" {
			break
		}
		keys = append(keys, key)
	}

	if len(keys) == 0 {
		// 回退到单个 Key
		if key := os.Getenv("GOOGLE_API_KEY"); key != "" {
			keys = append(keys, key)
		}
	}

	return &KeyPool{
		keys:     keys,
		failures: make(map[string]int),
		lastUsed: make(map[string]time.Time),
	}
}

// GetKey 轮询获取下一个可用的 Key
func (p *KeyPool) GetKey() (string, error) {
	p.mu.RLock()
	defer p.mu.RUnlock()

	if len(p.keys) == 0 {
		return "", fmt.Errorf("no API keys available")
	}

	// 原子递增,实现无锁轮询
	idx := atomic.AddUint64(&p.current, 1) % uint64(len(p.keys))
	key := p.keys[idx]

	// 如果某个 Key 连续失败超过 3 次,暂时跳过
	if p.failures[key] > 3 {
		// 找下一个可用的 Key
		for i := 0; i < len(p.keys); i++ {
			idx = (idx + 1) % uint64(len(p.keys))
			key = p.keys[idx]
			if p.failures[key] <= 3 {
				break
			}
		}
	}

	return key, nil
}

// ReportFailure 报告某个 Key 的失败
func (p *KeyPool) ReportFailure(key string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.failures[key]++
}

// ReportSuccess 报告某个 Key 的成功,重置失败计数
func (p *KeyPool) ReportSuccess(key string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.failures[key] = 0
	p.lastUsed[key] = time.Now()
}

环境变量配置

export GOOGLE_API_KEY_1="AIzaSy..."
export GOOGLE_API_KEY_2="AIzaSy..."
export GOOGLE_API_KEY_3="AIzaSy..."

模型选择:Flash vs Pro vs Experimental

Gemini 模型家族能力矩阵

模型输入上下文输出速度推理能力成本稳定性生产建议
gemini-2.0-flash1M tokens极快良好首选
gemini-2.0-pro2M tokens中等复杂任务
gemini-2.0-flash-exp1M tokens良好+不推荐生产
gemini-2.0-flash-lite1M tokens最快基础极低简单任务

成本模型详解

Gemini API 的计费基于两个维度:

  1. 输入 Token 数:包括系统提示词、用户消息、工具返回结果
  2. 输出 Token 数:模型生成的回复

成本估算示例(以 gemini-2.0-flash 为例):

场景平均输入平均输出单次成本日 1K 次成本
简单问答500 tokens200 tokens~$0.0001~$0.1
带工具调用2K tokens500 tokens~$0.0005~$0.5
长文档分析50K tokens2K tokens~$0.01~$10

省钱技巧

  1. 精简系统提示词:每减少 100 tokens 的 Instruction,千次调用节省约 $0.01
  2. 控制输出长度:在 Instruction 中要求"简洁回答",可显著降低输出 token
  3. 缓存重复内容:对于固定的系统提示词,使用 Gemini 的上下文缓存功能

动态模型路由策略

生产环境中最优的方案不是"选一种模型用到死",而是根据任务复杂度动态选择:

package model

import (
	"context"
	"strings"

	"google.golang.org/adk/model/gemini"
	"google.golang.org/genai"
)

// Router 根据查询特征选择最优模型
type Router struct {
	flashClient *genai.Client
	proClient   *genai.Client
}

// NewRouter 创建模型路由器
func NewRouter(ctx context.Context, apiKey string) (*Router, error) {
	flash, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{APIKey: apiKey})
	if err != nil {
		return nil, err
	}

	pro, err := gemini.NewModel(ctx, "gemini-2.0-pro", &genai.ClientConfig{APIKey: apiKey})
	if err != nil {
		return nil, err
	}

	return &Router{flashClient: flash, proClient: pro}, nil
}

// SelectModel 根据查询内容选择模型
func (r *Router) SelectModel(query string) *genai.Client {
	// 简单关键词启发式路由
	complexIndicators := []string{
		"代码", "code", "debug", "算法", "数学", "证明",
		"分析", "比较", "评估", "优化",
	}

	lowerQuery := strings.ToLower(query)
	for _, indicator := range complexIndicators {
		if strings.Contains(lowerQuery, indicator) {
			return r.proClient
		}
	}

	// 默认使用 Flash(更快更便宜)
	return r.flashClient
}

路由效果:在我们的客服 Agent 项目中,这种简单启发式路由让 85% 的请求走 Flash 模型,只有 15% 的复杂请求升级到 Pro,整体成本降低了 58%,而用户满意度没有明显下降。

代码中的模型切换

// 基础版本:通过环境变量切换
modelName := os.Getenv("GEMINI_MODEL")
if modelName == "" {
	modelName = "gemini-2.0-flash"
}

model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{
	APIKey: os.Getenv("GOOGLE_API_KEY"),
})

// 进阶版本:支持模型别名和自动降级
modelAlias := os.Getenv("GEMINI_MODEL_ALIAS")
switch modelAlias {
case "fast":
	modelName = "gemini-2.0-flash-lite"
case "balanced":
	modelName = "gemini-2.0-flash"
case "powerful":
	modelName = "gemini-2.0-pro"
default:
	modelName = "gemini-2.0-flash"
}

配额管理与限流策略

Gemini API 配额体系

每个 API Key 都有默认配额限制:

配额类型免费层级付费层级重置周期
Requests per minute60可提升1 分钟滑动窗口
Requests per day1,500可提升每日 UTC 0:00
Tokens per minute1M可提升1 分钟滑动窗口
Tokens per day10M可提升每日 UTC 0:00

监控配额使用

// 在响应头中,Google API 会返回配额信息
// X-RateLimit-Remaining: 剩余请求数
// X-RateLimit-Reset: 重置时间戳

// 建议在每个请求后记录配额状态
func logQuotaStatus(header http.Header) {
	remaining := header.Get("X-RateLimit-Remaining")
	reset := header.Get("X-RateLimit-Reset")
	if remaining != "" {
		log.Printf("[QUOTA] Remaining: %s, Reset: %s", remaining, reset)
	}
}

客户端限流实现

在代码层面实现令牌桶限流,避免触发服务端限流:

package ratelimit

import (
	"context"
	"time"

	"golang.org/x/time/rate"
)

// Limiter 封装速率限制器
type Limiter struct {
	limiter *rate.Limiter
}

// NewLimiter 创建限流器
// r: 每秒允许的请求数
// b: 桶容量(突发流量缓冲)
func NewLimiter(r rate.Limit, b int) *Limiter {
	return &Limiter{
		limiter: rate.NewLimiter(r, b),
	}
}

// Wait 等待获取令牌
func (l *Limiter) Wait(ctx context.Context) error {
	return l.limiter.Wait(ctx)
}

// 使用示例:限制每分钟 50 请求(留 10 请求缓冲)
// limiter := ratelimit.NewLimiter(rate.Every(time.Minute/50), 10)
// if err := limiter.Wait(ctx); err != nil { return err }

超配额错误处理

当收到 429 RESOURCE_EXHAUSTED 时,应该实现指数退避重试:

package retry

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/genai"
)

// ExponentialBackoff 实现指数退避重试
func ExponentialBackoff(ctx context.Context, operation func() error, maxRetries int) error {
	var err error
	baseDelay := time.Second

	for i := 0; i < maxRetries; i++ {
		err = operation()
		if err == nil {
			return nil
		}

		// 检查是否是配额错误
		if !isQuotaError(err) {
			return err // 非配额错误,立即返回
		}

		// 计算退避时间
		delay := baseDelay * time.Duration(1<<i)
		if delay > 30*time.Second {
			delay = 30 * time.Second
		}

		log.Printf("[RETRY] Quota exceeded, retrying in %v (attempt %d/%d)", delay, i+1, maxRetries)

		select {
		case <-time.After(delay):
			continue
		case <-ctx.Done():
			return ctx.Err()
		}
	}

	return fmt.Errorf("max retries exceeded: %w", err)
}

func isQuotaError(err error) bool {
	if err == nil {
		return false
	}
	errStr := err.Error()
	return contains(errStr, "RESOURCE_EXHAUSTED") ||
		contains(errStr, "Quota exceeded") ||
		contains(errStr, "429")
}

func contains(s, substr string) bool {
	return strings.Contains(s, substr)
}

常见问题与生产陷阱

Q:本地跑得好好的,部署到服务器报 UNAUTHORIZED

根因排查清单

  1. 服务器所在地区是否可以访问 Google API?(中国大陆需要代理)
  2. 环境变量是否正确注入?echo $GOOGLE_API_KEY 验证
  3. Key 是否被服务器 IP 限制?检查 Google AI Studio 中的 IP 白名单设置
  4. 服务器时间是否正确?时间偏差过大会导致 OAuth 签名失败

诊断脚本

#!/bin/bash
echo "=== API 连通性诊断 ==="
echo "1. 检查环境变量:"
echo "GOOGLE_API_KEY length: ${#GOOGLE_API_KEY}"

echo "2. 检查网络连通性:"
curl -s -o /dev/null -w "%{http_code}" https://generativelanguage.googleapis.com/v1beta/models
echo ""

echo "3. 检查代理设置:"
echo "HTTPS_PROXY: $HTTPS_PROXY"

echo "4. 测试 API 调用:"
curl -s "https://generativelanguage.googleapis.com/v1beta/models?key=$GOOGLE_API_KEY" | head -c 200
echo ""

Q:同一个 Key 有时候能用有时候报配额不足

根因:配额是按时间窗口重置的。如果业务有突发流量(如整点定时任务),容易触达窗口上限。

解决方案

  1. 平滑流量:将定时任务分散到不同时间点执行
  2. Key Pool:多个 Key 轮询使用
  3. 本地缓存:对重复查询结果进行短期缓存(TTL 5-10 分钟)
  4. 申请提升配额:在 Google AI Studio 中申请更高的 RPM/TPM

Q:Experimental 模型突然不可用了

根因:Experimental 模型是 Google 的实验性产品,随时可能:

  • 下线或替换
  • 更改 API 签名
  • 调整配额策略
  • 改变输出行为

生产铁律:永远不要在生产环境依赖 Experimental 模型。如果必须试用,做好随时切换回稳定模型的准备:

// 安全的模型选择逻辑
func selectProductionModel() string {
	// 优先使用稳定版本
	if model := os.Getenv("GEMINI_MODEL_STABLE"); model != "" {
		return model
	}
	// 回退到最稳定的默认选项
	return "gemini-2.0-flash"
}

Q:如何监控 API 使用成本?

方案 1:基于日志的估算

// 在每次模型调用后记录 token 使用量
func logUsage(response *genai.GenerateContentResponse) {
	if response.UsageMetadata != nil {
		log.Printf("[USAGE] Input: %d tokens, Output: %d tokens, Total: %d tokens",
			response.UsageMetadata.PromptTokenCount,
			response.UsageMetadata.CandidatesTokenCount,
			response.UsageMetadata.TotalTokenCount)
	}
}

方案 2:Google Cloud Monitoring

在 Google Cloud Console 中查看 API 使用量和计费详情。建议设置预算告警:

# 设置每日预算告警(通过 gcloud CLI)
gcloud billing budgets create \
  --billing-account=YOUR_BILLING_ACCOUNT \
  --display-name="Gemini API Daily Budget" \
  --budget-amount=10USD \
  --threshold-rule=percent=80 \
  --all-updates-rule-pubsub-topic=projects/my-project/topics/budget-alerts

下一步

模块 2 的四篇文章已经完成。你现在掌握了:ADK Go 的安装与验证、项目工程化结构、Hello World Agent 的完整构建、CLI/Web 运行模式的选型、API Key 的安全管理、以及模型选择的成本优化策略。

这些知识已经足够支撑你构建一个基础的 Agent 应用。接下来,我们将进入模块 3:工具使用(Tools)。这是 Agent 从"聊天机器人"进化为"能做事的智能体"的关键——学习如何自定义 Tool,让 Agent 真正连接到你的业务系统。

CLI vs Web 运行方式 | Function Tool 编写基础 →


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

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

常见问题

如何获取 Google Gemini API Key?

访问 Google AI Studio,登录 Google 账号后创建新的 API Key,选择项目并复制生成的密钥。

API Key 能否提交到代码仓库?

不能。真实 API Key 绝不能提交到 Git。建议使用 .env 文件(加入 .gitignore)、环境变量或云 Secret Manager。

Gemini Flash 和 Pro 有什么区别?

Flash 速度快、成本低,适合原型和日常任务;Pro 推理能力更强,适合复杂任务,但价格更高。

如何选择合适的 Gemini 模型?

根据任务复杂度、延迟要求和预算选择:快速原型用 Flash,复杂推理用 Pro,实验性任务可用 Experimental 模型。

生产环境如何管理 API Key?

生产环境应使用云 Secret Manager(如 Google Cloud Secret Manager 或 AWS Secrets Manager)注入运行时环境,避免硬编码。