Agent 核心概念:Model、Tool、Instruction 三要素

Agent 不是简单的 API 调用链,而是一个具备感知、推理、执行能力的自治运行时。理解其底层架构,是构建生产级 Agent 系统的先决条件。

ADK Go 将 Agent 抽象为三个正交维度:Model(推理引擎)、Tool(执行接口)、Instruction(行为契约)。三者通过运行时编排形成闭环,而非简单的线性调用。本文将从架构设计、交互机制、性能特征三个层面展开,并结合生产环境中的典型场景给出可落地的实践指南。


一、Agent 运行时架构总览

在深入三要素之前,先建立整体认知。ADK Go 的 Agent 运行时并非"LLM + 函数调用"的朴素拼接,而是一个具备状态管理、并发调度、错误回滚能力的执行框架。

┌─────────────────────────────────────────────────────────────┐
│                        Agent Runtime                         │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │  Instruction │───▶│    Model     │◀───│    Memory    │  │
│  │   (System)   │    │   (推理层)    │    │  (上下文状态) │  │
│  └──────────────┘    └──────┬───────┘    └──────────────┘  │
│                             │                                │
│                             ▼                                │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              Tool Router & Dispatcher                 │  │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐ │  │
│  │  │ Tool #1 │  │ Tool #2 │  │ Tool #3 │  │ Tool #N │ │  │
│  │  │(本地函数)│  │(HTTP服务)│  │(MCP协议)│  │(数据库) │ │  │
│  │  └─────────┘  └─────────┘  └─────────┘  └─────────┘ │  │
│  └──────────────────────────────────────────────────────┘  │
│                             │                                │
│                             ▼                                │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              Result Aggregator & Formatter            │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

关键洞察: Model 与 Tool 之间并非直接调用关系,而是通过 Tool Router 进行解耦。这种设计使得:

  • Tool 的增删不影响 Model 的推理逻辑
  • 单个 Tool 的失败不会导致整个 Agent 崩溃
  • 支持并发调用多个 Tool 并聚合结果

二、Model:推理引擎的选型与调优

2.1 Model 的本质职责

Model 在 ADK 架构中承担三重角色:

  1. 意图解析器(Intent Parser):将自然语言转化为结构化决策
  2. 策略生成器(Policy Generator):决定调用哪些 Tool、以何种顺序、传入什么参数
  3. 响应合成器(Response Synthesizer):将 Tool 的原始输出转化为符合用户语境的自然语言
model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{
    APIKey: os.Getenv("GOOGLE_API_KEY"),
    // 生产环境建议配置超时与重试
    HTTPClient: &http.Client{Timeout: 30 * time.Second},
})

2.2 模型选型策略:Flash vs Pro 的决策矩阵

Google Gemini 系列提供了不同定位的模型,选型直接影响成本、延迟与准确率。

维度Gemini 2.0 FlashGemini 2.0 Pro
定位高吞吐、低延迟复杂推理、高精度
典型延迟200-500ms (首 token)800ms-2s (首 token)
成本$0.075 / 1M tokens$1.25 / 1M tokens
上下文窗口1M tokens2M tokens
最佳场景高频对话、简单工具调用、实时交互代码生成、多步推理、长文档分析

生产选型决策树:

用户请求到达
是否需要复杂推理? ──是──▶ 使用 Pro 模型
    │否                        (代码分析、数学证明、
    ▼                          多文档交叉验证)
是否需要极低延迟? ──是──▶ 使用 Flash 模型
    │否                        (客服机器人、实时推荐、
    ▼                          高频问答)
是否需要平衡成本与质量?
采用分层策略:
  - 先用 Flash 进行意图分类
  - 复杂任务路由到 Pro
  - 简单任务直接由 Flash 完成

分层路由代码示例:

type TieredModelRouter struct {
    flashModel *genai.Model // 轻量模型:意图分类
    proModel   *genai.Model // 重型模型:复杂推理
}

func (r *TieredModelRouter) Route(ctx context.Context, userInput string) (*genai.Model, error) {
    // 第一步:用 Flash 判断复杂度
    complexity, err := r.classifyComplexity(ctx, userInput)
    if err != nil {
        return nil, err
    }
    
    switch complexity {
    case "simple":
        return r.flashModel, nil
    case "complex":
        return r.proModel, nil
    default:
        // 默认使用 Flash,降级保障可用性
        return r.flashModel, nil
    }
}

2.3 生成参数的工程化调优

参数工程含义生产建议
Temperature采样随机性确定性任务(如 JSON 生成)设为 0.0-0.2;创意任务 0.7-0.9
TopP核采样阈值与 Temperature 互斥调优,通常固定 0.95,微调 Temperature
MaxTokens输出上限根据场景设置硬上限,防止异常场景下的成本失控
TopK候选 token 数一般保持默认,仅在需要严格控制词汇表时调整

