Agent 核心概念:Model、Tool、Instruction 三要素
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 架构中承担三重角色:
- 意图解析器(Intent Parser):将自然语言转化为结构化决策
- 策略生成器(Policy Generator):决定调用哪些 Tool、以何种顺序、传入什么参数
- 响应合成器(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 Flash | Gemini 2.0 Pro |
|---|---|---|
| 定位 | 高吞吐、低延迟 | 复杂推理、高精度 |
| 典型延迟 | 200-500ms (首 token) | 800ms-2s (首 token) |
| 成本 | $0.075 / 1M tokens | $1.25 / 1M tokens |
| 上下文窗口 | 1M tokens | 2M 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 工程化实战干货。
