How to Enable Tracing in WayFlow#

python-icon Download Python Script

Python script/notebook for this guide.

Tracing how-to script

Prerequisites

This guide assumes familiarity with:

Tracing is a crucial aspect of any application, allowing developers to monitor and analyze the behavior of their system. In the context of an agentic framework like WayFlow, tracing allows you to understand the interactions between agents, tools, and other components.

In this guide, you will learn how to:

  • Create a SpanExporter

  • Set up tracing in WayFlow

  • Save your traces in a file

What is Tracing?#

Tracing refers to the process of collecting and analyzing data about the execution of a program or system. This data can include information about function calls, variable assignments, and other events that occur during execution. By analyzing this data, developers can identify performance bottlenecks, debug issues, and optimize their system for better performance.

Why is Tracing Important?#

Tracing is essential for several reasons:

  • Debugging: Tracing helps developers identify and diagnose issues in their agents. By analyzing the trace data, they can pinpoint the exact location and cause of errors.

  • Performance Optimization: Tracing provides insights into the performance characteristics of an agent, enabling developers to identify bottlenecks and optimize their architectures for better efficiency.

  • Monitoring: Tracing allows developers to monitor the behavior of their agents in real-time, enabling them to detect anomalies and respond promptly to issues.

Basic implementation#

To set up tracing in WayFlow, you need to provide an implementation of the SpanProcessor and SpanExporter classes.

A SpanProcessor is a common concept in the observability world. It is a component in the tracing pipeline responsible for receiving and processing spans as they are created and completed by the application. SpanProcessor sit between the tracing backend and the exporter, allowing developers to implement logic such as batching, filtering, modification, or immediate export of Spans. When a Span ends, the SpanProcessor determines what happens to it next, whether it’s sent off immediately, or collected for more efficient periodic export (e.g., doing batching). This flexible mechanism enables customization of trace data handling before it’s ultimately exported to backend observability systems.

A SpanExporter is a component that is responsible for sending finished spans, along with their collected trace data, from the application to an external backend or observability system for storage and analysis. The exporter receives spans from the SpanProcessor and translates them into the appropriate format for the target system, such as LangFuse, LangSmith, or OCI APM. Exporters encapsulate the logic required to connect, serialize, and transmit data, allowing OpenTelemetry to support a wide range of backends through a consistent, pluggable interface. This mechanism enables seamless integration of collected trace data with various monitoring and tracing platforms.

In the following sections you will learn how to implement a combination of SpanProcessor and SpanExporter that can export traces to a file.

SpanProcessor and SpanExporter#

Danger

Several security concerns arise when implementing SpanProcessors and SpanExporters, which include, but they are not limited to, the security of the network used to export traces, and the sensitivity of the information exported. Please refer to our Security Guidelines for more information.

As partially anticipated in the previous section, the most simple implementation of a SpanProcessor is the one that exports the received Span as-is, without any modification, as soon as the Span is closed. This implementation is provided by wayflowcore, and it is called SimpleSpanProcessor. You will use an instance of this SpanProcessor in this guide.

For what concerns the SpanExporter, you can implement a version of it that just prints the information contained in the Spans to a file at a given path. The implementation can focus on the export method, that opens the file in append mode, and it prints in it the content of the Spans retrieved through the to_tracing_info method.

import pprint
from pathlib import Path
from typing import List, Union

from wayflowcore.tracing.span import Span
from wayflowcore.tracing.spanexporter import SpanExporter

class FileSpanExporter(SpanExporter):
    """SpanExporter that prints spans to a file.

    This class can be used for diagnostic purposes.
    It prints the exported spans to a file.
    """

    def __init__(self, filepath: Union[str, Path]):
        if isinstance(filepath, str):
            filepath = Path(filepath)
        self.filepath: Path = filepath

    def export(self, spans: List[Span]) -> None:
        with open(self.filepath, "a") as file:
            for span in spans:
                print(
                    pprint.pformat(span.to_tracing_info(), width=80, compact=True),
                    file=file,
                )

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return True

    def startup(self) -> None:
        pass

    def shutdown(self) -> None:
        pass

You can now combine SimpleSpanProcessor with the FileSpanExporter you just implemented to set up the basic components that will let you export traces to the desired file.

from wayflowcore.tracing.spanprocessor import SimpleSpanProcessor

span_processor = SimpleSpanProcessor(
    span_exporter=FileSpanExporter(filepath="calculator_agent_traces.txt")
)

Tracking an agent#

Now that you have everything you need to process and export traces, you can work on your agent.

In this example, you are going to build a simple calculator agent with four tools, one for each of the basic operations: addition, subtraction, multiplication, division.

from wayflowcore.agent import Agent
from wayflowcore.models import VllmModel
from wayflowcore.tools import tool

@tool(description_mode="only_docstring")
def multiply(a: float, b: float) -> float:
    """Multiply two numbers"""
    return a * b


@tool(description_mode="only_docstring")
def divide(a: float, b: float) -> float:
    """Divide two numbers"""
    return a / b


@tool(description_mode="only_docstring")
def sum(a: float, b: float) -> float:
    """Sum two numbers"""
    return a + b


@tool(description_mode="only_docstring")
def subtract(a: float, b: float) -> float:
    """Subtract two numbers"""
    return a - b


llm = VllmModel(
    model_id="LLAMA_MODEL_ID",
    host_port="LLAMA_API_URL",
)

agent = Agent(
    agent_id="calculator_agent",
    name="Calculator agent",
    custom_instruction="You are a calculator agent. Please use tools to do math.",
    initial_message="Hi! I am a calculator agent. How can I help you?",
    llm=llm,
    tools=[sum, subtract, multiply, divide],
)

We now run your agent enabling traces, and using the FileSpanExporter in order to export the traces in a file. To do that, just wrap the execution loop of our agent in a Trace context manager.

from wayflowcore.tracing.span import ConversationSpan
from wayflowcore.tracing.trace import Trace

conversation = agent.start_conversation()
with Trace(span_processors=[span_processor]):
    with ConversationSpan(conversation=conversation) as conversation_span:
        conversation.execute()
        conversation.append_user_message("Compute 2+3")
        status = conversation.execute()
        conversation_span.record_end_span_event(execution_status=status)

You can now run our code and inspect the traces saved in your file.

Agent Spec Exporting/Loading#

You can export the agent configuration to its Agent Spec configuration using the AgentSpecExporter.

from wayflowcore.agentspec import AgentSpecExporter

config = AgentSpecExporter().to_json(agent)

Here is what the Agent Spec representation will look like ↓

Click here to see the assistant configuration.
{
  "component_type": "ExtendedAgent",
  "id": "calculator_agent",
  "name": "Calculator agent",
  "description": "",
  "metadata": {
    "__metadata_info__": {}
  },
  "inputs": [],
  "outputs": [],
  "llm_config": {
    "component_type": "VllmConfig",
    "id": "2f1ca95b-d333-43a6-9518-a995a87418c1",
    "name": "LLAMA_MODEL_ID",
    "description": null,
    "metadata": {
      "__metadata_info__": {}
    },
    "default_generation_parameters": null,
    "url": "LLAMA_API_URL",
    "model_id": "LLAMA_MODEL_ID"
  },
  "system_prompt": "You are a calculator agent. Please use tools to do math.",
  "tools": [
    {
      "component_type": "ServerTool",
      "id": "bd8ba23f-162c-4c79-831d-96e03e40d2bd",
      "name": "sum",
      "description": "Sum two numbers",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [
        {
          "type": "number",
          "title": "a"
        },
        {
          "type": "number",
          "title": "b"
        }
      ],
      "outputs": [
        {
          "type": "number",
          "title": "tool_output"
        }
      ]
    },
    {
      "component_type": "ServerTool",
      "id": "9bb68f19-8bd9-4089-8511-92afe680e21d",
      "name": "subtract",
      "description": "Subtract two numbers",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [
        {
          "type": "number",
          "title": "a"
        },
        {
          "type": "number",
          "title": "b"
        }
      ],
      "outputs": [
        {
          "type": "number",
          "title": "tool_output"
        }
      ]
    },
    {
      "component_type": "ServerTool",
      "id": "e7d348eb-ced2-4d75-b80a-90c1b700ce7f",
      "name": "multiply",
      "description": "Multiply two numbers",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [
        {
          "type": "number",
          "title": "a"
        },
        {
          "type": "number",
          "title": "b"
        }
      ],
      "outputs": [
        {
          "type": "number",
          "title": "tool_output"
        }
      ]
    },
    {
      "component_type": "ServerTool",
      "id": "29decfc0-687d-44de-bf96-3e0f8d716e8b",
      "name": "divide",
      "description": "Divide two numbers",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [
        {
          "type": "number",
          "title": "a"
        },
        {
          "type": "number",
          "title": "b"
        }
      ],
      "outputs": [
        {
          "type": "number",
          "title": "tool_output"
        }
      ]
    }
  ],
  "toolboxes": [],
  "context_providers": null,
  "can_finish_conversation": false,
  "max_iterations": 10,
  "initial_message": "Hi! I am a calculator agent. How can I help you?",
  "caller_input_mode": "always",
  "agents": [],
  "flows": [],
  "agent_template": {
    "component_type": "PluginPromptTemplate",
    "id": "0d9a17fb-25aa-419a-97e6-f9fec9b327c3",
    "name": "",
    "description": null,
    "metadata": {
      "__metadata_info__": {}
    },
    "messages": [
      {
        "role": "system",
        "contents": [
          {
            "type": "text",
            "content": "{% if custom_instruction %}{{custom_instruction}}{% endif %}"
          }
        ],
        "tool_requests": null,
        "tool_result": null,
        "display_only": false,
        "sender": null,
        "recipients": [],
        "time_created": "2025-09-02T16:00:18.701799+00:00",
        "time_updated": "2025-09-02T16:00:18.701801+00:00"
      },
      {
        "role": "user",
        "contents": [],
        "tool_requests": null,
        "tool_result": null,
        "display_only": false,
        "sender": null,
        "recipients": [],
        "time_created": "2025-09-02T16:00:18.691523+00:00",
        "time_updated": "2025-09-02T16:00:18.691721+00:00"
      },
      {
        "role": "system",
        "contents": [
          {
            "type": "text",
            "content": "{% if __PLAN__ %}The current plan you should follow is the following: \n{{__PLAN__}}{% endif %}"
          }
        ],
        "tool_requests": null,
        "tool_result": null,
        "display_only": false,
        "sender": null,
        "recipients": [],
        "time_created": "2025-09-02T16:00:18.701837+00:00",
        "time_updated": "2025-09-02T16:00:18.701838+00:00"
      }
    ],
    "output_parser": null,
    "inputs": [
      {
        "description": "\"custom_instruction\" input variable for the template",
        "type": "string",
        "title": "custom_instruction",
        "default": ""
      },
      {
        "description": "\"__PLAN__\" input variable for the template",
        "type": "string",
        "title": "__PLAN__",
        "default": ""
      },
      {
        "type": "array",
        "items": {},
        "title": "__CHAT_HISTORY__"
      }
    ],
    "pre_rendering_transforms": null,
    "post_rendering_transforms": [
      {
        "component_type": "PluginRemoveEmptyNonUserMessageTransform",
        "id": "6eae1b8c-4319-4bd0-bd49-0828e82d16fd",
        "name": "removeemptynonusermessage_messagetransform",
        "description": null,
        "metadata": {
          "__metadata_info__": {}
        },
        "component_plugin_name": "MessageTransformPlugin",
        "component_plugin_version": "25.4.0.dev0"
      }
    ],
    "tools": null,
    "native_tool_calling": true,
    "response_format": null,
    "native_structured_generation": true,
    "generation_config": null,
    "component_plugin_name": "PromptTemplatePlugin",
    "component_plugin_version": "25.4.0.dev0"
  },
  "component_plugin_name": "AgentPlugin",
  "component_plugin_version": "25.4.0.dev0",
  "agentspec_version": "25.4.1"
}

You can then load the configuration back to an assistant using the AgentSpecLoader.

from wayflowcore.agentspec import AgentSpecLoader

tool_registry = {
    multiply.name: multiply,
    divide.name: divide,
    sum.name: sum,
    subtract.name: subtract,
}
new_agent = AgentSpecLoader(tool_registry=tool_registry).load_json(config)

Note

This guide uses the following extension/plugin Agent Spec components:

  • PluginPromptTemplate

  • PluginRemoveEmptyNonUserMessageTransform

  • ExtendedAgent

See the list of available Agent Spec extension/plugin components in the API Reference

Using OpenTelemetry SpanProcessors#

OpenTelemetry is an open-source observability framework that provides standardized APIs and libraries to collect, process, and export telemetry data from distributed systems. This standard is agnostic with respect to the domain of application, so it can be easily adopted also for tracing in agentic frameworks.

Tracing in WayFlow is largely inspired by the OpenTelemetry standard, therefore most of the concepts and APIs overlap. For this reason, wayflowcore offers the implementation of two SpanProcessors that follow the OpenTelemetry standard:

These span processors wrap the OpenTelemetry implementation, transform WayFlow spans into OpenTelemetry ones, and emulate the expected behavior of the processor. Moreover, they allow using OpenTelemetry compatible SpanExporter, like, for example, those offered by the OpenTelemetry Exporters library.

Next steps#

Now that you’ve learned tracing in WayFlow, you might want to apply it in other scenarios:

Full code#

Click on the card at the top of this page to download the full code for this guide or copy the code below.

  1# Copyright © 2025 Oracle and/or its affiliates.
  2#
  3# This software is under the Universal Permissive License
  4# %%[markdown]
  5# Code Example - How to Enable Tracing
  6# ------------------------------------
  7
  8# How to use:
  9# Create a new Python virtual environment and install the latest WayFlow version.
 10# ```bash
 11# python -m venv venv-wayflowcore
 12# source venv-wayflowcore/bin/activate
 13# pip install --upgrade pip
 14# pip install "wayflowcore==26.1" 
 15# ```
 16
 17# You can now run the script
 18# 1. As a Python file:
 19# ```bash
 20# python howto_tracing.py
 21# ```
 22# 2. As a Notebook (in VSCode):
 23# When viewing the file,
 24#  - press the keys Ctrl + Enter to run the selected cell
 25#  - or Shift + Enter to run the selected cell and move to the cell below# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl) or Apache License
 26# 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0), at your option.
 27
 28import logging
 29import warnings
 30
 31warnings.filterwarnings("ignore")
 32logging.basicConfig(level=logging.CRITICAL)
 33
 34
 35# %%[markdown]
 36## Span Exporter Setup
 37
 38# %%
 39import pprint
 40from pathlib import Path
 41from typing import List, Union
 42
 43from wayflowcore.tracing.span import Span
 44from wayflowcore.tracing.spanexporter import SpanExporter
 45
 46class FileSpanExporter(SpanExporter):
 47    """SpanExporter that prints spans to a file.
 48
 49    This class can be used for diagnostic purposes.
 50    It prints the exported spans to a file.
 51    """
 52
 53    def __init__(self, filepath: Union[str, Path]):
 54        if isinstance(filepath, str):
 55            filepath = Path(filepath)
 56        self.filepath: Path = filepath
 57
 58    def export(self, spans: List[Span]) -> None:
 59        with open(self.filepath, "a") as file:
 60            for span in spans:
 61                print(
 62                    pprint.pformat(span.to_tracing_info(), width=80, compact=True),
 63                    file=file,
 64                )
 65
 66    def force_flush(self, timeout_millis: int = 30000) -> bool:
 67        return True
 68
 69    def startup(self) -> None:
 70        pass
 71
 72    def shutdown(self) -> None:
 73        pass
 74
 75# %%[markdown]
 76## Build Calculator Agent
 77
 78# %%
 79from wayflowcore.agent import Agent
 80from wayflowcore.models import VllmModel
 81from wayflowcore.tools import tool
 82
 83@tool(description_mode="only_docstring")
 84def multiply(a: float, b: float) -> float:
 85    """Multiply two numbers"""
 86    return a * b
 87
 88
 89@tool(description_mode="only_docstring")
 90def divide(a: float, b: float) -> float:
 91    """Divide two numbers"""
 92    return a / b
 93
 94
 95@tool(description_mode="only_docstring")
 96def sum(a: float, b: float) -> float:
 97    """Sum two numbers"""
 98    return a + b
 99
100
101@tool(description_mode="only_docstring")
102def subtract(a: float, b: float) -> float:
103    """Subtract two numbers"""
104    return a - b
105
106
107llm = VllmModel(
108    model_id="LLAMA_MODEL_ID",
109    host_port="LLAMA_API_URL",
110)
111
112agent = Agent(
113    agent_id="calculator_agent",
114    name="Calculator agent",
115    custom_instruction="You are a calculator agent. Please use tools to do math.",
116    initial_message="Hi! I am a calculator agent. How can I help you?",
117    llm=llm,
118    tools=[sum, subtract, multiply, divide],
119)
120
121# %%[markdown]
122## Export Config to Agent Spec
123
124# %%
125from wayflowcore.agentspec import AgentSpecExporter
126
127config = AgentSpecExporter().to_json(agent)
128
129# %%[markdown]
130## Load Agent Spec Config
131
132# %%
133from wayflowcore.agentspec import AgentSpecLoader
134
135tool_registry = {
136    multiply.name: multiply,
137    divide.name: divide,
138    sum.name: sum,
139    subtract.name: subtract,
140}
141new_agent = AgentSpecLoader(tool_registry=tool_registry).load_json(config)
142
143# %%[markdown]
144## Tracing Basics
145
146# %%
147from wayflowcore.tracing.spanprocessor import SimpleSpanProcessor
148
149span_processor = SimpleSpanProcessor(
150    span_exporter=FileSpanExporter(filepath="calculator_agent_traces.txt")
151)
152
153# %%[markdown]
154## Agent Execution With Tracing
155
156# %%
157from wayflowcore.tracing.span import ConversationSpan
158from wayflowcore.tracing.trace import Trace
159
160conversation = agent.start_conversation()
161with Trace(span_processors=[span_processor]):
162    with ConversationSpan(conversation=conversation) as conversation_span:
163        conversation.execute()
164        conversation.append_user_message("Compute 2+3")
165        status = conversation.execute()
166        conversation_span.record_end_span_event(execution_status=status)