生产陷阱: 将 Temperature 设为 0 并不能保证完全确定性。部分模型在 Temperature=0 时仍可能存在微小随机性。对强一致性要求的场景(如金融计算),应在应用层增加结果校验逻辑。


三、Tool:执行接口的设计哲学

3.1 Tool 的接口契约

ADK Go 中,Tool 是一个满足特定接口的 Go 结构体。这个接口设计体现了"最小可用契约"原则:

type Tool interface {
    Name() string        // 机器标识符:Model 通过此名称发起调用
    Description() string // 语义描述:Model 据此判断何时调用
    InputSchema() string // JSON Schema:约束参数结构与类型
    Call(ctx context.Context, input string) (string, error) // 执行体
}

架构意义:

  • Name()Description() 构成 Model 的"工具发现"机制
  • InputSchema() 是 LLM 生成结构化参数的契约保障
  • Call()context.Context 参数支持超时控制与链路取消

3.2 生产级 Tool 设计模式

模式一:幂等性设计(Idempotency)

Tool 可能被 Model 多次调用(重试、多步推理中的重复步骤),必须保证幂等:

type PaymentTool struct {
    idempotencyStore *IdempotencyStore // 基于 Redis / 数据库
}

func (p *PaymentTool) Call(ctx context.Context, input string) (string, error) {
    var args struct {
        OrderID       string  `json:"order_id"`
        Amount        float64 `json:"amount"`
        IdempotencyKey string `json:"idempotency_key"`
    }
    
    if err := json.Unmarshal([]byte(input), &args); err != nil {
        return "", fmt.Errorf("invalid input: %w", err)
    }
    
    // 检查是否已处理
    if processed, err := p.idempotencyStore.IsProcessed(ctx, args.IdempotencyKey); err != nil {
        return "", err
    } else if processed {
        return `{"status": "already_processed", "order_id": "` + args.OrderID + `"}`, nil
    }
    
    // 执行业务逻辑...
    
    // 标记为已处理
    if err := p.idempotencyStore.MarkProcessed(ctx, args.IdempotencyKey); err != nil {
        // 记录日志但不返回错误,避免重复扣款
        log.Printf("failed to mark idempotency key: %v", err)
    }
    
    return `{"status": "success", "order_id": "` + args.OrderID + `"}`, nil
}

模式二:超时与降级(Timeout & Degradation)

Tool 调用可能涉及外部网络请求,必须在 Agent 层面做好熔断与降级:

func (w *WeatherTool) Call(ctx context.Context, input string) (string, error) {
    // 为 Tool 调用设置更严格的子超时
    toolCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    
    result, err := w.fetchWeather(toolCtx, input)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            // 超时降级:返回缓存数据或友好提示
            return `{"error": "weather_service_timeout", "fallback": "请稍后重试或查询其他天气服务"}`, nil
        }
        return "", err
    }
    
    return result, nil
}

模式三:参数校验与防御性编程

LLM 生成的参数可能不符合预期,Tool 必须做严格校验:

func (t *SearchTool) Call(ctx context.Context, input string) (string, error) {
    var args struct {
        Query string `json:"query"`
        Limit int    `json:"limit"`
    }
    
    if err := json.Unmarshal([]byte(input), &args); err != nil {
        return "", fmt.Errorf("参数解析失败: %w", err)
    }
    
    // 防御性校验
    if strings.TrimSpace(args.Query) == "" {
        return "", fmt.Errorf("查询词不能为空")
    }
    if args.Limit <= 0 || args.Limit > 50 {
        args.Limit = 10 // 重置为默认值
    }
    
    // 执行搜索...
    return t.doSearch(ctx, args.Query, args.Limit)
}

3.3 Tool 注册与生命周期管理

// 生产环境建议将 Tool 作为依赖注入,而非每次新建
agent, err := llmagent.New(llmagent.Config{
    Name:  "production-agent",
    Model: model,
    Tools: []tool.Tool{
        // 带连接池的 HTTP Tool
        NewHTTPServiceTool(httpClient, "payment_service", "支付服务"),
        // 带缓存的查询 Tool
        NewCachedTool(NewWeatherTool(), 5*time.Minute),
        // 带熔断的外部服务 Tool
        NewCircuitBreakerTool(NewDatabaseTool(db), 5, 30*time.Second),
    },
})

四、Instruction:行为契约的工程化构建

4.1 Instruction 的架构定位

Instruction 不是"提示词工程"的花哨技巧,而是 Agent 的行为契约(Behavioral Contract)。它在每次请求中作为 System Prompt 注入,决定了:

  • 角色边界:Agent 能做什么、不能做什么
  • 输出规范:响应格式、语言风格、结构化要求
  • 决策偏好:在模糊场景下的默认策略
  • 安全约束:敏感信息处理、权限边界

