Docs/SDK/Google Gen AI

Instrument a Gemini agent (5-minute quickstart)

Add Dobby governance + evidence to any agent that calls Gemini directly through the google-genai SDK (from google import genai) with one import and one call. No agent code changes.

TL;DR: pip install "dobby-collector[google_genai]" + instrument_google_genai() once at startup. Every client.models.generate_content(...) emits llm.start + llm.completion with the model, prompt, completion, and token usage — landing on the run you open with start_run().
Why it matters. google-genai has no callback surface — LLM calls are plain method calls. An agent that calls Gemini directly otherwise produces telemetry with no model and no token usage, which trips the Policy Scanner's SOC 2 logging-completeness check (CC7.2). Instrumenting closes that gap automatically — no @track decorators. Note this integration does not open a run per call: it adds llm events to the ambient run you open with start_run() (or a fresh run if none is active, so the call still lands).

1. Get a connector + bearer token

  1. Open the Dobby dashboard and pick a tenant.
  2. Click Workloads → Connect → Dobby SDK, choose Python, and pick Google Gen AI (Gemini) as the framework.
  3. Copy the connector_id (wc_...) and api_key (dsdk_...) — the api_key is shown ONCE, store it in your secrets manager immediately.

2. Install

pip install "dobby-collector[google_genai]"

The [google_genai] extra pulls google-genai >= 1.0 (the google.genai package, NOT the legacy google-generativeai). If you already have it in your venv, the base dobby-collector package is enough.

3. Instrument your calls

from google import genai
from dobby_collector import init, start_run, end_run
from dobby_collector.integrations.google_genai import instrument_google_genai

# 1. Initialise the Dobby SDK once at process startup
init(
    api_key="dsdk_...",          # from your Dobby Workloads page
    connector_id="wc_...",        # ditto
    base_url="https://dobby-ai.com",
)

# 2. Instrument google-genai ONCE — patches client.models.generate_content
#    (sync + async + stream). The model is read from each call's model= arg,
#    so there is NOTHING else to configure.
instrument_google_genai()

# 3. Call Gemini as usual. Wrap related calls in start_run / end_run so the
#    llm events land on ONE workload_run.
client = genai.Client(api_key="...")

run = start_run(name="weekly_report", inputs={"week": "2026-W19"})
resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Summarise today's incidents",
)
end_run(run, outputs={"summary": resp.text}, status="success")
# → one workload_run in Dobby: model + prompt + completion + token usage
  • One init + one instrument call per process. Both are idempotent — calling twice is a no-op. uninstrument_google_genai() restores the originals.
  • You own the run boundaries. Wrap related Gemini calls in start_run() / end_run() so they group into one workload_run. Calls made with no active run still land — each in its own fresh run.
  • Non-blocking. Events buffer in-process; a background thread ships them every 10s (or sooner). Instrumentation NEVER raises into your agent — failures are logged and swallowed.

Streaming and async are supported too:

# Streaming is captured too — chunks aggregate into a single llm.completion
# when the stream finishes, and every chunk still passes through unchanged.
run = start_run(name="draft_report")
for chunk in client.models.generate_content_stream(
    model="gemini-2.5-flash",
    contents="Draft the incident report",
):
    print(chunk.text, end="")  # your handling — Dobby observes transparently
end_run(run, status="success")

# Async works identically: await client.aio.models.generate_content(...) and
# async for chunk in await client.aio.models.generate_content_stream(...).

4. Production: use env vars

# Production: read from env vars instead of hardcoding
export DOBBY_API_KEY="dsdk_..."
export DOBBY_CONNECTOR_ID="wc_..."
export DOBBY_BASE_URL="https://dobby-ai.com"

# Then in code:
from dobby_collector import init
init()   # auto-reads DOBBY_* env vars

5. Verify it worked

Within ~10 seconds of end_run() (or your generate_content() call when run-less), a new run appears on Workloads → Runs. Click into it to see the per-event timeline (model, prompt, completion, token usage). For programmatic verification in BigQuery:

SELECT
  external_run_id,
  status,
  JSON_VALUE(metadata_json, '$.framework')    AS framework,      -- "google_genai"
  JSON_VALUE(metadata_json, '$.sdk_version')  AS sdk_version,    -- "0.5.0" or newer
  JSON_VALUE(metadata_json, '$.llm_calls[0].model') AS model,    -- "gemini-2.5-flash"
  ARRAY_LENGTH(JSON_QUERY_ARRAY(metadata_json, '$.llm_calls'))  AS llm_calls
FROM `workload_run_payloads`
WHERE connector_id = 'wc_...'
ORDER BY created_at DESC
LIMIT 5

Expected on a successful run: framework="google_genai", model = the model you passed (e.g. gemini-2.5-flash), and llm_calls >= 1.

What the integration captures

google-genaiDobby SdkEventBecomes…
generate_content() calledllm.startAdds an llm_calls[] entry to the ambient run (model + prompt)
response returnedllm.completionFills completion + token usage (usage_metadata) on that llm_call
generate_content_stream()llm.start → llm.completionChunks aggregate into one completion when the stream finishes
start_run() / end_run()run.started / run.completedOpen + close the workload_run + trigger the compliance scan

Troubleshooting

The run has no llm_calls

Call instrument_google_genai() AFTER init() and BEFORE you construct the client / make the call. The patch is applied at call time, so a client created before instrumenting is fine — but the call itself must run after the patch.

My Gemini calls each land in a separate run

Expected when there is no active run. Wrap the calls you want grouped in start_run() end_run() — the llm events inherit that ambient run. Without it, each call lands in its own fresh run so nothing is dropped.

ImportError / instrumentation does nothing

Install the extra: pip install "dobby-collector[google_genai]" (or pip install "google-genai>=1.0"). Make sure you import from google.genai, not the legacy google-generativeai package — only the former is instrumented.

No run shows up in Dobby

The background sender may be killed before it flushes. Call dobby_collector.shutdown() before process exit (or rely on the atexit handler on normal exit). For short-lived processes, set flush_interval_seconds=2.0 on init().

Related

Need help? Reach out at [email protected] or open a ticket from your Dobby dashboard. Include your connector_id in the subject line for faster triage.
Python Collector: Google GenAI | Dobby AI Docs