How to Use the Event System#
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",
),
)
from wayflowcore.models import VllmModel
llm = VllmModel(
model_id="model-id",
host_port="VLLM_HOST_PORT",
)
from wayflowcore.models import OllamaModel
llm = OllamaModel(
model_id="model-id",
)
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:
Event Name |
Description |
|---|---|
Base event class containing information relevant to all events. |
|
Recorded when the LLM receives a generation request. |
|
Recorded when the LLM generates a response. |
|
Recorded when the agent/flow execution has started. |
|
Recorded when the agent/flow execution has ended. |
|
Recorded whenever a new conversation with an agent or a flow was created. |
|
Recorded whenever a new message was added to the conversation. |
|
Recorded whenever a new message starts being streamed to the conversation. |
|
Recorded whenever a message is being streamed and a delta is added to the conversation. |
|
Recorded whenever a streamed message to the conversation ends. |
|
Recorded whenever a conversation is started. |
|
Recorded whenever a conversation execution finishes. |
|
Recorded whenever a tool is executed. |
|
Recorded whenever a tool has finished execution. |
|
Recorded whenever a tool confirmation is required. |
|
Recorded whenever a tool confirmation has been handled. |
|
Recorded whenever a step is invoked. |
|
Recorded whenever a step invocation has finished. |
|
Recorded whenever a context provider is called. |
|
Recorded whenever a context provider has returned a result. |
|
Recorded whenever an iteration of a flow has started executing. |
|
Recorded whenever an iteration of a flow has finished executing. |
|
Recorded whenever an iteration of an agent has started executing. |
|
Recorded whenever an iteration of an agent has finished executing. |
|
Recorded whenever an exception occurs. |
|
Recorded at the start of the agent taking a decision on what to do next. |
|
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"
}
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: 45667ebd-800f-4a0e-8140-f6ccd06556e8
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)