Opper’s tracing endpoints allow for customizing logging of task completions into hierarchical views. It also allows for appending metrics and metadata to logs so that teams can observe behavior of models and task completions during development or in production.
How it works
Tracing works by creating and updating spans. Spans can have parents and children, which means that they can be connected to each other. By default, all task completions or knowledge base operations are automatically attached to a span which will have the name of the task. In the return values of operations the span_id is listed. All operations have a parent_span_id which can be used to connect the operation to a span.
Creating new spans
Task completions can be attached to spans. Here is a simple example where we create a root span named “data_processing”:
from opperai import Opper
import os
def main():
# Initialize Opper client
opper = Opper(http_bearer=os.getenv("OPPER_API_KEY"))
# Create a trace to track this processing session
session_span = opper.spans.create(name="data_processing")
# Sample data to process
sample_data = [
{"name": "Alice", "age": 30, "city": "New York"},
{"name": "Bob", "age": 25, "city": "San Francisco"},
{"name": "Charlie", "age": 35, "city": "Chicago"},
{"name": "Diana", "age": 28, "city": "Boston"},
]
personas = []
for i, record in enumerate(sample_data):
# Analyze the record and connect it to the trace
completion = opper.call(
name="analyze",
instructions="Analyze this person's data and provide insights about their profile.",
input=record,
parent_span_id=session_span.id,
)
analysis = completion.message
personas.append(analysis)
print(f"Analysis: {analysis}")
if __name__ == "__main__":
main()
It is possible to create flexible hierarchies of spans to capture the run of an agent or chatbot in the best way possible. It is often good to think about a span as a container of some kind of higher level operation. For example, for a chatbot you may want a root span to be a session, each round of the chat a round, and then have all underlying task completions inside the round span.
Span input and output fields can be useful to surface information.
Here we update the root span with information on what will be processed and the final output:
opper.spans.update(
span_id=session_span.id,
input=str(sample_data),
output=str(personas),
meta={"n_records": len(sample_data)}
)
Since it’s possible to choose exactly what to display in the input and output fields of a span it is a good way to render information for more easy digestion, maybe for non-technical experts and other peers
Adding metrics to spans
It is possible to add metrics to spans, to catch results of evaluations or performance measurements. Metrics can be filtered for in the tracing view in the dashboard.
Here is a simple example :
# Save a metric that captures number of personas that are blank
# and attach it to the root span
opper.span_metrics.create_metric(
span_id=session_span.id,
dimension="n_failed",
value=sum(1 for persona in personas if persona is None),
comment="Number of personas with failed summary",
)
We recommend thinking about metrics as a way to store various performance evaluations of the feature you are building. Metrics can hold things like thumbs up/down, results of tests from requirements and so forth.