Strona główna / Artykuły / Wskazówki praktyczne: Gemini dla programistów w Go: tworzenie agentów w Go

Wskazówki praktyczne: Gemini dla programistów w Go: tworzenie agentów w Go

Krok po kroku przewodnik po praktycznych wskazówkach: Gemini dla programistów Go: tworzenie agentów w Go – umowy, sprawdzania oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.

4187 słów

To przewodnictwo pokazuje, jak przejść od surowców do gotowego systemu w przypadku projektu Gemini for Go Developers: Building Agents in Go. Skupia się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu dodać do repozytorium, nie musząc zgadywać jego przeznaczenia. Na etapie przeglądu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu systemu. Konfigurację należy trzymać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny tajemnic oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury systemu.

Anatomia agenta

Gdy pracujesz nad „Anatomią etapu”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej próbie połączenia. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.

Projekt agenta: Oceniający gry retro

Podczas projektowania etapu Retro w ramach Agenta, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.

Zdolności i interakcja z użytkownikiem

Gdy przechodzisz przez etap możliwości i interakcji z użytkownikiem, najpierw spisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Zapisuj identyfikator żądania, identyfikator modelu oraz czas opóźnienia przy każdej próbie połączenia. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji. Gdy przechodzisz przez etap możliwości i interakcji z użytkownikiem, najpierw spisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny haseł oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

Umowy dotyczące narzędzi

Etap umów dotyczących narzędzi funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Zdokumentuj zarówno prawidłowy przebieg operacji, jak i ścieżkę przywracania do stanu poprzedniego. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Ustal stałe wartości interpretera oraz pliku blokującego zależności, zanim nauczysz mechanizmów pętli. Rozbieżności między laptopem a środowiskiem CI są najczęstszą przyczyną niewidzialnych awarii w demonstracjach API.

Strategia rozumowania

Faza strategii rozumowania działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Ustal wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Różnice między laptopem a środowiskiem CI to najczęstsza, niewidoczna przyczyna awarii w demonstracjach API.

Wdrażanie agenta za pomocą Go GenAI SDK

Implementacja agenta w ramach określonej fazy działa najlepiej, gdy jest traktowana jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres projektu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć sytuacje, w których realizacja jest częściowa i niewidoczna. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Rozbieżności pomiędzy laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API. Implementacja agenta w ramach określonej fazy działa najlepiej, gdy jest traktowana jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres projektu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

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!

Ramy rozwoju agentów

Na etapie ram rozwoju agentów należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości nieudanych należą do samego produktu, a nie są elementami dodatkowej optymalizacji. Należy oddzielić budowę klienta od pętli komunikacji, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanów rozmowy.

Wdrażanie agenta za pomocą Genkit

Aby wdrożyć agent z etapami, należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinno to wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną strukturę przepływu. Należy oddzielić budowę klienta od pętli komunikatów, aby można było wymieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.

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.")
}

Ruch w przepływie Genkit

W fazie realizacji przepływu Genkit należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć przypadkowe, częściowe ukończenie zadania. Oddziel konstrukcję klienta od pętli komunikatów, aby można było wymieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy. W fazie realizacji przepływu Genkit należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

/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."
}

Wdrażanie agenta za pomocą Agent Development Kit (ADK)

Podczas przechodzenia przez etap wdrażania agenta, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dopinane później. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.

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())
 }
}

Zapусk agenta ADK

Gdy przechodzisz przez etap uruchamiania agenta ADK, najpierw zapisz specyfikację: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.

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

Czas uruchamiania agenta

Gdy przechodzisz przez etap runtime’ów agenta, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadań. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji. Gdy przechodzisz przez etap runtime’ów agenta, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

Cloud Run: uniwersalny optymalny wybór dla backendu

Cloud Run jako uniwersalna platforma funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres działania. Dokumentuj zarówno prawidłowy przebieg operacji, jak i ścieżkę przywracania do stanu poprzedniego. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Ustal stałe wartości interpretera oraz pliku blokującego zależności, zanim zaczniesz używać pętli. Różnice między laptopem a środowiskiem CI to najczęstsza przyczyna ukrytych awarii w demonstracjach API.

Gemini Enterprise Agent Platform: zarządzane sesje i rozwiązania RAG dla przedsiębiorstw

Płaszczyzna Gemini Enterprise Agent Platform funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Ustal wartości interpretera oraz pliku blokującego zależności przed nauczeniem się obsługi pętli. Przesuwanie plików między laptopem a systemem CI to najczęstsza, niewidoczna przyczyna awarii w demonstracjach API.

Co dalej?

Faza „Co dalej?” funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadki częściowego ukończenia bez żadnych informacji. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Rozbieżności pomiędzy laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API. Faza „Co dalej?” funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Lista kontrolna operacyjna

Etap listy kontrolnej operacyjnej działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy.

Zapisuj czasy wykonywania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk.

Zabezpiecz interpreter oraz plik blokujący zależności przed rozpoczęciem nauki obsługi pętli. Różnice między laptopem a środowiskiem CI to najczęstsza przyczyna ukrytych awarii w demonstracjach API.

Zapewnij ludzką aprobatę dla operacji, które wiążą się z wydatkami lub zmianami w danych produkcyjnych. Konfiguracja w czasie kompilacji nie gwarantuje pełnej kompletności rozwiązania biznesowego.

Napisz krótki podręcznik: jak rotować klucze, jak opróżniać kolej z zadań, jak cofnąć ostatnią operację importu.

Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.

Zanim wdrożymy nową architekturę, należy zamrozić istniejące wersje, utworzyć dokładny zapis działań dla kluczowych etapów oraz potwierdzić kroki odwracające zmiany. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji uprawnień użytkowników oraz wyraźnego odpowiedzialnego za rotację haseł. Lepiej mieć nudną, niezawodną architekturę niż sprytnie przygotowane jednorazowe demonstracje.

Uwaga dotycząca wersji 05c61bdde7aa: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy działań obok plików testowych, aby późniejsze zmiany modeli pozostawały porównywalne.

Dla etapu 0 notatki dotyczącej wzmocnienia bezpieczeństwa należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.

Szczegół wzmocnienia bezpieczeństwa 0/951: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę, opierając się na ustalonej serii pytań, a nie na indywidualnych obserwacjach.

Gdy przechodzisz przez pierwszy etap notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz warunki umowy: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas wykonywania oraz koszt tokena lub zapytania. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

Szczegóły wzmocnienia bezpieczeństwa 1/951: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie informacji anegdotycznych.

Literatura pokrewna