Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 57 additions & 54 deletions python/packages/core/agent_framework/_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,23 @@ class SerializationProtocol(Protocol):
assert isinstance(user_msg, SerializationProtocol)
assert isinstance(restored_msg, SerializationProtocol)

The protocol is also implemented by simpler classes like ``UsageDetails``:
The protocol is also implemented by simpler classes like ``Content``:

.. code-block:: python

from agent_framework import UsageDetails
from agent_framework import Content
from agent_framework._serialization import SerializationProtocol

# Create usage tracking instance
usage = UsageDetails(input_token_count=150, output_token_count=75, total_token_count=225)
# Create a text content item
content = Content.from_text("The forecast is sunny.")

# Seamless serialization with type preservation
usage_dict = usage.to_dict()
restored_usage = UsageDetails.from_dict(usage_dict)
content_dict = content.to_dict() # {"type": "text", "text": "The forecast is sunny.", ...}
restored_content = Content.from_dict(content_dict)

# Both satisfy the SerializationProtocol
assert isinstance(usage, SerializationProtocol)
assert restored_usage.total_token_count == 225
assert isinstance(content, SerializationProtocol)
assert restored_content.text == "The forecast is sunny."

The protocol ensures consistent serialization behavior across all framework components,
enabling reliable data persistence, API communication, and object reconstruction
Expand Down Expand Up @@ -322,32 +323,31 @@ class SerializationMixin:

.. code-block:: python

from agent_framework._tools import BaseTool


class WeatherTool(BaseTool):
\"\"\"Example tool that extends BaseTool with additional properties exclusion.\"\"\"

# Inherits DEFAULT_EXCLUDE = {"additional_properties"} from BaseTool
from agent_framework import FunctionTool

def __init__(self, name: str, api_key: str, **kwargs):
super().__init__(name=name, description="Get weather information", **kwargs)
self.api_key = api_key # Will be serialized

# Additional properties are excluded from serialization
self.additional_properties = {"version": "1.0", "internal_config": {...}}
def get_weather(location: str) -> str:
return f"Weather in {location}: 72°F"


weather_tool = WeatherTool(name="get_weather", api_key="secret-key")
# FunctionTool lists "additional_properties" in DEFAULT_EXCLUDE and "func" in INJECTABLE
weather_tool = FunctionTool(
name="get_weather",
description="Get weather information",
func=get_weather,
additional_properties={"version": "1.0"},
)

# Serialization excludes additional_properties but includes other fields
# Serialization keeps the tool definition but leaves out excluded and injectable fields
tool_dict = weather_tool.to_dict()
# Result: {
# "type": "weather_tool",
# "type": "function_tool",
# "name": "get_weather",
# "description": "Get weather information",
# "api_key": "secret-key"
# # additional_properties excluded due to DEFAULT_EXCLUDE
# "approval_mode": "never_require",
# "input_model": {...},
# ...
# # additional_properties and func excluded
# }

**Agent framework with injectable dependencies:**
Expand All @@ -363,7 +363,9 @@ class CustomAgent(BaseAgent):
# Inherits DEFAULT_EXCLUDE = {"additional_properties"} from BaseAgent

def __init__(self, **kwargs):
super().__init__(name="custom-agent", description="A custom agent", **kwargs)
kwargs.setdefault("name", "custom-agent")
kwargs.setdefault("description", "A custom agent")
super().__init__(**kwargs)

# additional_properties stores runtime configuration but isn't serialized
self.additional_properties.update({
Expand All @@ -372,10 +374,7 @@ def __init__(self, **kwargs):
})


agent = CustomAgent(
context_provider=[...],
middleware=[...]
)
agent = CustomAgent()

# Serialization captures agent configuration but excludes runtime data
agent_dict = agent.to_dict()
Expand All @@ -384,8 +383,7 @@ def __init__(self, **kwargs):
# "id": "...",
# "name": "custom-agent",
# "description": "A custom agent",
# "context_provider": [...],
# "middleware": [...]
# "context_providers": []
# # additional_properties excluded
# }

Expand Down Expand Up @@ -543,14 +541,15 @@ def from_dict(

# Serialized data contains only the model configuration
client_data = {
"type": "open_ai_chat_client",
"type": "open_a_i_chat_client",
"model": "gpt-4o-mini",
# client is excluded from serialization
}

# Provide the OpenAI client during deserialization
# Provide the OpenAI client during deserialization through the
# constructor's ``async_client`` parameter
openai_client = AsyncOpenAI(api_key="your-api-key")
dependencies = {"open_ai_chat_client": {"client": openai_client}}
dependencies = {"open_a_i_chat_client": {"async_client": openai_client}}

# The chat client is reconstructed with the OpenAI client injected
client = OpenAIChatClient.from_dict(client_data, dependencies=dependencies)
Expand Down Expand Up @@ -585,34 +584,38 @@ async def get_current_weather(location: Annotated[str, "The city name"]) -> str:
weather_func = FunctionTool.from_dict(function_data, dependencies=dependencies)
# The function is now callable and ready for agent use

**MiddlewareTypes Context Injection** - Agent execution context:
**Instance-Specific Injection** - Different functions for different tools:

.. code-block:: python

from agent_framework._middleware import AgentContext
from agent_framework import BaseAgent
from agent_framework import FunctionTool

# AgentContext has INJECTABLE = {"agent", "result"}
context_data = {
"type": "agent_context",
"messages": [{"role": "user", "text": "Hello"}],
"stream": False,
"metadata": {"session_id": "abc123"},
# agent and result are excluded from serialization
}

# Inject agent and result during middleware processing
my_agent = BaseAgent(name="test-agent")
async def get_current_weather(location: str) -> str:
return f"Current weather in {location}: 72°F and sunny"


async def get_forecast(location: str) -> str:
return f"Tomorrow in {location}: 68°F and cloudy"


# "<field>:<value>" keys select the instance that receives each dependency
dependencies = {
"agent_context": {
"agent": my_agent,
"result": None, # Will be populated during execution
"function_tool": {
"name:get_weather": {"func": get_current_weather},
"name:get_forecast": {"func": get_forecast},
}
}

# Reconstruct context with agent dependency for middleware chain
context = AgentContext.from_dict(context_data, dependencies=dependencies)
# MiddlewareTypes can now access context.agent and process the execution
weather_tool = FunctionTool.from_dict(
{"type": "function_tool", "name": "get_weather", "description": "Current weather"},
dependencies=dependencies,
)
forecast_tool = FunctionTool.from_dict(
{"type": "function_tool", "name": "get_forecast", "description": "Weather forecast"},
dependencies=dependencies,
)
# Each tool is reconstructed with its own function

This injection system allows the agent framework to maintain clean separation
between serializable configuration and runtime dependencies like API clients,
Expand Down
Loading