Skip to main content

What is a Trace?

A trace in Galtea represents a single operation or function call that occurs during an AI agent’s execution. Traces capture the internal workings of your agent—such as tool calls, retrieval operations, chain orchestrations, and LLM invocations—providing deep visibility into how your agent processes requests. Traces are always linked to an inference result, enabling you to understand not just what your agent responded, but how it arrived at that response. Every trace must belong to a specific inference result.

Why Use Traces?

Debugging

Identify exactly where and why your agent failed or produced unexpected results.

Performance Optimization

Pinpoint slow operations with latency tracking at every step.

Compliance & Auditing

Maintain a complete audit trail of all operations for regulatory requirements.

Cost Analysis

Understand which operations consume the most resources.

Trace Hierarchy

Traces support parent-child relationships, allowing you to visualize the complete execution flow of your agent. When a traced function calls another traced function, the hierarchy is automatically captured.
Agent Call (root)
├── Route Query (CHAIN)
├── Retrieve Context (RETRIEVER)
│   └── Vector Search (TOOL)
├── Fetch Product Data (TOOL)
└── Calculate Discount (TOOL)
Each trace includes:
  • id: Unique identifier for the trace
  • parent_trace_id: Reference to the parent trace (null for root traces)
  • name: The operation name
  • type: Classification of the operation (TraceType)
  • description: Human-readable description of what the operation does

Trace Types

Traces are classified by type to help you understand the nature of each operation and debug issues more effectively.
TypeDefinitionWhy This Matters for Tracing
SPANGeneric durations of work in a trace.Default type for general operations that don’t fit other categories. Useful for grouping related work.
GENERATIONAI model generations including prompts, token usage, and costs.This is where cost (tokens) and latency come from. Clearly see these operations and identify expensive calls and bottlenecks.
EVENTDiscrete point-in-time events.Capture important moments without duration, like user interactions or state changes.
AGENTAgent that orchestrates flow and uses tools with LLM guidance.High-level orchestration nodes that coordinate multiple operations and make decisions.
TOOLTool/function calls (e.g., external APIs, calculations).Deterministic or external calls where inputs, outputs, and side effects determine correctness.
CHAINLinks between different application steps.Composite orchestration nodes that run multiple internal steps and pass data between stages.
RETRIEVERData retrieval steps (vector store, database).Operations that fetch contextual data which directly affect prompt relevance and the context window.
EVALUATORFunctions that assess LLM outputs.Operations that evaluate quality, safety, or correctness of generated content.
EMBEDDINGEmbedding model calls.Vector embedding operations for semantic search or similarity.
GUARDRAILComponents that protect against malicious content.Safety checks that filter or validate inputs/outputs.

The @trace Decorator

The @trace decorator automatically captures function inputs, outputs, timing, errors, and parent-child relationships.

Syntax Options

from galtea import Galtea, TraceType, trace


# Full specification
@trace(name="my_operation", type=TraceType.TOOL)
def my_function_1():
    # Function implementation ...
    print("Doing something...")


# Name only (type defaults to None, for unclassified operations)
@trace(name="custom_name")
def my_function_2():
    # Function implementation ...
    print("Doing something else...")


# Include function docstring as trace description
@trace(type=TraceType.TOOL, include_docstring=True)
def my_function_3(user_id: str):
    """Fetch user data from the database given a user ID."""
    # Function implementation ...
    print(f"Fetching data for user {user_id}...")


# Bare decorator (uses function name)
@trace()
def my_function_4():
    # Function implementation ...
    print("Doing another thing...")

Error Tracking

The decorator automatically captures exceptions. When an error occurs, the trace records:
  • The error message in the error field
  • The execution time until the error
  • Input data that caused the error
@trace(name="risky_operation", type=TraceType.TOOL)
def risky_call(self, data: str) -> str:
    if not data:
        raise ValueError("Data cannot be empty")
    return f"Processed: {data}"

Viewing Trace Hierarchy

After collecting traces, you can visualize the execution in the Dashboard or by usign the following code:
traces = galtea.traces.list(inference_result_id=inference_result.id)

print("Trace Hierarchy:")


def print_trace_tree(traces, parent_id=None, indent=0):
    for trace in traces:
        if trace.parent_trace_id == parent_id:
            prefix = "  " * indent + ("└─ " if indent > 0 else "")
            print(f"{prefix}{trace.name} ({trace.type}) - {trace.latency_ms:.2f}ms")
            print_trace_tree(traces, trace.id, indent + 1)


print_trace_tree(traces)
Example output:
main_agent (AGENT) - 245.30ms
└─ route_query (CHAIN) - 0.15ms
└─ search_documents (RETRIEVER) - 120.50ms
└─ call_llm (GENERATION) - 124.20ms

SDK Integration

Tracing Tutorial

Step-by-step guide to instrumenting your agent and collecting traces.

Trace Service

Manage and collect traces for your AI agent operations using the SDK.

Trace Properties

Inference Result
InferenceResult
required
The inference result this trace belongs to. Every trace must be linked to an inference result.
Name
string
required
The name of the traced operation (e.g., function name).
Type
TraceType
The type of operation: SPAN, GENERATION, EVENT, AGENT, TOOL, CHAIN, RETRIEVER, EVALUATOR, EMBEDDING, or GUARDRAIL.
Description
string
A human-readable description of the operation. Can be set manually via start_trace(description=...) or automatically from function docstrings using @trace(include_docstring=True). Maximum size: 32KB.
Parent Trace ID
string
The ID of the parent trace for hierarchical relationships.
Input Data
any
The input parameters passed to the operation. Maximum size: 128KB.
Output Data
any
The result returned by the operation. Maximum size: 128KB.
Error
string
Error message if the operation failed.
Latency (ms)
float
The execution time of the operation in milliseconds.
Start Time
string
ISO 8601 timestamp when the operation started.
End Time
string
ISO 8601 timestamp when the operation completed.
Metadata
any
Additional custom metadata about the trace. Maximum size: 128KB.

Best Practices

Choose descriptive names that clearly indicate the operation being traced:
# ✅ Good - descriptive
@trace(name="fetch_customer_orders")
# ❌ Bad - generic
@trace(name="step_1")
Trace operations that represent logical units of work, not every single function:
# ✅ Good - meaningful operation
@trace(name="search_products", type=TraceType.RETRIEVER)
def search_products(self, query):
    results = self._query_vector_db(query)  # Internal, not traced
    return self._format_results(results)  # Internal, not traced
Classify operations correctly to enable better filtering and analysis in the dashboard.
# ✅ Good - correct classification
@trace(name="generate_evaluation_metrics", type=TraceType.RETRIEVER)
def fetch_user_data(self, user_id: str) -> dict:
    # Function implementation ...
    return {"user_id": user_id, "data": "Sample data"}


# ❌ Bad - incorrect classification
@trace(name="generate_evaluation_metrics", type=TraceType.TOOL)
def fetch_user_data_incorrect(self, user_id: str) -> dict:
    # Function implementation ...
    return {"user_id": user_id, "data": "Sample data"}
The decorator captures function arguments automatically. Consider what’s useful for debugging:
@trace(name="process_document", type=TraceType.TOOL)
def process(self, doc_id: str) -> dict:
    # Only doc_id is captured as input, not the full document
    doc = self.fetch_document(doc_id)
    return {"summary": doc.summary, "status": "processed"}