// Package gemini provides a Go client for Google's Generative AI APIs. // // This package wraps the official google.golang.org/genai SDK to provide // a simplified interface for image generation (Imagen) and video generation (Veo). // // Basic usage: // // client, err := gemini.NewClient(gemini.Config{ // APIKey: os.Getenv("GEMINI_API_KEY"), // }) // if err != nil { // log.Fatal(err) // } // defer client.Close() // // // Generate an image // resp, err := client.GenerateImage(ctx, gemini.ImageRequest{ // Prompt: "A serene Japanese garden", // }) // // The client handles authentication and provides methods for both image and // video generation with configurable models and parameters. package gemini import ( "context" "fmt" "log/slog" "time" "google.golang.org/genai" ) const ( defaultTimeout = 120 * time.Second defaultVideoPollDelay = 10 * time.Second defaultVideoMaxWait = 5 * time.Minute // Retry configuration defaults defaultMaxRetries = 3 defaultInitialDelay = 100 * time.Millisecond defaultMaxDelay = 2 * time.Second ) // Config holds configuration options for the Gemini client type Config struct { APIKey string // Required: API key for authentication Timeout time.Duration // Optional: defaults to 120s VideoPollDelay time.Duration // Optional: delay between video status polls, defaults to 10s VideoMaxWait time.Duration // Optional: max time to wait for video generation, defaults to 5m Logger *slog.Logger // Optional: defaults to slog.Default() // Retry configuration for transient errors (5xx, timeouts) MaxRetries int // Optional: max retry attempts, defaults to 3 (0 disables retry) InitialDelay time.Duration // Optional: initial delay between retries, defaults to 100ms MaxDelay time.Duration // Optional: max delay between retries, defaults to 2s } // Client is the Gemini API client. // Supports automatic retry with exponential backoff for transient errors. type Client struct { genaiClient *genai.Client config *Config logger *slog.Logger maxRetries int initialDelay time.Duration maxDelay time.Duration } // NewClient creates a new Gemini API client with automatic retry for transient errors. func NewClient(ctx context.Context, config Config) (*Client, error) { if config.APIKey == "" { return nil, fmt.Errorf("%w: API key is required", ErrInvalidConfig) } if config.Timeout == 0 { config.Timeout = defaultTimeout } if config.VideoPollDelay == 0 { config.VideoPollDelay = defaultVideoPollDelay } if config.VideoMaxWait == 0 { config.VideoMaxWait = defaultVideoMaxWait } if config.Logger == nil { config.Logger = slog.Default() } // Set retry defaults maxRetries := config.MaxRetries if maxRetries == 0 { maxRetries = defaultMaxRetries } initialDelay := config.InitialDelay if initialDelay == 0 { initialDelay = defaultInitialDelay } maxDelay := config.MaxDelay if maxDelay == 0 { maxDelay = defaultMaxDelay } // Create genai client with API key genaiClient, err := genai.NewClient(ctx, &genai.ClientConfig{ APIKey: config.APIKey, Backend: genai.BackendGeminiAPI, }) if err != nil { return nil, fmt.Errorf("create genai client: %w", err) } return &Client{ genaiClient: genaiClient, config: &config, logger: config.Logger, maxRetries: maxRetries, initialDelay: initialDelay, maxDelay: maxDelay, }, nil } // Close closes the underlying genai client func (c *Client) Close() error { // The genai client doesn't have a Close method, but we keep this for future compatibility return nil } // retryWithBackoff executes fn with exponential backoff for transient errors. // Returns the result of fn if successful, or the last error if all retries fail. func (c *Client) retryWithBackoff(ctx context.Context, operation string, fn func() error) error { var lastErr error for attempt := 0; attempt <= c.maxRetries; attempt++ { if err := ctx.Err(); err != nil { return err } lastErr = fn() if lastErr == nil { return nil } // Only retry retryable errors if !IsRetryableError(lastErr) { return lastErr } // Don't retry on last attempt if attempt == c.maxRetries { break } // Calculate backoff delay delay := c.initialDelay * time.Duration(1< c.maxDelay { delay = c.maxDelay } c.logger.Warn("retrying after transient error", "operation", operation, "attempt", attempt+1, "max_retries", c.maxRetries, "delay", delay, "error", lastErr, ) // Wait for backoff or context cancellation select { case <-ctx.Done(): return ctx.Err() case <-time.After(delay): } } return lastErr } // Health checks if the client can communicate with the Gemini API // by listing available models func (c *Client) Health(ctx context.Context) error { iter, err := c.genaiClient.Models.List(ctx, nil) if err != nil { return fmt.Errorf("health check failed: %w", err) } // Try to get at least one model to verify connectivity _, err = iter.Next(ctx) if err != nil { return fmt.Errorf("health check failed: %w", err) } return nil }