4.2 Instruction 的层次化设计

生产环境中,建议将 Instruction 拆分为三个层次:

┌─────────────────────────────────────────────┐
│  Layer 3: 场景层(动态注入)                   │
│  "当前用户是 VIP,优先推荐高阶功能"            │
├─────────────────────────────────────────────┤
│  Layer 2: 领域层(业务相关)                   │
│  "你是电商客服助手,熟悉退换货政策..."         │
├─────────────────────────────────────────────┤
│  Layer 1: 基础层(通用约束)                   │
│  "你是专业助手,回答要准确、简洁、有礼貌。      │
│   不确定时承认不知道,不编造信息。"             │
└─────────────────────────────────────────────┘

代码实现:

type InstructionBuilder struct {
    baseLayer    string
    domainLayer  string
    contextLayer string
}

func (b *InstructionBuilder) Build() string {
    var parts []string
    if b.baseLayer != "" {
        parts = append(parts, b.baseLayer)
    }
    if b.domainLayer != "" {
        parts = append(parts, b.domainLayer)
    }
    if b.contextLayer != "" {
        parts = append(parts, b.contextLayer)
    }
    return strings.Join(parts, "\n\n")
}

// 使用示例
builder := &InstructionBuilder{
    baseLayer: `# 基础行为约束
- 使用中文回答用户问题
- 涉及金额时必须注明货币单位
- 不确定的信息明确告知用户"我无法确认"`,
    
    domainLayer: `# 电商客服领域知识
- 7天无理由退换货政策:签收后7天内,商品未使用可退
- 运费险:VIP用户自动赠送,普通用户需自行购买`,
    
    contextLayer: fmt.Sprintf(`# 当前会话上下文
- 用户等级: %s
- 当前时间: %s
- 会话ID: %s`, userLevel, now.Format("2006-01-02 15:04"), sessionID),
}

4.3 Instruction 工程最佳实践

原则反例正例
具体优于抽象“回答要有帮助”“回答控制在 100 字以内,使用 bullet points”
正向优于负向“不要编造信息”“仅基于提供的资料回答,超出范围时回复’我无法确认'”
结构化优于散文大段连续文本使用 Markdown 标题、列表、代码块组织
示例优于描述“以 JSON 格式返回”“返回格式示例:{"status": "ok", "data": [...]}

生产陷阱: 在 Instruction 中堆砌过多约束会导致"指令稀释"(Instruction Dilution)——模型对单个指令的遵循率随指令数量增加而下降。建议核心约束不超过 5-7 条,复杂场景通过 Tool 拆分而非 Instruction 堆砌。


五、三要素协同:运行时交互机制

5.1 完整交互时序

以下是一个多轮 Tool 调用的完整时序,展示了 Model、Tool、Instruction 如何在 ADK 运行时中协同:

sequenceDiagram
    participant User
    participant Runtime as ADK Runtime
    participant Model as LLM Model
    participant ToolA as WeatherTool
    participant ToolB as LocationTool

    User->>Runtime: "帮我查一下明天北京和上海的天气,适合穿什么"
    Runtime->>Model: Instruction + User Input + History
    Note over Model: 推理:需要查询两个城市天气
    Model-->>Runtime: ToolCall(LocationTool, {"city": "北京"})
    Runtime->>ToolA: Call(ctx, {"city": "北京"})
    ToolA-->>Runtime: "北京明天晴,15-25°C"
    Runtime->>Model: ToolResult(LocationTool, "北京明天晴...")
    
    Model-->>Runtime: ToolCall(LocationTool, {"city": "上海"})
    Runtime->>ToolA: Call(ctx, {"city": "上海"})
    ToolA-->>Runtime: "上海明天多云,18-26°C"
    Runtime->>Model: ToolResult(LocationTool, "上海明天多云...")
    
    Note over Model: 推理:整合结果,生成穿衣建议
    Model-->>Runtime: FinalResponse("北京明天晴天...")
    Runtime-->>User: "北京明天天气晴朗..."

5.2 状态管理与上下文窗口

ADK Runtime 自动管理对话历史,但生产环境需要关注上下文窗口的消耗:

// 监控上下文使用情况(伪代码)
type ContextMonitor struct {
    maxTokens int
}

func (m *ContextMonitor) CheckUsage(history []Message) error {
    totalTokens := estimateTokens(history)
    if totalTokens > m.maxTokens*0.8 {
        // 触发上下文压缩策略
        return m.compressHistory(history)
    }
    return nil
}

func (m *ContextMonitor) compressHistory(history []Message) error {
    // 策略1:丢弃最早的对话轮次
    // 策略2:使用模型对历史进行摘要
    // 策略3:将早期 Tool 调用结果替换为摘要
}

