Google Genkit Go 引入基于渐进式披露的 Agent Skills 功能,将指令和脚本模块化打包。有效防止上下文膨胀、降低 token 消耗。
基于大语言模型的 AI 智能体越来越多地被用来自动化复杂工作流。然而,随着它们的职责范围扩大,管理提示上下文的大小成为了一个挑战。将每一项标准操作流程、参考指南和文档都加载到持久的上下文窗口中是不可持续的:这会消耗宝贵的 token、分散模型的焦点,并增加错误响应的可能性。
为了解决这个问题,我们在 Genkit for TypeScript、Go、Dart 和 Python 中添加了对 Agent Skills 的支持。本文将展示如何在 Go 中使用 Genkit 的 Agent Skills。AI 智能体技能标准让开发者能够将专业知识打包成可被发现的能力,AI 智能体只在需要时才会加载这些能力。它们就像专业知识包,在后台保持待命状态,直到需要的时刻才被激活。
Agent Skills 基于一个叫做渐进式公开的原则运作,这意味着信息只有在必要时才会被呈现给模型。技能使用一个包含两个部分的 SKILL.md 文件来定义:前置元数据和正文。前置元数据包含技能的描述和任何额外的元数据。正文部分包含实际的模型指令。此外,还可以添加参考文档或脚本等补充文件。
按照规范,技能在磁盘上的组织方式如下:
skill-name/
├── SKILL.md # 必需:元数据 + 指令
├── scripts/ # 可选:可执行代码
├── references/ # 可选:文档
├── assets/ # 可选:模板、资源
└── ... # 任何其他文件或目录
技能前置元数据示例如下:
---
name: adr-template
description: Activate this skill when proposing significant architectural changes, documenting codebase refactorings, or resolving design/technical debates. Use this skill to author and maintain Architecture Decision Records (ADRs) to preserve engineering context.
license: Apache-2.0
metadata:
author: example-org
version: "1.0"
---
最初,AI 智能体框架会从 SKILL.md 文件加载技能定义,但只将前置元数据暴露给 AI 智能体的系统提示。这是它需要了解的信息,以判断是否应该激活该技能。随着对话的进展,最终 AI 智能体会碰到可能需要该技能的情况,激活过程就会开始,加载该技能的完整正文。根据正文内容,AI 智能体可能会决定加载额外的参考资料或使用打包的脚本,完成该任务的公开周期。
这个流程总结在以下图表中:
渐进式公开模式提供了三个主要优势:
Token 效率:Genkit 最初仅加载技能的元数据,只在技能变为活跃状态时才注入详细指令。
精简实现:技能是 markdown 文件的集合,可以与代码以相同的方式分发。无需配置任何额外基础设施即可受益于技能。
资源打包:技能可以包含自己的脚本(Python、Node.js、Bash),AI 智能体可以执行这些脚本,确保它拥有完成工作的正确工具。
要理解 Agent Skills 在 Genkit Go 中如何运作,我们必须首先了解 Genkit 的中间件架构。Genkit 中间件充当一系列拦截并封装关键模型生命周期阶段的钩子:
Model Wrapper(WrapModel):在每次迭代中的模型 API 调用内触发一次,处理关于模型调用本身的逻辑,如重试、回退和缓存。
Tool Wrapper(WrapTool):每次工具执行时触发一次,在同一迭代中的并行工具调用时可能会并发运行。
Generate Wrapper(WrapGenerate):在每次工具循环迭代中触发一次(N 个工具轮转意味着 N+1 次调用),处理需要查看整个对话的逻辑,如改写、系统提示注入和消息累积。
Agent Skills 基于这个钩子系统构建。通过监控传入的提示,中间件检测匹配的描述并动态激活技能。
下面是演示如何向你的 Genkit 流程添加技能支持的代码片段:
resp, err := genkit.Generate(ctx, g,
ai.WithPrompt("How do I run tests in this repo?"),
ai.WithUse(&middleware.Skills{SkillPaths: []string{"./skills"}}),
)
技能使用分为三个不同的阶段:
发现:当使用技能中间件初始化 Genkit 时,系统会扫描你配置的 SkillPaths 中的 SKILL.md 文件,并将它们的元数据注入到系统提示中。
激活:当用户请求与某个技能的描述匹配时,Genkit 会调用 use_skill 工具来检索当前任务所需的具体指令。
执行:SKILL.md 文件的完整内容,以及对打包资源(如脚本和参考资料)的访问权限被加载到活跃上下文中,以引导模型完成精确的工作流程。
要开始在 Genkit 中使用 Agent Skills,首先请确保你拥有最新版本的 Genkit Go SDK:
go get github.com/firebase/genkit/go
这一步是可选的,但建议执行。在 macOS 或 Linux 上,运行:
curl -sL cli.genkit.dev | bash
在 Windows 上,从以下地址下载二进制文件:cli.genkit.dev
更多细节可在 https://cli.genkit.dev 找到
SDK 安装完成后,你可以注册技能中间件并在 Generate 调用期间提供它。以下代码示例定义了一个由 Genkit 流程驱动的食谱生成命令行工具:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
"github.com/firebase/genkit/go/plugins/middleware"
"google.golang.org/genai"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: recipe <food|ingredient>")
os.Exit(1)
}
input := os.Args[1]
ctx := context.Background()
g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}, &middleware.Middleware{}))
recipeFlow := genkit.DefineFlow(g, "recipeFlow", func(ctx context.Context, input string) (string, error) {
prompt := fmt.Sprintf("Provide a recipe using %s", input)
return genkit.GenerateText(ctx, g,
ai.WithModel(googlegenai.ModelRef("googleai/gemini-flash-latest", &genai.GenerateContentConfig{
ThinkingConfig: &genai.ThinkingConfig{
ThinkingLevel: genai.ThinkingLevelLow,
},
})),
ai.WithSystem(
"You are a professional chef assistant with wide knowledge about recipes. "+
"The user will give you a food or ingredient and you need to respond with a recipe. "+
"Use specialized knowledge (skills) whenever possible. "+
"Respond with ASCII formatting optimized for terminal output (no markdown).",
),
ai.WithPrompt(prompt),
ai.WithUse(&middleware.Skills{SkillPaths: []string{"./skills"}}),
)
})
result, err := recipeFlow.Run(ctx, input)
if err != nil {
log.Fatalf("Error running flow: %v", err)
}
fmt.Println(result)
}
注意,我们使用 ai.WithUse 函数选项在 genkit.GenerateText 中配置 Skills 中间件。在实例化的 Skills 中间件中,我们通过 SkillPaths 映射 ./skills 文件夹,在本示例中包含两个技能:"banana-bread" 和 "cheese-bread"。
使用 "cheese" 这个词运行应用程序会激活 "cheese-bread" 技能并返回相应的食谱:
$ go run main.go cheese
+-------------------------------------------------------------+
| TRADITIONAL BRAZILIAN CHEESE BREAD |
| (Pao de Queijo) |
+-------------------------------------------------------------+
Naturally gluten-free, crispy on the outside, and chewy inside.
===============================================================
INGREDIENTS
===============================================================
* Tapioca Flour (Sour Starch) .. 2 cups (240g)
* Whole Milk .................. 1/2 cup (120ml)
* Water ....................... 1/2 cup (120ml)
* Vegetable Oil ............... 1/3 cup (80ml)
* Salt ........................ 1 tsp
* Eggs (Room Temp) ............ 2 large
* Grated Parmesan/Queijo ...... 1.5 cups (150g)
===============================================================
EQUIPMENT
===============================================================
* Medium saucepan
* Mixing bowl or Stand mixer with paddle attachment
* Baking sheet with parchment paper
* Ice cream scoop (optional)
===============================================================
INSTRUCTIONS
===============================================================
你可以在此处查看该技能的内容,并下载此示例的完整代码。
更贴近实际的示例
虽然上述食谱流程演示了基本的按需加载,但在生产系统中,智能体需要编排并应用专门的指令来处理非确定性任务。下面是一个使用 Genkit Go 和 Gemini 3.1 Flash Image(Nano Banana 2)构建的多模态艺术品修复应用:
```go
package main
import (
"context"
"encoding/base64"
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
"github.com/firebase/genkit/go/plugins/middleware"
"google.golang.org/genai"
)
// Input defines the schema for the Renaissance flow, accepting a Base64 image data URI.
type Input struct {
URL string `json:"url"`
}
// Output defines the schema for the flow output, containing both text explanation and image data.
type Output struct {
Text string `json:"text"`
Image string `json:"image"` // Base64 image data URI
}
func main() {
// Configure logging to suppress verbose Genkit trace and info logs on standard output.
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelError,
}))
slog.SetDefault(logger)
if len(os.Args) < 2 {
log.Fatalf("Usage: go run . <path-to-image>")
}
filePath := os.Args[1]
// Read and convert the image to a Base64 Data URI.
data, err := os.ReadFile(filePath)
if err != nil {
log.Fatalf("Failed to read file: %v", err)
}
dataURI := "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(data)
ctx := context.Background()
// 1. Initialize Genkit with standard Google AI and Middleware plugins.
g := genkit.Init(ctx, genkit.WithPlugins(
&googlegenai.GoogleAI{},
&middleware.Middleware{},
))
// 2. Define a Genkit Flow for actual image-to-image restoration.
renaissanceFlow := genkit.DefineFlow(g, "renaissanceFlow", func(ctx context.Context, input Input) (Output, error) {
// 3. Prompt the model with multimodal input and configure it to output both TEXT and IMAGE modalities.
resp, err := genkit.Generate(ctx, g,
ai.WithModel(googlegenai.ModelRef("googleai/gemini-3.1-flash-image", &genai.GenerateContentConfig{
ResponseModalities: []string{"TEXT", "IMAGE"},
})),
ai.WithSystem("You are Renaissance, a multimodal art restoration AI. First, analyze the image to determine "+
"the type of image, then invoke the corresponding restoration skills as appropriate. "+
"Finally, perform a highly precise restoration and output the restored IMAGE. "+
"REQUIRED: In your response, return a text part summarizing your process and "+
"naming any skills used. ",
),
ai.WithMessages(
ai.NewUserMessage(
ai.NewTextPart("Please restore this image using the appropriate techniques."),
ai.NewMediaPart("image/jpeg", input.URL),
),
),
// Load agent skills dynamically from the "./skills" directory.
ai.WithUse(&middleware.Skills{SkillPaths: []string{"./skills"}}),
)
if err != nil {
return Output{}, err
}
// Return both the explanation and the generated media content (image data URI).
return Output{
Text: resp.Text(),
Image: resp.Media(),
}, nil
})
// 4. Run the flow with the prepared input.
fmt.Println("Running renaissanceFlow...")
result, err := renaissanceFlow.Run(ctx, Input{URL: dataURI})
if err != nil {
log.Fatalf("Flow execution failed: %v", err)
}
// 5. Render the textual restoration plan if it is actual text and not the raw image data.
if result.Text != "" && !strings.HasPrefix(result.Text, "data:") {
fmt.Printf("\nRestoration Plan:\n%s\n", result.Text)
} else {
fmt.Println("\nRestoration process completed (no separate textual explanation returned).")
}
// 6. Parse, decode, and save the output image locally.
if !strings.HasPrefix(result.Image, "data:") {
log.Fatalf("Unsupported or invalid image format returned by the flow")
}
parts := strings.Split(result.Image, ";base64,")
if len(parts) != 2 {
log.Fatalf("Invalid data URI format received from flow")
}
decodedImage, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
log.Fatalf("Failed to decode restored image base64 data: %v", err)
}
ext := filepath.Ext(filePath)
base := filePath[:len(filePath)-len(ext)]
outputPath := base + "_restor
此应用包含三个技能:素描、绘画和摄影,因为每种不同的艺术风格都需要一套专门的自定义修复指令。下面以绘画技能的 SKILL.md 文件为例:
---
name: paintings
description: Restoration protocol for fine art paintings.
---
# Painting restoration
Perform direct, minimal-intervention restoration of fine art paintings. Prioritize historical integrity, original pigment depth, and surface textures over cosmetic modernization. Do not include any elements, nor introduce any techniques not present in the original piece.
### 1. Core conservation mandates
* **Absolute Fidelity:** Strictly forbidden to add new subjects, figures, scenery, details, or narrative elements under any circumstances.
* **Anti-Modernization:** Retain original brushstroke characteristics. Do not flatten support textures.
* **Patina & Craquelure:** Respect the natural crack network (craquelure) as part of the artwork's history. Do not fill or erase cracks unless they pose an active risk of paint loss.
### 2. Step-by-step restoration process
1. **Varnish Correction:** Gently balance aged, yellowed natural varnishes to recover original pigment chromaticity without forcing modern, high-contrast saturation.
2. **Soot & Grime Reduction:** Isolate and lift surface grime while protecting delicate, thin underlying glaze layers.
3. **Inpaint Major Loss:** Where paint layers are missing, use references from the surviving elements to bridge the gap. Replicate the original brush direction, speed, height, transparency, and light source.
接下来,让我们向这个应用提供一幅画作,看看它的实际运行效果。这是 Elías García Martínez 创作的《Ecce Homo》(《瞧,这个人》),拍摄于那次臭名昭著的修复尝试之前:
将此文件保存为 ecce_homo.png,然后运行应用(注意:要运行此示例,需要在环境中配置 GEMINI_API_KEY):
go run main.go ecce_homo.png
终端输出如下:
$ go run main.go ecce_homo.png
Running renaissanceFlow...
Restoration Plan:
* **Process:** I identified the image as an antique religious painting, specifically a devotional portrait of Jesus Christ as the Man of Sorrows (Ecce Homo). The restoration process focused on simulating a precise physical conservation. First, surface cleaning was emulated to remove layers of grime and dulled, yellowed varnish, which revealed the original vibrant deep reds and rich, dark skin tones. This improved color fidelity and value contrast. Second, the structural integrity of the canvas or panel was addressed by minimizing non-artistic cracks and visible canvas weave (craquelure), particularly across the face and upper garment, making the portrait sharper and more coherent while retaining a sense of age. The distinctive textured purple robe with speckles of light was preserved, with only the largest, distracting surface blemishes removed. Finally, the scroll-like background structure, which was originally pale and indistinct, was reinforced by refining the scroll scrollwork and adding realistic, antique parchment texture and localized aging, making it a clear visual frame that integrated well with the portrait.
* **Skills Used:** `paintings`.
Successfully saved restored image to ecce_homo_restored.png
正如终端输出所示,模型正确地将该图像识别为绘画作品,并激活了相应的修复技能。修复结果如下:
请注意,由于大型语言模型具有非确定性,你可能需要尝试几次才能获得满意的结果。本文介绍的修复技能也仍处于早期阶段,需要由专业人士进一步调优。
你可以在此处下载本示例的完整代码。尝试为其提供其他类型的图像,例如照片和绘画,观察它如何激活相应的技能。
遵循以下实践,优化你的 Agent Skills 集成:
编写详细的描述: YAML frontmatter 中的 description 是触发中间件的主要依据。请使用清晰的祈使句,让模型知道何时调用 use_skill。
保持专注: 每项技能都应该专注做好一件事。避免创建单体式的“无所不能”技能。
确定性工具: 在技能中捆绑轻量级脚本。这能为 AI 智能体提供“事实来源”,从而绕过其内部训练的局限性。
模块化资源: 使用 references/ 文件夹保持 SKILL.md 简洁。AI 智能体可以按需读取这些文件。
Agent Skills 为管理 Genkit Go 中的开发者专业知识提供了一个模块化、可扩展的框架。集成技能中间件有助于应用以更高的可靠性执行复杂的多步骤任务。
准备好构建你的第一个支持技能的 Genkit 应用了吗?
阅读 Genkit 入门页面。
查看中间件文档,深入了解相关内容。
探索 Agent Skills 规范,了解这项开放标准。
Go Gopher 吉祥物由 Renee French 创作,并根据知识共享署名 4.0 许可证授权使用。