How to Use the Tracing Module to Observe and Debug Agent Executions in AgentScope
Enable tracing by initializing AgentScope with a tracing_url, then apply decorators like @trace_llm or @trace_reply to capture OpenTelemetry spans for every agent interaction.
AgentScope is an open-source multi-agent framework that provides built-in observability through its tracing module. By leveraging OpenTelemetry, you can use the tracing module to observe and debug agent executions in real-time, capturing the full lifecycle of LLM calls, tool invocations, and agent responses. This guide walks through enabling tracing, decorating your components, and interpreting the resulting telemetry data.
How Tracing Works in AgentScope
The tracing system in AgentScope is built on OpenTelemetry and consists of several coordinated components. When trace_enabled is set to True (either via agentscope.init or manual configuration), the system activates span creation for decorated functions.
The core workflow involves:
-
Setup: The
setup_tracingfunction insrc/agentscope/tracing/_setup.pyconfigures the OTLP exporter with your provided endpoint and registers aBatchSpanProcessorto batch and transmit spans efficiently. -
Tracer Retrieval: The
_get_tracer()function retrieves a tracer named"agentscope"(versioned with__version__) from the same setup module. -
Enablement Check: Before creating any span,
_check_tracing_enabled()insrc/agentscope/tracing/_trace.pyreads the global flag_config.trace_enabledto determine if tracing should proceed. -
Decoration: Specialized decorators like
trace,trace_llm,trace_reply,trace_format,trace_toolkit, andtrace_embedding(all defined insrc/agentscope/tracing/_trace.py) wrap target functions to start spans, capture request/response attributes, and set status codes. -
Attribute Extraction: The
_get_*_attributesutilities insrc/agentscope/tracing/_extractor.pyconvert internal AgentScope objects (messages, tool calls, embeddings) into OpenTelemetry-compatible attribute dictionaries. -
Attribute Definitions: Constants for attribute keys are centralized in
src/agentscope/tracing/_attributes.pyviaSpanAttributesandOperationNameValuesclasses.
When a decorated function executes, the decorator creates a span with a generated operation name, records request attributes, executes the function, then records response attributes or exceptions, naturally forming a parent-child hierarchy that mirrors your call chain.
Enabling Tracing in Your Application
Before you can observe agent executions, you must install the optional dependencies and initialize the tracing system.
Installing OpenTelemetry Dependencies
Tracing requires the OpenTelemetry Python SDK. Install it via pip:
pip install opentelemetry-sdk opentelemetry-exporter-otlp
These packages are declared as optional extras in the AgentScope repository, so you may also install them as extras if supported by your distribution.
Initializing with agentscope.init
The simplest way to enable tracing is to provide a tracing_url when initializing AgentScope:
import agentscope
# Connect to Langfuse, Arize Phoenix, or any OTLP-compatible backend
agentscope.init(tracing_url="https://cloud.langfuse.com/api/public/otel/v1/traces")
This call internally executes setup_tracing from src/agentscope/tracing/_setup.py, configures the OTLP exporter with your endpoint, registers a BatchSpanProcessor, and sets the global _config.trace_enabled = True.
Manual Setup
If you need finer control, import and call setup_tracing directly:
from agentscope.tracing import setup_tracing
setup_tracing(
endpoint="http://localhost:4318/v1/traces",
service_name="my-agent-service"
)
Decorating Components for Observability
Once tracing is enabled, apply decorators to the functions and methods you want to observe. The src/agentscope/tracing/_trace.py file provides specialized decorators for different component types.
General Function Tracing
Use the @trace decorator for arbitrary functions:
from agentscope.tracing import trace
@trace(name="my_custom_func")
async def my_custom_func(x: int) -> int:
return x * 2
This creates a span named my_custom_func and records the input x and the output value as attributes.
LLM Calls
Decorate your model's __call__ method with @trace_llm to capture full request and response payloads:
from agentscope.tracing import trace_llm
from agentscope.model import ChatModelBase, ChatResponse
class MyLLM(ChatModelBase):
@trace_llm
async def __call__(self, messages: list[dict]) -> ChatResponse:
# Simulated response
return ChatResponse(id="msg", content=[])
Spans are labeled with the operation name chat and include the full request/response payloads. See the implementation in trace_llm.
Agent Reply
Track agent response generation with @trace_reply:
from agentscope.tracing import trace_reply
from agentscope.agent import AgentBase
from agentscope.message import Msg, TextBlock
class EchoAgent(AgentBase):
@trace_reply
async def reply(self) -> Msg:
return Msg("assistant", [TextBlock(type="text", text="Hello!")])
Formatter
Capture formatting logic with @trace_format:
from agentscope.tracing import trace_format
from agentscope.formatter import FormatterBase
class SimpleFormatter(FormatterBase):
@trace_format
async def format(self) -> list[dict]:
return [{"role": "user", "content": "Hello"}]
Toolkit (Tool Calls)
Trace tool invocations with @trace_toolkit:
from agentscope.tracing import trace_toolkit
from agentscope.tool import Toolkit, ToolResponse
from agentscope.message import TextBlock
class MyToolkit(Toolkit):
@trace_toolkit
async def call_tool_function(self, tool_call):
# Your tool logic here
return ToolResponse(content=[TextBlock(type="text", text="Done")])
Embedding Model
Monitor embedding generation with @trace_embedding:
from agentscope.tracing import trace_embedding
from agentscope.embedding import EmbeddingModelBase
class MyEmbedding(EmbeddingModelBase):
@trace_embedding
async def __call__(self, texts: list[str]) -> list[list[float]]:
return [[0.1, 0.2, 0.3] for _ in texts]
Viewing and Interpreting Traces
Once your application runs with decorated components, the OpenTelemetry exporter sends span data to your configured backend. In tools like Langfuse or Arize Phoenix, you will see a hierarchical trace view:
Agent.reply
├─ LLM.chat (or LLM.embed)
├─ Toolkit.call_tool_function
└─ Formatter.format
Each node displays request attributes (e.g., message content, tool input parameters) and response attributes (e.g., generated text, tool output). Errors appear with a red ERROR status and include the captured exception message and stack trace. This structure allows you to pinpoint latency bottlenecks, inspect payload contents, and debug failures across the entire agent execution chain.
Complete End-to-End Example
Here is a runnable example that ties together initialization, decoration, and execution:
import agentscope
from agentscope.tracing import trace_reply, trace_llm
from agentscope.agent import AgentBase
from agentscope.model import ChatModelBase, ChatResponse
from agentscope.message import Msg, TextBlock
# 1️⃣ Initialise tracing (replace with your backend URL)
agentscope.init(tracing_url="https://cloud.langfuse.com/api/public/otel/v1/traces")
# 2️⃣ Define a traced LLM
class EchoLLM(ChatModelBase):
@trace_llm
async def __call__(self, messages, **kwargs):
# Echo the last user message
user_msg = messages[-1]["content"]
return ChatResponse(
id="msg",
content=[TextBlock(type="text", text=user_msg)]
)
# 3️⃣ Define a traced agent that uses the LLM
class EchoAgent(AgentBase):
def __init__(self):
super().__init__(name="echo")
self.llm = EchoLLM(stream=False)
@trace_reply
async def reply(self) -> Msg:
# Simple single‑turn conversation
user_msg = [{"role": "user", "content": "Hello"}]
llm_res = await self.llm(user_msg)
return Msg("assistant", llm_res.content, "assistant")
# 4️⃣ Run the agent
agent = EchoAgent()
response = await agent()
print(response.content) # → [TextBlock(type='text', text='Hello')]
After executing this script, your tracing backend (e.g., Langfuse) displays:
EchoAgent.replyas the parent span.EchoLLM.__call__as a child span containing request attribute"user_message": "Hello"and response attribute"generated_text": "Hello".
This visibility allows you to debug execution flows, inspect message payloads, and identify performance bottlenecks in real time.
Key Source Files
The tracing implementation spans several files in the src/agentscope/tracing/ directory:
Summary
- Enable tracing by passing
tracing_urltoagentscope.init()or callingsetup_tracing()directly fromsrc/agentscope/tracing/_setup.py. - Decorate components using specialized decorators (
@trace_llm,@trace_reply,@trace_toolkit, etc.) fromsrc/agentscope/tracing/_trace.pyto capture request/response data. - Observe hierarchies in OTLP-compatible backends like Langfuse or Arize Phoenix, where parent spans (agents) contain child spans (LLMs, tools, formatters).
- Debug effectively by inspecting span attributes for inputs, outputs, and exceptions without modifying your core business logic.
Frequently Asked Questions
Do I need to modify my existing agent code to enable tracing?
No. You only need to initialize tracing with agentscope.init(tracing_url="...") and add decorators to the methods you want to observe. The decorators in src/agentscope/tracing/_trace.py wrap your existing functions without changing their signatures or behavior, capturing inputs and outputs only when trace_enabled is True.
What backends are compatible with AgentScope tracing?
AgentScope uses the OpenTelemetry Protocol (OTLP) to export spans. Any OTLP-compatible backend works, including Langfuse, Arize Phoenix, Jaeger, Zipkin (via OpenTelemetry Collector), and the standard OpenTelemetry Collector itself. You simply provide the OTLP endpoint URL when calling setup_tracing() or agentscope.init().
How do I trace synchronous functions?
The decorators in src/agentscope/tracing/_trace.py handle both synchronous and asynchronous functions automatically. When you apply @trace, @trace_llm, or any other specialized decorator to a sync function, the wrapper detects the function type and manages the span context appropriately. You do not need separate decorators for sync and async code.
Can I disable tracing for specific components while keeping it enabled globally?
Yes. Tracing is controlled by the global _config.trace_enabled flag, but you decide which methods to decorate. Simply omit the @trace_* decorators on components you want to exclude from telemetry. Alternatively, conditionally apply decorators based on your own configuration logic. Since the decorators in src/agentscope/tracing/_trace.py only create spans when the global flag is enabled, removing the decorator entirely is the most reliable way to exclude specific functions from tracing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →