Basic Tracing
ABV provides flexible ways to create and manage traces and their constituent observations (spans and generations).Install package
@observe Decorator
The@observe() decorator provides a convenient way to automatically trace function executions, including capturing their inputs, outputs, execution time, and any errors. It supports both synchronous and asynchronous functions.
Parameters:
name: Optional[str]: Custom name for the created span or generation observation. Defaults to the function name.as_type: Optional[Literal["generation"]]: If set to"generation", a ABV generation object is created, suitable for LLM calls. Otherwise, a regular span is created.capture_input: bool: Whether to capture function arguments as input. Defaults to env varABV_OBSERVE_DECORATOR_IO_CAPTURE_ENABLEDorTrueif not set.capture_output: bool: Whether to capture function return value as output. Defaults to env varABV_OBSERVE_DECORATOR_IO_CAPTURE_ENABLEDorTrueif not set.transform_to_string: Optional[Callable[[Iterable], str]]: For functions that return generators (sync or async), this callable can be provided to transform the collected chunks into a single string for theoutputfield. If not provided, and all chunks are strings, they will be concatenated. Otherwise, the list of chunks is stored.
Trace Context and Special Keyword Arguments:
The@observe decorator automatically propagates the OTEL trace context. If a decorated function is called from within an active ABV span (or another OTEL span), the new observation will be nested correctly.
You can also pass special keyword arguments to a decorated function to control its tracing behavior:
abv_trace_id: str: Explicitly set the trace ID for this function call. Must be a valid W3C Trace Context trace ID (32-char hex). If you have a trace ID from an external system, you can useABV.create_trace_id(seed=external_trace_id)to generate a valid deterministic ID.abv_parent_observation_id: str: Explicitly set the parent observation ID. Must be a valid W3C Trace Context span ID (16-char hex).
The observe decorator is capturing the args, kwargs and return value of decorated functions by default. This may lead to performance issues in your application if you have large or deeply nested objects there. To avoid this, explicitly disable function IO capture on the decorated function by passing
capture_input=False and/or capture_output=False parameters.Context Managers
You can create spans or generations anywhere in your application. If you need more control than the@observe decorator, the primary way to do this is using context managers (with with statements), which ensure that observations are properly started and ended.
abv.start_as_current_span(): Creates a new span and sets it as the currently active observation in the OTEL context for its duration. Any new observations created within this block will be its children.abv.start_as_current_generation(): Similar to the above, but creates a specialized โgenerationโ observation for LLM calls.
Manual Observations
For scenarios where you need to create an observation (a span or generation) without altering the currently active OpenTelemetry context, you can useabv.start_span() or abv.start_generation().
Key Characteristics:
- No Context Shift: Unlike their
start_as_current_...counterparts, these methods do not set the new observation as the active one in the OpenTelemetry context. The previously active span (if any) remains the current context for subsequent operations in the main execution flow. - Parenting: The observation created by
start_span()orstart_generation()will still be a child of the span that was active in the context at the moment of its creation. - Manual Lifecycle: These observations are not managed by a
withblock and therefore must be explicitly ended by calling their.end()method. - Nesting Children:
- Subsequent observations created using the global
abv.start_as_current_span()(or similar global methods) will not be children of these โmanualโ observations. Instead, they will be parented by the original active span. - To create children directly under a โmanualโ observation, you would use methods on that specific observation object (e.g.,
manual_span.start_as_current_span(...)).
- Subsequent observations created using the global
- Record work that is self-contained or happens in parallel to the main execution flow but should still be part of the same overall trace (e.g., a background task initiated by a request).
- Manage the observationโs lifecycle explicitly, perhaps because its start and end are determined by non-contiguous events.
- Obtain an observation object reference before itโs tied to a specific context block.
Nesting Observations
Observe Decorator
The function call hierarchy is automatically captured by the@observe decorator reflected in the trace.
Context Managers
Nesting is handled automatically by OpenTelemetryโs context propagation. When you create a new observation (span or generation) using start_as_current_span or start_as_current_generation, it becomes a child of the observation that was active in the context when it was created.Manual
If you are creating observations manually (not _as_current_), you can use the methods on the parent ABVSpan or ABVGeneration object to create children. These children will not become the current context unless their _as_current_ variants are used.Updating Observations
You can update observations with new information as your code executes.- For spans/generations created via context managers or assigned to variables: use the
.update()method on the object. - To update the currently active observation in the context (without needing a direct reference to it): use
abv.update_current_span()orabv.update_current_generation().
ABVSpan.update()** / ABVGeneration.update() parameters:**
| Parameter | Type | Description | Applies To |
|---|---|---|---|
input | Optional[Any] | Input data for the operation. | Both |
output | Optional[Any] | Output data from the operation. | Both |
metadata | Optional[Any] | Additional metadata (JSON-serializable). | Both |
version | Optional[str] | Version identifier for the code/component. | Both |
level | Optional[SpanLevel] | Severity: "DEBUG", "DEFAULT", "WARNING", "ERROR". | Both |
status_message | Optional[str] | A message describing the status, especially for errors. | Both |
completion_start_time | Optional[datetime] | Timestamp when the LLM started generating the completion (streaming). | Generation |
model | Optional[str] | Name/identifier of the AI model used. | Generation |
model_parameters | Optional[Dict[str, MapValue]] | Parameters used for the model call (e.g., temperature). | Generation |
usage_details | Optional[Dict[str, int]] | Token usage (e.g., {"input_tokens": 10, "output_tokens": 20}). | Generation |
cost_details | Optional[Dict[str, float]] | Cost information (e.g., {"total_cost": 0.0023}). | Generation |
prompt | Optional[PromptClient] | Associated PromptClient object from ABV prompt management. | Generation |
Setting Trace Attributes
Trace-level attributes apply to the entire trace, not just a single observation. You can set or update these using:- The
.update_trace()method on anyABVSpanorABVGenerationobject within that trace. abv.update_current_trace()to update the trace associated with the currently active observation.
| Parameter | Type | Description |
|---|---|---|
name | Optional[str] | Name for the trace. |
user_id | Optional[str] | ID of the user associated with this trace. |
session_id | Optional[str] | Session identifier for grouping related traces. |
version | Optional[str] | Version of your application/service for this trace. |
input | Optional[Any] | Overall input for the entire trace. |
output | Optional[Any] | Overall output for the entire trace. |
metadata | Optional[Any] | Additional metadata for the trace. |
tags | Optional[List[str]] | List of tags to categorize the trace. |
public | Optional[bool] | Whether the trace should be publicly accessible (if configured). |
Trace Input/Output Behavior
Trace input and output are automatically set from the root observation (first span/generation) by default.Default Behavior
Override Default Behavior
If you need different trace inputs/outputs than the root observation, explicitly set them:Critical for LLM-as-a-Judge Features
LLM-as-a-judge and evaluation features typically rely on trace-level inputs and outputs. Make sure to set these appropriately:Trace and Observation IDs
ABV uses W3C Trace Context compliant IDs:- Trace IDs: 32-character lowercase hexadecimal string (16 bytes).
- Observation IDs (Span IDs): 16-character lowercase hexadecimal string (8 bytes).
abv.get_current_trace_id(): Gets the trace ID of the currently active observation.abv.``get_current_observation_id``(): Gets the ID of the currently active observation.span_obj.trace_idandspan_obj.id: Access IDs directly from aABVSpanorABVGenerationobject.
ABV.create_trace_id(seed: Optional[str] = None)(static method): Generates a new trace ID. If aseedis provided, the ID is deterministic. Use the same seed to get the same ID. This is useful for correlating external IDs with ABV traces.
trace_id (and optionally a parent_span_id) from an external source (e.g., another service, a batch job), you can link new observations to it using the trace_context parameter. Note that OpenTelemetry offers native cross-service context propagation, so this is not necessarily required for calls between services that are instrumented with OTEL.
Client Management
flush()
Manually triggers the sending of all buffered observations (spans, generations, scores, media metadata) to the ABV API. This is useful in short-lived scripts or before exiting an application to ensure all data is persisted.flush() method blocks until the queued data is processed by the respective background threads.
shutdown()
Gracefully shuts down the ABV client. This includes:- Flushing all buffered data (similar to
flush()). - Waiting for background threads (for data ingestion and media uploads) to finish their current tasks and terminate.
shutdown() before your application exits to prevent data loss and ensure clean resource release. The SDK automatically registers an atexit hook to call shutdown() on normal program termination, but manual invocation is recommended in scenarios like:
- Long-running daemons or services when they receive a shutdown signal.
- Applications where
atexitmight not reliably trigger (e.g., certain serverless environments or forceful terminations).