Агент Gemini на Go: параўнанне голага SDK, Genkit і ADK
Адзін агент-ацэншчык рэтра-гульняў, пабудаваны на Go трыма спосабамі — праз GenAI SDK, Genkit і Agent Development Kit, — і як запусціць кожны ў Cloud Run ці на агентнай платформе Google.
Стварэнне прыкладных програм з асіставаннем AI і автонамных агентаў на мове Go можа вестыся на трох разных рэвалюцыйных роўнах. У гэтым апісанні дэталізаваныя механізмы агента, выбраны конкрэтны прыклад выкарыстоўвання — апрацоўчык рэтро-гэймав, і пасля гэтага ён ствараецца тры разы:
- Цікл агента нізкага рэвалюцыйнага роўна, напісаны безпосерэдна на базе SDK Go GenAI.
- Структураваны, орыѐнтаваны на прайскупленне праблемы пайплайн, створаны з адпамогай Genkit.
- Модульная сістэма з калькамі агентаў з сэсіямі, створаная на базе кітэна развіцця агентаў Google (Agent Development Kit, 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, ён працуе на будзь-якай платформе-бэкендзе, пры чым 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 для кожнага крока. Гэта ідэальны выбар, калі роба адбываецца па структураваным, дэтэрміністычным прабегу.
Версія трох: кіт для разработкі агентаў
Калі Genkit нацэлёваны на структураваныя пайплайны прыемоў, то ADK ад Google працуе на высокай ступені абстракцыі. Ён створаны для автонамных канверсацыйных агентаў, оркестрацыі калькі агентаў і складных архітэктураў, якім патрэбны давялеглі тэмпаратуры і рашэнні на адной з баз дадзенаў.
Кіт таксама стандартызуе жыццёвы цыклы агентаў, дэлегаванне задач саб-агентам і протакол камунікацыі агент-агент (A2A), і можа выкарыстоўваць калькі прадаўцоў модэляў праз адаптары, як і 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. Пасля перыяду з’яносу ў сеткі агент можа продаваць свою працу за дапамою ID взаімадзеі, не втрачаючы кантэксту.
- Ідэнтыфікацыя завадбы без секрэтных даных. Не трэба жорсткая прыкладзенае 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, якім патрэбны кантролюемыя сесіі, памяць і утверджэння ў кантэксце на рэвэну.