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-genaiUsage
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()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.
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:
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.textThe full story lives in Sessions and End-User Identity.
