chatgemini

package module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 18 Imported by: 0

README

chat-gemini

Google Gemini provider for the chat multi-provider AI client

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base.


gitlab.com/phpboyscout/go/chat-gemini registers the Google Gemini provider for the chat core. It is a thin adapter over Google's official google.golang.org/genai SDK, mapping the provider-neutral chat.ChatClient surface — Add, Ask, Chat, SetTools, streaming, and encrypted persistence — onto Gemini's Chats API.

You link it only if you use Gemini. The chat core stays SDK-free; each vendor SDK lives in its own module and registers via a blank import, so a Gemini-only tool never compiles the Anthropic or OpenAI SDKs (and vice versa).

Install

go get gitlab.com/phpboyscout/go/chat-gemini

Usage

Activate the provider with a blank import; the package's init() registers the gemini provider (and its HTTP-status extractor for cross-provider fallback) with the core registry. Then construct a client through the core chat.New:

package main

import (
	"context"
	"fmt"

	"gitlab.com/phpboyscout/go/chat"
	_ "gitlab.com/phpboyscout/go/chat-gemini" // activate the Gemini provider
)

func main() {
	ctx := context.Background()

	client, err := chat.New(ctx, chat.Settings{
		Config: chat.Config{
			Provider: chat.ProviderGemini,
			Model:    "gemini-3.5-flash", // optional; defaults to chat.DefaultModelGemini
			Token:    "…",                // or resolved from GEMINI_API_KEY
		},
	})
	if err != nil {
		panic(err)
	}

	answer, err := client.Chat(ctx, "Summarise this changelog in one line.")
	if err != nil {
		panic(err)
	}

	fmt.Println(answer)
}

Keeping up with Gemini

Two generators, both run deliberately rather than automatically applied.

internal/genmodels regenerates the per-model capability table. Google is the one provider that reports capabilities outright — token limits, maxTemperature, topP, thinking — so no probing is needed:

GEMINI_API_KEY=... go run ./internal/genmodels > models_generated.go

internal/detectmodels watches for Google shipping something chat.DefaultModelGemini does not reflect, and raises an issue in this project when it does, with the real capability delta attached.

GEMINI_API_KEY=... go run ./internal/detectmodels -dry-run

The one thing Google does not report is preview status, which the formula turns on — a default must not point at something that may be withdrawn, and two Pro models already have been. The detector infers it from the model id and flags it as an inference to check, not an answer.

It runs weekly — 03:00 Europe/London on Mondays — from a DETECT_MODELS=true scheduled pipeline. Neither -dry-run nor -update-snapshot touches the forge — only a plain run raises. internal/detectmodels/snapshot.json is the committed baseline, and it moves when a human resolves the issue, not when CI runs.

Credentials

The token is resolved by the core's ResolveAPIKey precedence:

  1. Config.Token (explicit literal), then
  2. Config.Credentials (env-var reference, then host-injected keychain lookup, then literal), then
  3. the well-known fallback environment variable GEMINI_API_KEY.

Two backends

This module registers two providers, and they differ in how they are addressed and how they authenticate.

Provider Addressed by Authenticated by
chat.ProviderGemini the Gemini API endpoint GEMINI_API_KEY, per the precedence above
chat.ProviderGeminiVertex Config.Project and Config.Location Google application default credentials
client, err := chat.New(ctx, chat.Settings{Config: chat.Config{
	Provider: chat.ProviderGeminiVertex,
	Project:  "acme-prod",     // or GOOGLE_CLOUD_PROJECT
	Location: "europe-west2",  // or GOOGLE_CLOUD_LOCATION
}})

Explicit configuration wins; the environment variables are a fallback so a working gcloud setup does not have to be restated. Construction fails when neither supplies a value, rather than guessing a region.

Three things this deliberately refuses:

  • An API key on the Vertex path. Vertex express mode is not supported, so a Config.Token set alongside gemini-vertex is an error rather than a different authentication mode.
  • GOOGLE_GENAI_USE_VERTEXAI. The SDK reads this variable and switches backend on it. This module names its backend explicitly and refuses to construct when the variable asks for a backend the caller did not, because a backend chosen by the environment is one it cannot report or validate.
  • Guessing a location. The SDK will default some paths to global. This module would rather fail and be told.

Both providers resolve capabilities against the same table, since Vertex serves the same models under the same names. See spec 0017.

Capabilities

  • ReAct tool-calling loop — register chat.Tools via SetTools; the adapter drives Gemini function calls, executes handlers (sequentially or in parallel when Config.ParallelTools is set), and feeds the results back until a final answer or Config.MaxSteps is reached.
  • Structured output (Ask) — a Config.ResponseSchema (an *invopop/jsonschema.Schema) is converted to a genai.Schema and enforced as application/json response output.
  • Streaming (StreamChat) — token deltas, tool-call start/end, and a terminal complete event are delivered through a chat.StreamCallback.
  • Multimodal — images, PDF, audio, and video attachments are carried as inline-blob parts on the user turn (subject to the core's media allowlist).
  • Usage accounting — Gemini UsageMetadata (prompt, candidate, cached, and thought token counts) is mapped into the provider-neutral chat.Usage and reported per round-trip via Config.UsageObserver.
  • PersistenceSave/Restore round-trip the conversation history as a chat.Snapshot.
  • Stateless one-shot callsConfig.Stateless makes every call carry only its own turns: nothing sent from earlier calls, nothing retained after. One client then serves a batch of independent documents, across goroutines, rather than re-sending the accumulated prefix on every call. Tool calling is unaffected — the ReAct loop keeps its turns within the call and discards them on return.

Configuration knobs

Set on chat.Config:

Field Effect
Model Gemini model id; empty ⇒ chat.DefaultModelGemini.
MaxTokens generationConfig.maxOutputTokens; ≤0 ⇒ chat.DefaultMaxTokensGemini (8192).
MaxSteps ReAct loop bound; ≤0 ⇒ chat.DefaultMaxSteps.
SystemPrompt Carried as the Gemini systemInstruction.
ParallelTools / MaxParallelTools Concurrent tool execution within a step.
BaseURL Overrides the API endpoint (validated by the core).
Stateless One-shot calls: the chat session is seeded from nothing and its turns are not kept.
GenaiNewClient Test seam: overrides the genai client constructor.

Design

This module carries exactly two dependencies of substance: the chat core and the google.golang.org/genai SDK. A depfootprint_test.go guard fails the build if the go-tool-base framework, any other vendor AI SDK, or CLI/observability weight ever enters the graph.

The per-provider modules are thin adapters, too tightly coupled to the core to warrant their own docs sites — all provider documentation lives on the core docs site.

Version compatibility

chat-gemini requires the core version named in its own go.mod, and is built and tested against exactly that version. Do not compare the two version numbers — each module releases when that module changes, so they move independently.

Install this module and let it bring the core with it:

go get gitlab.com/phpboyscout/go/chat-gemini
go mod tidy

See version compatibility for what a mismatch does, and why requiring chat directly at @latest is the way to break it.

Licence

MIT — see LICENSE.

Documentation

Index

Constants

View Source
const (
	EnvCloudProject  = "GOOGLE_CLOUD_PROJECT"
	EnvCloudLocation = "GOOGLE_CLOUD_LOCATION"
)

EnvCloudProject and EnvCloudLocation are the standard Google Cloud variables this module falls back to when Config.Project or Config.Location is empty. Explicit configuration wins; these exist so a developer with a working gcloud environment does not have to restate it. See spec 0017 D7.

View Source
const EnvGeminiKey = "GEMINI_API_KEY"

EnvGeminiKey is the well-known unprefixed environment variable used as the ecosystem fallback when no explicit credential is configured.

View Source
const EnvUseVertex = "GOOGLE_GENAI_USE_VERTEXAI"

EnvUseVertex is the SDK's own backend switch. This module does not honour it, and refuses to construct when it is set without Vertex having been asked for. See spec 0017 D1 and D6.

Variables

This section is empty.

Functions

func New added in v0.9.0

func New(ctx context.Context, settings chat.Settings, opts ...Option) (chat.ChatClient, error)

New builds a Gemini client, accepting options this provider owns. Reach for it when you need one; otherwise chat.New with the provider name does the same job through the registry.

It goes through chat.NewWithFactory rather than calling build directly, so it gets every guard chat.New applies: the endpoint is validated before credentials reach it, the logger this package logs through is resolved, provider defaults are applied, unapplicable settings are dropped and reported, and the built client is asserted able to carry what survived.

Calling build directly is what this used to do, which accepted an insecure endpoint that chat.New refuses and panicked when no Logger was set. See https://gitlab.com/phpboyscout/go/chat-openai/-/issues/2, which reports the same defect in the sibling module, and spec 0015.

Types

type Gemini

type Gemini struct {
	chat.UsageTracker
	// contains filtered or unexported fields
}

Gemini implements the chat.ChatClient interface using Google's Generative AI SDK.

func (*Gemini) Add

func (g *Gemini) Add(_ context.Context, prompt string, media ...chat.Media) error

Add appends a user message (with any media) to the conversation history.

func (*Gemini) AddCached added in v0.5.0

func (g *Gemini) AddCached(ctx context.Context, prompt string, media ...chat.Media) error

AddCached appends a user turn and arranges for it to be served from a provider-side cache on this and subsequent calls.

The cache is created lazily on first use, keyed by a hash of the content, and left to expire rather than deleted — so there is no lifecycle for a caller to manage and no cleanup path to get wrong.

func (*Gemini) ApplyPolicyNow added in v0.11.0

func (g *Gemini) ApplyPolicyNow(ctx context.Context, policy chat.HistoryPolicy) (int, error)

ApplyPolicyNow bounds the retained conversation immediately, rather than waiting for the next request to do it.

It is the sequence this client runs before every request — describe the turns, ask the policy, rewrite from the result — without the pending turn, because nothing is about to be answered. Unlike the other providers there is no system turn to hold out: Gemini keeps the system instruction in its generation config rather than in the conversation, so the transcript is conversation throughout.

A nil policy means the configured one. With none configured there is nothing to apply and it returns zero rather than an error.

func (*Gemini) Ask

func (g *Gemini) Ask(ctx context.Context, question string, target any, media ...chat.Media) error

func (*Gemini) Chat

func (g *Gemini) Chat(ctx context.Context, prompt string, media ...chat.Media) (string, error)

Chat sends a message and returns the response content, handling tool calls internally.

func (*Gemini) History added in v0.9.0

func (g *Gemini) History() chat.History

History reports the conversation this client will re-send, counting the retained turns and anything buffered by Add and not yet sent.

Known is true — this provider owns its transcript. A Gemini session is created per call from this history, so what is counted here is exactly what the next call will carry.

func (*Gemini) Model added in v0.6.0

func (g *Gemini) Model() string

Model implements chat.ModelIdentifier, reporting the model this client will actually use — the configured one, or the default it fell back to.

func (*Gemini) Provider added in v0.6.0

func (g *Gemini) Provider() chat.Provider

Provider implements chat.ModelIdentifier.

func (*Gemini) Restore

func (g *Gemini) Restore(snapshot *chat.Snapshot) error

Restore replaces the current conversation state with a previously saved snapshot.

func (*Gemini) Save

func (g *Gemini) Save() (*chat.Snapshot, error)

Save captures the current Gemini conversation state as a snapshot.

func (*Gemini) SetTools

func (g *Gemini) SetTools(tools []chat.Tool) error

SetTools configures the tools available to the AI.

func (*Gemini) StreamChat

func (g *Gemini) StreamChat(ctx context.Context, prompt string, callback chat.StreamCallback, media ...chat.Media) (string, error)

StreamChat implements StreamingChatClient.

func (*Gemini) SupportsEffort added in v0.3.0

func (g *Gemini) SupportsEffort()

SupportsEffort marks Gemini as able to carry Config.Effort, via thinkingConfig.thinkingLevel.

Gemini's ladder has four rungs against the neutral five, so EffortXHigh and EffortMax clamp to its highest. The clamp is documented rather than silent: asking for more effort than a provider offers should give you its most, not an error.

func (*Gemini) SupportsSampling added in v0.3.0

func (g *Gemini) SupportsSampling()

SupportsSampling marks Gemini as able to carry Config.Temperature and TopP. Measured accepted across 0-2 and 0-1 respectively, with server-side range validation.

func (*Gemini) SupportsStateless added in v0.2.0

func (g *Gemini) SupportsStateless()

SupportsStateless marks Gemini as honouring chat.Config.Stateless.

type Option added in v0.9.0

type Option func(*options)

newGemini initializes a new Gemini chat client. Option configures a Gemini client at construction.

These are knobs only this provider understands. The core's Config carries what every provider means the same way; anything one provider owns belongs to that provider's own constructor, where the compiler can see it. See https://gitlab.com/phpboyscout/go/chat/-/wikis/specs/0011-chat-provider-conformance D10.

func WithClientConstructor added in v0.9.0

func WithClientConstructor(
	newClient func(context.Context, *genai.ClientConfig) (*genai.Client, error),
) Option

WithClientConstructor replaces genai.NewClient, so a test can substitute the SDK client without reaching a real endpoint.

It replaces chat.Config.GenaiNewClient, removed from the core in v0.10.0. That field was an `any` this module type-asserted at runtime, so a caller who passed the wrong shape learned about it from an error at construction. This signature is checked by the compiler, and the error path it needed is gone.

Directories

Path Synopsis
internal
detectmodels command
Command detectmodels reports when Google has shipped something the module's default model does not reflect.
Command detectmodels reports when Google has shipped something the module's default model does not reflect.
genmodels command
Command genmodels regenerates the per-model capability table.
Command genmodels regenerates the per-model capability table.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL