Tworzenie agenta Gemini w Go: porównanie SDK Raw, Genkit i ADK
Jeden ekspert od oceny gier retro stworzył trzy rozwiązania w języku Go, wykorzystując GenAI SDK, Genkit oraz Agent Development Kit, a także pokazał, jak uruchomić każde z nich na Cloud Run lub platformie agentów Google.
Budowanie aplikacji wykorzystujących sztuczną inteligencję oraz autonomicznych agentów w języku Go może odbywać się na trzech różnych poziomach abstrakcji. Ten przewodnik analizuje mechanizmy działania agenta, wybiera konkretny przypadek użycia – oceniacza gier retro – a następnie buduje go trzy razy:
- Pętla agenta na niskim poziomie, napisana bezpośrednio z wykorzystaniem SDK Go GenAI.
- Strukturalna, zorientowana na przepływ architektura budowana za pomocą Genkit.
- Modularny system wielu agentów z sesjami, stworzony na bazie Google Agent Development Kit (ADK).
Na końcu omówione są środowiska uruchomieniowe umożliwiające niezawodne wdrażanie agentów napisanych w Go w chmurze.
Co czyni coś agentem
Termin ten jest używany swobodnie, jednak w inżynierii sztucznej inteligencji ma dość precyzyjne znaczenie architektoniczne: system autonomiczny składający się z trzech części – modelu LLM, zestawu narzędzi takich jak funkcje wywoływalne lub API oraz mechanizmu, który uruchamia wszystko w ramach pętli sprzężenia zwrotnego.
Model bez narzędzi to po prostu generator tekstu. Może odpowiadać jedynie na podstawie swoich ustalonych wag treningowych lub informacji zawartych w zadaniu. Nadanie mu autonomii, czyli możliwości sprawdzania stanu zewnętrznego, testowania hipotez i zmieniania rzeczy w rzeczywistym świecie, wymaga połączenia go z wykonywalnymi narzędziami.
Jeden z detalów architektonicznych łatwo przeoczyć. Model sam w sobie nigdy nie uruchamia kodu ani nie wywołuje zewnętrznych API. Pomiędzy nimi znajduje się lokalny mechanizm, w tym przypadku program w języku Go. Gdy model postanawia, że potrzebuje informacji z zewnątrz lub podjęcia jakiejś akcji, wysyła uporządkowany wniosek o wywołanie narzędzia. Mechanizm ten uruchamia odpowiednią funkcję w Go, przechwytuje wynik i przekazuje go z powrotem do następnego kroku modelu. Model następnie analizuje nowy kontekst i albo wywołuje inne narzędzie, albo pisze swoją ostateczną odpowiedź.
Nie każde narzędzie działa lokalnie. Gemini oferuje wbudowane narzędzia, takie jak funkcja Google Search grounding, które są wykonywane po stronie serwera. Po ich zadeklarowaniu w konfiguracji żądania API przetwarza zapytania wyszukiwawcze i dołącza metadane dotyczące tych danych do odpowiedzi, bez konieczności powrotu do lokalnego komputera. Jednak w przypadku niestandardowej logiki domeny wszystko jest kontrolowane przez narzędzie Go: deklaruje ono schemat, przekazuje argumenty i zwraca zserializowane wyniki.
Przypadek użycia: oceniający gry retro
Każdy, kto kolekcjonuje gry wideo retro, zna ten typowy problem. Na targach z grami retro łatwo jest kupić egzemplarz ulubionej gry, by potem odkryć w domu, że już ją posiada. Ponadto ten hobby charakteryzuje się niestabilnymi cenami, różnymi poziomami stanu (kasetka bez pudełka, kompletna w pudełku lub CIB, zapieczętowana fabrycznie) oraz częstymi fałszywymi ofertami.
Sprawdzanie ceny proponowanej za pomocą Gemini w czasie rzeczywistym, stojąc przy stoisku, okazuje się zaskakująco przydatne. Wielu kupujących chętnie zapłaci odpowiednio wyższą cenę za możliwość zakupu osobiście oraz za wspieranie lokalnych sprzedawców, ale nikt nie chce zostać oszukany ani zapłacić za fałszywy kartrydż zbyt wysokiej kwoty.
Agent oceniający bierze pod uwagę obie strony: porównuje indywidualną listę kolekcji z aktualnymi danymi z sieci, aby określić sprawiedliwą wartość rynkową.
Jak go używa kolekcjoner
Kolekcjoner wpisuje zwykłe pytania, takie jak:
- Pytanie, czy dany tytuł, na przykład Chrono Trigger, już znajduje się w kolekcji.
- Informacja o kompletnym egzemplarzu EarthBound dla SNES w oryginalnej pudełce sprzedawanym za 350 dolarów, wraz z pytaniem, czy już go ktoś posiada oraz czy cena jest korzystniejsza niż obecne ceny rynkowe.
- Prośba o informację o cenie, jaką zapłacono za Castlevania: Symphony of the Night, oraz o to, czy jej wartość rynkowa od tamtej pory wzrosła.
Umowy dotyczące narzędzi
Aby udzielić odpowiedzi bez wymyślania danych o zapasach czy cenach, agent opiera się na dwóch źródłach informacji:
search_catalogto lokalna funkcja w języku Go, która wyszukuje dane w własnej bazie danych kolekcjonera. Szuka kluczowych słów w tytułach i platformach, a następnie podaje informacje o właścicielu, stanie przedmiotu, dacie nabycia oraz cenie zakupu.google_searchto narzędzie służące do sprawdzania informacji po stronie serwera. Wyszukuje aktualne, sprawiedliwe ceny na platformach handlowych i portalach aukcyjnych, a także ostatnie potwierdzone transakcje i standardy dotyczące stanu przedmiotów.
Strategia rozumowania
Przy ewentualnej transakcji agent postępuje według określonej sekwencji. Najpierw sprawdza lokalny katalog, aby ustalić, czy gra jest już w posiadaniu klienta i w jakim stanie się znajduje. Następnie wyszukuje w internecie aktualne ceny na danej platformie i dla danego poziomu stanu. Na koniec łączy te informacje w ocenę: proponowaną cenę w porównaniu z średnią rynkową, możliwości ulepszenia, takie jak zamiana kasetki bez pudełka na wersję w pudełku, oraz jasną rekomendację.
Wersja pierwsza: Go GenAI SDK
Budowanie bezpośrednio na SDK (google.golang.org/genai) zapewnia najniższy poziom abstrakcji, odpowiadający w pełni protokolowi komunikacyjnemu API Gemini. Bez żadnego frameworku pomiędzy kodem a API sam decydujesz o wysyłaniu zapytań, historii rozmów oraz kryteriach zatrzymania pętli.
Działa przy małych projektach i skryptach użytkowych, dla wszystkich, którzy badają, jak faktycznie funkcjonują wywołania funkcji i ich implementacja, a także w przypadkach o bardzo specyficznych wymaganiach dotyczących pętli, których nie obejmują standardowe frameworki. Wynikiem jest jeden samodzielny plik binarny w języku Go bez żadnych zależności od frameworków, dzięki czemu można go uruchomić na zwykłym hostingu internetowym lub API, takim jak Google Cloud Run, Kubernetes lub zwykła maszyna wirtualna.
Pełna, działająca implementacja:
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,
})
}
}
}
Aby to uruchomić, skonfiguruj projekt w Google Cloud i zaloguj się za pomocą domyślnych uprawnień aplikacji, używając polecenia gcloud auth application-default login:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
W terminalu otwiera się interaktywna rozmowa. Rozmowa może trwać kilka rund i kończy się, gdy wpiszesz exit, naciśniesz Ctrl+C lub wyślesz EOF za pomocą 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!
Wersja SDK sprawia, że przepływ sterowania jest jasny i łatwy do zrozumienia. Dzięki silnym modelom oraz dużym oknom tokenów napisanie własnych mechanizmów obsługi, pętli wysyłania narzędzi oraz niezbędnych elementów nośnych okazuje się zaskakująco proste.
Jednak ta łatwość jest pułapką. Każda linia kodu stworzonego od zera staje się trwałym kosztem konserwacji oraz potencjalnym źródłem subtelnych błędów. Tworzenie ram roboczych dla agentów od zera w najlepszym przypadku odwraca uwagę od wdrażania nowych funkcji, a w najgorszym – tworzy górę długu technicznego, który odciąga zasoby od rzeczywistych celów biznesowych. Najlepszy kod to żaden kod, a drugi najlepszy to taki, który rozwiązuje problem przy użyciu jak najmniejszej ilości kodu dostosowanego pod konkretne potrzeby.
Dlaczego przenieść się na framework?
Surowa pętla SDK jest trudna do prześcignięcia pod względem nauki mechanizmów niskiego poziomu lub tworzenia specjalistycznych pętli, jednak aplikacje produkcyjne wymagają wyższego poziomu abstrakcji z kilku powodów:
- Automaticzne odzwierciedlanie schematów. Ręczne tworzenie schematów JSON za pomocą
&genai.Schema{...}jest uciążliwe i podatne na błędy. Frameworki produkcyjne wytwarzają schematy na podstawie natywnych struktur Go oraz komentarzy dokumentacyjnych. - Obserwowalność i śledzenie. W warunkach produkcyjnych wymagane są natychmiastowe ślady działania z OpenTelemetry, szczegółowe dane o opóźnieniach w poszczególnych narzędziach oraz metryki zużycia tokenów.
- Zarządzanie promptami. Prompty zapisane jako ciągi znaków w języku Go utrudniają współpracę z specjalistami od promptów i wykluczają możliwość wersjonowania szablonów oddzielnie od wersji binarnych.
- Trwałość sesji i stan. Przechowywanie historii wieloetapowych rozmów w ramach bezstanowych żądań HTTP wymaga bezpiecznego pod względem wątków, odseparowanego magazynowania danych.
Ekosystem Go posiada dwa główne frameworki o otwartym kodzie dostosowane do tych potrzeb: Genkit oraz Agent Development Kit.
Wersja druga: Genkit
Genkit charakteryzuje się średnim poziomem abstrakcji i wprowadza dyscyplinę inżynierii oprogramowania oraz uporządkowaną możliwość obserwacji w aplikacjach AI. Wszystko jest zorganizowane wokół „przepływów” – silnie typowanych, obserwowalnych łańcuchów przetwarzania – oraz narzędzi, którymi są bezpieczne pod względem typów funkcje w Go z automatycznym generowaniem schematów.
Podoba się do zadań generowania wykonywanych raz, takich jak narzędzia wiersza poleceń, przetwarzanie zbiorcze i punkty końcowe webhook, a także do deterministycznych procesów pracy i agentów jednodomenowych, jak pipeline wielomodalnego restytuowania zdjęć. Poprzez pluginy obsługuje kilku dostawców modeli. Ponieważ aplikacja Genkit to po prostu standardowy serwer HTTP w języku Go, działa na każdej platformie backendowej, przy czym Google Cloud Run jest preferowanym wyborem ze względu na łatwe hostowanie kontenerów i automatyczne skalowanie.
Aplikacja oceniająca napisana z użyciem 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.")
}
Rozpoczęcie przepływu
Zainstaluj serwer Genkit z skonfigurowanym projektem Google Cloud:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export PORT=8080
go run main.go
Z innego terminala wyślij prośbę o ocenę:
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 .
Proces jest wykonywany, wywołuje narzędzie katalogowe i zwraca ustrukturyzowany pakiet oceny:
{
"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 eliminuje ręcznie pisany pętla wysyłania danych. Rozpakowuje argumenty do natywnych struktur Go, uruchamia narzędzie, zwraca treść do modelu i automatycznie generuje elementy OpenTelemetry dla każdego kroku. Jest idealnym rozwiązaniem, gdy obciążenie pracy przebiega według uporządkowanego, deterministycznego schematu.
Wersja trzecia: Agent Development Kit
Podczas gdy Genkit skupia się na uporządkowanych pipeline’ach aplikacji, ADK firmy Google działa na wyższym poziomie abstrakcji. Został zaprojektowany dla autonomicznych agentów konwersacyjnych, orkiestracji wielu agentów oraz złożonych architektur wymagających długoterminowej pamięci i rozwiązań typu RAG w środowiskach korporacyjnych.
Kit ten standaryzuje również cykle życia agentów, delegację zadań pomiędzy podagentami oraz protokół komunikacji między agentami (A2A), a dzięki adapterom może korzystać z wielu dostawców modeli, tak jak to robi Genkit.
Dla wdrożenia platforma Gemini Enterprise Agent jest preferowanym rozwiązaniem, jeśli chcesz, aby Google zarządzał przechowywaniem sesji, łącznikami korporacyjnymi oraz pamięcią, dzięki czemu nie musisz samodzielnie programować warstw bazy danych. Zespoły korzystające z własnych magazynów stanu lub mikrosług w kontenerach lepiej radzą sobie z Google Cloud Run.
Ocena w 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())
}
}
Uruchamianie agenta ADK
ADK dostarcza uniwersalnego uruchamiacza, full.NewLauncher(), więc nie ma potrzeby ręcznego tworzenia mechanizmów routingu HTTP, konwertowania danych do formatu JSON ani szablonów sesji. Argumenty linii poleceń przekazywane do uruchamiacza wybierają tryb interakcji bez żadnych zmian w kodzie aplikacji.
Aby rozpocząć interaktywną rozmowę w terminalu:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
Uruchamianie odbywa się w trybie konsoli, a osoba oceniająca może być bezpośrednio pytana:
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 samodzielnie zarządza stanem rozmowy oraz ciągłością sesji, dzięki czemu kolejne pytania zachowują pełny kontekst wcześniejszych kroków.
Jako alternatywę dostępna jest wbudowana interfejs użytkownika internetowego dla programistów, dostarczana razem z API REST:
go run main.go web webui api
Otwarcie http://localhost:8080 umożliwia interaktywną rozmowę z odpowiedziami w czasie rzeczywistym, historię sesji oraz szczegółową wizualizację przebiegu wykonywania narzędzi, obejmującą zarówno dostosowane narzędzie funkcji search_catalog, jak i wbudowane rozwiązanie GoogleSearch.
ADK wyraźnie oddziela definicję agenta (llmagent), powiązania z narzędziami (functiontool oraz wbudowane elementy takie jak geminitool.GoogleSearch) oraz środowisko wykonywania (launcher i runner). Dzięki temu staje się preferowanym frameworkiem dla złożonych hierarchii wielu agentów oraz asystentów konwersacyjnych.
Gdzie działają agenci Go
Napisanie agenta w Go to tylko połowa drogi. Następnie potrzebne jest środowisko wykonawcze, które potrafi radzić sobie z długotrwałymi odpowiedziami strumieniowymi, wywołaniami narzędzi w tle, bezpiecznymi danych uwierzytelniających oraz nagłymi wzrostami ruchu bez problemów operacyjnych. Go kompiluje się do pojedynczych, samodzielnych binarów z niemal zerowymi kosztami działania, dzięki czemu agenci napisani w tym języku są szybkie i tanie w hostowaniu. W zależności od wymagań frameworku i stanu istnieją dwa główne cele.
Cloud Run – uniwersalny backend
Google Cloud Run stanowi główny cel dla agentów w formacie kontenerowym w języku Go, niezależnie od tego, czy zostały one stworzone przy użyciu surowego SDK, procesów Genkit, czy narzędzi ADK. Budowa aplikacji w formacie Docker w kilku etapach umożliwia skompilowanie agenta do minimalnego obrazu typu scratch lub distroless bez żadnych zależności runtime. Ponieważ nie trzeba uruchamiać ciężkiego interpretera ani maszyny wirtualnej, usługa szybko skaluje się do poziomu zera, unikając kosztownych opóźnień związanych z uruchamianiem, które występują przy cięższych środowiskach runtime.
Główne zalety Cloud Run dla agentów w języku Go:
- Skalowanie do zera przy szybkiej równoczesności. Płacisz tylko za milisekundy wykorzystania CPU, gdy agent faktycznie pracuje lub przetwarza odpowiedzi narzędzi, a lekkie goroutines w języku Go pozwalają jednej instancji kontenera przetwarzać setki zadań agenta równocześnie przy niewielkim zużyciu pamięci.
- Długi czas oczekiwania na żądania. Domyślnie czas ten wynosi 5 minut, ale można go wydłużyć do 60 minut (3 600 sekund). Dzięki temu pętle rozumowania wieloetapowego, złożone zadania badawcze oraz iteracyjne zespoły podagentów mają wystarczająco dużo czasu na zakończenie pracy.
- Strumieniowanie dwukierunkowe i WebSockets. Natywnie obsługiwane są transfer w formie fragmentów w protokole HTTP/2, Server-Sent Events oraz WebSockets. W przypadku agentów multimodalnych pracujących w czasie rzeczywistym lub przekształcających głos w głos w ramach API Gemini Live, WebSockets umożliwiają ciągłe strumieniowanie w obie strony pomiędzy klientem a serwerem.
- Afinitet sesji. Agent, który przechowuje tymczasowe pamięci cache w pamięci lub lokalny kontekst pomiędzy kolejnymi krokami, może użyć opcji
--session-affinity, opartej na adrese IP klienta lub pliku cookie, aby wysyłać dalsze informacje do tej samej instancji kontenera. - Odporność bez stanu. W środowisku produkcyjnym dane powinny być przechowywane w zewnętrznych, zarządzanych bazach danych, takich jak Firestore, Redis czy Cloud SQL. Po przerwie w połączeniu sieciowym agent może kontynuować działanie dzięki swojemu identyfikatorowi interakcji, bez utraty kontekstu.
- Tożsamość zadań bez poufnych danych. Nie są potrzebne hardkodowane klucze API ani dane uwierzytelniające, ponieważ agent autoryzuje się w Gemini Developer API, Vertex AI i Cloud Storage za pośrednictwem konta usługowego Cloud Run.
Platforma agentów Gemini Enterprise
Agenci konwersacyjne ADK oraz rozwiązania korporacyjne, które muszą przechowywać sesje, utrzymywać długoterminową pamięć i opierać odpowiedzi na wiedzy firmy, mogą korzystać z platformy Gemini Enterprise Agent Platform, określanej jako następca Vertex AI Agent Engine, która oferuje w pełni zarządzane, bezserwerowe środowisko wykonywania. Zamiast konfigurować bazy danych i pisać własne adaptery do przechowywania sesji, zespoły otrzymują zarządzaną infrastrukturę z następującymi funkcjami:
- Rozdzielona trwałość sesji. Długoterminowe przechowywanie rozmów (
SessionServicew ADK) jest oddzielone od tymczasowego cyklu wykonywania w formie strumienia (LiveSession), z automatycznym przywracaniem sesji po ponownym połączeniu.
Wybór spośród trzech rozwiązań
Aby poznać zasady wywoływania funkcji i ich implementacji lub do prostych zadań, wystarczy surowy cykl SDK. Do zorganizowanych, deterministycznych procesów lub agentów działających w jednej domenie i dostępnych jako punkty końcowe HTTP, odpowiedni jest Genkit. Dla asystentów konwersacyjnych, hierarchii wielu agentów, delegacji i pamięci długoterminowej lepszą bazą jest ADK. Cloud Run nadaje się do wszystkich trzech przypadków, natomiast platforma Gemini Enterprise Agent Platform stanowi naturalne środowisko dla agentów ADK wymagających zarządzanych sesji, pamięci oraz funkcji typu enterprise grounding.
Literatura pokrewna
- Kontrola zachowania agenta Bedrock: etapy zapytań, parserzy i procesy inferencji — Jak przejąć kontrolę nad procesami przedprzetwarzania, orkiestracji i poprzetwarzania w Bedrock za pomocą Terraform, wykorzystując parser Lambda, kontrakt JSON oraz ustawienia inferencji dla poszczególnych etapów.
- Chmurna przeglądarka: bezpieczna platforma przeglądarki zdalnej dla każdego agenta AI — Projekt referencyjny dla usługi przeglądarki zdalnej neutralnej wobec modeli: brama wejściowa, procesory, żywy obraz WebRTC, mechanizmy zatwierdzania, zabezpieczenia przed SSRF, funkcja odtwarzania oraz wszystkie ustawienia konfiguracyjne.