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()
Note

A single call renders on its own in all three tabs, with no extra wrapper. In Python (neatlogs.wrap()) and TypeScript (wrapGoogleGenAI()), the parentless LLM span is the trace root, and embeddings get one automatic WORKFLOW parent. In Go, WrapGenAI() opens a WORKFLOW root automatically. To put several calls under one trace, group them with a root.

Warning

TypeScript uses wrapGoogleGenAI() only. TypeScript integrations are explicit; there is no provider-key auto-instrumentation surface. See TypeScript SDK → Supported Libraries.

Note

In Python, the genai.Client() must be constructed after neatlogs.init() — it caches its transport at construction time. The TypeScript and Go wrappers patch the instance you hand them, so import and construction order don't matter.

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.