Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/python-merge-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
id: azure-functions-setup
- name: Test with pytest
timeout-minutes: 10
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 600 --retries 3 --retry-delay 10
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 900 --retries 3 --retry-delay 10
working-directory: ./python
- name: Test core samples
timeout-minutes: 10
Expand Down
4 changes: 2 additions & 2 deletions python/CODING_STANDARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ The package follows a flat import structure:

- **Core**: Import directly from `agent_framework`
```python
from agent_framework import ChatAgent, ai_function
from agent_framework import ChatAgent, tool
```

- **Components**: Import from `agent_framework.<component>`
Expand Down Expand Up @@ -336,7 +336,7 @@ Think about caching where appropriate. Cache the results of expensive operations

```python
# ✅ Preferred - cache expensive computations
class AIFunction:
class FunctionTool:
def __init__(self, ...):
self._cached_parameters: dict[str, Any] | None = None

Expand Down
4 changes: 2 additions & 2 deletions python/packages/ag-ui/agent_framework_ag_ui/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

import httpx
from agent_framework import (
AIFunction,
BaseChatClient,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
Content,
FunctionTool,
use_chat_middleware,
use_function_invocation,
)
Expand Down Expand Up @@ -239,7 +239,7 @@ def _register_server_tool_placeholder(self, tool_name: str) -> None:
if any(getattr(tool, "name", None) == tool_name for tool in config.additional_tools):
return

placeholder: AIFunction[Any, Any] = AIFunction(
placeholder: FunctionTool[Any, Any] = FunctionTool(
name=tool_name,
description="Server-managed tool placeholder (AG-UI)",
func=None,
Expand Down
46 changes: 23 additions & 23 deletions python/packages/ag-ui/agent_framework_ag_ui/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import date, datetime
from typing import Any

from agent_framework import AgentResponseUpdate, AIFunction, ChatResponseUpdate, Role, ToolProtocol
from agent_framework import AgentResponseUpdate, ChatResponseUpdate, FunctionTool, Role, ToolProtocol

# Role mapping constants
AGUI_TO_FRAMEWORK_ROLE: dict[str, Role] = {
Expand Down Expand Up @@ -160,10 +160,10 @@ def make_json_safe(obj: Any) -> Any: # noqa: ANN401

def convert_agui_tools_to_agent_framework(
agui_tools: list[dict[str, Any]] | None,
) -> list[AIFunction[Any, Any]] | None:
"""Convert AG-UI tool definitions to Agent Framework AIFunction declarations.
) -> list[FunctionTool[Any, Any]] | None:
"""Convert AG-UI tool definitions to Agent Framework FunctionTool declarations.

Creates declaration-only AIFunction instances (no executable implementation).
Creates declaration-only FunctionTool instances (no executable implementation).
These are used to tell the LLM about available tools. The actual execution
happens on the client side via @use_function_invocation.

Expand All @@ -174,18 +174,18 @@ def convert_agui_tools_to_agent_framework(
agui_tools: List of AG-UI tool definitions with name, description, parameters

Returns:
List of AIFunction declarations, or None if no tools provided
List of FunctionTool declarations, or None if no tools provided
"""
if not agui_tools:
return None

result: list[AIFunction[Any, Any]] = []
result: list[FunctionTool[Any, Any]] = []
for tool_def in agui_tools:
# Create declaration-only AIFunction (func=None means no implementation)
# Create declaration-only FunctionTool (func=None means no implementation)
# When func=None, the declaration_only property returns True,
# which tells @use_function_invocation to return the function call
# without executing it (so it can be sent back to the client)
func: AIFunction[Any, Any] = AIFunction(
func: FunctionTool[Any, Any] = FunctionTool(
name=tool_def.get("name", ""),
description=tool_def.get("description", ""),
func=None, # CRITICAL: Makes declaration_only=True
Expand Down Expand Up @@ -229,36 +229,36 @@ def convert_tools_to_agui_format(

results: list[dict[str, Any]] = []

for tool in tool_list:
if isinstance(tool, dict):
for tool_item in tool_list:
if isinstance(tool_item, dict):
# Already in dict format, pass through
results.append(tool) # type: ignore[arg-type]
elif isinstance(tool, AIFunction):
# Convert AIFunction to AG-UI tool format
results.append(tool_item) # type: ignore[arg-type]
elif isinstance(tool_item, FunctionTool):
# Convert FunctionTool to AG-UI tool format
results.append(
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters(),
"name": tool_item.name,
"description": tool_item.description,
"parameters": tool_item.parameters(),
}
)
elif callable(tool):
# Convert callable to AIFunction first, then to AG-UI format
from agent_framework import ai_function
elif callable(tool_item):
# Convert callable to FunctionTool first, then to AG-UI format
from agent_framework import tool

ai_func = ai_function(tool)
ai_func = tool(tool_item)
results.append(
{
"name": ai_func.name,
"description": ai_func.description,
"parameters": ai_func.parameters(),
}
)
elif isinstance(tool, ToolProtocol):
elif isinstance(tool_item, ToolProtocol):
# Handle other ToolProtocol implementations
# For now, we'll skip non-AIFunction tools as they may not have
# For now, we'll skip non-FunctionTool instances as they may not have
# the parameters() method. This matches .NET behavior which only
# converts AIFunctionDeclaration instances.
# converts FunctionToolDeclaration instances.
continue

return results if results else None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ The package uses a clean, orchestrator-based architecture:
You can create your own agent factories following the same pattern as the examples:

```python
from agent_framework import ChatAgent, ai_function
from agent_framework import ChatAgent, tool
from agent_framework import ChatClientProtocol
from agent_framework.ag_ui import AgentFrameworkAgent

@ai_function
@tool
def my_tool(param: str) -> str:
"""My custom tool."""
return f"Result: {param}"
Expand Down Expand Up @@ -294,9 +294,9 @@ wrapped_agent = AgentFrameworkAgent(
Human-in-the-loop is automatically handled when tools are marked for approval:

```python
from agent_framework import ai_function
from agent_framework import tool

@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def sensitive_action(param: str) -> str:
"""This action requires user approval."""
return f"Executed with {param}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

"""Example agent demonstrating predictive state updates with document writing."""

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool
from agent_framework.ag_ui import AgentFrameworkAgent


@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def write_document(document: str) -> str:
"""Write a document. Use markdown formatting to format the document.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from enum import Enum
from typing import Any

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool
from pydantic import BaseModel, Field


Expand All @@ -23,7 +23,7 @@ class TaskStep(BaseModel):
status: StepStatus = Field(default=StepStatus.ENABLED, description="Whether the step is enabled or disabled")


@ai_function(
@tool(
name="generate_task_steps",
description="Generate execution steps for a task",
approval_mode="always_require",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from enum import Enum
from typing import Any

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool
from agent_framework.ag_ui import AgentFrameworkAgent
from pydantic import BaseModel, Field

Expand Down Expand Up @@ -49,7 +49,7 @@ class Recipe(BaseModel):
instructions: list[str] = Field(..., description="Step-by-step cooking instructions")


@ai_function
@tool
def update_recipe(recipe: Recipe) -> str:
"""Update the recipe with new or modified content.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import asyncio
from typing import Any

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool
from agent_framework.ag_ui import AgentFrameworkAgent


@ai_function
@tool
async def research_topic(topic: str) -> str:
"""Research a topic and generate a comprehensive report.

Expand All @@ -35,7 +35,7 @@ async def research_topic(topic: str) -> str:
return f"Research report on '{topic}':\n" + "\n".join(results)


@ai_function
@tool
async def create_presentation(title: str, num_slides: int) -> str:
"""Create a presentation with multiple slides.

Expand All @@ -55,7 +55,7 @@ async def create_presentation(title: str, num_slides: int) -> str:
return f"Created presentation '{title}' with {num_slides} slides:\n" + "\n".join(slides)


@ai_function
@tool
async def analyze_data(dataset: str) -> str:
"""Analyze a dataset and produce insights.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

from typing import Any

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool
from agent_framework.ag_ui import AgentFrameworkAgent


@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def create_calendar_event(title: str, date: str, time: str) -> str:
"""Create a calendar event.

Expand All @@ -23,7 +23,7 @@ def create_calendar_event(title: str, date: str, time: str) -> str:
return f"Calendar event '{title}' created for {date} at {time}"


@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email.

Expand All @@ -38,7 +38,7 @@ def send_email(to: str, subject: str, body: str) -> str:
return f"Email sent to {to} with subject '{subject}'"


@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def book_meeting_room(room_name: str, date: str, start_time: str, end_time: str) -> str:
"""Book a meeting room.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
TextMessageStartEvent,
ToolCallStartEvent,
)
from agent_framework import ChatAgent, ChatClientProtocol, ChatMessage, Content, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, ChatMessage, Content, tool
from agent_framework.ag_ui import AgentFrameworkAgent
from pydantic import BaseModel, Field

Expand All @@ -39,7 +39,7 @@ class TaskStep(BaseModel):
status: StepStatus = Field(default=StepStatus.PENDING, description="The status of the step")


@ai_function
@tool
def generate_task_steps(steps: list[TaskStep]) -> str:
"""Generate a list of task steps for completing a task.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
"""Example agent demonstrating Tool-based Generative UI (Feature 5)."""

import sys
from typing import Any, TypedDict
from typing import TYPE_CHECKING, Any, TypedDict

from agent_framework import AIFunction, ChatAgent, ChatClientProtocol, ChatOptions
from agent_framework import ChatAgent, ChatClientProtocol, FunctionTool
from agent_framework.ag_ui import AgentFrameworkAgent

if sys.version_info >= (3, 13):
from typing import TypeVar # type: ignore # pragma: no cover
else:
from typing_extensions import TypeVar # type: ignore # pragma: no cover

if TYPE_CHECKING:
from agent_framework import ChatOptions

# Declaration-only tools (func=None) - actual rendering happens on the client side
generate_haiku = AIFunction[Any, str](
generate_haiku = FunctionTool[Any, str](
name="generate_haiku",
description="""Generate a haiku with image and gradient background (FRONTEND_RENDER).

Expand Down Expand Up @@ -62,7 +65,7 @@
},
)

create_chart = AIFunction[Any, str](
create_chart = FunctionTool[Any, str](
name="create_chart",
description="""Create an interactive chart (FRONTEND_RENDER).

Expand Down Expand Up @@ -90,7 +93,7 @@
},
)

display_timeline = AIFunction[Any, str](
display_timeline = FunctionTool[Any, str](
name="display_timeline",
description="""Display an interactive timeline (FRONTEND_RENDER).

Expand Down Expand Up @@ -118,7 +121,7 @@
},
)

show_comparison_table = AIFunction[Any, str](
show_comparison_table = FunctionTool[Any, str](
name="show_comparison_table",
description="""Show a comparison table (FRONTEND_RENDER).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

from typing import Any

from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework import ChatAgent, ChatClientProtocol, tool


@ai_function
@tool
def get_weather(location: str) -> dict[str, Any]:
"""Get the current weather for a location.

Expand Down Expand Up @@ -39,7 +39,7 @@ def get_weather(location: str) -> dict[str, Any]:
}


@ai_function
@tool
def get_forecast(location: str, days: int = 3) -> str:
"""Get the weather forecast for a location.

Expand Down
Loading
Loading