How to Use the Event System#

python-icon Download Python Script

Python script/notebook for this guide.

Event System how-to script

Prerequisites

This guide assumes familiarity with:

The event system in WayFlow provides a powerful framework for monitoring and debugging agents and flows. By capturing detailed runtime data through structured events, it offers deep insights into interactions between agents, flows, tools, and LLMs.

This guide introduces the core concepts of the event system, describes available event types and listeners, and provides practical examples for effective implementation.

At its heart, WayFlow’s event system records and communicates key occurrences during an execution. Each event is a structured data object that captures details of a specific action or state change, such as starting a conversation, executing a tool, or generating an LLM response. Events include metadata like unique identifiers, timestamps, and relevant contextual information.

The system follows a publish-subscribe model: events are published as they occur, and components called listeners subscribe to receive and react to them. This separation of event generation and handling allows developers to add custom behaviors or logging without altering the core logic of agents or flows.

Basic Implementation#

To use the event system effectively, you need to understand its two main components: Events and EventListeners.

  • Events are data structures that represent occurrences within WayFlow, organized into different types for various scenarios.

  • EventListeners are components that react to these published events.

Let’s explore this with two practical examples:

Example 1: Computing LLM Token Usage#

A key use of the event system is tracking resource consumption, such as monitoring token usage during LLM interactions. Since token usage affects operational costs, this data can inform prompt and model optimization. By subscribing to LLM response events, developers can aggregate and analyze token usage across a conversation.

class TokenUsageListener(EventListener):
    """Custom event listener to track token usage from LLM responses."""
    def __init__(self):
        self.total_tokens_used = 0

    def __call__(self, event: Event):
        if isinstance(event, LlmGenerationResponseEvent):
            token_usage = event.completion.token_usage
            if token_usage:
                self.total_tokens_used += token_usage.total_tokens
                logging.info(f"Tokens used in this response: {token_usage.total_tokens}")
                logging.info(f"Running total tokens used: {self.total_tokens_used}")

    def get_total_tokens_used(self):
        """Return the total number of tokens used."""
        return self.total_tokens_used

In this example, TokenUsageListener is a custom listener that calculates total token usage by summing the tokens reported in each LlmGenerationResponseEvent.

Example 2: Tracking Tool Calls#

Another useful application is monitoring tool invocations within an agentic workflow. Understanding which tools are used and their frequency helps developers evaluate the effectiveness of their toolset and identify opportunities for improvement. By listening to tool execution events, you can log each call and track usage patterns.

class ToolCallListener(EventListener):
    """Custom event listener to track the number and type of tool calls."""
    def __init__(self):
        self.tool_calls = defaultdict(int)

    def __call__(self, event: ToolExecutionStartEvent):
        if isinstance(event, ToolExecutionStartEvent):
            self.tool_calls[str(event.tool.name)] += 1

    def get_tool_call_summary(self):
        """Return a summary of tool calls."""
        return self.tool_calls

This snippet illustrates how to create a ToolCallListener to track tool invocations using ToolExecutionStartEvent.

With both listeners implemented, let’s apply them in a conversation with an Agent.

For LLMs, WayFlow supports multiple API providers. Select an LLM from the options below:

from wayflowcore.models import OCIGenAIModel, OCIClientConfigWithApiKey

llm = OCIGenAIModel(
    model_id="provider.model-id",
    compartment_id="compartment-id",
    client_config=OCIClientConfigWithApiKey(
        service_endpoint="https://url-to-service-endpoint.com",
    ),
)

Now, let’s set up an agent:

@tool(description_mode="only_docstring")
def add(a: float, b: float) -> float:
    """Add two numbers.

    Parameters:
        a: The first number.
        b: The second number.

    Returns:
        float: The sum of the two numbers.
    """
    return a + b

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

    Parameters:
        a: The first number.
        b: The second number.

    Returns:
        float: The product of the two numbers.
    """
    return a * b

agent = Agent(llm=llm, tools=[add, multiply], name="Calculator Agent")

Using the agent in a conversation:

token_listener = TokenUsageListener()
tool_call_listener = ToolCallListener()

event_listeners = [token_listener, tool_call_listener]

with register_event_listeners(event_listeners):
    conversation = agent.start_conversation()
    conversation.append_user_message("Calculate 6*2+3 using the tools you have.")
    status = conversation.execute()

print(f"Total Tokens Used in Conversation: {token_listener.get_total_tokens_used()}")
tool_summary = tool_call_listener.get_tool_call_summary()
print(f"Tool Call Summary: {tool_summary}")

Both listeners are registered within a context manager using register_event_listeners during agent execution, ensuring they capture all relevant events.

Beyond the events highlighted here, WayFlow offers a wide range of events for detailed monitoring. Below is a table explaining various types of events in Wayflow:

WayFlow Event Types#

Event Name

Description

Event

Base event class containing information relevant to all events.

LlmGenerationRequestEvent

Recorded when the LLM receives a generation request.

LlmGenerationResponseEvent

Recorded when the LLM generates a response.

ConversationalComponentExecutionStartedEvent

Recorded when the agent/flow execution has started.

ConversationalComponentExecutionFinishedEvent

Recorded when the agent/flow execution has ended.

ConversationCreatedEvent

Recorded whenever a new conversation with an agent or a flow was created.

ConversationMessageAddedEvent

Recorded whenever a new message was added to the conversation.

ConversationMessageStreamStartedEvent

Recorded whenever a new message starts being streamed to the conversation.

ConversationMessageStreamChunkEvent

Recorded whenever a message is being streamed and a delta is added to the conversation.

ConversationMessageStreamEndedEvent

Recorded whenever a streamed message to the conversation ends.

ConversationExecutionStartedEvent

Recorded whenever a conversation is started.

ConversationExecutionFinishedEvent

Recorded whenever a conversation execution finishes.

ToolExecutionStartEvent

Recorded whenever a tool is executed.

ToolExecutionResultEvent

Recorded whenever a tool has finished execution.

ToolConfirmationRequestStartEvent

Recorded whenever a tool confirmation is required.

ToolConfirmationRequestEndEvent

Recorded whenever a tool confirmation has been handled.

StepInvocationStartEvent

Recorded whenever a step is invoked.

StepInvocationResultEvent

Recorded whenever a step invocation has finished.

ContextProviderExecutionRequestEvent

Recorded whenever a context provider is called.

ContextProviderExecutionResultEvent

Recorded whenever a context provider has returned a result.

FlowExecutionIterationStartedEvent

Recorded whenever an iteration of a flow has started executing.

FlowExecutionIterationFinishedEvent

Recorded whenever an iteration of a flow has finished executing.

AgentExecutionIterationStartedEvent

Recorded whenever an iteration of an agent has started executing.

AgentExecutionIterationFinishedEvent

Recorded whenever an iteration of an agent has finished executing.

ExceptionRaisedEvent

Recorded whenever an exception occurs.

AgentNextActionDecisionStartEvent

Recorded at the start of the agent taking a decision on what to do next.

AgentDecidedNextActionEvent

Recorded whenever the agent decided what to do next.

See Events for more information. You can implement custom EventListener for these events as shown in the examples above.

Agent Spec Exporting/Loading#

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

from wayflowcore.agentspec import AgentSpecExporter

serialized_assistant = 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": "3ede6823-de48-4f1d-9453-ae6d259d3de8",
    "name": "Calculator Agent",
    "description": "",
    "metadata": {
        "__metadata_info__": {}
    },
    "inputs": [],
    "outputs": [],
    "llm_config": {
        "component_type": "VllmConfig",
        "id": "903495a0-9333-484b-b875-3e0405f3ecc6",
        "name": "llm_86037e63__auto",
        "description": null,
        "metadata": {
            "__metadata_info__": {}
        },
        "default_generation_parameters": null,
        "url": "LLAMA_API_URL",
        "model_id": "LLAMA_MODEL_ID"
    },
    "system_prompt": "",
    "tools": [
        {
            "component_type": "ServerTool",
            "id": "4907e7b5-7a30-48b0-9911-5f674e2a4ff2",
            "name": "add",
            "description": "Add two numbers.\n\nParameters:\n    a: The first number.\n    b: The second number.\n\nReturns:\n    float: The sum of the two numbers.",
            "metadata": {
                "__metadata_info__": {}
            },
            "inputs": [
                {
                    "type": "number",
                    "title": "a"
                },
                {
                    "type": "number",
                    "title": "b"
                }
            ],
            "outputs": [
                {
                    "type": "number",
                    "title": "tool_output"
                }
            ]
        },
        {
            "component_type": "ServerTool",
            "id": "c9c2a079-b34e-44ac-93d8-864fc3ff3f87",
            "name": "multiply",
            "description": "Multiply two numbers.\n\nParameters:\n    a: The first number.\n    b: The second number.\n\nReturns:\n    float: The product of the two numbers.",
            "metadata": {
                "__metadata_info__": {}
            },
            "inputs": [
                {
                    "type": "number",
                    "title": "a"
                },
                {
                    "type": "number",
                    "title": "b"
                }
            ],
            "outputs": [
                {
                    "type": "number",
                    "title": "tool_output"
                }
            ]
        }
    ],
    "toolboxes": [],
    "human_in_the_loop": true,
    "context_providers": null,
    "can_finish_conversation": false,
    "raise_exceptions": false,
    "max_iterations": 10,
    "initial_message": "Hi! How can I help you?",
    "caller_input_mode": "always",
    "agents": [],
    "flows": [],
    "agent_template": {
        "component_type": "PluginPromptTemplate",
        "id": "f05c7228-c45d-4605-90eb-89e66e4fe7da",
        "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": "2026-01-06T10:47:26.111071+00:00",
                "time_updated": "2026-01-06T10:47:26.111071+00:00"
            },
            {
                "role": "system",
                "contents": [
                    {
                        "type": "text",
                        "content": "$$__CHAT_HISTORY_PLACEHOLDER__$$"
                    }
                ],
                "tool_requests": null,
                "tool_result": null,
                "display_only": false,
                "sender": null,
                "recipients": [],
                "time_created": "2026-01-06T10:47:26.106776+00:00",
                "time_updated": "2026-01-06T10:47:26.106777+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": "2026-01-06T10:47:26.111096+00:00",
                "time_updated": "2026-01-06T10:47:26.111096+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": "0a2cc909-11bc-4533-8ff1-2867938f4bb8",
                "name": "removeemptynonusermessage_messagetransform",
                "description": null,
                "metadata": {
                    "__metadata_info__": {}
                },
                "component_plugin_name": "MessageTransformPlugin",
                "component_plugin_version": "26.1.0.dev5"
            }
        ],
        "tools": null,
        "native_tool_calling": true,
        "response_format": null,
        "native_structured_generation": true,
        "generation_config": null,
        "component_plugin_name": "PromptTemplatePlugin",
        "component_plugin_version": "26.1.0.dev5"
    },
    "component_plugin_name": "AgentPlugin",
    "component_plugin_version": "26.1.0.dev5",
    "agentspec_version": "25.4.1"
}

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

from wayflowcore.agentspec import AgentSpecLoader

agent = AgentSpecLoader(tool_registry={'add': add, 'multiply': multiply}).load_json(serialized_assistant)

Next Steps#

After exploring the event system in WayFlow, consider learning more about related features to further enhance your agentic applications:

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 Apache License 2.0
  4# %%[markdown]
  5# Code Example - How to Use the Event System
  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_event_system.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# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
 26# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.
 27
 28
 29from collections import defaultdict
 30import logging
 31
 32from wayflowcore.events.event import Event, LlmGenerationResponseEvent, ToolExecutionStartEvent
 33from wayflowcore.events.eventlistener import EventListener, register_event_listeners
 34from wayflowcore.models import VllmModel
 35from wayflowcore.agent import Agent
 36from wayflowcore.tools import tool
 37
 38
 39# %%[markdown]
 40## TokenUsage
 41
 42# %%
 43class TokenUsageListener(EventListener):
 44    """Custom event listener to track token usage from LLM responses."""
 45    def __init__(self):
 46        self.total_tokens_used = 0
 47
 48    def __call__(self, event: Event):
 49        if isinstance(event, LlmGenerationResponseEvent):
 50            token_usage = event.completion.token_usage
 51            if token_usage:
 52                self.total_tokens_used += token_usage.total_tokens
 53                logging.info(f"Tokens used in this response: {token_usage.total_tokens}")
 54                logging.info(f"Running total tokens used: {self.total_tokens_used}")
 55
 56    def get_total_tokens_used(self):
 57        """Return the total number of tokens used."""
 58        return self.total_tokens_used
 59
 60
 61# %%[markdown]
 62## Tool Call Listener
 63
 64# %%
 65class ToolCallListener(EventListener):
 66    """Custom event listener to track the number and type of tool calls."""
 67    def __init__(self):
 68        self.tool_calls = defaultdict(int)
 69
 70    def __call__(self, event: ToolExecutionStartEvent):
 71        if isinstance(event, ToolExecutionStartEvent):
 72            self.tool_calls[str(event.tool.name)] += 1
 73
 74    def get_tool_call_summary(self):
 75        """Return a summary of tool calls."""
 76        return self.tool_calls
 77
 78llm = VllmModel(
 79    model_id="LLAMA_MODEL_ID",
 80    host_port="LLAMA_API_URL",
 81)
 82
 83
 84# %%[markdown]
 85## Agent
 86
 87# %%
 88@tool(description_mode="only_docstring")
 89def add(a: float, b: float) -> float:
 90    """Add two numbers.
 91
 92    Parameters:
 93        a: The first number.
 94        b: The second number.
 95
 96    Returns:
 97        float: The sum of the two numbers.
 98    """
 99    return a + b
100
101@tool(description_mode="only_docstring")
102def multiply(a: float, b: float) -> float:
103    """Multiply two numbers.
104
105    Parameters:
106        a: The first number.
107        b: The second number.
108
109    Returns:
110        float: The product of the two numbers.
111    """
112    return a * b
113
114agent = Agent(llm=llm, tools=[add, multiply], name="Calculator Agent")
115
116
117
118# %%[markdown]
119## Conversation
120
121# %%
122token_listener = TokenUsageListener()
123tool_call_listener = ToolCallListener()
124
125event_listeners = [token_listener, tool_call_listener]
126
127with register_event_listeners(event_listeners):
128    conversation = agent.start_conversation()
129    conversation.append_user_message("Calculate 6*2+3 using the tools you have.")
130    status = conversation.execute()
131
132print(f"Total Tokens Used in Conversation: {token_listener.get_total_tokens_used()}")
133tool_summary = tool_call_listener.get_tool_call_summary()
134print(f"Tool Call Summary: {tool_summary}")
135
136
137# %%[markdown]
138## Export config to Agent Spec
139
140# %%
141from wayflowcore.agentspec import AgentSpecExporter
142
143serialized_assistant = AgentSpecExporter().to_json(agent)
144
145
146# %%[markdown]
147## Load Agent Spec config
148
149# %%
150from wayflowcore.agentspec import AgentSpecLoader
151
152agent = AgentSpecLoader(tool_registry={'add': add, 'multiply': multiply}).load_json(serialized_assistant)