Package: bitfab-py (imported as bitfab). Python ≥ 3.10.
Module Exports
BitfabTracingProcessor is only exported when openai-agents is installed. from bitfab import BitfabTracingProcessor will raise ImportError otherwise.
Type Aliases
class Bitfab
__init__
Transport environment variables
span
Decorator. Wraps the decorated function (sync or async) with a span.
Returns: a decorator that returns a function with the same signature.
Semantics:
- When
enabled=False, returns the function unchanged
name defaults to the function’s __name__
capture_when="nested" records the span only when another Bitfab span is active. Without a parent, the decorated function runs normally and does not create a root trace. The default is "always"; unknown values warn once and use that default
- Nested spans propagate via
contextvars.ContextVar — safe across asyncio.gather, threads, and sync/async boundaries
- Spans exist only where you create them: nesting is automatic, but only between spans that exist. One wrapper around the outermost function records a single-node trace. See Instrumentation
- Exceptions are recorded on the span and re-raised
test_run_id is rarely set directly; replay() injects it via a replay context
finalize records a serializable view of a streaming result as the span output (the raw result is always returned to the caller, unchanged). On an async generator it receives the list of yielded chunks, assembled after iteration so the caller has already streamed every chunk (non-blocking, non-destructive). On a plain sync/async function it receives the return value and is applied inline before the span is recorded, so a live single-consumer stream is blocked on and consumed. Prefer an async generator for streaming. May be async on async/async-generator spans, must be sync on sync spans. A throwing finalize records an error instead of crashing. Pair with finalizers.openai_chunks / finalizers.anthropic_events. See Tracing streaming functions
trace
Experimental. New API; behavior may change in a future release. Requires
Python 3.12+ for subtree capture.
Decorator. Records a span for the decorated function and for every first-party function it calls, at any depth, without those functions being decorated.
Returns: a decorator that returns a function with the same signature.
Semantics:
- The root span behaves exactly as
span(). Descendant spans are typed "function" and named by qualified name (Order.process), with <locals>. stripped
- First-party means the package directory containing the decorated function, found by walking up while
__init__.py exists. Standard library, site-packages, and Bitfab’s own code never record spans
- Capture is scoped to the traced call:
sys.monitoring events are installed on entry and removed on exit, so code outside a traced call is unaffected
- Lambdas, generator expressions, comprehensions, and decorator wrappers (a function taking only
*args, **kwargs) are skipped. include_wrappers=True records wrappers
- Skipping is transparent: a skipped wrapper does not become a parent, so the function it wraps parents to the real caller
max_depth and max_spans bound a subtree; exceeding either stops recording without affecting the call, and logs a one-time warning per traced function so a truncated trace is not mistaken for code that never ran
- Free-threaded builds warn once: subtree capture is unverified there, though the root span is unaffected
self / cls are stripped from recorded inputs. Exceptions are recorded on the failing frame via PY_UNWIND and re-raised
- Sync, async, and async-generator roots are all supported. Suspension does not close a span; a coroutine’s span spans its full lifetime
- On Python 3.11 and earlier, the root span is still recorded and a one-time warning explains that descendants were skipped
- Nesting a
trace() root inside another is not fully supported: the inner function records once, but its descendants parent to the outer root instead of to it
- Work dispatched to
ThreadPoolExecutor / threading.Thread is not captured. An abandoned generator records no span
- Descendant spans are not replay mock targets. A
span()-decorated function called under a trace() root is currently recorded twice (its own decorator plus subtree capture), so do not mix the two decorators in one call tree yet
get_function
wrap_baml
Framework integration → see BAML framework guide for examples.
Returns: an async wrapper with the same signature as method, plus a .collector attribute holding the most recent call’s BAML Collector (None before the first call or if baml-py is not installed). Pass on_collector to receive the Collector after each call.
Raises:
ValueError if form 1 is used without baml_client in constructor
ValueError if the method has no __name__
Semantics:
- If
baml-py is not installed, the method is called directly without instrumentation
- Otherwise calls
get_current_span().set_prompt(...) and get_current_span().add_context({...}) with extracted metadata
- Must be invoked inside a
@span-decorated function for the prompt/context to attach to anything
get_trace
Returns a DetachedTrace handle for annotating a trace after its root span has closed, from any process or thread.
Raises: ValueError if trace_id is not a canonical Bitfab trace ID.
Semantics:
- All methods on the returned handle are blocking, like
get_trace_span: each returns once the server has applied the change
- When
enabled=False, methods return immediately without sending
- Server returns 404 if no trace exists with that ID; the method raises rather than logging
get_trace_span
Fetches one persisted span without loading its trace. trace_id is the canonical Bitfab trace ID. Exactly one of the span’s Bitfab id or name is required. Numeric occurrences are zero-based in start-time order. Returns None when no trace or span matches.
replay
on_item_start fires when a worker begins processing an item, before replay setup or customer code runs. It carries type="started", running lifecycle totals, and the historical trace/span identity. Pair it with on_item_finish, which fires exactly once per item as it finishes with running totals (completed, total, succeeded, errored), test_run_id, and the required finished item; it never represents whole-run completion. item.trace_id is the server replay traces.id, read back off the ingest response and surfaced as the item finishes (its trace is flushed on finish; None only if that flush could not confirm delivery in time), while item.original_trace_id is the original historical trace (source_trace_id is a deprecated alias); finished items also carry input, result, original_output, error, trace_error, replay_error, duration_ms, tokens, model, and db_snapshot_ref. Replay doesn’t know pass/fail yet (verdicts are assigned later), so the totals only split ran-ok vs errored. After item execution, the SDK flushes the shared OTel pipeline and waits for Bitfab to confirm each trace completion and expected span count before finalizing the test run. A raising lifecycle callback never crashes the run. The deprecated on_progress callback receives the same per-item events plus its legacy item-less terminal complete event, and is ignored when on_item_finish is also provided. Pass the ready-made report_replay_progress reporter as both on_item_start and on_item_finish; the Bitfab plugin uses its stderr events to identify in-flight traces and write finished per-item result files.
Two call forms. replay(decorated_fn): the trace function key is read from the @span decorator (raises if undecorated). replay("key", fn): explicit key with any plain callable; the SDK wraps it in a span under the key internally. The explicit-key form is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent SDK, OpenAI Agents) replay, since they have no decorated root in the app. When replay auto-wraps a plain callable, a recorded dict root input (e.g. a LangGraph state) is passed as a single positional argument (matching TypeScript); decorated functions always get the keyword-args splat, with or without a redundant matching key. An explicit key that contradicts the decorator’s key raises. max_concurrency=None means unlimited; 1 means sequential. When trace_ids is passed, limit is ignored with a warning (an explicit ID list already determines how many traces replay). db_branch=True requests a DB branch per replay item with the mirror’s own sizing (False or omission leaves branching off); each bounded worker resolves its own branch and get_current_replay_branch() exposes it inside fn. Its keys tune the branch: db_branch={"min_cu": 2, "max_cu": 2, "warmup_sql": "SELECT 1;"}, all optional. min_cu and max_cu are the compute’s autoscaling floor and ceiling (0.25 to 56). Equal values pin a fixed size, allowed up to 56; an autoscaling range may not span more than 8 CU or exceed 16 CU; setting them equal pins the size so items stay comparable. warmup_sql is appended to the branch’s readiness check, so it warms the cache before fn sees the branch and its time is not charged to the replayed call. Invalid warm-up SQL fails the branch rather than quietly handing back a cold one. Omit the keys and the branch keeps the mirror’s own defaults.
A trace replays only when its root span has serializable inputs, or it was instrumented through a framework handler (whose recorded root input is serializable). If the original inputs were stubbed as non-serializable at capture time, the trace cannot be replayed.
call
Executes a server-configured BAML function locally using env_vars.
Raises: ValueError on lookup failure or execution error.
Framework Integrations
Handlers returned by these methods plug into each framework’s
callback/processor/hook surface and emit Bitfab spans automatically. They reuse
the owning Bitfab client’s lazy OTel worker, so Bitfab.close() releases the
decorator and framework transport together. Directly constructed LangGraph or
Claude handlers own their transport and expose close(timeout=30.0) plus a
context manager. For usage examples and semantics, see the per-framework
guides.
get_langgraph_callback_handler
Returns a LangChain/LangGraph BaseCallbackHandler. Pass via config={"callbacks": [handler]} when invoking. The handler-created root is replayable from the framework input, so a separate @span root is only needed for meaningful surrounding application work. See LangGraph framework guide.
Aliased as get_langchain_callback_handler(trace_function_key) for plain LangChain projects; the returned handler and behavior are identical. The handler class is also exported as BitfabLangChainCallbackHandler.
get_openai_tracing_processor
Raises: ImportError if openai-agents is not installed. Captures agent internals; pair it with get_openai_agent_handler for a replayable root. See OpenAI Agents framework guide.
get_openai_agent_handler
Returns a handler whose wrap_run(agent, input, **run_kwargs) is a drop-in for Runner.run that records a keyed, replayable root span carrying the run input (the tracing processor’s spans nest underneath). For streamed runs, wrap_run_streamed(agent, input, **run_kwargs) is an async-generator drop-in for Runner.run_streamed: iterate it to consume the same stream events while the run is traced. (bitfab.replay() re-runs the non-streaming wrap_run, not this async generator.) See OpenAI Agents framework guide.
get_claude_agent_handler
Returns a handler exposing instrument_options(options), wrap_response(stream, input=...), and wrap_query(stream, input=...) for the Claude Agent SDK. Pass input=prompt to the wrap call to record a replayable root span. See Claude Agent SDK framework guide.
wrap_baml
See the BAML framework guide for examples; full signature under wrap_baml above.
class BitfabFunction
Returned from client.get_function(key).
Delegates to the parent Bitfab instance with the bound trace_function_key. The get_*_handler methods reuse the bound key so a span root and the handler share it (the documented same-key nesting pattern), with no repeated string. wrap_baml is the exception: it opens no span and uses no key, enriching the current span instead, so call it inside a function wrapped by this handle’s span.
Module Functions
get_current_span()
Returns a no-op object when called outside a span context. The no-op’s methods do nothing and never raise.
get_current_trace()
Returns a no-op object when called outside a span context.
get_current_replay_branch()
Call it inside the replayed function to get the branch resolved for the item currently running. Returns None outside a replay item and for an item whose source trace carried no DB snapshot reference, which is the fallback path: url = branch.database_url if branch else os.environ["DATABASE_URL"].
The value object is immutable and per item, built from the replay ContextVar, so concurrent items each see their own branch. Reading database_url marks the trace as having used the branch (reported as accessed); the other attributes inspect the branch without exposing the connection string and deliberately do not mark, and repr() redacts the URL.
Every field the service puts on the lease is copied onto the branch under its snake_case name, so one added server-side is readable before you upgrade the SDK. database_url is the sole exception, since it is the credential and the only member that may mark the branch as accessed.
flush_traces(timeout: float = 30.0)
Forces the private OpenTelemetry batch processor to export pending spans, and
waits for any remaining legacy mutation requests, up to timeout seconds.
Returns True when queued exports completed successfully within the deadline,
or False when delivery failed or the flush timed out. Use before process exit
in short-lived scripts.
Bitfab.close(timeout: float = 30.0)
Flushes pending requests and permanently shuts down this client’s private OTel
transports within one total deadline. The method is idempotent and returns
False if delivery or shutdown misses the deadline. Bitfab also supports
with Bitfab(...) as client: and calls close() on context exit. Use either
form when a long-running process creates transient clients; shared clients may
remain open until the process-wide exit hook runs.
Classes (Context Handles)
CurrentSpan
CurrentTrace
set_metadata shallow-merges with later keys winning. add_context accumulates entries. drop flags the trace to be dropped: once flagged, spans that complete afterward are not uploaded at all, and the flag rides out on the completion payload, so at completion the server scrubs any payloads that already raced out (the trace, its external trace, and sibling spans), deletes the archived S3 objects, and marks it dropped instead of completed, keeping only a skeleton audit row. It is a no-op outside a span and never raises.
DetachedTrace
Returned by client.get_trace(trace_id), where trace_id is the canonical Bitfab trace ID. Methods have the same semantics as CurrentTrace but send to the server immediately and block until it responds, so a later read always observes the write. They raise if the server rejects the update, and are silent no-ops if the client is disabled or input validation fails.
TypedDicts & Dataclasses
AllowedEnvVars
ReplayItem
ReplayResult
A trace error means the replayed function started and raised. A replay error means Bitfab could not invoke it, for example because database warmup, input loading, or mock preparation failed. If delivery or finalization later fails, replay() raises ReplayError; its items, test_run_id, test_run_url, and cause preserve the partial result and original whole-run exception.
If database branch resolution fails, replay_error is a DbBranchReplayError with code, the server message, original_trace_id, and an optional cause. Resolver codes such as branch_create_failed, snapshot_from_replaced_origin, invalid_snapshot_ref, and internal_error therefore remain available in memory, progress events, result files, and ReplayError.items. HTTP, timeout, and network failures while requesting a lease use lease_request_failed, with the original client exception as cause.
serialize_replay_result
Returns indented JSON while preserving structured fields from trace_error and replay_error, including DbBranchReplayError.code, original_trace_id, and nested cause. Use it for direct-run stdout instead of json.dumps(..., default=str), which reduces exceptions to strings.
Error Behavior Summary