Google’s Gemini models represent a family of multimodal AI models accessible through the Google AI (Generative AI) API and the Vertex AI platform. The Go SDK (google.golang.org/genai) provides idiomatic access to text generation, embedding creation, function calling, and multi-turn chat sessions. Unlike OpenAI’s REST-first approach, Google’s SDK is gRPC-based with generated client libraries, offering strongly-typed request/response structures and built-in retry logic.
Gemini models are natively multimodal—they process text, images, audio, and video in a single model rather than routing to specialized sub-models. This simplifies architectures that need to analyze mixed-media content. The embedding models (text-embedding-004) generate high-quality vectors for semantic search, and the generative models (gemini-2.0-flash, gemini-2.5-pro) support structured output, function calling, and system instructions natively.
As an example, a multi-provider embedding pipeline might integrate Google’s Gemini SDK as one of its embedding providers. The SDK’s EmbedContent method generates vectors for document indexing and query encoding, running alongside OpenAI and Ollama providers behind a unified embedding interface. Multi-turn chat sessions using Gemini can power conversational search interfaces where the model maintains context across follow-up queries, enabling natural-language interactions with the search index.
The Gemini SDK supports two authentication modes: API key (for Google AI Studio) and service account credentials (for Vertex AI). The API key approach is simpler for development—set the GOOGLE_API_KEY environment variable or pass it directly to the client constructor. For production Vertex AI deployments, the SDK uses Application Default Credentials (ADC) and requires a GCP project ID and region.
Google offers Gemini through two surfaces: Google AI (ai.google.dev) for developers using API keys with usage-based billing, and Vertex AI (cloud.google.com/vertex-ai) for enterprise deployments with IAM, VPC controls, and GCP billing. The Go SDK supports both backends through a single genai.NewClient constructor—pass an API key for Google AI, or configure WithCloud options for Vertex AI. The model capabilities are identical; the difference is in authentication, governance, and pricing.
Each Gemini model variant is optimized for different trade-offs. gemini-2.5-pro is the most capable model for complex reasoning and long-context tasks. gemini-2.0-flash offers lower latency and cost for high-throughput workloads. Model configuration includes temperature (creativity vs. determinism), top-P/top-K sampling parameters, maximum output tokens, and safety settings that control content filtering thresholds.
Gemini’s input format uses a Content structure containing an array of Part objects. Each part can be text, inline image data, file references, or function call results. This part-based architecture enables mixing modalities in a single request—for example, sending an image alongside a text question about it. The SDK provides helper types (genai.Text, genai.ImageData, genai.FileData) for constructing multimodal requests.
Gemini supports constrained decoding through ResponseSchema, which forces the model to output valid JSON matching a specified schema. Set ResponseMIMEType: "application/json" and provide a ResponseSchema with the expected structure. The model’s output is guaranteed to parse as valid JSON conforming to the schema—no regex extraction or retry loops needed. This is essential for building reliable data extraction pipelines.
Gemini’s function calling allows the model to invoke application-defined functions during a conversation. You declare available tools with their parameter schemas, and the model responds with a FunctionCall part when it determines a tool invocation is appropriate. The application executes the function and returns the result as a FunctionResponse part, enabling the model to incorporate real-time data from APIs, databases, or local computations into its responses.
Google’s embedding models (text-embedding-004) produce 768-dimensional vectors optimized for semantic similarity, classification, and clustering tasks. The EmbedContent method accepts a task type parameter (retrieval_document, retrieval_query, semantic_similarity, classification) that adjusts the embedding space for the intended use case. Using the correct task type improves retrieval quality—document embeddings and query embeddings are projected into slightly different subspaces to optimize for asymmetric search.
Unlike most embedding APIs that produce a single vector per text, Gemini’s embedding model accepts a TaskType parameter that optimizes the vector for its intended use. Use retrieval_document when embedding documents for indexing and retrieval_query when embedding search queries. This asymmetric approach produces better retrieval results than symmetric embeddings because queries and documents have fundamentally different characteristics (queries are short and intent-focused; documents are long and information-dense).
Initialize the Gemini client and generate text:
package main
import (
"context"
"fmt"
"log"
"os"
"google.golang.org/genai"
)
func main() {
ctx := context.Background()
// Create client with API key (Google AI Studio)
client, err := genai.NewClient(ctx, &genai.ClientConfig{
APIKey: os.Getenv("GOOGLE_API_KEY"),
Backend: genai.BackendGoogleAI,
})
if err != nil {
log.Fatalf("creating client: %v", err)
}
// Configure the model
config := &genai.GenerateContentConfig{
Temperature: genai.Ptr(float32(0.7)),
MaxOutputTokens: 1024,
TopP: genai.Ptr(float32(0.9)),
SystemInstruction: &genai.Content{
Parts: []genai.Part{
genai.Text("You are a helpful technical assistant."),
},
},
}
// Generate content
resp, err := client.Models.GenerateContent(ctx,
"gemini-2.0-flash",
genai.NewUserContent(genai.Text("Explain how B-trees work in databases")),
config,
)
if err != nil {
log.Fatalf("generating content: %v", err)
}
// Extract text from response
for _, candidate := range resp.Candidates {
for _, part := range candidate.Content.Parts {
fmt.Println(part)
}
}
}
Maintain conversation context across multiple turns:
// Chat manages a multi-turn conversation with Gemini.
type Chat struct {
client *genai.Client
model string
history []*genai.Content
config *genai.GenerateContentConfig
}
func NewChat(client *genai.Client, model string,
config *genai.GenerateContentConfig) *Chat {
return &Chat{
client: client,
model: model,
config: config,
}
}
// Send sends a user message and returns the assistant's response.
func (c *Chat) Send(ctx context.Context, message string) (string, error) {
// Add user message to history
userContent := &genai.Content{
Role: "user",
Parts: []genai.Part{genai.Text(message)},
}
c.history = append(c.history, userContent)
// Build request with full history
chatConfig := *c.config
chatConfig.History = c.history[:len(c.history)-1]
resp, err := c.client.Models.GenerateContent(ctx,
c.model,
userContent,
&chatConfig,
)
if err != nil {
// Remove the failed message from history
c.history = c.history[:len(c.history)-1]
return "", fmt.Errorf("generating response: %w", err)
}
// Extract response text
var responseText string
if len(resp.Candidates) > 0 && resp.Candidates[0].Content != nil {
for _, part := range resp.Candidates[0].Content.Parts {
if text, ok := part.(genai.Text); ok {
responseText += string(text)
}
}
// Add assistant response to history
c.history = append(c.history, resp.Candidates[0].Content)
}
return responseText, nil
}
// Usage:
// chat := NewChat(client, "gemini-2.0-flash", config)
// resp1, _ := chat.Send(ctx, "What is a B-tree?")
// resp2, _ := chat.Send(ctx, "How does it compare to an LSM tree?")
The Gemini SDK passes the full conversation history with each request. The client is responsible for managing history size to stay within the model’s context window. Implement a sliding window that drops the oldest messages when approaching the token limit, or summarize earlier conversation turns into a condensed system message. The CountTokens method lets you measure history size before sending a request.
Generate task-aware embeddings for semantic search:
type GeminiEmbedder struct {
client *genai.Client
model string
}
func NewGeminiEmbedder(client *genai.Client) *GeminiEmbedder {
return &GeminiEmbedder{
client: client,
model: "text-embedding-004",
}
}
// EmbedDocuments generates embeddings optimized for document retrieval.
func (e *GeminiEmbedder) EmbedDocuments(ctx context.Context,
texts []string) ([][]float32, error) {
return e.embedBatch(ctx, texts, "retrieval_document")
}
// EmbedQuery generates an embedding optimized for query retrieval.
func (e *GeminiEmbedder) EmbedQuery(ctx context.Context,
query string) ([]float32, error) {
results, err := e.embedBatch(ctx, []string{query}, "retrieval_query")
if err != nil {
return nil, err
}
return results[0], nil
}
func (e *GeminiEmbedder) embedBatch(ctx context.Context,
texts []string, taskType string) ([][]float32, error) {
// Gemini supports batch embedding up to 100 texts
var allEmbeddings [][]float32
// Process in chunks of 100
for i := 0; i < len(texts); i += 100 {
end := i + 100
if end > len(texts) {
end = len(texts)
}
chunk := texts[i:end]
// Build content parts for the batch
contents := make([]*genai.Content, len(chunk))
for j, text := range chunk {
contents[j] = &genai.Content{
Parts: []genai.Part{genai.Text(text)},
}
}
result, err := e.client.Models.EmbedContent(ctx,
e.model,
contents,
&genai.EmbedContentConfig{
TaskType: taskType,
},
)
if err != nil {
return nil, fmt.Errorf("embedding batch starting at %d: %w", i, err)
}
for _, emb := range result.Embeddings {
allEmbeddings = append(allEmbeddings, emb.Values)
}
}
return allEmbeddings, nil
}
A semantic search platform uses Gemini chat sessions to power a conversational search experience. The user asks a natural-language question, the system embeds the query using Gemini’s embedding model, retrieves relevant documents from the vector index, and feeds them as context to a Gemini generative model that synthesizes an answer. Follow-up questions maintain context through the chat history, enabling refinement queries like “what about the performance implications?” without restating the original topic.
Using Gemini’s ResponseSchema to extract structured data from unstructured text. For example, parsing code review comments into structured issue objects with severity, category, file path, and suggested fix. The schema constraint guarantees valid JSON output, eliminating the fragile regex-based extraction that plagues many LLM integration patterns.
The Gemini embedding model serves as one provider in a multi-provider embedding pipeline. When users configure Google AI as their preferred provider, the system routes embedding requests to text-embedding-004 through the Go SDK. The task-type-aware embeddings provide a quality advantage for search use cases, while the batch API enables efficient bulk indexing of document collections.
Gemini’s function calling enables the model to query live data sources during a conversation. In a search application, the model can call a search_index function to retrieve documents, a get_document_stats function to fetch metadata, or a run_aggregation function to compute analytics—all without the user needing to know the underlying API. The model decides when to call functions based on the conversation context.
Declare tools and handle function call responses:
// Define available tools
searchTool := &genai.Tool{
FunctionDeclarations: []*genai.FunctionDeclaration{
{
Name: "search_documents",
Description: "Search the document index for relevant content",
Parameters: &genai.Schema{
Type: genai.TypeObject,
Properties: map[string]*genai.Schema{
"query": {
Type: genai.TypeString,
Description: "The search query",
},
"max_results": {
Type: genai.TypeInteger,
Description: "Maximum number of results to return",
},
},
Required: []string{"query"},
},
},
},
}
config := &genai.GenerateContentConfig{
Tools: []*genai.Tool{searchTool},
}
// Generate content - model may request a function call
resp, err := client.Models.GenerateContent(ctx,
"gemini-2.0-flash",
genai.NewUserContent(genai.Text("Find documents about B-tree indexing")),
config,
)
// Check if the model wants to call a function
for _, candidate := range resp.Candidates {
for _, part := range candidate.Content.Parts {
if fc, ok := part.(genai.FunctionCall); ok {
// Execute the function locally
results := executeSearch(fc.Args["query"].(string))
// Send function result back to the model
funcResp, err := client.Models.GenerateContent(ctx,
"gemini-2.0-flash",
genai.NewUserContent(
genai.FunctionResponse{
Name: fc.Name,
Response: map[string]any{"results": results},
},
),
config,
)
// funcResp now contains the model's answer incorporating the search results
}
}
}
Stream generated content for real-time display:
// Stream generates content with real-time token streaming.
func StreamGenerate(ctx context.Context, client *genai.Client,
model string, prompt string, onToken func(string)) error {
config := &genai.GenerateContentConfig{
Temperature: genai.Ptr(float32(0.7)),
}
iter := client.Models.GenerateContentStream(ctx,
model,
genai.NewUserContent(genai.Text(prompt)),
config,
)
for resp, err := range iter {
if err != nil {
return fmt.Errorf("streaming error: %w", err)
}
for _, candidate := range resp.Candidates {
for _, part := range candidate.Content.Parts {
if text, ok := part.(genai.Text); ok {
onToken(string(text))
}
}
}
}
return nil
}
// Usage:
// err := StreamGenerate(ctx, client, "gemini-2.0-flash",
// "Explain distributed consensus",
// func(token string) { fmt.Print(token) },
// )
The Gemini SDK handles gRPC streaming internally and exposes it as a Go iterator. Each iteration yields a partial response containing one or more content parts. Unlike REST-based streaming (SSE/NDJSON), gRPC streaming provides built-in flow control, automatic reconnection, and typed message framing. The SDK transparently handles connection management, retries, and backpressure—the application just iterates over the response channel.
CountTokens method to measure prompt size before sending requests. This avoids wasting API calls on prompts that exceed the model’s context window. Token counting is a lightweight operation that doesn’t consume generation quota.genai.Client once at application startup and reuse it for all requests. Creating a new client per request incurs TLS handshake and gRPC channel setup overhead (~100–300 ms).FinishReason may be SAFETY instead of STOP, with no content returned. Always check the finish reason and handle blocked responses gracefully—showing a user-friendly message rather than crashing or returning empty results.TaskType parameter when calling EmbedContent uses a generic embedding that isn’t optimized for any specific task. For search applications, always specify retrieval_document for indexing and retrieval_query for queries. Using the wrong task type (or no task type) degrades retrieval quality by 5–15% on standard benchmarks.FunctionResponse so the model can self-correct.