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()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.
TypeScript uses wrapGoogleGenAI() only. TypeScript integrations are explicit; there is no provider-key auto-instrumentation surface. See TypeScript SDK → Supported Libraries.
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:
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.
