Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion python/packages/ag-ui/agent_framework_ag_ui/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def _register_server_tool_placeholder(self, tool_name: str) -> None:
if any(getattr(tool, "name", None) == tool_name for tool in additional_tools):
return

placeholder: FunctionTool[Any, Any] = FunctionTool(
placeholder: FunctionTool[Any] = FunctionTool(
name=tool_name,
description="Server-managed tool placeholder (AG-UI)",
func=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from agent_framework import (
Content,
Message,
prepare_function_call_results,
)

from ._utils import (
Expand Down Expand Up @@ -697,8 +696,7 @@ def agent_framework_messages_to_agui(messages: list[Message] | list[dict[str, An
elif content.type == "function_result":
# Tool result content - extract call_id and result
tool_result_call_id = content.call_id
# Serialize result to string using core utility
content_text = prepare_function_call_results(content.result)
content_text = content.result if content.result is not None else ""

agui_msg: dict[str, Any] = {
"id": msg.message_id if msg.message_id else generate_event_id(), # Always include id
Expand Down
3 changes: 1 addition & 2 deletions python/packages/ag-ui/agent_framework_ag_ui/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
Content,
Message,
SupportsAgentRun,
prepare_function_call_results,
)
from agent_framework._middleware import FunctionMiddlewarePipeline
from agent_framework._tools import (
Expand Down Expand Up @@ -360,7 +359,7 @@ def _emit_tool_result(
events.append(ToolCallEndEvent(tool_call_id=content.call_id))
flow.tool_calls_ended.add(content.call_id) # Track ended tool calls

result_content = prepare_function_call_results(content.result)
result_content = content.result if content.result is not None else ""
message_id = generate_event_id()
events.append(
ToolCallResultEvent(
Expand Down
6 changes: 3 additions & 3 deletions python/packages/ag-ui/agent_framework_ag_ui/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def make_json_safe(obj: Any) -> Any: # noqa: ANN401

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

Creates declaration-only FunctionTool instances (no executable implementation).
Expand All @@ -181,13 +181,13 @@ def convert_agui_tools_to_agent_framework(
if not agui_tools:
return None

result: list[FunctionTool[Any, Any]] = []
result: list[FunctionTool[Any]] = []
for tool_def in agui_tools:
# Create declaration-only FunctionTool (func=None means no implementation)
# When func=None, the declaration_only property returns True,
# which tells the function invocation mixin to return the function call
# without executing it (so it can be sent back to the client)
func: FunctionTool[Any, Any] = FunctionTool(
func: FunctionTool[Any] = FunctionTool(
name=tool_def.get("name", ""),
description=tool_def.get("description", ""),
func=None, # CRITICAL: Makes declaration_only=True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from agent_framework import ChatOptions

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

Expand Down Expand Up @@ -71,7 +71,7 @@
},
)

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

Expand Down Expand Up @@ -99,7 +99,7 @@
},
)

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

Expand Down Expand Up @@ -127,7 +127,7 @@
},
)

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

Expand Down
28 changes: 8 additions & 20 deletions python/packages/ag-ui/tests/ag_ui/test_message_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def test_agent_framework_to_agui_function_result_dict():
"""Test converting FunctionResultContent with dict result to AG-UI."""
msg = Message(
role="tool",
contents=[Content.from_function_result(call_id="call-123", result={"key": "value", "count": 42})],
contents=[Content.from_function_result(call_id="call-123", result='{"key": "value", "count": 42}')],
message_id="msg-789",
)

Expand All @@ -568,8 +568,8 @@ def test_agent_framework_to_agui_function_result_none():

assert len(messages) == 1
agui_msg = messages[0]
# None serializes as JSON null
assert agui_msg["content"] == "null"
# None result maps to empty string (FunctionTool.invoke returns "" for None)
assert agui_msg["content"] == ""


def test_agent_framework_to_agui_function_result_string():
Expand All @@ -591,7 +591,7 @@ def test_agent_framework_to_agui_function_result_empty_list():
"""Test converting FunctionResultContent with empty list result to AG-UI."""
msg = Message(
role="tool",
contents=[Content.from_function_result(call_id="call-123", result=[])],
contents=[Content.from_function_result(call_id="call-123", result="[]")],
message_id="msg-789",
)

Expand All @@ -604,16 +604,10 @@ def test_agent_framework_to_agui_function_result_empty_list():


def test_agent_framework_to_agui_function_result_single_text_content():
"""Test converting FunctionResultContent with single TextContent-like item."""
from dataclasses import dataclass

@dataclass
class MockTextContent:
text: str

"""Test converting FunctionResultContent with single TextContent-like item (pre-parsed)."""
msg = Message(
role="tool",
contents=[Content.from_function_result(call_id="call-123", result=[MockTextContent("Hello from MCP!")])],
contents=[Content.from_function_result(call_id="call-123", result='["Hello from MCP!"]')],
message_id="msg-789",
)

Expand All @@ -626,19 +620,13 @@ class MockTextContent:


def test_agent_framework_to_agui_function_result_multiple_text_contents():
"""Test converting FunctionResultContent with multiple TextContent-like items."""
from dataclasses import dataclass

@dataclass
class MockTextContent:
text: str

"""Test converting FunctionResultContent with multiple TextContent-like items (pre-parsed)."""
msg = Message(
role="tool",
contents=[
Content.from_function_result(
call_id="call-123",
result=[MockTextContent("First result"), MockTextContent("Second result")],
result='["First result", "Second result"]',
)
],
message_id="msg-789",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
TextSpanRegion,
UsageDetails,
get_logger,
prepare_function_call_results,
)
from agent_framework._settings import SecretString, load_settings
from agent_framework._types import _get_data_bytes_as_str # type: ignore
Expand Down Expand Up @@ -653,7 +652,7 @@ def _prepare_message_for_anthropic(self, message: Message) -> dict[str, Any]:
a_content.append({
"type": "tool_result",
"tool_use_id": content.call_id,
"content": prepare_function_call_results(content.result),
"content": content.result if content.result is not None else "",
"is_error": content.exception is not None,
})
case "text_reasoning":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
TextSpanRegion,
UsageDetails,
get_logger,
prepare_function_call_results,
)
from agent_framework._settings import load_settings
from agent_framework.exceptions import ServiceInitializationError, ServiceInvalidRequestError, ServiceResponseException
Expand Down Expand Up @@ -1390,7 +1389,7 @@ def _prepare_tool_outputs_for_azure_ai(
if tool_outputs is None:
tool_outputs = []
tool_outputs.append(
ToolOutput(tool_call_id=call_id, output=prepare_function_call_results(content.result))
ToolOutput(tool_call_id=call_id, output=content.result if content.result is not None else "")
)
elif content.type == "function_approval_response":
if tool_approvals is None:
Expand Down
26 changes: 12 additions & 14 deletions python/packages/azure-ai/tests/test_azure_ai_agent_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,9 +1024,10 @@ def __init__(self, name: str, value: int):

client = create_test_azure_ai_chat_client(mock_agents_client, agent_id="test-agent")

# Test with BaseModel result
# Test with BaseModel result (pre-parsed as it would be from FunctionTool.invoke)
mock_result = MockResult(name="test", value=42)
function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=mock_result)
expected_json = mock_result.to_json()
function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=expected_json)

run_id, tool_outputs, tool_approvals = client._prepare_tool_outputs_for_azure_ai([function_result]) # type: ignore

Expand All @@ -1035,8 +1036,7 @@ def __init__(self, name: str, value: int):
assert tool_outputs is not None
assert len(tool_outputs) == 1
assert tool_outputs[0].tool_call_id == "call_456"
# Should use model_dump_json for BaseModel
expected_json = mock_result.to_json()
# Should use pre-parsed result string directly
assert tool_outputs[0].output == expected_json


Expand All @@ -1051,10 +1051,14 @@ def __init__(self, data: str):

client = create_test_azure_ai_chat_client(mock_agents_client, agent_id="test-agent")

# Test with multiple results - mix of BaseModel and regular objects
# Test with multiple results - pre-parsed as FunctionTool.invoke would produce
mock_basemodel = MockResult(data="model_data")
results_list = [mock_basemodel, {"key": "value"}, "string_result"]
function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=results_list)
# FunctionTool.parse_result would serialize this to a JSON string
from agent_framework import FunctionTool

pre_parsed = FunctionTool.parse_result(results_list)
function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=pre_parsed)

run_id, tool_outputs, tool_approvals = client._prepare_tool_outputs_for_azure_ai([function_result]) # type: ignore

Expand All @@ -1063,14 +1067,8 @@ def __init__(self, data: str):
assert len(tool_outputs) == 1
assert tool_outputs[0].tool_call_id == "call_456"

# Should JSON dump the entire results array since len > 1
expected_results = [
mock_basemodel.to_dict(),
{"key": "value"},
"string_result",
]
expected_output = json.dumps(expected_results)
assert tool_outputs[0].output == expected_output
# Result is pre-parsed string (already JSON)
assert tool_outputs[0].output == pre_parsed


async def test_azure_ai_chat_client_convert_required_action_approval_response(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
ResponseStream,
UsageDetails,
get_logger,
prepare_function_call_results,
validate_tool_mode,
)
from agent_framework._settings import SecretString, load_settings
Expand Down Expand Up @@ -528,7 +527,7 @@ def _convert_content_to_bedrock_block(self, content: Content) -> dict[str, Any]
return None

def _convert_tool_result_to_blocks(self, result: Any) -> list[dict[str, Any]]:
prepared_result = prepare_function_call_results(result)
prepared_result = result if isinstance(result, str) else FunctionTool.parse_result(result)
try:
parsed_result = json.loads(prepared_result)
except json.JSONDecodeError:
Expand Down
2 changes: 1 addition & 1 deletion python/packages/bedrock/tests/test_bedrock_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_build_request_serializes_tool_history() -> None:
),
Message(
role="tool",
contents=[Content.from_function_result(call_id="call-1", result={"answer": "72F"})],
contents=[Content.from_function_result(call_id="call-1", result='{"answer": "72F"}')],
),
]

Expand Down
2 changes: 1 addition & 1 deletion python/packages/claude/agent_framework_claude/_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def _prepare_tools(

return create_sdk_mcp_server(name=TOOLS_MCP_SERVER_NAME, tools=sdk_tools), tool_names

def _function_tool_to_sdk_mcp_tool(self, func_tool: FunctionTool[Any, Any]) -> SdkMcpTool[Any]:
def _function_tool_to_sdk_mcp_tool(self, func_tool: FunctionTool[Any]) -> SdkMcpTool[Any]:
"""Convert a FunctionTool to an SDK MCP tool.

Args:
Expand Down
4 changes: 2 additions & 2 deletions python/packages/core/agent_framework/_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def as_tool(
stream_callback: Callable[[AgentResponseUpdate], None]
| Callable[[AgentResponseUpdate], Awaitable[None]]
| None = None,
) -> FunctionTool[BaseModel, str]:
) -> FunctionTool[BaseModel]:
"""Create a FunctionTool that wraps this agent.

Keyword Args:
Expand Down Expand Up @@ -511,7 +511,7 @@ async def agent_wrapper(**kwargs: Any) -> str:
# Create final text from accumulated updates
return AgentResponse.from_updates(response_updates).text

agent_tool: FunctionTool[BaseModel, str] = FunctionTool(
agent_tool: FunctionTool[BaseModel] = FunctionTool(
name=tool_name,
description=tool_description,
func=agent_wrapper,
Expand Down
Loading
Loading