六、生产陷阱与最佳实践

6.1 生产陷阱(Production Pitfalls)

陷阱表现规避方案
Tool 幻觉Model 调用不存在的 Tool,或参数格式错误严格的 InputSchema 校验 + 调用前参数校验
无限循环Model 在 Tool 调用间反复横跳,无法收敛设置最大迭代次数(Max Iterations),通常为 5-10 次
上下文膨胀长对话导致 Token 成本激增、延迟增加实现上下文压缩策略,定期摘要历史对话
敏感信息泄露Tool 返回的数据包含用户隐私,被直接暴露在 Tool 层或 Formatter 层增加数据脱敏逻辑
级联故障某个 Tool 超时导致整个 Agent 不可用每个 Tool 独立超时 + 熔断降级 + 错误隔离

6.2 最佳实践(Best Practices)

1. 模型选型:建立成本-质量权衡意识

  • 用 Flash 处理 80% 的简单请求,用 Pro 处理 20% 的复杂请求
  • 对延迟敏感的场景(如实时客服),设置端到端 P99 延迟 SLO

2. Tool 设计:面向失败设计(Design for Failure)

  • 每个 Tool 必须有超时、重试、降级策略
  • Tool 的 error 返回应区分"业务错误"(可展示给用户)和"系统错误"(需内部处理)
  • 关键 Tool 操作必须保证幂等性

3. Instruction 工程:精准优于冗长

  • 核心行为约束控制在 5-7 条以内
  • 使用"如果…那么…“的结构化条件语句
  • 对输出格式要求,提供具体示例而非抽象描述

4. 可观测性:构建全链路监控

  • 记录每次 Model 调用的延迟、Token 消耗、Tool 调用链
  • 对异常模式(如高频 Tool 失败、上下文窗口告警)设置告警
  • 保留对话日志用于后续的 Instruction 迭代优化

七、最小化生产级 Agent 配置

综合以上原则,一个具备生产可用性的最小配置示例:

package main

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

    "github.com/google/generative-ai-go/genai"
    "google.golang.org/api/option"
)

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

    // 1. 初始化 Model(带超时配置)
    client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GOOGLE_API_KEY")))
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    model := client.GenerativeModel("gemini-2.0-flash")
    model.SetTemperature(0.3)  // 适度确定性,保留一定灵活性
    model.SetMaxOutputTokens(2048)

    // 2. 定义 Instruction(分层构建)
    instruction := `# 角色定义
你是智能助手,帮助用户查询信息并给出建议。

# 行为约束
1. 使用中文回答,保持专业且友好的语气
2. 调用工具时确保参数准确,不确定时向用户确认
3. 涉及外部数据时注明数据来源和时间
4. 遇到错误时向用户说明原因,不隐藏异常

# 输出格式
- 天气查询:"[城市]今天[天气],气温[范围],建议[穿衣建议]"
- 错误提示:"抱歉,[具体原因]。您可以[替代方案]"`

    // 3. 注册 Tools(带超时和错误处理)
    tools := []tool.Tool{
        NewTimeoutTool(NewWeatherTool(), 5*time.Second),
        NewTimeoutTool(NewSearchTool(), 10*time.Second),
    }

    // 4. 创建 Agent
    agent, err := llmagent.New(llmagent.Config{
        Name:        "production-assistant",
        Model:       model,
        Instruction: instruction,
        Tools:       tools,
    })
    if err != nil {
        log.Fatal(err)
    }

    // 5. 运行(生产环境应包装为 HTTP/gRPC 服务)
    response, err := agent.Run(ctx, "北京今天天气怎么样?")
    if err != nil {
        log.Printf("Agent error: %v", err)
        return
    }
    
    log.Println(response)
}

八、总结

Model、Tool、Instruction 三要素构成了 ADK Go Agent 的稳固三角:

  • Model 是推理中枢,选型需权衡成本、延迟、质量,生产环境推荐分层路由策略
  • Tool 是执行接口,设计需遵循幂等、超时、防御性校验原则,面向失败设计
  • Instruction 是行为契约,构建需层次化、结构化、具体化,避免指令稀释

三者的协同不是简单的线性调用,而是通过 ADK Runtime 的调度器、状态管理器、错误处理器形成闭环。理解这一架构,才能从"能用的 Agent"迈向"生产级的 Agent 系统”。


下一步

掌握核心概念后,通过 Hello World Agent 实践完整的 Agent 构建流程:

项目结构规范与 .env 管理 | Hello World Agent →


想深入学习 Go ADK 的生产实践与性能优化?关注「全栈之巅-梦兽编程」公众号,每周更新 Go / AI 工程化实战干货。