Створення агента Gemini на Go: порівняння Raw SDK, Genkit та ADK
Один експерт з оцінки ретро-ігор створив три рішення на мові Go за допомогою GenAI SDK, Genkit та Agent Development Kit, а також пояснив, як запустити кожне з них на Cloud Run чи на платформі агентів Google.
Створення додатків із використанням ШІ та автономних агентів на мові Go може відбуватися на трьох різних рівнях абстракції. У цьому огляді розбираються механізми агента, обирається конкретний приклад застосування — оцінювач ретро-ігор, — а потім цей агент створюється тричі:
- Цикл агента низького рівня, написаний безпосередньо з використанням SDK GenAI для Go.
- Структурована, орієнтована на потоки система, створена за допомогою Genkit.
- Модульна багатоагентна система з сеансами, побудована на наборі засобів розробки агентів Google (ADK).
У кінці розглядаються середовища виконання, які дозволяють надійно розгортати агенти на Go в хмарі.
Що робить щось агентом
Цей термін використовується неформально, проте в інженерії ШІ він має досить точне архітектурне значення: автономна система, яка складається з трьох частин — LLM, набору інструментів, таких як функції чи API, та механізму, який виконує все в межах циклу зворотного зв’язку.
Модель без інструментів — це просто генератор тексту. Вона може відповідати лише на основі своїх фіксованих параметрів навчання чи інформації, наданої у запиті. Щоб надати їй автономії, тобто здатності аналізувати зовнішнє середовище, перевіряти гіпотези та змінювати щось у реальному світі, необхідно під’єднати її до виконуваних інструментів.
Одну архітектурну деталь легко пропустити. Сама модель ніколи не виконує коду та не викликає зовнішніх API. Проміжним елементом є локальний інструментарій — у цьому випадку програма на Go, яка знаходиться посередині. Коли модель вирішує, що їй потрібна зовнішня інформація чи дія, вона надсилає структурований запит на виклик інструменту. Інструментарій виконує відповідну функцію на Go, фіксує результат та передає його назад у наступний етап роботи моделі. Потім модель аналізує новий контекст та або викликає інший інструмент, або формулює свою остаточну відповідь.
Не кожен інструмент працює локально. Gemini пропонує вбудовані інструменти, такі як функція пошуку в Google, які виконуються на сервері. Як тільки це вказується у конфігурації запиту, API обробляє запити на пошук та додає метадані до відповіді без необхідності повторного звернення до локального сервера. Однак для спеціальної логіки домену вся робота виконується за допомогою мови Go: визначення схеми, передача аргументів та повернення серіалізованих результатів.
Сценарій використання: оцінювач ретро-ігор
Кожен, хто колекціонує ретро-відеоігри, знає цю типову проблему. На ринку ретро-ігор легко купити примірник улюбленої гри, щоб потім виявити вдома, що вона вже є у вашій колекції. Крім того, цей хобі відомий нестабільними цінами, різними рівнями стану (касета без упаковки, повна упаковка або CIB, упаковка з заводською пломбою) та частими фальшивими пропозиціями.
Перевірка ціни запиту через Gemini в реальному часі, стоячи біля кіоску, виявляється дивовижно корисною. Багато покупців охоче платять премію за можливість придбати товар особисто та підтримати місцевих продавців, але ніхто не хоче стати жертвою шахрайства чи переплатити за фальшивий картридж.
Агент-оцінювач враховує обидві сторони: він порівнює особистий каталог товарів із актуальними даними з Інтернету, щоб визначити справедливу ринкову вартість.
Як це використовує колекціонер
Колекціонер вводить звичайні запитання, наприклад:
- Запит про те, чи вже є в колекції певна гра, скажімо Chrono Trigger.
- Інформація про гру EarthBound для SNES у оригінальній упаковці, яку пропонують за 350 доларів, з запитанням про те, чи вона вже належить комусь, та чи є ця ціна вищою за поточні ринкові значення.
Контракти з інструментами
Щоб дати відповідь, не вигадуючи інформацію про запаси чи ціни, агент спирається на два джерела правди:
search_catalog— це локальна функція на мові Go, яка здійснює пошук у власній базі даних колекціонера. Вона шукає ключові слова в назвах та платформах, а потім повідомляє про власника, стан, дату придбання та ціну покупки.google_search— це інструмент для пошуку на серверній стороні. Він шукає на ринкових платформах та сервісах для відстеження аукціонів поточні справедливі ціни, нещодавні підтверджені продажі та показники стану.
Стратегія міркувань
Для можливої покупки агент діє за фіксованою послідовністю. Спочатку він перевіряє місцевий каталог, щоб з’ясувати, чи є гра в наявності та в якому стані. Потім шукає в Інтернеті поточні ціни на цій платформі та для цього рівня стану. Нарешті він об’єднує отримані дані у оцінку: запропоновану ціну порівняно з середньою ринковою ціною, можливості покращення, такі як заміна картриджа без упаковки на версію в коробці, та чітку рекомендацію.
Версія один: Go GenAI SDK
Розробка безпосередньо на основі SDK (google.golang.org/genai) забезпечує найнижчий рівень абстракції, що однозначно відповідає протоколу Gemini API. Оскільки між вашим кодом та API немає жодної фреймворк-структури, ви самостійно контролюєте використання інструментів, історію розмов та критерії зупинки циклу.
Це підходить для невеликих проектів та скриптів, для тих, хто вивчає, як насправді працюють виклики функцій та їх виконання, а також для ситуацій із дуже специфічними вимогами до циклів, які не покривають стандартні фреймворки. Результат компілюється у один самодостатній бінарний файл Go без залежностей від фреймворків, тож його можна розгорнути на звичайних сервісах для вебу чи API, таких як Google Cloud Run, Kubernetes чи звичайна віртуальна машина.
Повна, працездатна реалізація:
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"google.golang.org/genai"
)
// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
Title string `json:"title"`
Platform string `json:"platform"`
Year int `json:"year"`
Condition string `json:"condition"` // e.g. "Loose Cartridge", "CIB (Complete in Box)", "Mint"
PricePaid float64 `json:"price_paid"`
Notes string `json:"notes"`
}
// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
{
Title: "Chrono Trigger",
Platform: "Super Nintendo (SNES)",
Year: 1995,
Condition: "CIB (Complete in Box)",
PricePaid: 210.00,
Notes: "Includes original map and registration card.",
},
{
Title: "EarthBound",
Platform: "Super Nintendo (SNES)",
Year: 1994,
Condition: "Loose Cartridge",
PricePaid: 180.00,
Notes: "Authentic board verified; label in excellent shape.",
},
{
Title: "Castlevania: Symphony of the Night",
Platform: "Sony PlayStation",
Year: 1997,
Condition: "CIB (Black Label)",
PricePaid: 135.00,
Notes: "Original soundtrack disc included.",
},
}
// searchCatalogTool searches the local collection for matching games.
func searchCatalogTool(args map[string]any) map[string]any {
query, _ := args["query"].(string)
queryLower := strings.ToLower(strings.TrimSpace(query))
var matches []GameItem
for _, item := range localCatalog {
if strings.Contains(strings.ToLower(item.Title), queryLower) ||
strings.Contains(strings.ToLower(item.Platform), queryLower) {
matches = append(matches, item)
}
}
if len(matches) == 0 {
return map[string]any{
"found": false,
"message": fmt.Sprintf("No items matching %q found in your collection.", query),
}
}
return map[string]any{
"found": true,
"count": len(matches),
"results": matches,
}
}
func main() {
ctx := context.Background()
// Initialise GenAI client for Gemini Enterprise
client, err := genai.NewClient(ctx, &genai.ClientConfig{
Project: os.Getenv("GOOGLE_CLOUD_PROJECT"),
Location: "global",
Backend: genai.BackendEnterprise,
})
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
// 1. Declare custom function schema for collection lookup
catalogToolDecl := &genai.FunctionDeclaration{
Name: "search_catalog",
Description: "Search the collector's personal inventory for owned games by title or platform.",
Parameters: &genai.Schema{
Type: genai.TypeObject,
Properties: map[string]*genai.Schema{
"query": {
Type: genai.TypeString,
Description: "Game title or platform to search (e.g. 'EarthBound', 'SNES').",
},
},
Required: []string{"query"},
},
}
// 2. Configure model tools: custom function declaration + Google Search grounding
config := &genai.GenerateContentConfig{
SystemInstruction: &genai.Content{
Parts: []*genai.Part{
{Text: "You are an expert Retro Game Appraiser. When evaluating purchases, check the user's " +
"collection catalog first to see if they already own the item, then check current market " +
"prices using Google Search to evaluate whether the deal is fair, overpriced, or a bargain."},
},
},
Tools: []*genai.Tool{
{
FunctionDeclarations: []*genai.FunctionDeclaration{catalogToolDecl},
},
{
GoogleSearch: &genai.GoogleSearch{},
},
},
}
// 3. Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
fmt.Println("\nGoodbye!")
os.Exit(0)
}()
model := "gemini-3.8-flash"
var contents []*genai.Content
fmt.Println("Retro Game Appraiser (SDK Agent)")
fmt.Println("Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.")
fmt.Println("-----------------------------------------------------------------")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("\nUser: ")
if !scanner.Scan() {
fmt.Println("\nGoodbye!")
break
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}
if strings.EqualFold(input, "exit") {
fmt.Println("Goodbye!")
break
}
contents = append(contents, &genai.Content{
Role: "user",
Parts: []*genai.Part{genai.NewPartFromText(input)},
})
// 4. The Agent Loop: model generation -> tool dispatch -> feedback -> until final answer
for {
resp, err := client.Models.GenerateContent(ctx, model, contents, config)
if err != nil {
log.Printf("error generating content: %v", err)
break
}
if len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil {
log.Println("received empty response candidate from model")
break
}
// Append the model's response to the conversation history
modelContent := resp.Candidates[0].Content
contents = append(contents, modelContent)
// Check if the model requested any client-side tool executions
funcCalls := resp.FunctionCalls()
if len(funcCalls) == 0 {
fmt.Printf("\nAppraiser: %s\n", resp.Text())
break
}
// Execute each requested tool and prepare response parts
var responseParts []*genai.Part
for _, call := range funcCalls {
fmt.Printf("[Harness] Executing tool: %s(args=%v)\n", call.Name, call.Args)
var result map[string]any
switch call.Name {
case "search_catalog":
result = searchCatalogTool(call.Args)
default:
result = map[string]any{"error": fmt.Sprintf("unsupported tool: %s", call.Name)}
}
responseParts = append(responseParts, genai.NewPartFromFunctionResponse(call.Name, result))
}
// Return tool execution results as a user turn
contents = append(contents, &genai.Content{
Role: "user",
Parts: responseParts,
})
}
}
}
Щоб запустити його, налаштуйте проект у Google Cloud та увійдіть за допомогою стандартних облікових даних додатку за командою gcloud auth application-default login:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
У терміналі відкривається інтерактивний чат. Розмова може тривати кілька раундів та закінчується, коли ви введете exit, натиснете Ctrl+C або надішлете EOF за допомогою Ctrl+D:
Retro Game Appraiser (SDK Agent)
Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.
-----------------------------------------------------------------
User: I found a copy of EarthBound for SNES in mint Complete-in-Box (CIB) condition for $350. Do I already own it, and is $350 a good deal compared to current market prices?
[Harness] Executing tool: search_catalog(args=map[query:EarthBound])
Appraiser: Here is your collection check and appraisal for **EarthBound (SNES)**:
1. **Current Collection Status**:
- You currently own **EarthBound** on Super Nintendo as a **Loose Cartridge**, purchased for **$180.00**.
2. **Market Price Appraisal**:
- Verified market sales for an authentic, **Complete-in-Box (CIB)** copy of EarthBound typically range between **$1,200.00 and $1,500.00** depending on the condition of the box, tray, and original player's guide.
3. **Recommendation**:
- At **$350.00**, a genuine Mint CIB copy is an **exceptional deal** (more than 70% below prevailing market value).
- **Caution**: Because EarthBound is one of the most heavily counterfeited SNES titles, inspect the box printing, registration card, and PCB board carefully before completing the transaction. If verified authentic, this is an outstanding opportunity to upgrade your loose copy to CIB.
User: exit
Goodbye!
Версія SDK робить логіку керування чіткою та зрозумілою. Завдяки потужним моделям та великим вікнам токенів написання внутрішніх механізмів, циклів виклику інструментів та допоміжних структур видається на диво простим.
Проте ця простота є пасткою. Кожен рядок коду саморозробленої фреймворк-структури стає постійною витратою на технічне обслуговування та можливим джерелом прихованих помилок. Розробка фреймворку агента з нуля у кращому випадку відволікає увагу від реалізації функцій, а в гіршому — створює купу технічного боргу, який відволікає зусилля від справжньої бізнес-мети. Найкращий код — це взагалі жоден код, а наступний за якістю — це те, що вирішує проблему з мінімумом спеціально написаного коду.
Чому варто перейти на фреймворк
Чистий цикл SDK важко перевершити для вивчення низькорівневих механізмів чи для створення індивідуальних циклів, проте продакшн-застосунки потребують вищого рівня абстракції з кількох причин:
- Автоматичне відображення схеми. Ручне створення JSON-схем за допомогою
&genai.Schema{...}є трудомістким та схильним до помилок. Фреймворки для продакшну отримують схеми з вбудованих структур Go та коментарів до документації. - Оперативне спостереження та відстеження. У умовах продакшну необхідні вбудовані функції OpenTelemetry для відстеження, розбивка затримок за окремими інструментами та показники споживання токенів.
- Керування запитами. Запити, закодовані як рядки Go, ускладнюють співпрацю з інженерами запитів та не дозволяють окремо версіонувати шаблони від бінарних релізів.
- Стабільність сеансів та стан. Для зберігання історії багатьох діалогів у безстанових HTTP-запитах потрібне безпечне за змінами та роз’єднане зберігання даних.
У екосистемі Go існують два основні фреймворки з відкритим кодом для цих потреб: Genkit та Agent Development Kit.
Версія два: Genkit
Genkit знаходиться на середньому рівні абстракції та вносить принципи інженерії програмного забезпечення та структуровану можливість спостереження у AI-застосунки. Усе організовано навколо потоків — сильно типованих, спостережуваних конвеєрів, та інструментів — типобезпечних функцій Go з автоматичним створенням схеми.
Він підходить для одноразових завдань генерації, таких як інструменти командного рядка, пакетна обробка та кінцеві точки webhook, а також для детерміністичних робочих процесів та агентів однієї доменної системи, наприклад, багатомодальних підходів до відновлення фотографій. Він підтримує кількох постачальників моделей через плагіни. Оскільки додаток Genkit — це просто стандартний HTTP-сервер на Go, він може працювати на будь-якій платформі backend, причому Google Cloud Run є найбільш популярним варіантом завдяки зручному хостингу контейнерів та автоматичному масштабуванню.
Додаток оцінювача, написаний з використанням Genkit Go:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
"google.golang.org/genai"
)
// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
Title string `json:"title"`
Platform string `json:"platform"`
Year int `json:"year"`
Condition string `json:"condition"`
PricePaid float64 `json:"price_paid"`
Notes string `json:"notes"`
}
// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
{
Title: "Chrono Trigger",
Platform: "Super Nintendo (SNES)",
Year: 1995,
Condition: "CIB (Complete in Box)",
PricePaid: 210.00,
Notes: "Includes original map and registration card.",
},
{
Title: "EarthBound",
Platform: "Super Nintendo (SNES)",
Year: 1994,
Condition: "Loose Cartridge",
PricePaid: 180.00,
Notes: "Authentic board verified; label in excellent shape.",
},
{
Title: "Castlevania: Symphony of the Night",
Platform: "Sony PlayStation",
Year: 1997,
Condition: "CIB (Black Label)",
PricePaid: 135.00,
Notes: "Original soundtrack disc included.",
},
}
type CatalogRequest struct {
Query string `json:"query" jsonschema:"description=The game title or platform to search in the inventory"`
}
type CatalogResponse struct {
Found bool `json:"found"`
Message string `json:"message,omitempty"`
Count int `json:"count,omitempty"`
Results []GameItem `json:"results,omitempty"`
}
type AppraiserRequest struct {
Prompt string `json:"prompt" jsonschema:"description=The collector's question or purchase offer to evaluate"`
}
type AppraiserResponse struct {
Appraisal string `json:"appraisal"`
}
func main() {
ctx := context.Background()
// 1. Initialise Genkit with Vertex AI plugin
g := genkit.Init(ctx,
genkit.WithPlugins(&googlegenai.VertexAI{
ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
Location: "global",
}),
)
// 2. Define strongly-typed tool with automatic schema generation
catalogTool := genkit.DefineTool(
g,
"search_catalog",
"Search the collector's personal inventory for owned games by title or platform.",
func(ctx *ai.ToolContext, req CatalogRequest) (CatalogResponse, error) {
queryLower := strings.ToLower(strings.TrimSpace(req.Query))
queryWords := strings.Fields(queryLower)
var matches []GameItem
for _, item := range localCatalog {
itemText := strings.ToLower(item.Title + " " + item.Platform)
allMatch := true
for _, word := range queryWords {
if !strings.Contains(itemText, word) {
allMatch = false
break
}
}
if allMatch {
matches = append(matches, item)
}
}
if len(matches) == 0 {
return CatalogResponse{
Found: false,
Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
}, nil
}
return CatalogResponse{
Found: true,
Count: len(matches),
Results: matches,
}, nil
},
)
// 3. Define structured appraisal flow with typed request and response
appraiserFlow := genkit.DefineFlow(
g,
"appraise_game",
func(ctx context.Context, req AppraiserRequest) (AppraiserResponse, error) {
resp, err := genkit.Generate(ctx, g,
ai.WithModelName("vertexai/gemini-3.8-flash"),
ai.WithSystem(
"You are an expert Retro Game Appraiser. Assist collectors by evaluating prospective purchases, "+
"cross-referencing their personal inventory, and assessing fair market valuations. "+
"Always search the collection catalog using search_catalog before providing purchase recommendations.",
),
ai.WithConfig(&genai.GenerateContentConfig{
ThinkingConfig: &genai.ThinkingConfig{IncludeThoughts: true},
Tools: []*genai.Tool{
{
GoogleSearch: &genai.GoogleSearch{},
},
},
}),
ai.WithPrompt(req.Prompt),
ai.WithTools(catalogTool),
)
if err != nil {
return AppraiserResponse{}, fmt.Errorf("appraisal generation failed: %w", err)
}
return AppraiserResponse{Appraisal: resp.Text()}, nil
},
)
// 4. Mount flow directly using Genkit's built-in HTTP handler
mux := http.NewServeMux()
mux.Handle("POST /api/appraise", genkit.Handler(appraiserFlow))
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server := &http.Server{
Addr: ":" + port,
Handler: mux,
}
// Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
serverCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() {
log.Printf("Retro Game Appraiser (Genkit) listening on :%s", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server failed: %v", err)
}
}()
<-serverCtx.Done()
log.Println("\nShutting down server gracefully...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Fatalf("server forced shutdown: %v", err)
}
log.Println("Server exited cleanly.")
}
Запуск потоку
Запустіть сервер Genkit із налаштованим проектом Google Cloud:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export PORT=8080
go run main.go
З іншого терміналу надішліть запит на оцінку:
curl -s -X POST http://localhost:8080/api/appraise \
-H "Content-Type: application/json" \
-d '{"data": {"prompt": "I found a copy of EarthBound for SNES for $350. Do I own it, and is it a good deal?"}}' | jq .
Флой потрібно запустити, він викликає інструмент каталогу та повертає структурований пакет оцінки:
{
"appraisal": "### 1. Catalog Check\n**Yes, you already own it.**\n* **Title:** *EarthBound* (SNES, 1994)\n* **Status in Collection:** Loose Cartridge\n* **Condition/Notes:** Authentic board verified; label in excellent shape.\n* **Price Paid:** $180\n\n---\n\n### 2. Market Appraisal & Deal Analysis\n* **Loose Cartridge:** The current going market rate for an authentic loose copy ranges between **$320 and $380**. At **$350**, it is priced right at **fair market value**—neither an overpriced listing nor a significant bargain.\n* **Complete in Box (CIB) / Boxed with Guide:** If this listing happens to include the original big box and strategy guide with scratch-and-sniff cards, $350 would be an extraordinary steal (CIB copies regularly sell for **$1,500–$2,500+**).\n\n---\n\n### 3. Recommendation\n* **Pass (if Loose):** Since you already have an authentic copy in excellent condition, paying retail market price ($350) for a duplicate loose cart does not offer strong value or upside.\n* **Buy immediately (if Complete/Boxed):** Only pull the trigger if it includes the original packaging or represents a major condition upgrade/variant.\n* **Buyer Beware:** If you do ever consider another copy, always inspect the PCB (printed circuit board) screws and chips, as *EarthBound* is one of the most frequently counterfeited games on the SNES."
}
Genkit усуває необхідність вручну писати цикли обробки даних. Він перетворює аргументи на вбудовані структури Go, запускає інструмент, повертає результати моделі та автоматично генерує записи OpenTelemetry для кожного кроку. Це ідеальне рішення, коли робота відбувається за структурованим, детермінованим алгоритмом.
Версія три: Kit для розробки агентів
Якщо Genkit орієнтований на структуровані потоки обробки даних у додатках, то ADK від Google працює на високому рівні абстракції. Він розроблений для автономних конверсаційних агентів, оркестрації кількох агентів та складних архітектур, які потребують довгострокової пам’яті та корпоративних технологій RAG.
Цей набір також стандартизує життєві цикли агентів, механізми делегування завдань підагентам та протоколи комунікації між агентами, і може використовувати кілька постачальників моделей через адаптери, як це робить Genkit.
Для розгортання платформа Gemini Enterprise Agent є найкращим варіантом, якщо ви хочете, щоб Google керував зберіганням сеансів, підключеннями до корпоративних систем та пам’яттю, щоб вам не доводилося самостійно розробляти шари баз даних. Команди, які використовують власні системи зберігання стану чи контейнеризовані мікросервіси, краще обслуговуються сервісом Google Cloud Run.
Оцінювач у ADK v2:
package main
import (
"context"
"fmt"
"log"
"os"
"strings"
"google.golang.org/genai"
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/cmd/launcher"
"google.golang.org/adk/v2/cmd/launcher/full"
"google.golang.org/adk/v2/model/gemini"
"google.golang.org/adk/v2/tool"
"google.golang.org/adk/v2/tool/functiontool"
"google.golang.org/adk/v2/tool/geminitool"
)
// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
Title string `json:"title"`
Platform string `json:"platform"`
Year int `json:"year"`
Condition string `json:"condition"`
PricePaid float64 `json:"price_paid"`
Notes string `json:"notes"`
}
// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
{
Title: "Chrono Trigger",
Platform: "Super Nintendo (SNES)",
Year: 1995,
Condition: "CIB (Complete in Box)",
PricePaid: 210.00,
Notes: "Includes original map and registration card.",
},
{
Title: "EarthBound",
Platform: "Super Nintendo (SNES)",
Year: 1994,
Condition: "Loose Cartridge",
PricePaid: 180.00,
Notes: "Authentic board verified; label in excellent shape.",
},
{
Title: "Castlevania: Symphony of the Night",
Platform: "Sony PlayStation",
Year: 1997,
Condition: "CIB (Black Label)",
PricePaid: 135.00,
Notes: "Original soundtrack disc included.",
},
}
type CatalogRequest struct {
Query string `json:"query" jsonschema:"The game title or platform to search in the inventory."`
}
type CatalogResponse struct {
Found bool `json:"found"`
Message string `json:"message,omitempty"`
Count int `json:"count,omitempty"`
Results []GameItem `json:"results,omitempty"`
}
func main() {
ctx := context.Background()
// 1. Initialise Gemini Model adapter for Gemini Enterprise
model, err := gemini.NewModel(ctx, "gemini-3.8-flash", &genai.ClientConfig{
Project: os.Getenv("GOOGLE_CLOUD_PROJECT"),
Location: "global",
Backend: genai.BackendEnterprise,
})
if err != nil {
log.Fatalf("failed to create Gemini model: %v", err)
}
// 2. Wrap collection lookup as an ADK Function Tool
catalogTool, err := functiontool.New(functiontool.Config{
Name: "search_catalog",
Description: "Search the collector's personal inventory for owned games by title or platform.",
}, func(ctx agent.Context, req CatalogRequest) (CatalogResponse, error) {
queryLower := strings.ToLower(strings.TrimSpace(req.Query))
var matches []GameItem
for _, item := range localCatalog {
if strings.Contains(strings.ToLower(item.Title), queryLower) ||
strings.Contains(strings.ToLower(item.Platform), queryLower) {
matches = append(matches, item)
}
}
if len(matches) == 0 {
return CatalogResponse{
Found: false,
Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
}, nil
}
return CatalogResponse{
Found: true,
Count: len(matches),
Results: matches,
}, nil
})
if err != nil {
log.Fatalf("failed to create catalog tool: %v", err)
}
// 3. Define autonomous LLM Agent
appraiserAgent, err := llmagent.New(llmagent.Config{
Name: "retro_game_appraiser",
Model: model,
Description: "Expert appraiser that analyzes retro video game purchases and collection inventory.",
Instruction: "You are an expert Retro Game Appraiser. Assist collectors by verifying collection " +
"status with search_catalog, assessing condition variants, and offering objective buying recommendations.",
Tools: []tool.Tool{
catalogTool,
geminitool.GoogleSearch{},
},
})
if err != nil {
log.Fatalf("failed to create appraiser agent: %v", err)
}
// 4. Configure launcher and execute
config := &launcher.Config{
AgentLoader: agent.NewSingleLoader(appraiserAgent),
}
l := full.NewLauncher()
if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
log.Fatalf("run failed: %v\n\n%s", err, l.CommandLineSyntax())
}
}
Запуск агента ADK
ADK постачає універсальний запускач full.NewLauncher(), тому немає потреби вручну створювати механізми маршрутизації HTTP, форматування JSON чи базовий код для роботи з сеансами. Аргументи командного рядка, передані запускачу, визначають режим взаємодії без жодних змін у коді програми.
Щоб розпочати інтерактивний чат у терміналі:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
Запускач працює в режимі консолі, і оцінювача можна запитати безпосередньо:
User: Do I have Chrono Trigger in my collection?
Agent: Yes, you have Chrono Trigger in your collection! Here are the details from your inventory:
* Title: Chrono Trigger
* Platform: Super Nintendo (SNES)
* Release Year: 1995
* Condition: CIB (Complete in Box)
* Price Paid: $210.00
* Notes: Includes original map and registration card.
User: What did I pay for it?
Agent: You paid $210.00 for it.
ADK самостійно керує станом розмови та безперервністю сеансу, тому наступні запитання зберігають повний контекст попередніх діалогів.
Як альтернатива, існує вбудований веб-інтерфейс для розробників, який постачається разом із REST API:
go run main.go web webui api
Відкриття http://localhost:8080 дозволяє користуватися інтерактивним чатом із миттєвими відповідями, історією сеансів та можливістю детального перегляду процесу виконання інструментів, що охоплює як спеціальний інструмент функції search_catalog, так і вбудований інструмент GoogleSearch.
ADK чітко розділяє визначення агента (llmagent), прив’язки інструментів (functiontool та вбудовані елементи, такі як geminitool.GoogleSearch) та середовище виконання (launcher та runner). Це робить його ідеальною платформою для складних багатоагентних ієрархій та конверсаційних асистентів.
Де виконуються агенти Go
Написання агента на Go — це лише половина шляху. Після цього йому потрібне середовище виконання, яке здатне ефективно обробляти довготривалі потокові відповіді, виклики фонових інструментів, захищені облікові дані та сплески трафіку без проблем у експлуатації. Go компілюється у єдині самодостатні бінарні файли майже без додаткових витрат під час виконання, тому агенти, написані на цій мові, працюють швидко та їх можна дешево розміщувати. Залежно від вимог фреймворку та стану системи існують два основні варіанти реалізації.
Cloud Run — універсальний бекенд
Google Cloud Run є основною метою для контейнеризованих агентів на Go, незалежно від того, чи вони створені за допомогою базового SDK, потоків Genkit чи виконувачів ADK. Багатоступеневе будування в Docker дозволяє скомпілювати агента у мінімальне зображення типу scratch або distroless без жодних залежностей під час виконання. Оскільки не потрібно запускати важкий інтерпретатор чи віртуальну машину, сервіс швидко масштабується до нуля, уникаючи навантаження від процесу початкового запуску, яке характерне для більш важких середовищ виконання.
Основні переваги Cloud Run для агентів на Go:
- Масштабування до нуля з високою конкурентоспроможністю. Ви платите лише за мілісекунди роботи CPU, поки агент фактично працює або обробляє відповіді інструментів, а легкі горутини Go дозволяють одній інстанції контейнера одночасно обробляти сотні операцій агента з мінімальним використанням пам’яті.
- Довгий тайм-аут запитів. За замовчуванням тайм-аут становить 5 хвилин, але його можна подовжити до 60 хвилин (3 600 секунд). Це дає можливість багатокроковим логічним процесам, складним завданням на пошук інформації та ітеративним групам підагентів завершити свою роботу.
- Двостороннє стрімінг та WebSockets. Підтримується вбудований HTTP/2 з чанковою передачею даних, Server-Sent Events та WebSockets. Для агентів, які працюють у режимі голосу до голосу чи у реальному часі з використанням кількох форматів даних через API Gemini Live, WebSockets забезпечують безперервний двосторонній стрімінг між клієнтом та сервером.
- Афінність сеансів. Агент, який зберігає тимчасові кеші в пам’яті чи локальний контекст між кроками роботи, може використовувати параметр
--session-affinityза IP-адресою клієнта чи куки, щоб надсилати подальші запити до тієї самої інстанції контейнера. - Стійкість без стану. У продакшені дані мають зберігатися у зовнішніх керованих сховищах даних, таких як Firestore, Redis чи Cloud SQL. Після переривання з’єднання під час виконання завдання агент може продовжити роботу за допомогою свого ідентифікатора взаємодії, не втрачаючи контексту.
- Ідентичність завдань без секретів. Не потрібні жорстко закодовані API-ключі чи облікові дані, оскільки агент автентифікується у Gemini Developer API, Vertex AI та Cloud Storage через супутній обліковий запис сервісу Cloud Run.
Платформа агентів Gemini Enterprise
Агенти ADK для розмовного взаємодії та корпоративні рішення, які мають підтримувати сесії, зберігати довгострокову пам’ять та ґрунтувати відповіді у корпоративних знаннях, можуть використовувати платформу Gemini Enterprise Agent Platform, яку називають наступницею Vertex AI Agent Engine. Вона пропонує повністю кероване, безсерверне середовище виконання. Замість налаштування баз даних та створення власних адаптерів для зберігання сесій команди отримують керовану інфраструктуру з такими функціями:
- Розділене зберігання сесій. Довгострокове зберігання розмов (
SessionServiceу ADK) відокремлене від тимчасового циклу виконання у потоці (LiveSession), який забезпечує автоматичне відновлення сесії під час повторного підключення.
Вибір між трьома
Щоб дізнатися, як працюють виклики функцій та їх базування, або для невеликої утиліти, достатньо базового циклу SDK. Для структурованої, детермінованої обробки даних чи агента, який працює в одній доменній сфері та є доступним через HTTP-кінцеву точку, підходить Genkit. Для конверсаційних асистентів, ієрархій багатьох агентів, делегування обов’язків та довгострокової пам’яті кращою основою є ADK. Cloud Run підходить для всіх трьох варіантів, тоді як Gemini Enterprise Agent Platform є природним середовищем для агентів ADK, яким потрібні керовані сеанси, пам’ять та корпоративне базування.