How to Create New WayFlow Components#
WayFlow Plugins are the expected mean that users can use to introduce new concepts and components, or extensions to existing ones, such that they can be integrated seamlessly into the serialization, deserialization, and Agent Spec conversion processes of WayFlow.
In this guide, you will learn how to build a WayFlow plugin to introduce a new custom component in WayFlow, and make sure that it can be serialized, deserialized, and converted to Agent Spec.
You are going to build a specialized ServerTool that reads the content of a file given its path. Then we are going to use this tool as part of an Agent, build the plugins, and show how to use them for WayFlow’s serialization and Agent Spec conversion.
Basic usage#
As first step, we build our new tool extension. We call it ReadFileTool, and we give it an attribute
to specify which file extensions are allowed to be read. The tool implementation in this example is mocked,
and it just returns the content of two predefined filepaths.
from typing import Any, Callable, Dict
from wayflowcore.property import StringProperty
from wayflowcore.serialization.context import DeserializationContext, SerializationContext
from wayflowcore.serialization.serializer import SerializableObject
from wayflowcore.tools import ServerTool
class ReadFileTool(ServerTool):
def __init__(self, allowed_extensions: list[str] | None = None):
self.allowed_extensions = allowed_extensions or [".txt"]
super().__init__(
name="read_file_tool",
description="Read the content of a file",
func=self._get_read_file_function(),
input_descriptors=[StringProperty(name="file_path")],
output_descriptors=[StringProperty(name="file_content")],
)
def _get_read_file_function(self) -> Callable[[str], Any]:
def read_file(file_path: str) -> Any:
# We mock the implementation for this example
if not any(file_path.endswith(allowed_extension) for allowed_extension in self.allowed_extensions):
return "Unsupported file extension"
return {
"movies.txt": "According to IMDB, the best movie of all time is `The Shawshank Redemption`",
"videogames.txt": "According to IGN, the best videogame of all time is `The Legend of Zelda: Breath of the Wild.`",
}.get(file_path, "File not found")
return read_file
def _serialize_to_dict(self, serialization_context: SerializationContext) -> Dict[str, Any]:
# We return the dictionary of all the elements that we need in order to reconstruct the same exact instance
# once we deserialize back. The dictionary values should be only serialized attributes, it should not contain
# instances of objects that are not serializable. Developers can use serialization methods exposed by the
# wayflowcore.serialization.serializer module to serialize complex objects.
return {"allowed_extensions": self.allowed_extensions}
@classmethod
def _deserialize_from_dict(cls, input_dict: Dict[str, Any], deserialization_context: DeserializationContext) -> SerializableObject:
# We return an instance of this class built with the information we serialized. Developers can use deserialization
# methods exposed by the wayflowcore.serialization.serializer module to deserialize attributes having complex classes.
return ReadFileTool(input_dict.get("allowed_extensions", None))
In this example we extended Tool, but a new component that does not inherit from existing concepts can be created,
and it can be connected to the serialization mechanism by simply extending the SerializableObject interface.
Note that there are also extensions of the SerializableObject class, like the SerializableDataclass,
which offers basic serialization implementation for dataclass annotated classes.
We can now build our agent: we select the LLM we want to use to orchestrate it, write custom instructions to inform it about which files are available, and give it instructions to read them if needed.
from wayflowcore import Agent
from wayflowcore.models import VllmModel
llm = VllmModel(
model_id="LLAMA_MODEL_ID",
host_port="LLAMA_API_URL",
)
custom_instructions = """
You have the following files available:
- movies.txt
- videogames.txt
Read the content of the right file - based on the user query - with the tool, and answer the user.
"""
assistant = Agent(
custom_instruction=custom_instructions,
tools=[ReadFileTool()],
llm=llm,
)
As already mentioned at the beginning of this guide, WayFlow plugins take also care of the Agent Spec conversion.
Therefore, in order to create complete WayFlow plugins, we have to create the new tool also in Agent Spec.
To do that, we extend the ServerTool implementation from pyagentspec and we add the allowed_extensions attribute.
Then we create the Agent Spec plugins that will take care of the serialization/deserialization to/from Agent Spec.
from typing import Optional
from pydantic import Field
from pyagentspec.tools import ServerTool as AgentSpecServerTool
class AgentSpecReadFileTool(AgentSpecServerTool):
allowed_extensions: Optional[list[str]] = Field(default_factory=lambda: [".txt"])
from pyagentspec.serialization.pydanticserializationplugin import PydanticComponentSerializationPlugin
from pyagentspec.serialization.pydanticdeserializationplugin import PydanticComponentDeserializationPlugin
agentspec_read_file_tool_serialization_plugin = PydanticComponentSerializationPlugin({"AgentSpecReadFileTool": AgentSpecReadFileTool})
agentspec_read_file_tool_deserialization_plugin = PydanticComponentDeserializationPlugin({"AgentSpecReadFileTool": AgentSpecReadFileTool})
Now that the Agent Spec plugins are ready, we can use them in our WayFlow plugins for the ReadFileTool. We build the WayflowSerializationPlugin and the WayflowDeserializationPlugin by specifying a plugin name, a version, the Agent Spec plugins (if any) that are required to serialize and deserialize the new Tool, and implement the conversion logic for it.
from pyagentspec import Component as AgentSpecComponent
from pyagentspec.property import Property as AgentSpecProperty
from pyagentspec.serialization import ComponentSerializationPlugin, ComponentDeserializationPlugin
from wayflowcore.agentspec._agentspecconverter import WayflowToAgentSpecConversionContext
from wayflowcore.agentspec._runtimeconverter import AgentSpecToWayflowConversionContext
from wayflowcore.serialization.plugins import ToolRegistryT, WayflowSerializationPlugin, WayflowDeserializationPlugin
from wayflowcore.serialization.serializer import SerializableObject
plugin_name = "WayflowReadFileTool"
plugin_version = "1.0.0"
class WayflowReadFileToolSerializationPlugin(WayflowSerializationPlugin):
@property
def plugin_name(self) -> str:
return plugin_name
@property
def plugin_version(self) -> str:
return plugin_version
@property
def supported_component_types(self) -> list[str]:
return ["ReadFileTool"]
@property
def required_agentspec_serialization_plugins(self) -> list[ComponentSerializationPlugin]:
return [agentspec_read_file_tool_serialization_plugin]
def convert_to_agentspec(
self,
conversion_context: "WayflowToAgentSpecConversionContext",
runtime_component: SerializableObject,
referenced_objects: dict[str, AgentSpecComponent],
) -> AgentSpecComponent:
return AgentSpecReadFileTool(
id=runtime_component.id,
name=runtime_component.name,
description=runtime_component.description,
allowed_extensions=runtime_component.allowed_extensions,
metadata=runtime_component.__metadata_info__,
inputs=[AgentSpecProperty(json_schema=p.to_json_schema()) for p in runtime_component.input_descriptors],
outputs=[AgentSpecProperty(json_schema=p.to_json_schema()) for p in runtime_component.output_descriptors],
)
class WayflowReadFileToolDeserializationPlugin(WayflowDeserializationPlugin):
@property
def plugin_name(self) -> str:
return plugin_name
@property
def plugin_version(self) -> str:
return plugin_version
@property
def supported_component_types(self) -> list[str]:
return ["ReadFileTool"]
@property
def required_agentspec_deserialization_plugins(self) -> list[ComponentDeserializationPlugin]:
return [agentspec_read_file_tool_deserialization_plugin]
def convert_to_wayflow(
self,
conversion_context: "AgentSpecToWayflowConversionContext",
agentspec_component: AgentSpecComponent,
tool_registry: ToolRegistryT,
converted_components: dict[str, Any],
) -> Any:
return ReadFileTool(agentspec_component.allowed_extensions)
Now that we have built the plugins, we can use them in serialization and deserialization.
from wayflowcore.serialization.serializer import serialize, autodeserialize
serialized_assistant = serialize(assistant, plugins=[WayflowReadFileToolSerializationPlugin()])
deserialized_assistant = autodeserialize(serialized_assistant, plugins=[WayflowReadFileToolDeserializationPlugin()])
Finally, we can try to run our agent and ask it some information contained in a file.
conversation = assistant.start_conversation()
status = conversation.execute()
assistant_reply = conversation.get_last_message()
print("\nAssistant >>>", assistant_reply.content)
conversation.append_user_message("What's the best movie")
status = conversation.execute()
assistant_reply = conversation.get_last_message()
print("\nAssistant >>>", assistant_reply.content)
# Example of conversation:
# Assistant >>> Hi! How can I help you?
# User >>> What's the best movie
# Assistant >>> According to IMDB, the best movie of all time is The Shawshank Redemption
Agent Spec Exporting/Loading#
You can export the assistant configuration to its Agent Spec configuration using the AgentSpecExporter.
from wayflowcore.agentspec import AgentSpecExporter
config = AgentSpecExporter(plugins=[WayflowReadFileToolSerializationPlugin()]).to_json(assistant)
Here is what the Agent Spec representation will look like ↓
Click here to see the assistant configuration.
{
"component_type": "ExtendedAgent",
"id": "9602dfd1-77aa-42c7-8ebb-097cbdf6eb29",
"name": "agent_ed163a03__auto",
"description": "",
"metadata": {
"__metadata_info__": {}
},
"inputs": [],
"outputs": [],
"llm_config": {
"component_type": "VllmConfig",
"id": "c1cc758f-c022-4c9c-88d4-ac6cc027a897",
"name": "llm_2609a7b7__auto",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"default_generation_parameters": null,
"url": "MODEL-URL",
"model_id": "MODEL-ID"
},
"system_prompt": "\nYou have the following files available:\n- movies.txt\n- videogames.txt\n\nRead the content of the right file - based on the user query - with the tool, and answer the user.\n",
"tools": [
{
"component_type": "AgentSpecReadFileTool",
"id": "f1097ec5-343d-434a-b599-39b8c8ec9c76",
"name": "read_file_tool",
"description": "Read the content of a file",
"metadata": {},
"inputs": [
{
"type": "string",
"title": "file_path"
}
],
"outputs": [
{
"type": "string",
"title": "file_content"
}
],
"allowed_extensions": [
".txt"
],
"component_plugin_name": "PydanticComponentPlugin",
"component_plugin_version": "26.1.0.dev0"
}
],
"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": "af4f8ec3-ac2c-43fc-852b-6be2f48f55cb",
"name": "",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"messages": [
{
"role": "system",
"contents": [
{
"type": "text",
"content": "{%- if __TOOLS__ -%}\nEnvironment: ipython\nCutting Knowledge Date: December 2023\n\nYou are a helpful assistant with tool calling capabilities. Only reply with a tool call if the function exists in the library provided by the user. If it doesn't exist, just reply directly in natural language. When you receive a tool call response, use the output to format an answer to the original user question.\n\nYou have access to the following functions. To call a function, please respond with JSON for a function call.\nRespond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}.\nDo not use variables.\n\n[{% for tool in __TOOLS__%}{{tool.to_openai_format() | tojson}}{{', ' if not loop.last}}{% endfor %}]\n{%- endif -%}\n"
}
],
"tool_requests": null,
"tool_result": null,
"display_only": false,
"sender": null,
"recipients": [],
"time_created": "2025-11-18T18:54:31.910732+00:00",
"time_updated": "2025-11-18T18:54:31.910732+00:00"
},
{
"role": "system",
"contents": [
{
"type": "text",
"content": "{%- if custom_instruction -%}Additional instructions:\n{{custom_instruction}}{%- endif -%}"
}
],
"tool_requests": null,
"tool_result": null,
"display_only": false,
"sender": null,
"recipients": [],
"time_created": "2025-11-18T18:54:31.910753+00:00",
"time_updated": "2025-11-18T18:54:31.910753+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": "2025-11-18T18:54:31.908396+00:00",
"time_updated": "2025-11-18T18:54:31.908397+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-11-18T18:54:31.910769+00:00",
"time_updated": "2025-11-18T18:54:31.910769+00:00"
}
],
"output_parser": {
"component_type": "PluginJsonToolOutputParser",
"id": "9d783ae5-68db-4015-90cd-7becff0e1ad6",
"name": "jsontool_outputparser",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"tools": null,
"component_plugin_name": "OutputParserPlugin",
"component_plugin_version": "26.1.0.dev0"
},
"inputs": [
{
"description": "\"__TOOLS__\" input variable for the template",
"title": "__TOOLS__"
},
{
"description": "\"custom_instruction\" input variable for the template",
"type": "string",
"title": "custom_instruction"
},
{
"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": "c1b5c055-4e6a-47a2-9a89-49b77132aff6",
"name": "removeemptynonusermessage_messagetransform",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"component_plugin_name": "MessageTransformPlugin",
"component_plugin_version": "26.1.0.dev0"
},
{
"component_type": "PluginCoalesceSystemMessagesTransform",
"id": "292f22e0-2b83-4a5c-9c99-0a9eb5d04639",
"name": "coalescesystemmessage_messagetransform",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"component_plugin_name": "MessageTransformPlugin",
"component_plugin_version": "26.1.0.dev0"
},
{
"component_type": "PluginLlamaMergeToolRequestAndCallsTransform",
"id": "199c50af-30f0-4d8d-a50c-86afdccaf089",
"name": "llamamergetoolrequestandcalls_messagetransform",
"description": null,
"metadata": {
"__metadata_info__": {}
},
"component_plugin_name": "MessageTransformPlugin",
"component_plugin_version": "26.1.0.dev0"
}
],
"tools": null,
"native_tool_calling": false,
"response_format": null,
"native_structured_generation": true,
"generation_config": null,
"component_plugin_name": "PromptTemplatePlugin",
"component_plugin_version": "26.1.0.dev0"
},
"component_plugin_name": "AgentPlugin",
"component_plugin_version": "26.1.0.dev0",
"agentspec_version": "25.4.1"
}
component_type: ExtendedAgent
id: a5b53483-10ab-48ff-bc1d-562c89ab89ab
name: agent_514df23e__auto
description: ''
metadata:
__metadata_info__: {}
inputs: []
outputs: []
llm_config:
component_type: VllmConfig
id: e67b1ce6-bd21-4a33-a407-8defa56c867e
name: llm_f2262eaa__auto
description: null
metadata:
__metadata_info__: {}
default_generation_parameters: null
url: MODEL-URL
model_id: MODEL-ID
system_prompt: '
You have the following files available:
- movies.txt
- videogames.txt
Read the content of the right file - based on the user query - with the tool, and
answer the user.
'
tools:
- component_type: AgentSpecReadFileTool
id: a8f4bd9d-ec84-4762-b063-b460cd6bd91e
name: read_file_tool
description: Read the content of a file
metadata: {}
inputs:
- type: string
title: file_path
outputs:
- type: string
title: file_content
allowed_extensions:
- .txt
component_plugin_name: PydanticComponentPlugin
component_plugin_version: 26.1.0.dev0
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: e3918256-4e22-4a94-bb73-6f530ac3a9bd
name: ''
description: null
metadata:
__metadata_info__: {}
messages:
- role: system
contents:
- type: text
content: '{%- if __TOOLS__ -%}
Environment: ipython
Cutting Knowledge Date: December 2023
You are a helpful assistant with tool calling capabilities. Only reply with
a tool call if the function exists in the library provided by the user. If
it doesn''t exist, just reply directly in natural language. When you receive
a tool call response, use the output to format an answer to the original user
question.
You have access to the following functions. To call a function, please respond
with JSON for a function call.
Respond in the format {"name": function name, "parameters": dictionary of
argument name and its value}.
Do not use variables.
[{% for tool in __TOOLS__%}{{tool.to_openai_format() | tojson}}{{'', '' if
not loop.last}}{% endfor %}]
{%- endif -%}
'
tool_requests: null
tool_result: null
display_only: false
sender: null
recipients: []
time_created: '2025-11-18T18:55:16.416935+00:00'
time_updated: '2025-11-18T18:55:16.416935+00:00'
- role: system
contents:
- type: text
content: '{%- if custom_instruction -%}Additional instructions:
{{custom_instruction}}{%- endif -%}'
tool_requests: null
tool_result: null
display_only: false
sender: null
recipients: []
time_created: '2025-11-18T18:55:16.416954+00:00'
time_updated: '2025-11-18T18:55:16.416954+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: '2025-11-18T18:55:16.414612+00:00'
time_updated: '2025-11-18T18:55:16.414612+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-11-18T18:55:16.416970+00:00'
time_updated: '2025-11-18T18:55:16.416970+00:00'
output_parser:
component_type: PluginJsonToolOutputParser
id: 58030d61-6b25-48e4-992d-556acfcbc6cd
name: jsontool_outputparser
description: null
metadata:
__metadata_info__: {}
tools: null
component_plugin_name: OutputParserPlugin
component_plugin_version: 26.1.0.dev0
inputs:
- description: '"__TOOLS__" input variable for the template'
title: __TOOLS__
- description: '"custom_instruction" input variable for the template'
type: string
title: custom_instruction
- 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: 3aef0150-1ea1-4b05-9074-92968f936306
name: removeemptynonusermessage_messagetransform
description: null
metadata:
__metadata_info__: {}
component_plugin_name: MessageTransformPlugin
component_plugin_version: 26.1.0.dev0
- component_type: PluginCoalesceSystemMessagesTransform
id: edbc25ed-0543-4bf4-97cb-be4908ae8b98
name: coalescesystemmessage_messagetransform
description: null
metadata:
__metadata_info__: {}
component_plugin_name: MessageTransformPlugin
component_plugin_version: 26.1.0.dev0
- component_type: PluginLlamaMergeToolRequestAndCallsTransform
id: cf79ec30-db91-4cef-92ba-e198c59e56dc
name: llamamergetoolrequestandcalls_messagetransform
description: null
metadata:
__metadata_info__: {}
component_plugin_name: MessageTransformPlugin
component_plugin_version: 26.1.0.dev0
tools: null
native_tool_calling: false
response_format: null
native_structured_generation: true
generation_config: null
component_plugin_name: PromptTemplatePlugin
component_plugin_version: 26.1.0.dev0
component_plugin_name: AgentPlugin
component_plugin_version: 26.1.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
new_agent = AgentSpecLoader(plugins=[WayflowReadFileToolDeserializationPlugin()]).load_json(config)
Note
This guide uses the following extension/plugin Agent Spec components:
AgentPluginMessageTransformPluginPromptTemplatePluginOutputParserPlugin
See the list of available Agent Spec extension/plugin components in the API Reference
Next steps#
Now that you have learned how to build WayFlow plugins, you may proceed to Load and Execute an Agent Spec Configuration.
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 Create New WayFlow Components
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_plugins.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
28import logging
29import warnings
30
31warnings.filterwarnings("ignore")
32logging.basicConfig(level=logging.CRITICAL)
33
34
35# %%[markdown]
36## Create the new tool to read a file
37
38# %%
39from typing import Any, Callable, Dict
40from wayflowcore.property import StringProperty
41from wayflowcore.serialization.context import DeserializationContext, SerializationContext
42from wayflowcore.serialization.serializer import SerializableObject
43from wayflowcore.tools import ServerTool
44
45class ReadFileTool(ServerTool):
46
47 def __init__(self, allowed_extensions: list[str] | None = None):
48 self.allowed_extensions = allowed_extensions or [".txt"]
49 super().__init__(
50 name="read_file_tool",
51 description="Read the content of a file",
52 func=self._get_read_file_function(),
53 input_descriptors=[StringProperty(name="file_path")],
54 output_descriptors=[StringProperty(name="file_content")],
55 )
56
57 def _get_read_file_function(self) -> Callable[[str], Any]:
58 def read_file(file_path: str) -> Any:
59 # We mock the implementation for this example
60 if not any(file_path.endswith(allowed_extension) for allowed_extension in self.allowed_extensions):
61 return "Unsupported file extension"
62 return {
63 "movies.txt": "According to IMDB, the best movie of all time is `The Shawshank Redemption`",
64 "videogames.txt": "According to IGN, the best videogame of all time is `The Legend of Zelda: Breath of the Wild.`",
65 }.get(file_path, "File not found")
66 return read_file
67
68 def _serialize_to_dict(self, serialization_context: SerializationContext) -> Dict[str, Any]:
69 # We return the dictionary of all the elements that we need in order to reconstruct the same exact instance
70 # once we deserialize back. The dictionary values should be only serialized attributes, it should not contain
71 # instances of objects that are not serializable. Developers can use serialization methods exposed by the
72 # wayflowcore.serialization.serializer module to serialize complex objects.
73 return {"allowed_extensions": self.allowed_extensions}
74
75 @classmethod
76 def _deserialize_from_dict(cls, input_dict: Dict[str, Any], deserialization_context: DeserializationContext) -> SerializableObject:
77 # We return an instance of this class built with the information we serialized. Developers can use deserialization
78 # methods exposed by the wayflowcore.serialization.serializer module to deserialize attributes having complex classes.
79 return ReadFileTool(input_dict.get("allowed_extensions", None))
80
81
82# %%[markdown]
83## Create the agent
84
85# %%
86from wayflowcore import Agent
87from wayflowcore.models import VllmModel
88
89llm = VllmModel(
90 model_id="LLAMA_MODEL_ID",
91 host_port="LLAMA_API_URL",
92)
93
94custom_instructions = """
95You have the following files available:
96- movies.txt
97- videogames.txt
98
99Read the content of the right file - based on the user query - with the tool, and answer the user.
100"""
101
102assistant = Agent(
103 custom_instruction=custom_instructions,
104 tools=[ReadFileTool()],
105 llm=llm,
106)
107
108# %%[markdown]
109## Create Agent Spec components and plugins
110
111# %%
112from typing import Optional
113from pydantic import Field
114from pyagentspec.tools import ServerTool as AgentSpecServerTool
115
116class AgentSpecReadFileTool(AgentSpecServerTool):
117 allowed_extensions: Optional[list[str]] = Field(default_factory=lambda: [".txt"])
118
119from pyagentspec.serialization.pydanticserializationplugin import PydanticComponentSerializationPlugin
120from pyagentspec.serialization.pydanticdeserializationplugin import PydanticComponentDeserializationPlugin
121
122agentspec_read_file_tool_serialization_plugin = PydanticComponentSerializationPlugin({"AgentSpecReadFileTool": AgentSpecReadFileTool})
123agentspec_read_file_tool_deserialization_plugin = PydanticComponentDeserializationPlugin({"AgentSpecReadFileTool": AgentSpecReadFileTool})
124
125# %%[markdown]
126## Create Wayflow plugins for serialization and Agent Spec conversion
127
128# %%
129from pyagentspec import Component as AgentSpecComponent
130from pyagentspec.property import Property as AgentSpecProperty
131from pyagentspec.serialization import ComponentSerializationPlugin, ComponentDeserializationPlugin
132from wayflowcore.agentspec._agentspecconverter import WayflowToAgentSpecConversionContext
133from wayflowcore.agentspec._runtimeconverter import AgentSpecToWayflowConversionContext
134from wayflowcore.serialization.plugins import ToolRegistryT, WayflowSerializationPlugin, WayflowDeserializationPlugin
135from wayflowcore.serialization.serializer import SerializableObject
136
137plugin_name = "WayflowReadFileTool"
138plugin_version = "1.0.0"
139
140class WayflowReadFileToolSerializationPlugin(WayflowSerializationPlugin):
141
142 @property
143 def plugin_name(self) -> str:
144 return plugin_name
145
146 @property
147 def plugin_version(self) -> str:
148 return plugin_version
149
150 @property
151 def supported_component_types(self) -> list[str]:
152 return ["ReadFileTool"]
153
154 @property
155 def required_agentspec_serialization_plugins(self) -> list[ComponentSerializationPlugin]:
156 return [agentspec_read_file_tool_serialization_plugin]
157
158 def convert_to_agentspec(
159 self,
160 conversion_context: "WayflowToAgentSpecConversionContext",
161 runtime_component: SerializableObject,
162 referenced_objects: dict[str, AgentSpecComponent],
163 ) -> AgentSpecComponent:
164 return AgentSpecReadFileTool(
165 id=runtime_component.id,
166 name=runtime_component.name,
167 description=runtime_component.description,
168 allowed_extensions=runtime_component.allowed_extensions,
169 metadata=runtime_component.__metadata_info__,
170 inputs=[AgentSpecProperty(json_schema=p.to_json_schema()) for p in runtime_component.input_descriptors],
171 outputs=[AgentSpecProperty(json_schema=p.to_json_schema()) for p in runtime_component.output_descriptors],
172 )
173
174
175class WayflowReadFileToolDeserializationPlugin(WayflowDeserializationPlugin):
176 @property
177 def plugin_name(self) -> str:
178 return plugin_name
179
180 @property
181 def plugin_version(self) -> str:
182 return plugin_version
183
184 @property
185 def supported_component_types(self) -> list[str]:
186 return ["ReadFileTool"]
187
188 @property
189 def required_agentspec_deserialization_plugins(self) -> list[ComponentDeserializationPlugin]:
190 return [agentspec_read_file_tool_deserialization_plugin]
191
192 def convert_to_wayflow(
193 self,
194 conversion_context: "AgentSpecToWayflowConversionContext",
195 agentspec_component: AgentSpecComponent,
196 tool_registry: ToolRegistryT,
197 converted_components: dict[str, Any],
198 ) -> Any:
199 return ReadFileTool(agentspec_component.allowed_extensions)
200
201# %%[markdown]
202## Serialize and deserialize the agent
203
204# %%
205from wayflowcore.serialization.serializer import serialize, autodeserialize
206
207serialized_assistant = serialize(assistant, plugins=[WayflowReadFileToolSerializationPlugin()])
208deserialized_assistant = autodeserialize(serialized_assistant, plugins=[WayflowReadFileToolDeserializationPlugin()])
209
210# %%[markdown]
211## Export config to Agent Spec
212
213# %%
214from wayflowcore.agentspec import AgentSpecExporter
215
216config = AgentSpecExporter(plugins=[WayflowReadFileToolSerializationPlugin()]).to_json(assistant)
217
218# %%[markdown]
219## Load Agent Spec config
220
221# %%
222from wayflowcore.agentspec import AgentSpecLoader
223
224new_agent = AgentSpecLoader(plugins=[WayflowReadFileToolDeserializationPlugin()]).load_json(config)
225
226# %%[markdown]
227## Agent Execution
228
229# %%
230conversation = assistant.start_conversation()
231
232status = conversation.execute()
233assistant_reply = conversation.get_last_message()
234print("\nAssistant >>>", assistant_reply.content)
235conversation.append_user_message("What's the best movie")
236status = conversation.execute()
237assistant_reply = conversation.get_last_message()
238print("\nAssistant >>>", assistant_reply.content)
239
240# Example of conversation:
241# Assistant >>> Hi! How can I help you?
242# User >>> What's the best movie
243# Assistant >>> According to IMDB, the best movie of all time is The Shawshank Redemption