Google Gemini

Trace the google-genai / @google/genai / genai SDK in Python, TypeScript, and Go.

Wrap the Google GenAI client and generate_content / generate_content_stream (and chat sessions) are captured as LLM spans.

Prerequisites

pip install -U "neatlogs[google-genai]" google-genai

Usage

import os
import neatlogs

neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="gemini-demo")

from google import genai

# Client must be created after init(); wrap() patches this instance.
client = neatlogs.wrap(genai.Client())

resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="In one sentence, what is Gemini?",
)
print(resp.text)

neatlogs.flush()
neatlogs.shutdown()
Warning

The tabs differ in rooting — this is deliberate. Python (neatlogs.wrap()) and Go (neatlogs.WrapGenAI()) open a WORKFLOW root automatically, so a single call renders with no extra wrapper. TypeScript uses instrumentations: ['google_genai'], which captures the LLM calls but does not open a root on its own (it patches via OpenInference, not our wrapper) — so the TS example wraps the run in span({ kind: 'WORKFLOW' }). Without that wrapper the TS trace would have a parentless LLM span and wouldn't render.

To put several calls under one trace in either language, group them with a root.

Note

In Python, the genai.Client() must be constructed after neatlogs.init() — it caches its transport at construction time.

Sessions & end-user

Picture a chat product built on Gemini: each back-and-forth is its own generate_content call, but you want them stitched into one conversation and tied to the person typing. Wrap a turn in identify() (Go binds it on ctx with Identify()) and the trace root each Gemini call opens picks up the session and end-user — same one-liner across all three languages:

# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
    resp = client.models.generate_content(model="gemini-2.5-flash", contents=message)

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.

def summarize(document, user):
    # Standalone workflow — attribute to the end-user; do NOT pass session_id.
    with neatlogs.identify(
        end_user_id=str(user.id),
        end_user_metadata={"plan": user.plan},
    ):
        resp = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=f"Summarize:\n{document}",
        )
        return resp.text

The full story lives in Sessions and End-User Identity.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.