Практычныя прытамулкі: Gemini для разработчыкаў на Go: стварэнне агентаў у Go
Практычныя прыказкі: Gemini для разработчыкаў на Go: стварэнне агентаў у Go — контракты, перакананняі і месцы для коду для команд, якія викорыстоўваюць гэты патэрн.
У гэтым керавану практычным спосабам перакладзена вся сяродвесць ад сыр'ёў да готавага системнага рашэння для проекту «Gemini for Go Developers: Building Agents in Go». Акцэнт ставіцца на практычныя крокі, чыстае перакананне ў правильнасці дзействаў і код, які можна проста дадаць у репазітарый без неабязковасці з'ясоўвання меты. У стадзіі агульнага апісання неабходна з'явіць вхідныя даны, адпаведальнага за крок і критэрыя завершэння прычынам перад змінайом коду. Аператары должны магчымае перадзванаць крок з вядомай точкі контролю, не прабуючы з'ясаваць схованы стан. Конфігурацыю трэба залічыць паза кодам прыкладнага програму. Файлы сераўіса, хранальнікі секрэтных данных і флагі функцый належаць у аднам месца, якое аператары можаць пераглядаць, не чытаяўшы весь код.
Структура агента
Калі працюеце над «Анатаміяю стэджу», спачатку запісайце угоду: неабяжлівыя даны, сігнал успеху і тое, што выканаецца у разе частковага нявыполнення. Такі список пераканаецца ў тым, што пазнейшыя змены коду будуць чыстымі. Документавайце як шлях успеху, так і шлях вяснавання. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў ёсць часткай продукту, а не пазнейшым дапрацоўкам. Зявляйце логі з ідэнтыфікаторам запиту, ідэнтыфікаторам моделі і часам затрымкі праз кожны вызов. Без такога следу періядычныя памылкі прадастаўця выглядаюць як багі ў прыемніку.
Дзяўерэнне агента: Адказчык за ацэнку ретро-гэйму
Працюючы над дзеянням «Retro» у проектацыі агента, спачатку запісуйце умовы працы: неабяжлівыя даны, сигнал успеху і тое, што выходзіць на падзею частковага невыпання. Такі список контроля дапамагае заліцварыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшоў, невыпанне павінна вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг дзеянняў. Запісвайце ID запытку, ID модэлю і час адпаведзі на кожны вызов. Без такога следу періядычныя падзеі з боку прадаўцоў выглядаюць як багі ў самай прыемлівай.
Магчымасці і взаінасунак з корыстнікам
Калі працюеце над стадзіяй «Магчымасці» і «Аб’ектна взаімадзея», спачатку запісайце контракт: неабяжлівыя данні, сигнал працэйскага успеху і тое, што выканаецца пад частковым нявыполненнем. Такі список контроля дапамагае заліцьваваць чыстасцю пазнейшых змян у кодзе. Спрэцьвуйце гэтую стадзію як контракт межа даннімі і перакананымі выходнымі рэзультатамі. Дайце назвы артыфактам, задаце критэрыя працэйскага успеху і адмовіцеся ад тыхоўскага частковага завершэння. Зявляйце логі з ідэнтыфікаторам запросу, ідэнтыфікаторам моделі і часам затрымкі праз кожны вызов. Без такога следу періядычныя кантакты прадастоўніка выглядаюць як багі ў прыемніку. Калі працюеце над стадзіяй «Магчымасці» і «Аб’ектна взаімадзея», спачатку запісайце контракт: неабяжлівыя данні, сигнал працэйскага успеху і тое, што выканаецца пад частковым нявыполненнем. Такі список контроля дапамагае заліцьваваць чыстасцю пазнейшых змян у кодзе. Зберагаюце настройкі праза код прыемніка. Файлы сяродавішча, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць без неабяжлівага чытання всей структуры.
Контракты інструментаў
Этап стварэння контрактаў інструментаў працюе наякшэй, калі яго розглядаць як мерыемую паверхню. Запісаць адна ідеальная транскрыпцыю, адзін прыклад неудачы і прыметку па адвярненню змян перш чым расширваць сферу дзеяння. Дакументаваць як успішны, так і вярнучы паты. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў є часткай продукту, а не наступным етапам дорабачання. Зафіксаваць інтэрпретара і файл з блокірацыяй залежнасцей пры тэставанні циклаў. Разлікі межы ноутбука і системы CI є найпашчэрэйшым тыхім сбоем пад час дэманстрацый API.
Стратэгія аргументаў
Стадія стратэгіі аналізу працюе найкраща, калі яе розглядаць як меркаваны аспект. Запісаце адна ідеальная версія, адзін прыклад неудачы і запіс працэў павернення да пярвоначальнага стану пры расшырэнні масштаба. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына неудачы павінна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцоўкі дзеянняў. Зафіксавайце інтэрпретара і файл з правіламі залежнасцяў пры навучэнні циклу. Разлік у параметрах межаў лэптопа і системы CI ёсць найчастэйшай прычыной тых, чаму дэманстраціі API не працуюць.
Адклёнчэнне агента за дапамою Go GenAI SDK
Агент, які выконваець задачы на певным этапе, працюе найэфектывнейша, калі яго розглядаць як меравальную плошчу. Зафіксавайце адны ідеальны прыклад роботы, адну справу з бягамі та прыметкі па поверненню да пачатковага стану пры перадзеяванні масштаба задачы. Разглядзайце гэты этап як кантракт межа вхіднымі даннымі та перакананымі выходнымі рэзультатамі. Даўце назвы всім элементам, задаць критэрыя успеху та не падтрымвайце частковыя рэшэння без паведамлення. Зафіксавайце інтэрпретар та файлы з інформацыяю пра залежнасці пры пачатку роботы з цикламі. Разніця ў работе на ноутбуку та у сервісах типу CI ёст калькольнай прычыной непрацясці пад час дэманстрацый API. Агент, які выконваець задачы на певным этапе, працюе найэфектывнейша, калі яго розглядаць як меравальную плошчу. Зафіксавайце адны ідеальны прыклад роботы, адну справу з бягамі та прыметкі па поверненню да пачатковага стану пры перадзеяванні масштаба задачы. Зберагайце настройкі праза код аплікацыі. Файлы сяродавішча, хранільнікі секрэтных данных та флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаць адрабоўваць контроль, не чытаючы весь код.
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,
})
}
}
}
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
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!
Фрэймворкі для разработкі агентаў
На стадзіі фрэймворкаў для разработкі агентаў неабходна праканалізаваць вхідныя даны, абавесцяўца крока і крэатывы завершэння працы перш чым змяніць код. Аператары должны магчымае запускіць крок з вядомай точкі контролю без неабясненага вычыслення скрытых станоў. Неабходна задокументаваць як шлях успеху, так і шлях вяснавання проблем. Праказвы, людзкія контралі і обработка неканальных паведамленняў є часткай продукту, а не элементамі пазнейшага дапрацоўкі. Неабходна аддзеліць стварэнне кліента ад цыклу паведамленняў, каб можна было змяніць прадаючых без перапісвання машыны станоў дыялогу.
Рэалізацыя агента з Genkit
Для рэалізацыі агента з стадіям неабходна пазначыць вхідныя даны, адпаведальнага за кожную стадію і крэтыяры завершэння пры змены коду. Аперацыйныя працавнікі должны магчымае перайсці на выкананне стадіі з вядомага пункта контролю, не прыпускаючы стану, які залишаецца незрозумелым. Лепей выбіраць маленькія, тэставаныя елементы заместо велікіх скрыптав. Калі стадія не выканаецца, прычына неудачы должна вказываць на адзін конкрэтны элемент, а не на заплутаную структуру працы. Раздзеляйце процес стварэння кліента ад цыклу перадачы паведамленняў, ў такі спосаб працавнікі-пастачыкі можна будзе заменіць без перапісвання машыны стану дыялогу.
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
Для стадії адміністрування прагу Genkit неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыры завершэння пры змяне коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Спрацавляйце з гэтай стадіяй як з кантрактом межаў вхідных дадзеных і перакананых выходных рэзультатаў. Даць назвы артыфактам, узначыць перакананні пра успех і адмовіцца ад беззвучнага частковага завершэння. Раздзеліце стварэнне кліента ад цыклу паведамленняў, каб можна было змяніць прадастаўцаў без перапісву стану машыны дыялога. Для стадії адміністрування прагу Genkit неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыры завершэння пры змяне коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Зберагачыце настройкі параду коду прыкладнення. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць, не чытаяўшы весь граф.
/p>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."
}
Адаптаванне агента за допамогай кітэў развіцця агентаў (ADK)
Калі працуеце над адаптаваннем агента, спачатку запісайце умовы ўгоды: неабходныя даны, сігнал успеху і тое, што выканаецца у разы частковага невялікога браку. Такі чарт дапамагае заліцвачыць змяны ў кодзе пазней. Дакументавайце як шлях успеху, так і шлях вяснавання ситуацыі. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў є часткай продукту, а не дадатковымі правкамі пазней. Зявляйце логі з ідэнтыфікаторам запиту, ідэнтыфікаторам моделі і часам адпаведзення за кожны вызов. Без такога логу періядыя памылкі прадастальніка выглядаюць як багі ў самай аплікацыі.
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», спачатку запісайце шаблон: неабяжлівыя даны, сигнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список дапамагае заліцьваты змяны ў кодзе.
Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына нявыпання павінна вказываць на адзін конкрэтны элемент, а не на заплутаную структуру.
Запісвайце ідэнтыфікатор запиту, ідэнтыфікатор моделі і час адказу праз кожны вызов. Без такога лістынгу періядычныя кантакты з прадаўцам выглядаюць як багі ў самай прыемлівай.
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.
go run main.go web webui api
Часы выканання агента
Калі працуеце над стадзіяй Agent runtimes, спачатку запісайце контракт: неабяжлівыя даннэ, сигнал успеху і тое, што выходзіць пад частковыя адказы. Такі список контроля дапамагае заставіць пазнейшыя змены коду быць чыстымі. Спрэтавайце гэту стадзію як контракт межа даннемі і перакананымі выходамі. Дайце назву артыкулам, задаце перакананні успеху і адмовіцеся ад тыхоўскага частковага завершэння. Зявляйце логі з ідэнтыфікаторам запросу, ідэнтыфікаторам моделі і часам затрымкі праз кожны вызов. Без такога следу періодычныя памылкі прадастаўцоў выглядаюць як багі ў прыемніку. Калі працуеце над стадзіяй Agent runtimes, спачатку запісайце контракт: неабяжлівыя даннэ, сигнал успеху і тое, што выходзіць пад частковыя адказы. Такі список контроля дапамагае заставіць пазнейшыя змены коду быць чыстымі. Зберагаюце настройкі за межамі коду прыемніка. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функций должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць без неабяжлівага чытання всей структуры.
Cloud Run: універсальны выгодны варыянт для бэкенду
Cloud Run як універсальны раштрын працюе найэфектывней, калі яго спрыяваць як меравальную плошчу. Запісаўце адны ідеальны прыклад роботы, адну ситуацыю неудачы і прыметкі па адвярненню перад расшырэнням масштаба. Дакументавайце як шлях успеху, так і шлях вярнення да нормы. Перапрыбуткі, людзкія контрольны пункты і обработка некоректных паведамленняў є часткай продукту, а не дадатковым удосконаленнем пазнейша. Закрепіце інтэрпретар і файл з блокаванням залежнасцяў прытаму, калі ўчыце циклы. Разніця межу лэптопам і системай CI є найчастэйшым таямным абрывам для дэманстрацый API.
Gemini Enterprise Agent Platform: кераваныя сесіі і корпаратыўны RAG
Платформа Gemini Enterprise Agent Platform працюе наякшым чынам, калі яе розглядаць як меравальную плошчу. Зафіксавайце адны ідеальны прымер роботы, адзін прыклад неудачы і запіс працэй з вярненням да пачатковага стану, прычаму расшырэння сферы дзеяння. Валіце маленькія, тэставальныя элементы замест большых скрыптов. Калі якісь крок не выйшае, прычына неудачы павінна вказываць на адную адпаведальнасць, а не на заплутаны ланцюг задач. Зафіксавайце інтэрпретара та файл з правіламі залежнасцяў прычаму навучэння ціклу. Разлікы межаў лептапа і системы CI ёсць найпашчэрэзнейшым тыхім факторам, які спрабоўвае зламаць дэманстрацыі API.
Што далей?
Этап «Што далей?» працюе найэфектывней, калі яго розглядаць як вимерную паверхню. Зафіксавце адны ідеальны прымер роботы, адны прыклад неудачі і запіс пра вярнэнне да пачатковага стану перш чым расширваць масштабы. Разглядзіце гэты этап як кантракт межа вхіднымі даннымі і перакананымі выходнымі рэзультатамі. Дайце назвы всім элементам, задаце критэрыя успеху і не падзейвайцеся частым, непазначаным завершэнням задачі. Зафіксавце версію інтэрпретара і файлы з інформацыяю пра залежнасці перш чым выучваць логіку циклаў. Разлікы межа ноутбукам і системай CI ёсць найчастэйшым прычынам непазначанага зламу пад час дэманстрацый API. Этап «Што далей?» працюе найэфектывней, калі яго розглядаць як вимерную паверхню. Зафіксавце адны ідеальны прымер роботы, адны прыклад неудачі і запіс пра вярнэнне да пачатковага стану перш чым расширваць масштабы. Зберагаўце настройкі праза код аплікацыі. Файлы сяродавішняе сераўісу, хранілішчы секрэтных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куда аператары можу пераглядаць іх без неабходнасці чытання всей структуры.
Чэрніця эксплуатацыі
Этап перагляду канцэлкі працоўных процедурах работае наяўней, калі яго спрыймаюць як мерыябельную плошчу. Запісайце адну ідеальную версію, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану, перш чым расширваць сферу дзеяння.
Запісвайце часы выканання і косты токеноў або запытаў разам з функцыйнальнымі рэзультатамі. Відразлівае паказанне костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмавайшанняў у спяльныя среды.
Зафіксавайце інтэрпретара і файл з правіламі залежнасцяў пры практыкуванні циклаў. Разлікы межаў між ноутбукам і системай CI ёсць найчастэйшым таямным перашкодам для дэмавайшанняў API.
Заставьце людзкую апраўдку для тых элементаў, якія витрачаюць грошы або зменяюць даны ў працэсе виробніцтва. Компіляцыйны момент не ўзроўнаважваеся з повнасцю бізнес-процэсаў.
Напісце кароткі посібнік: як зменяць канты, як спрачыслаць чергу, як вярнуцца да пачатковага стану пасля апошней інтеграцыі.
Лепшыя маленькія, тэставаныя елементы, чым велікія скрыпты. Калі якісь крок не выйшае, адказнасць за гэта патрабуецца толькі ад однаго элемента, а не ад усіяўшайся ланцужні.
Перад апраноўкай стака неабходна заморазіць версіі, зафіксаваць «золаты» транскрыпт для критычнага шляху і паверыцца ў крокі для вярнення да пачатковага стану. У спільных средах трэба наладзіць ліміты частоты запытоў, пераканацца ў правільнасці належнасці ресурсаў і вызначыць чыстага адпаведальнага за зміны секрэтных дадзенняў. Лепшая ўпорядкованасць і надзеянае функцыонаванне, чым крэатывныя, але еднакратныя дэманстрацыі.
Прымечанне для пакета 05c61bdde7aa: не клаціць ключы прадаўцаў у репазітары, задаць ліміт токена на кожную сесію і зберагаць транскрыпты разам з фіксатрамі для ацэнкі, ўпрымку, каб пазнейшыя замены модэляў заставаліся порупачнымі.
Для стадіі 0 пры практыцы змецчэння неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшоў, прычына неудачы павінна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны процес.
Дзеянне змецчэння 0/951: вымерыць час выканання, класію адказаў і колькасць выкорыстоўваных токенав для гэтай прыметкі, а пасля вырашыць, чы робіць змяну на адной пазуроўанай сэтцы пытанняў, а не на адной лічбе.
Калі працюеце над першым этапам зміцнення, спачатку запісайце умовы кантракта: неабяжлівыя даны, сигнал успеху і тое, што выходзіць у разе частковага невыпання. Такі список контроля дапамагае заліцвачваць будучыя змены коду. Запісуйце час выканання і вартась токена або запытку праз адныя з рэзультатаў функцыональнасці. Відразувая візуабельнась вартасей запобегае неспакоўным рахункам, калі процес пераходзіць з дэмаверсіі ў спяльныя среды.
Дзеянне зміцнення 1/951: вымерайце час выканання, класію паканаў і вартась токена для гэтага пункту, а потым выберайце, чы робіць змену на аднойчынных крэтарях, а не на аснове індывідуальных спазыроў.