generateContent / generate_content requests your code sends to Gemini through the official Google Gen AI SDK (@google/genai in TypeScript, google-genai in Python): the prompt, the model, token counts, and latency. Bitfab works out the cost from the model and the token counts.
Canonical signatures: TypeScript wrapGemini · Python wrap_gemini
Language Support
Under a trace root
Beneath a trace root, everymodels.generateContent / models.generate_content call is recorded with no wrapper, as its own llm span named Models.generateContent (Python: Models.generate_content, or AsyncModels.generate_content for client.aio). The span attaches under the function that made the call.
detect, then classify, then an llm span named Models.generateContent (Models.generate_content in Python) carrying the prompt, tokens, and cost.
In TypeScript, the build transform rewrites each .models.generateContent(...) and .models.generateContentStream(...) call site in your code, so it works in bundled apps and the SDK never loads or patches @google/genai. In Python, the SDK patches the google-genai models classes when a trace() root is set up.
exclude on the root matches the method name ("generateContent", "generateContentStream" / "generate_content", "generate_content_stream") or the span name, and withNode() / node() on a wrapper (below) configures the one span.
Streaming
AgenerateContentStream / generate_content_stream call is recorded as one llm span named Models.generateContentStream (Models.generate_content_stream in Python) when the stream finishes, is closed, or fails, with usage read from the last chunk. What your code reads from the stream is unchanged. A stream returned from the trace root keeps the trace open until your code reads it. A stream your code drops without reading to the end is recorded when the runtime frees it, with the request and whatever was read but usually no usage, and its trace finishes then. Streamed calls always run for real during a replay.
A withNode() / node() placed directly on the client method records the stream on its own span instead of opening a second one. That span ends when the stream does, and the node’s finalize is not used.
Wrapping a call
UsewrapGemini() / wrap_gemini() when you trace with withSpan() / span() instead of a trace root. It wraps the function that sends the request and writes the prompt and usage into the span that is current when the call starts. It opens no span of its own.
onResponse / on_response receives each response after it is recorded. Wrappers never record a stream.
No duplicate spans
Each model call is recorded once:- A wrapper around a call the trace root already recorded adds nothing.
- A call the LangChain callback handler is recording as an LLM run is not recorded a second time, including a call made inside your own LangChain chat model class, streamed or not. A call made alongside a LangChain run in the same trace is still recorded, except beside the first LangChain run in a TypeScript process (see Limitations).
- In TypeScript, calls made inside framework libraries in
node_modules(such as@langchain/google-genai) are never rewritten by the build transform, so only the framework’s own integration records them. In Python, the SDK patches the client classes, so a call another library makes through the client is recorded too, unless that library has a Bitfab integration recording it.
What gets captured
The context fields go in one context entry on the span. A key the response does not carry is left out. Thinking tokens are billed at the output rate, so they are counted in
outputTokens and also reported on their own as reasoningTokens.
Limitations
- Only
generateContentandgenerateContentStream. Embeddings (embedContent), image and video generation, token counting, and the Live API are not recorded. - Calls outside any trace are not recorded. A wrapper used outside a span or trace records nothing and never starts a trace of its own.
- TypeScript automatic capture needs the build transform. Code the transform does not compile (for example a call made from a dependency) is not rewritten; wrap it instead.
- TypeScript: calls beside the first LangChain run in a process. The SDK hooks into LangChain in the background when the first Bitfab LangChain handler or
withTrace()root is created. If a LangChain run starts before that finishes, calls made inside it are still recorded once, but a call made at the same time outside it (for example inPromise.all) is not recorded while that first run is open. Later runs are not affected. - Python automatic capture needs Python 3.12+, the same as
trace()subtree capture.