renamed all (#3207)

This commit is contained in:
Eduard van Valkenburg
2026-01-14 05:54:07 +00:00
committed by GitHub
parent 1ae0b09e42
commit d8cf8361bd
125 changed files with 1024 additions and 1027 deletions
@@ -26,8 +26,8 @@ from a2a.types import Message as A2AMessage
from a2a.types import Part as A2APart
from a2a.types import Role as A2ARole
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -193,11 +193,11 @@ class A2AAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Get a response from the agent.
This method returns the final result of the agent's execution
as a single AgentRunResponse object. The caller is blocked until
as a single AgentResponse object. The caller is blocked until
the final result is available.
Args:
@@ -212,7 +212,7 @@ class A2AAgent(BaseAgent):
"""
# Collect all updates and use framework to consolidate updates into response
updates = [update async for update in self.run_stream(messages, thread=thread, **kwargs)]
return AgentRunResponse.from_agent_run_response_updates(updates)
return AgentResponse.from_agent_run_response_updates(updates)
async def run_stream(
self,
@@ -220,11 +220,11 @@ class A2AAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Run the agent as a stream.
This method will return the intermediate steps and final results of the
agent's execution as a stream of AgentRunResponseUpdate objects to the caller.
agent's execution as a stream of AgentResponseUpdate objects to the caller.
Args:
messages: The message(s) to send to the agent.
@@ -245,7 +245,7 @@ class A2AAgent(BaseAgent):
if isinstance(item, Message):
# Process A2A Message
contents = self._parse_contents_from_a2a(item.parts)
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=contents,
role=Role.ASSISTANT if item.role == A2ARole.agent else Role.USER,
response_id=str(getattr(item, "message_id", uuid.uuid4())),
@@ -260,7 +260,7 @@ class A2AAgent(BaseAgent):
for message in task_messages:
# Use the artifact's ID from raw_representation as message_id for unique identification
artifact_id = getattr(message.raw_representation, "artifact_id", None)
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=message.contents,
role=message.role,
response_id=task.id,
@@ -269,7 +269,7 @@ class A2AAgent(BaseAgent):
)
else:
# Empty task
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[],
role=Role.ASSISTANT,
response_id=task.id,
+8 -8
View File
@@ -21,8 +21,8 @@ from a2a.types import (
)
from a2a.types import Role as A2ARole
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatMessage,
DataContent,
ErrorContent,
@@ -131,7 +131,7 @@ async def test_run_with_message_response(a2a_agent: A2AAgent, mock_a2a_client: M
response = await a2a_agent.run("Hello agent")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
assert response.messages[0].role == Role.ASSISTANT
assert response.messages[0].text == "Hello from agent!"
@@ -146,7 +146,7 @@ async def test_run_with_task_response_single_artifact(a2a_agent: A2AAgent, mock_
response = await a2a_agent.run("Generate a report")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
assert response.messages[0].role == Role.ASSISTANT
assert response.messages[0].text == "Generated report content"
@@ -165,7 +165,7 @@ async def test_run_with_task_response_multiple_artifacts(a2a_agent: A2AAgent, mo
response = await a2a_agent.run("Generate multiple outputs")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 3
assert response.messages[0].text == "First artifact content"
@@ -185,7 +185,7 @@ async def test_run_with_task_response_no_artifacts(a2a_agent: A2AAgent, mock_a2a
response = await a2a_agent.run("Do something with no output")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.response_id == "task-empty"
@@ -357,13 +357,13 @@ async def test_run_stream_with_message_response(a2a_agent: A2AAgent, mock_a2a_cl
mock_a2a_client.add_message_response("msg-stream-123", "Streaming response from agent!", "agent")
# Collect streaming updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in a2a_agent.run_stream("Hello agent"):
updates.append(update)
# Verify streaming response
assert len(updates) == 1
assert isinstance(updates[0], AgentRunResponseUpdate)
assert isinstance(updates[0], AgentResponseUpdate)
assert updates[0].role == Role.ASSISTANT
assert len(updates[0].contents) == 1
@@ -24,7 +24,7 @@ from ag_ui.core import (
ToolCallStartEvent,
)
from agent_framework import (
AgentRunResponseUpdate,
AgentResponseUpdate,
FunctionApprovalRequestContent,
FunctionCallContent,
FunctionResultContent,
@@ -81,9 +81,9 @@ class AgentFrameworkEventBridge:
self.should_stop_after_confirm: bool = False # Flag to stop run after confirm_changes
self.suppressed_summary: str = "" # Store LLM summary to show after confirmation
async def from_agent_run_update(self, update: AgentRunResponseUpdate) -> list[BaseEvent]:
async def from_agent_run_update(self, update: AgentResponseUpdate) -> list[BaseEvent]:
"""
Convert an AgentRunResponseUpdate to AG-UI events.
Convert an AgentResponseUpdate to AG-UI events.
Args:
update: The agent run update to convert.
@@ -646,11 +646,11 @@ class DefaultOrchestrator(Orchestrator):
yield end_event
if response_format and all_updates:
from agent_framework import AgentRunResponse
from agent_framework import AgentResponse
from pydantic import BaseModel
logger.info(f"Processing structured output, update count: {len(all_updates)}")
final_response = AgentRunResponse.from_agent_run_response_updates(
final_response = AgentResponse.from_agent_run_response_updates(
all_updates, output_format_type=response_format
)
@@ -169,7 +169,7 @@ The package uses a clean, orchestrator-based architecture:
- **AgentFrameworkAgent**: Lightweight wrapper that delegates to orchestrators
- **Orchestrators**: Handle different execution flows (default, human-in-the-loop, etc.)
- **Confirmation Strategies**: Domain-specific confirmation messages (extensible)
- **AgentFrameworkEventBridge**: Converts AgentRunResponseUpdate to AG-UI events
- **AgentFrameworkEventBridge**: Converts AgentResponseUpdate to AG-UI events
- **Message Adapters**: Bidirectional conversion between AG-UI and Agent Framework message formats
- **FastAPI Endpoint**: Streaming HTTP endpoint with Server-Sent Events (SSE)
@@ -198,10 +198,10 @@ def my_tool(param: str) -> str:
def my_custom_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
"""Create a custom agent with the specified chat client.
Args:
chat_client: The chat client to use for the agent
Returns:
A configured AgentFrameworkAgent instance
"""
@@ -211,7 +211,7 @@ def my_custom_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
chat_client=chat_client,
tools=[my_tool],
)
return AgentFrameworkAgent(
agent=agent,
name="MyCustomAgent",
@@ -302,13 +302,13 @@ from agent_framework.ag_ui import AgentFrameworkAgent, ConfirmationStrategy
class CustomConfirmationStrategy(ConfirmationStrategy):
def on_approval_accepted(self, steps: list[dict[str, Any]]) -> str:
return "Your custom approval message!"
def on_approval_rejected(self, steps: list[dict[str, Any]]) -> str:
return "Your custom rejection message!"
def on_state_confirmed(self) -> str:
return "State changes confirmed!"
def on_state_rejected(self) -> str:
return "State changes rejected!"
@@ -349,7 +349,7 @@ class MyCustomOrchestrator(Orchestrator):
def can_handle(self, context: ExecutionContext) -> bool:
# Return True if this orchestrator should handle the request
return context.input_data.get("custom_mode") == True
async def run(self, context: ExecutionContext):
# Custom execution logic
yield RunStartedEvent(...)
@@ -12,7 +12,7 @@ from ag_ui.core import (
ToolCallResultEvent,
ToolCallStartEvent,
)
from agent_framework import AgentRunResponseUpdate, FunctionCallContent, FunctionResultContent, TextContent
from agent_framework import AgentResponseUpdate, FunctionCallContent, FunctionResultContent, TextContent
from agent_framework_ag_ui._events import AgentFrameworkEventBridge
@@ -28,7 +28,7 @@ async def test_tool_call_flow():
arguments={"location": "Seattle"},
)
update1 = AgentRunResponseUpdate(contents=[tool_call])
update1 = AgentResponseUpdate(contents=[tool_call])
events1 = await bridge.from_agent_run_update(update1)
# Should have: ToolCallStartEvent, ToolCallArgsEvent
@@ -49,7 +49,7 @@ async def test_tool_call_flow():
result="Weather in Seattle: Rainy, 52°F",
)
update2 = AgentRunResponseUpdate(contents=[tool_result])
update2 = AgentResponseUpdate(contents=[tool_result])
events2 = await bridge.from_agent_run_update(update2)
# Should have: ToolCallEndEvent, ToolCallResultEvent
@@ -78,7 +78,7 @@ async def test_text_with_tool_call():
arguments={"location": "San Francisco", "days": 3},
)
update = AgentRunResponseUpdate(contents=[text_content, tool_call])
update = AgentResponseUpdate(contents=[text_content, tool_call])
events = await bridge.from_agent_run_update(update)
# Should have: TextMessageStart, TextMessageContent, ToolCallStart, ToolCallArgs
@@ -107,7 +107,7 @@ async def test_multiple_tool_results():
FunctionResultContent(call_id="tool-3", result="Result 3"),
]
update = AgentRunResponseUpdate(contents=results)
update = AgentResponseUpdate(contents=results)
events = await bridge.from_agent_run_update(update)
# Should have 3 pairs of ToolCallEndEvent + ToolCallResultEvent = 6 events
@@ -3,7 +3,7 @@
"""Tests for document writer predictive state flow with confirm_changes."""
from ag_ui.core import EventType, StateDeltaEvent, ToolCallArgsEvent, ToolCallEndEvent, ToolCallStartEvent
from agent_framework import AgentRunResponseUpdate, FunctionCallContent, FunctionResultContent, TextContent
from agent_framework import AgentResponseUpdate, FunctionCallContent, FunctionResultContent, TextContent
from agent_framework_ag_ui._events import AgentFrameworkEventBridge
@@ -26,7 +26,7 @@ async def test_streaming_document_with_state_deltas():
name="write_document_local",
arguments='{"document":"Once',
)
update1 = AgentRunResponseUpdate(contents=[tool_call_start])
update1 = AgentResponseUpdate(contents=[tool_call_start])
events1 = await bridge.from_agent_run_update(update1)
# Should have ToolCallStartEvent and ToolCallArgsEvent
@@ -35,7 +35,7 @@ async def test_streaming_document_with_state_deltas():
# Second chunk - incomplete JSON, should try partial extraction
tool_call_chunk2 = FunctionCallContent(call_id="call_123", name="write_document_local", arguments=" upon a time")
update2 = AgentRunResponseUpdate(contents=[tool_call_chunk2])
update2 = AgentResponseUpdate(contents=[tool_call_chunk2])
events2 = await bridge.from_agent_run_update(update2)
# Should emit StateDeltaEvent with partial document
@@ -76,7 +76,7 @@ async def test_confirm_changes_emission():
result="Document written.",
)
update = AgentRunResponseUpdate(contents=[tool_result])
update = AgentResponseUpdate(contents=[tool_result])
events = await bridge.from_agent_run_update(update)
# Should have: ToolCallEndEvent, ToolCallResultEvent, StateSnapshotEvent, confirm_changes sequence
@@ -116,7 +116,7 @@ async def test_text_suppression_before_confirm():
# Text content that should be suppressed
text = TextContent(text="I have written a story about pirates.")
update = AgentRunResponseUpdate(contents=[text])
update = AgentResponseUpdate(contents=[text])
events = await bridge.from_agent_run_update(update)
@@ -151,7 +151,7 @@ async def test_no_confirm_for_non_predictive_tools():
result="Sunny, 72°F",
)
update = AgentRunResponseUpdate(contents=[tool_result])
update = AgentResponseUpdate(contents=[tool_result])
events = await bridge.from_agent_run_update(update)
# Should NOT have confirm_changes
@@ -180,7 +180,7 @@ async def test_state_delta_deduplication():
name="write_document_local",
arguments='{"document":"Same text"}',
)
update1 = AgentRunResponseUpdate(contents=[tool_call1])
update1 = AgentResponseUpdate(contents=[tool_call1])
events1 = await bridge.from_agent_run_update(update1)
# Count state deltas
@@ -194,7 +194,7 @@ async def test_state_delta_deduplication():
name="write_document_local",
arguments='{"document":"Same text"}', # Identical content
)
update2 = AgentRunResponseUpdate(contents=[tool_call2])
update2 = AgentResponseUpdate(contents=[tool_call2])
events2 = await bridge.from_agent_run_update(update2)
# Should NOT emit state delta (same value)
@@ -221,7 +221,7 @@ async def test_predict_state_config_multiple_fields():
name="create_post",
arguments='{"title":"My Post","body":"Post content"}',
)
update = AgentRunResponseUpdate(contents=[tool_call])
update = AgentResponseUpdate(contents=[tool_call])
events = await bridge.from_agent_run_update(update)
# Should emit StateDeltaEvent for both fields
@@ -5,7 +5,7 @@
import json
from agent_framework import (
AgentRunResponseUpdate,
AgentResponseUpdate,
FunctionApprovalRequestContent,
FunctionCallContent,
FunctionResultContent,
@@ -19,7 +19,7 @@ async def test_basic_text_message_conversion():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(contents=[TextContent(text="Hello")])
update = AgentResponseUpdate(contents=[TextContent(text="Hello")])
events = await bridge.from_agent_run_update(update)
assert len(events) == 2
@@ -35,8 +35,8 @@ async def test_text_message_streaming():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update1 = AgentRunResponseUpdate(contents=[TextContent(text="Hello ")])
update2 = AgentRunResponseUpdate(contents=[TextContent(text="world")])
update1 = AgentResponseUpdate(contents=[TextContent(text="Hello ")])
update2 = AgentResponseUpdate(contents=[TextContent(text="world")])
events1 = await bridge.from_agent_run_update(update1)
events2 = await bridge.from_agent_run_update(update2)
@@ -61,7 +61,7 @@ async def test_skip_text_content_for_structured_outputs():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread", skip_text_content=True)
update = AgentRunResponseUpdate(contents=[TextContent(text='{"result": "data"}')])
update = AgentResponseUpdate(contents=[TextContent(text='{"result": "data"}')])
events = await bridge.from_agent_run_update(update)
# No events should be emitted
@@ -74,9 +74,9 @@ async def test_skip_text_content_for_empty_text():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update1 = AgentRunResponseUpdate(contents=[TextContent(text="Hello ")])
update2 = AgentRunResponseUpdate(contents=[TextContent(text="")]) # Empty chunk
update3 = AgentRunResponseUpdate(contents=[TextContent(text="world")])
update1 = AgentResponseUpdate(contents=[TextContent(text="Hello ")])
update2 = AgentResponseUpdate(contents=[TextContent(text="")]) # Empty chunk
update3 = AgentResponseUpdate(contents=[TextContent(text="world")])
events1 = await bridge.from_agent_run_update(update1)
events2 = await bridge.from_agent_run_update(update2)
@@ -105,7 +105,7 @@ async def test_tool_call_with_name():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(contents=[FunctionCallContent(name="search_web", call_id="call_123")])
update = AgentResponseUpdate(contents=[FunctionCallContent(name="search_web", call_id="call_123")])
events = await bridge.from_agent_run_update(update)
assert len(events) == 1
@@ -121,17 +121,15 @@ async def test_tool_call_streaming_args():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
# First chunk: name only
update1 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="search_web", call_id="call_123")])
update1 = AgentResponseUpdate(contents=[FunctionCallContent(name="search_web", call_id="call_123")])
events1 = await bridge.from_agent_run_update(update1)
# Second chunk: arguments chunk 1 (name can be empty string for continuation)
update2 = AgentRunResponseUpdate(
contents=[FunctionCallContent(name="", call_id="call_123", arguments='{"query": "')]
)
update2 = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_123", arguments='{"query": "')])
events2 = await bridge.from_agent_run_update(update2)
# Third chunk: arguments chunk 2
update3 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_123", arguments='AI"}')])
update3 = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_123", arguments='AI"}')])
events3 = await bridge.from_agent_run_update(update3)
# First update: ToolCallStartEvent
@@ -169,9 +167,9 @@ async def test_streaming_tool_call_no_duplicate_start_events():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
# Simulate streaming tool call: first chunk has name, subsequent chunks have name=""
update1 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="get_weather", call_id="call_789")])
update2 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_789", arguments='{"loc":')])
update3 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_789", arguments='"SF"}')])
update1 = AgentResponseUpdate(contents=[FunctionCallContent(name="get_weather", call_id="call_789")])
update2 = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_789", arguments='{"loc":')])
update3 = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_789", arguments='"SF"}')])
events1 = await bridge.from_agent_run_update(update1)
events2 = await bridge.from_agent_run_update(update2)
@@ -195,7 +193,7 @@ async def test_tool_result_with_dict():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
result_data = {"status": "success", "count": 42}
update = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=result_data)])
update = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=result_data)])
events = await bridge.from_agent_run_update(update)
# Should emit ToolCallEndEvent + ToolCallResultEvent
@@ -216,7 +214,7 @@ async def test_tool_result_with_string():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result="Search complete")])
update = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result="Search complete")])
events = await bridge.from_agent_run_update(update)
assert len(events) == 2
@@ -231,7 +229,7 @@ async def test_tool_result_with_none():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=None)])
update = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=None)])
events = await bridge.from_agent_run_update(update)
assert len(events) == 2
@@ -247,7 +245,7 @@ async def test_multiple_tool_results_in_sequence():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionResultContent(call_id="call_1", result="Result 1"),
FunctionResultContent(call_id="call_2", result="Result 2"),
@@ -284,7 +282,7 @@ async def test_function_approval_request_basic():
function_call=func_call,
)
update = AgentRunResponseUpdate(contents=[approval])
update = AgentResponseUpdate(contents=[approval])
events = await bridge.from_agent_run_update(update)
# Should emit: ToolCallEndEvent + CustomEvent
@@ -312,7 +310,7 @@ async def test_empty_predict_state_config():
)
# Tool call with arguments
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(name="write_doc", call_id="call_1", arguments='{"content": "test"}'),
FunctionResultContent(call_id="call_1", result="Done"),
@@ -347,7 +345,7 @@ async def test_tool_not_in_predict_state_config():
)
# Different tool name
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(name="search_web", call_id="call_1", arguments='{"query": "AI"}'),
FunctionResultContent(call_id="call_1", result="Results"),
@@ -376,7 +374,7 @@ async def test_state_management_tracking():
)
# Streaming tool call
update1 = AgentRunResponseUpdate(
update1 = AgentResponseUpdate(
contents=[
FunctionCallContent(name="write_doc", call_id="call_1"),
FunctionCallContent(name="", call_id="call_1", arguments='{"content": "Hello"}'),
@@ -389,7 +387,7 @@ async def test_state_management_tracking():
assert bridge.pending_state_updates["document"] == "Hello"
# Tool result should update current_state
update2 = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
update2 = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
await bridge.from_agent_run_update(update2)
# current_state should be updated
@@ -413,7 +411,7 @@ async def test_wildcard_tool_argument():
)
# Complete tool call with dict arguments
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(
name="create_recipe",
@@ -503,7 +501,7 @@ async def test_state_snapshot_after_tool_result():
)
# Tool call with streaming args
update1 = AgentRunResponseUpdate(
update1 = AgentResponseUpdate(
contents=[
FunctionCallContent(name="write_doc", call_id="call_1"),
FunctionCallContent(name="", call_id="call_1", arguments='{"content": "Test"}'),
@@ -512,7 +510,7 @@ async def test_state_snapshot_after_tool_result():
await bridge.from_agent_run_update(update1)
# Tool result should trigger StateSnapshotEvent
update2 = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
update2 = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
events = await bridge.from_agent_run_update(update2)
# Should have: ToolCallEnd, ToolCallResult, StateSnapshot, ToolCallStart (confirm_changes), ToolCallArgs, ToolCallEnd
@@ -528,12 +526,12 @@ async def test_message_id_persistence_across_chunks():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
# First chunk
update1 = AgentRunResponseUpdate(contents=[TextContent(text="Hello ")])
update1 = AgentResponseUpdate(contents=[TextContent(text="Hello ")])
events1 = await bridge.from_agent_run_update(update1)
message_id = events1[0].message_id
# Second chunk
update2 = AgentRunResponseUpdate(contents=[TextContent(text="world")])
update2 = AgentResponseUpdate(contents=[TextContent(text="world")])
events2 = await bridge.from_agent_run_update(update2)
# Should use same message_id
@@ -548,14 +546,14 @@ async def test_tool_call_id_tracking():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
# First chunk with name
update1 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="search", call_id="call_1")])
update1 = AgentResponseUpdate(contents=[FunctionCallContent(name="search", call_id="call_1")])
await bridge.from_agent_run_update(update1)
assert bridge.current_tool_call_id == "call_1"
assert bridge.current_tool_call_name == "search"
# Second chunk with args but no name
update2 = AgentRunResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_1", arguments='{"q":"AI"}')])
update2 = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="call_1", arguments='{"q":"AI"}')])
events2 = await bridge.from_agent_run_update(update2)
# Should still track same tool call
@@ -576,7 +574,7 @@ async def test_tool_name_reset_after_result():
)
# Tool call
update1 = AgentRunResponseUpdate(
update1 = AgentResponseUpdate(
contents=[
FunctionCallContent(name="write_doc", call_id="call_1"),
FunctionCallContent(name="", call_id="call_1", arguments='{"content": "Test"}'),
@@ -587,7 +585,7 @@ async def test_tool_name_reset_after_result():
assert bridge.current_tool_call_name == "write_doc"
# Tool result with predictive state (should trigger confirm_changes and reset)
update2 = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
update2 = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")])
await bridge.from_agent_run_update(update2)
# Tool name should be reset
@@ -613,7 +611,7 @@ async def test_function_approval_with_wildcard_argument():
),
)
update = AgentRunResponseUpdate(contents=[approval_content])
update = AgentResponseUpdate(contents=[approval_content])
events = await bridge.from_agent_run_update(update)
# Should emit StateSnapshotEvent with entire parsed args as value
@@ -639,7 +637,7 @@ async def test_function_approval_missing_argument():
function_call=FunctionCallContent(name="process", call_id="call_1", arguments='{"other_field": "value"}'),
)
update = AgentRunResponseUpdate(contents=[approval_content])
update = AgentResponseUpdate(contents=[approval_content])
events = await bridge.from_agent_run_update(update)
# Should not emit StateSnapshotEvent since argument not found
@@ -654,7 +652,7 @@ async def test_empty_predict_state_config_no_deltas():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread", predict_state_config={})
# Tool call with arguments
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(name="search", call_id="call_1"),
FunctionCallContent(name="", call_id="call_1", arguments='{"query": "test"}'),
@@ -678,7 +676,7 @@ async def test_tool_with_no_matching_config():
)
# Tool call for different tool
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(name="search_web", call_id="call_1"),
FunctionCallContent(name="", call_id="call_1", arguments='{"query": "test"}'),
@@ -698,7 +696,7 @@ async def test_tool_call_without_name_or_id():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
# This should not crash but log an error
update = AgentRunResponseUpdate(contents=[FunctionCallContent(name="", call_id="", arguments='{"arg": "val"}')])
update = AgentResponseUpdate(contents=[FunctionCallContent(name="", call_id="", arguments='{"arg": "val"}')])
events = await bridge.from_agent_run_update(update)
# Should emit ToolCallArgsEvent with generated ID
@@ -717,7 +715,7 @@ async def test_state_delta_count_logging():
# Emit multiple state deltas with different content each time
for i in range(15):
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionCallContent(name="", call_id="call_1", arguments=f'{{"text": "Content variation {i}"}}'),
]
@@ -739,7 +737,7 @@ async def test_tool_result_with_empty_list():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=[])])
update = AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_123", result=[])])
events = await bridge.from_agent_run_update(update)
assert len(events) == 2
@@ -761,7 +759,7 @@ async def test_tool_result_with_single_text_content():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[FunctionResultContent(call_id="call_123", result=[MockTextContent("Hello from MCP tool!")])]
)
events = await bridge.from_agent_run_update(update)
@@ -785,7 +783,7 @@ async def test_tool_result_with_multiple_text_contents():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[
FunctionResultContent(
call_id="call_123",
@@ -813,7 +811,7 @@ async def test_tool_result_with_model_dump_objects():
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[FunctionResultContent(call_id="call_123", result=[MockModel(value=1), MockModel(value=2)])]
)
events = await bridge.from_agent_run_update(update)
@@ -2,7 +2,7 @@
"""Tests for human in the loop (function approval requests)."""
from agent_framework import AgentRunResponseUpdate, FunctionApprovalRequestContent, FunctionCallContent
from agent_framework import AgentResponseUpdate, FunctionApprovalRequestContent, FunctionCallContent
from agent_framework_ag_ui._events import AgentFrameworkEventBridge
@@ -27,7 +27,7 @@ async def test_function_approval_request_emission():
function_call=func_call,
)
update = AgentRunResponseUpdate(contents=[approval_request])
update = AgentResponseUpdate(contents=[approval_request])
events = await bridge.from_agent_run_update(update)
# Should emit ToolCallEndEvent + CustomEvent for approval request
@@ -66,7 +66,7 @@ async def test_function_approval_request_with_confirm_changes():
function_call=func_call,
)
update = AgentRunResponseUpdate(contents=[approval_request])
update = AgentResponseUpdate(contents=[approval_request])
events = await bridge.from_agent_run_update(update)
# Should emit: ToolCallEndEvent, CustomEvent, and confirm_changes (Start, Args, End) = 5 events
@@ -129,7 +129,7 @@ async def test_multiple_approval_requests():
function_call=func_call_2,
)
update = AgentRunResponseUpdate(contents=[approval_1, approval_2])
update = AgentResponseUpdate(contents=[approval_1, approval_2])
events = await bridge.from_agent_run_update(update)
# Should emit ToolCallEndEvent + CustomEvent for each approval (4 events total)
@@ -174,7 +174,7 @@ async def test_function_approval_request_sets_stop_flag():
function_call=func_call,
)
update = AgentRunResponseUpdate(contents=[approval_request])
update = AgentResponseUpdate(contents=[approval_request])
await bridge.from_agent_run_update(update)
assert bridge.should_stop_after_confirm is True
@@ -6,7 +6,7 @@ from collections.abc import AsyncGenerator
from types import SimpleNamespace
from typing import Any
from agent_framework import AgentRunResponseUpdate, FunctionInvocationConfiguration, TextContent, ai_function
from agent_framework import AgentResponseUpdate, FunctionInvocationConfiguration, TextContent, ai_function
from agent_framework_ag_ui._agent import AgentConfig
from agent_framework_ag_ui._orchestrators import DefaultOrchestrator, ExecutionContext
@@ -36,9 +36,9 @@ class DummyAgent:
thread: Any,
tools: list[Any] | None = None,
**kwargs: Any,
) -> AsyncGenerator[AgentRunResponseUpdate, None]:
) -> AsyncGenerator[AgentResponseUpdate, None]:
self.seen_tools = tools
yield AgentRunResponseUpdate(contents=[TextContent(text="ok")], role="assistant")
yield AgentResponseUpdate(contents=[TextContent(text="ok")], role="assistant")
class RecordingAgent:
@@ -59,9 +59,9 @@ class RecordingAgent:
thread: Any,
tools: list[Any] | None = None,
**kwargs: Any,
) -> AsyncGenerator[AgentRunResponseUpdate, None]:
) -> AsyncGenerator[AgentResponseUpdate, None]:
self.seen_messages = messages
yield AgentRunResponseUpdate(contents=[TextContent(text="ok")], role="assistant")
yield AgentResponseUpdate(contents=[TextContent(text="ok")], role="assistant")
async def test_default_orchestrator_merges_client_tools() -> None:
@@ -9,7 +9,7 @@ from types import SimpleNamespace
from typing import Any
from agent_framework import (
AgentRunResponseUpdate,
AgentResponseUpdate,
ChatMessage,
TextContent,
ai_function,
@@ -55,7 +55,7 @@ async def test_human_in_the_loop_json_decode_error() -> None:
agent = StubAgent(
default_options={"tools": [approval_tool], "response_format": None},
updates=[AgentRunResponseUpdate(contents=[TextContent(text="response")], role="assistant")],
updates=[AgentResponseUpdate(contents=[TextContent(text="response")], role="assistant")],
)
context = TestExecutionContext(
input_data=input_data,
@@ -451,7 +451,7 @@ async def test_structured_output_processing() -> None:
agent = StubAgent(
default_options=DEFAULT_OPTIONS,
updates=[
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text='{"ingredients": ["tomato"], "message": "Added tomato"}')],
role="assistant",
)
@@ -691,7 +691,7 @@ async def test_confirm_changes_closes_active_message_before_finish() -> None:
from agent_framework import FunctionCallContent, FunctionResultContent
updates = [
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[
FunctionCallContent(
name="write_document_local",
@@ -700,7 +700,7 @@ async def test_confirm_changes_closes_active_message_before_finish() -> None:
)
]
),
AgentRunResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")]),
AgentResponseUpdate(contents=[FunctionResultContent(call_id="call_1", result="Done")]),
]
orchestrator = DefaultOrchestrator()
@@ -792,9 +792,9 @@ async def test_agent_protocol_fallback_paths() -> None:
thread: Any = None,
tools: list[Any] | None = None,
**kwargs: Any,
) -> AsyncGenerator[AgentRunResponseUpdate, None]:
) -> AsyncGenerator[AgentResponseUpdate, None]:
self.messages_received = messages
yield AgentRunResponseUpdate(contents=[TextContent(text="response")], role="assistant")
yield AgentResponseUpdate(contents=[TextContent(text="response")], role="assistant")
from agent_framework import ChatMessage, TextContent
@@ -9,8 +9,8 @@ from typing import Any, Generic
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseChatClient,
ChatMessage,
@@ -81,7 +81,7 @@ class StubAgent(AgentProtocol):
def __init__(
self,
updates: list[AgentRunResponseUpdate] | None = None,
updates: list[AgentResponseUpdate] | None = None,
*,
agent_id: str = "stub-agent",
agent_name: str | None = "stub-agent",
@@ -91,7 +91,7 @@ class StubAgent(AgentProtocol):
self.id = agent_id
self.name = agent_name
self.description = "stub agent"
self.updates = updates or [AgentRunResponseUpdate(contents=[TextContent(text="response")], role="assistant")]
self.updates = updates or [AgentResponseUpdate(contents=[TextContent(text="response")], role="assistant")]
self.default_options: dict[str, Any] = (
default_options if isinstance(default_options, dict) else {"tools": None, "response_format": None}
)
@@ -105,8 +105,8 @@ class StubAgent(AgentProtocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
return AgentRunResponse(messages=[], response_id="stub-response")
) -> AgentResponse:
return AgentResponse(messages=[], response_id="stub-response")
def run_stream(
self,
@@ -114,8 +114,8 @@ class StubAgent(AgentProtocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
async def _stream() -> AsyncIterator[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
async def _stream() -> AsyncIterator[AgentResponseUpdate]:
self.messages_received = [] if messages is None else list(messages) # type: ignore[arg-type]
self.tools_received = kwargs.get("tools")
for update in self.updates:
@@ -8,8 +8,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
AIFunction,
ChatAgent,
@@ -1544,7 +1544,7 @@ async def test_azure_ai_chat_client_agent_basic_run() -> None:
response = await agent.run("Hello! Please respond with 'Hello World' exactly.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
assert "Hello World" in response.text
@@ -1561,7 +1561,7 @@ async def test_azure_ai_chat_client_agent_basic_run_streaming() -> None:
full_message: str = ""
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
assert chunk is not None
assert isinstance(chunk, AgentRunResponseUpdate)
assert isinstance(chunk, AgentResponseUpdate)
if chunk.text:
full_message += chunk.text
@@ -1585,14 +1585,14 @@ async def test_azure_ai_chat_client_agent_thread_persistence() -> None:
first_response = await agent.run(
"Remember this number: 42. What number did I just tell you to remember?", thread=thread
)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert "42" in first_response.text
# Second message - test conversation memory
second_response = await agent.run(
"What number did I tell you to remember in my previous message?", thread=thread
)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert "42" in second_response.text
@@ -1609,7 +1609,7 @@ async def test_azure_ai_chat_client_agent_existing_thread_id() -> None:
first_response = await first_agent.run("My name is Alice. Remember this.", thread=thread)
# Validate first response
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# The thread ID is set after the first response
@@ -1628,7 +1628,7 @@ async def test_azure_ai_chat_client_agent_existing_thread_id() -> None:
response2 = await second_agent.run("What is my name?", thread=thread)
# Validate that the agent remembers the previous conversation
assert isinstance(response2, AgentRunResponse)
assert isinstance(response2, AgentResponse)
assert response2.text is not None
# Should reference Alice from the previous conversation
assert "alice" in response2.text.lower()
@@ -1648,7 +1648,7 @@ async def test_azure_ai_chat_client_agent_code_interpreter():
response = await agent.run("Write Python code to calculate the factorial of 5 and show the result.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
# Factorial of 5 is 120
assert "120" in response.text or "factorial" in response.text.lower()
@@ -1683,7 +1683,7 @@ async def test_azure_ai_chat_client_agent_file_search():
response = await agent.run("Who is the youngest employee in the files?")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
# Should find information about Alice Johnson (age 24) being the youngest
assert any(term in response.text.lower() for term in ["alice", "johnson", "24"])
@@ -1723,7 +1723,7 @@ async def test_azure_ai_chat_client_agent_hosted_mcp_tool() -> None:
options={"max_tokens": 200},
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
@@ -1748,7 +1748,7 @@ async def test_azure_ai_chat_client_agent_level_tool_persistence():
# First run - agent-level tool should be available
first_response = await agent.run("What's the weather like in Chicago?")
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the agent-level weather tool
assert any(term in first_response.text.lower() for term in ["chicago", "sunny", "25"])
@@ -1756,7 +1756,7 @@ async def test_azure_ai_chat_client_agent_level_tool_persistence():
# Second run - agent-level tool should still be available (persistence test)
second_response = await agent.run("What's the weather in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should use the agent-level weather tool again
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "25"])
@@ -1781,7 +1781,7 @@ async def test_azure_ai_chat_client_agent_chat_options_run_level() -> None:
},
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
@@ -1805,7 +1805,7 @@ async def test_azure_ai_chat_client_agent_chat_options_agent_level() -> None:
"Provide a brief, helpful response.",
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
@@ -10,7 +10,7 @@ from uuid import uuid4
import pytest
from agent_framework import (
AgentRunResponse,
AgentResponse,
ChatAgent,
ChatClientProtocol,
ChatMessage,
@@ -1303,7 +1303,7 @@ async def test_integration_agent_existing_thread():
thread = first_agent.get_new_thread()
first_response = await first_agent.run("My hobby is photography. Remember this.", thread=thread, store=True)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Preserve the thread for reuse
@@ -1321,6 +1321,6 @@ async def test_integration_agent_existing_thread():
# Reuse the preserved thread
second_response = await second_agent.run("What is my hobby?", thread=preserved_thread)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
assert "photography" in second_response.text.lower()
@@ -9,7 +9,7 @@ invoked during durable entity execution.
from dataclasses import dataclass
from typing import Protocol
from agent_framework import AgentRunResponse, AgentRunResponseUpdate
from agent_framework import AgentResponse, AgentResponseUpdate
@dataclass(frozen=True)
@@ -27,14 +27,14 @@ class AgentResponseCallbackProtocol(Protocol):
async def on_streaming_response_update(
self,
update: AgentRunResponseUpdate,
update: AgentResponseUpdate,
context: AgentCallbackContext,
) -> None:
"""Handle a streaming response update emitted by the agent."""
async def on_agent_response(
self,
response: AgentRunResponse,
response: AgentResponse,
context: AgentCallbackContext,
) -> None:
"""Handle the final agent response."""
@@ -35,7 +35,7 @@ from enum import Enum
from typing import Any, cast
from agent_framework import (
AgentRunResponse,
AgentResponse,
BaseContent,
ChatMessage,
DataContent,
@@ -693,8 +693,8 @@ class DurableAgentStateResponse(DurableAgentStateEntry):
)
@staticmethod
def from_run_response(correlation_id: str, response: AgentRunResponse) -> DurableAgentStateResponse:
"""Creates a DurableAgentStateResponse from an AgentRunResponse."""
def from_run_response(correlation_id: str, response: AgentResponse) -> DurableAgentStateResponse:
"""Creates a DurableAgentStateResponse from an AgentResponse."""
return DurableAgentStateResponse(
correlation_id=correlation_id,
created_at=_parse_created_at(response.created_at),
@@ -15,8 +15,8 @@ from typing import Any, cast
import azure.durable_functions as df
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatMessage,
ErrorContent,
Role,
@@ -95,7 +95,7 @@ class AgentEntity:
self,
context: df.DurableEntityContext,
request: RunRequest | dict[str, Any] | str,
) -> AgentRunResponse:
) -> AgentResponse:
"""(Deprecated) Execute the agent with a message directly in the entity.
Args:
@@ -103,7 +103,7 @@ class AgentEntity:
request: RunRequest object, dict, or string message (for backward compatibility)
Returns:
AgentRunResponse enriched with execution metadata.
AgentResponse enriched with execution metadata.
"""
return await self.run(context, request)
@@ -111,7 +111,7 @@ class AgentEntity:
self,
context: df.DurableEntityContext,
request: RunRequest | dict[str, Any] | str,
) -> AgentRunResponse:
) -> AgentResponse:
"""Execute the agent with a message directly in the entity.
Args:
@@ -119,7 +119,7 @@ class AgentEntity:
request: RunRequest object, dict, or string message (for backward compatibility)
Returns:
AgentRunResponse enriched with execution metadata.
AgentResponse enriched with execution metadata.
"""
if isinstance(request, str):
run_request = RunRequest(message=request, role=Role.USER)
@@ -159,7 +159,7 @@ class AgentEntity:
if response_format:
run_kwargs["options"]["response_format"] = response_format
agent_run_response: AgentRunResponse = await self._invoke_agent(
agent_response: AgentResponse = await self._invoke_agent(
run_kwargs=run_kwargs,
correlation_id=correlation_id,
thread_id=thread_id,
@@ -168,11 +168,11 @@ class AgentEntity:
logger.debug(
"[AgentEntity.run] Agent invocation completed - response type: %s",
type(agent_run_response).__name__,
type(agent_response).__name__,
)
try:
response_text = agent_run_response.text if agent_run_response.text else "No response"
response_text = agent_response.text if agent_response.text else "No response"
logger.debug(f"Response: {response_text[:100]}...")
except Exception as extraction_error:
logger.error(
@@ -181,12 +181,12 @@ class AgentEntity:
exc_info=True,
)
state_response = DurableAgentStateResponse.from_run_response(correlation_id, agent_run_response)
state_response = DurableAgentStateResponse.from_run_response(correlation_id, agent_response)
self.state.data.conversation_history.append(state_response)
logger.debug("[AgentEntity.run] AgentRunResponse stored in conversation history")
logger.debug("[AgentEntity.run] AgentResponse stored in conversation history")
return agent_run_response
return agent_response
except Exception as exc:
logger.exception("[AgentEntity.run] Agent execution failed.")
@@ -196,7 +196,7 @@ class AgentEntity:
role=Role.ASSISTANT, contents=[ErrorContent(message=str(exc), error_code=type(exc).__name__)]
)
error_response = AgentRunResponse(messages=[error_message])
error_response = AgentResponse(messages=[error_message])
# Create and store error response in conversation history
error_state_response = DurableAgentStateResponse.from_run_response(correlation_id, error_response)
@@ -211,7 +211,7 @@ class AgentEntity:
correlation_id: str,
thread_id: str,
request_message: str,
) -> AgentRunResponse:
) -> AgentResponse:
"""Execute the agent, preferring streaming when available."""
callback_context: AgentCallbackContext | None = None
if self.callback is not None:
@@ -229,7 +229,7 @@ class AgentEntity:
stream_candidate = await stream_candidate
return await self._consume_stream(
stream=cast(AsyncIterable[AgentRunResponseUpdate], stream_candidate),
stream=cast(AsyncIterable[AgentResponseUpdate], stream_candidate),
callback_context=callback_context,
)
except TypeError as type_error:
@@ -248,32 +248,32 @@ class AgentEntity:
else:
logger.debug("Agent does not expose run_stream; falling back to run().")
agent_run_response = await self._invoke_non_stream(run_kwargs)
await self._notify_final_response(agent_run_response, callback_context)
return agent_run_response
agent_response = await self._invoke_non_stream(run_kwargs)
await self._notify_final_response(agent_response, callback_context)
return agent_response
async def _consume_stream(
self,
stream: AsyncIterable[AgentRunResponseUpdate],
stream: AsyncIterable[AgentResponseUpdate],
callback_context: AgentCallbackContext | None = None,
) -> AgentRunResponse:
"""Consume streaming responses and build the final AgentRunResponse."""
updates: list[AgentRunResponseUpdate] = []
) -> AgentResponse:
"""Consume streaming responses and build the final AgentResponse."""
updates: list[AgentResponseUpdate] = []
async for update in stream:
updates.append(update)
await self._notify_stream_update(update, callback_context)
if updates:
response = AgentRunResponse.from_agent_run_response_updates(updates)
response = AgentResponse.from_agent_run_response_updates(updates)
else:
logger.debug("[AgentEntity] No streaming updates received; creating empty response")
response = AgentRunResponse(messages=[])
response = AgentResponse(messages=[])
await self._notify_final_response(response, callback_context)
return response
async def _invoke_non_stream(self, run_kwargs: dict[str, Any]) -> AgentRunResponse:
async def _invoke_non_stream(self, run_kwargs: dict[str, Any]) -> AgentResponse:
"""Invoke the agent without streaming support."""
run_callable = getattr(self.agent, "run", None)
if run_callable is None or not callable(run_callable):
@@ -283,14 +283,14 @@ class AgentEntity:
if inspect.isawaitable(result):
result = await result
if not isinstance(result, AgentRunResponse):
raise TypeError(f"Agent run() must return an AgentRunResponse instance; received {type(result).__name__}")
if not isinstance(result, AgentResponse):
raise TypeError(f"Agent run() must return an AgentResponse instance; received {type(result).__name__}")
return result
async def _notify_stream_update(
self,
update: AgentRunResponseUpdate,
update: AgentResponseUpdate,
context: AgentCallbackContext | None,
) -> None:
"""Invoke the streaming callback if one is registered."""
@@ -310,7 +310,7 @@ class AgentEntity:
async def _notify_final_response(
self,
response: AgentRunResponse,
response: AgentResponse,
context: AgentCallbackContext | None,
) -> None:
"""Invoke the final response callback if one is registered."""
@@ -11,8 +11,8 @@ from typing import TYPE_CHECKING, Any, TypeAlias, cast
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatMessage,
get_logger,
@@ -46,10 +46,10 @@ else:
class AgentTask(_TypedCompoundTask):
"""A custom Task that wraps entity calls and provides typed AgentRunResponse results.
"""A custom Task that wraps entity calls and provides typed AgentResponse results.
This task wraps the underlying entity call task and intercepts its completion
to convert the raw result into a typed AgentRunResponse object.
to convert the raw result into a typed AgentResponse object.
"""
def __init__(
@@ -77,7 +77,7 @@ class AgentTask(_TypedCompoundTask):
self.id = entity_task.id
def try_set_value(self, child: TaskBase) -> None:
"""Transition the AgentTask to a terminal state and set its value to `AgentRunResponse`.
"""Transition the AgentTask to a terminal state and set its value to `AgentResponse`.
Parameters
----------
@@ -104,7 +104,7 @@ class AgentTask(_TypedCompoundTask):
response,
)
# Set the typed AgentRunResponse as this task's result
# Set the typed AgentResponse as this task's result
self.set_value(is_error=False, value=response)
except Exception as e:
logger.exception(
@@ -118,18 +118,18 @@ class AgentTask(_TypedCompoundTask):
self._first_error = child.result
self.set_value(is_error=True, value=self._first_error)
def _load_agent_response(self, agent_response: AgentRunResponse | dict[str, Any] | None) -> AgentRunResponse:
"""Convert raw payloads into AgentRunResponse instance."""
def _load_agent_response(self, agent_response: AgentResponse | dict[str, Any] | None) -> AgentResponse:
"""Convert raw payloads into AgentResponse instance."""
if agent_response is None:
raise ValueError("agent_response cannot be None")
logger.debug("[load_agent_response] Loading agent response of type: %s", type(agent_response))
if isinstance(agent_response, AgentRunResponse):
if isinstance(agent_response, AgentResponse):
return agent_response
if isinstance(agent_response, dict):
logger.debug("[load_agent_response] Converting dict payload using AgentRunResponse.from_dict")
return AgentRunResponse.from_dict(agent_response)
logger.debug("[load_agent_response] Converting dict payload using AgentResponse.from_dict")
return AgentResponse.from_dict(agent_response)
raise TypeError(f"Unsupported type for agent_response: {type(agent_response)}")
@@ -137,14 +137,14 @@ class AgentTask(_TypedCompoundTask):
self,
response_format: type[BaseModel] | None,
correlation_id: str,
response: AgentRunResponse,
response: AgentResponse,
) -> None:
"""Ensure the AgentRunResponse value is parsed into the expected response_format."""
"""Ensure the AgentResponse value is parsed into the expected response_format."""
if response_format is not None and not isinstance(response.value, response_format):
response.try_parse_value(response_format)
logger.debug(
"[DurableAIAgent] Loaded AgentRunResponse.value for correlation_id %s with type: %s",
"[DurableAIAgent] Loaded AgentResponse.value for correlation_id %s with type: %s",
correlation_id,
type(response.value).__name__,
)
@@ -190,7 +190,7 @@ class DurableAIAgent(AgentProtocol):
# We return an AgentTask here which is a TaskBase subclass.
# This is an intentional deviation from AgentProtocol which defines run() as async.
# The AgentTask can be yielded in Durable Functions orchestrations and will provide
# a typed AgentRunResponse result.
# a typed AgentResponse result.
def run( # type: ignore[override]
self,
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
@@ -203,7 +203,7 @@ class DurableAIAgent(AgentProtocol):
This method implements AgentProtocol and returns an AgentTask (subclass of TaskBase)
that can be yielded in Durable Functions orchestrations. The task's result will be
a typed AgentRunResponse.
a typed AgentResponse.
Args:
messages: The message(s) to send to the agent
@@ -212,7 +212,7 @@ class DurableAIAgent(AgentProtocol):
**kwargs: Additional arguments (enable_tool_calls)
Returns:
An AgentTask that resolves to an AgentRunResponse when yielded
An AgentTask that resolves to an AgentResponse when yielded
Example:
@app.orchestration_trigger(context_name="context")
@@ -220,7 +220,7 @@ class DurableAIAgent(AgentProtocol):
agent = app.get_agent(context, "MyAgent")
thread = agent.get_new_thread()
response = yield agent.run("Hello", thread=thread)
# response is typed as AgentRunResponse
# response is typed as AgentResponse
"""
message_str = self._normalize_messages(messages)
@@ -266,7 +266,7 @@ class DurableAIAgent(AgentProtocol):
# Call the entity to get the underlying task
entity_task = self.context.call_entity(entity_id, "run", run_request.to_dict())
# Wrap it in an AgentTask that will convert the result to AgentRunResponse
# Wrap it in an AgentTask that will convert the result to AgentResponse
agent_task = AgentTask(
entity_task=entity_task,
response_format=response_format,
@@ -286,7 +286,7 @@ class DurableAIAgent(AgentProtocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterator[AgentRunResponseUpdate]:
) -> AsyncIterator[AgentResponseUpdate]:
"""Run the agent with streaming (not supported for durable agents).
Raises:
@@ -10,7 +10,7 @@ from unittest.mock import ANY, AsyncMock, Mock, patch
import azure.durable_functions as df
import azure.functions as func
import pytest
from agent_framework import AgentRunResponse, ChatMessage, ErrorContent
from agent_framework import AgentResponse, ChatMessage, ErrorContent
from agent_framework_azurefunctions import AgentFunctionApp
from agent_framework_azurefunctions._app import WAIT_FOR_RESPONSE_FIELD, WAIT_FOR_RESPONSE_HEADER
@@ -332,7 +332,7 @@ class TestAgentEntityOperations:
"""Test that entity can run agent operation."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Test response")])
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Test response")])
)
entity = AgentEntity(mock_agent)
@@ -343,7 +343,7 @@ class TestAgentEntityOperations:
{"message": "Test message", "thread_id": "test-conv-123", "correlationId": "corr-app-entity-1"},
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Test response"
assert entity.state.message_count == 2
@@ -351,7 +351,7 @@ class TestAgentEntityOperations:
"""Test that the entity stores conversation history."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response 1")])
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response 1")])
)
entity = AgentEntity(mock_agent)
@@ -390,7 +390,7 @@ class TestAgentEntityOperations:
"""Test that the entity increments the message count."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response")])
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
)
entity = AgentEntity(mock_agent)
@@ -437,7 +437,7 @@ class TestAgentEntityFactory:
"""Test that the entity function handles the run operation."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response")])
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
)
entity_function = create_agent_entity(mock_agent)
@@ -465,7 +465,7 @@ class TestAgentEntityFactory:
"""Test that the entity function handles the deprecated run_agent operation for backward compatibility."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response")])
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
)
entity_function = create_agent_entity(mock_agent)
@@ -619,7 +619,7 @@ class TestErrorHandling:
mock_context, {"message": "Test message", "thread_id": "conv-1", "correlationId": "corr-app-error-1"}
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
content = result.messages[0].contents[0]
assert isinstance(content, ErrorContent)
@@ -12,7 +12,7 @@ from typing import Any, TypeVar
from unittest.mock import AsyncMock, Mock, patch
import pytest
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, ChatMessage, ErrorContent, Role
from agent_framework import AgentResponse, AgentResponseUpdate, ChatMessage, ErrorContent, Role
from pydantic import BaseModel
from agent_framework_azurefunctions._durable_agent_state import (
@@ -37,12 +37,12 @@ def _role_value(chat_message: DurableAgentStateMessage) -> str:
return str(role_value)
def _agent_response(text: str | None) -> AgentRunResponse:
"""Create an AgentRunResponse with a single assistant message."""
def _agent_response(text: str | None) -> AgentResponse:
"""Create an AgentResponse with a single assistant message."""
message = (
ChatMessage(role="assistant", text=text) if text is not None else ChatMessage(role="assistant", contents=[])
)
return AgentRunResponse(messages=[message])
return AgentResponse(messages=[message])
class RecordingCallback:
@@ -54,12 +54,12 @@ class RecordingCallback:
async def on_streaming_response_update(
self,
update: AgentRunResponseUpdate,
update: AgentResponseUpdate,
context: Any,
) -> None:
await self.stream_mock(update, context)
async def on_agent_response(self, response: AgentRunResponse, context: Any) -> None:
async def on_agent_response(self, response: AgentResponse, context: Any) -> None:
await self.response_mock(response, context)
@@ -132,7 +132,7 @@ class TestAgentEntityRunAgent:
assert getattr(sent_message.role, "value", sent_message.role) == "user"
# Verify result
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Test response"
async def test_run_agent_executes_agent(self) -> None:
@@ -159,18 +159,18 @@ class TestAgentEntityRunAgent:
assert getattr(sent_message.role, "value", sent_message.role) == "user"
# Verify result
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Test response"
async def test_run_agent_streaming_callbacks_invoked(self) -> None:
"""Ensure streaming updates trigger callbacks and run() is not used."""
updates = [
AgentRunResponseUpdate(text="Hello"),
AgentRunResponseUpdate(text=" world"),
AgentResponseUpdate(text="Hello"),
AgentResponseUpdate(text=" world"),
]
async def update_generator() -> AsyncIterator[AgentRunResponseUpdate]:
async def update_generator() -> AsyncIterator[AgentResponseUpdate]:
for update in updates:
yield update
@@ -192,7 +192,7 @@ class TestAgentEntityRunAgent:
},
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert "Hello" in result.text
assert callback.stream_mock.await_count == len(updates)
assert callback.response_mock.await_count == 1
@@ -239,7 +239,7 @@ class TestAgentEntityRunAgent:
},
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Final response"
assert callback.stream_mock.await_count == 0
assert callback.response_mock.await_count == 1
@@ -605,7 +605,7 @@ class TestErrorHandling:
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-1"}
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
content = result.messages[0].contents[0]
assert isinstance(content, ErrorContent)
@@ -624,7 +624,7 @@ class TestErrorHandling:
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-2"}
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
content = result.messages[0].contents[0]
assert isinstance(content, ErrorContent)
@@ -643,7 +643,7 @@ class TestErrorHandling:
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-3"}
)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
content = result.messages[0].contents[0]
assert isinstance(content, ErrorContent)
@@ -682,7 +682,7 @@ class TestErrorHandling:
)
# Even on error, message info should be preserved
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
content = result.messages[0].contents[0]
assert isinstance(content, ErrorContent)
@@ -793,7 +793,7 @@ class TestRunRequestSupport:
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Response"
async def test_run_agent_with_dict_request(self) -> None:
@@ -814,7 +814,7 @@ class TestRunRequestSupport:
result = await entity.run(mock_context, request_dict)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == "Response"
async def test_run_agent_with_string_raises_without_correlation(self) -> None:
@@ -869,7 +869,7 @@ class TestRunRequestSupport:
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.text == '{"answer": 42}'
assert result.value is None
@@ -887,7 +887,7 @@ class TestRunRequestSupport:
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
# Agent should have been called (tool disabling is framework-dependent)
mock_agent.run.assert_called_once()
@@ -6,7 +6,7 @@ from typing import Any
from unittest.mock import Mock
import pytest
from agent_framework import AgentRunResponse, AgentThread, ChatMessage
from agent_framework import AgentResponse, AgentThread, ChatMessage
from azure.durable_functions.models.Task import TaskBase, TaskState
from agent_framework_azurefunctions import AgentFunctionApp, DurableAIAgent
@@ -39,7 +39,7 @@ def _create_entity_task(task_id: int = 1) -> TaskBase:
class TestAgentResponseHelpers:
"""Tests for helper utilities that prepare AgentRunResponse values."""
"""Tests for helper utilities that prepare AgentResponse values."""
@staticmethod
def _create_agent_task() -> AgentTask:
@@ -48,7 +48,7 @@ class TestAgentResponseHelpers:
def test_load_agent_response_from_instance(self) -> None:
task = self._create_agent_task()
response = AgentRunResponse(messages=[ChatMessage(role="assistant", text='{"foo": "bar"}')])
response = AgentResponse(messages=[ChatMessage(role="assistant", text='{"foo": "bar"}')])
loaded = task._load_agent_response(response)
@@ -57,7 +57,7 @@ class TestAgentResponseHelpers:
def test_load_agent_response_from_serialized(self) -> None:
task = self._create_agent_task()
serialized = AgentRunResponse(messages=[ChatMessage(role="assistant", text="structured")]).to_dict()
serialized = AgentResponse(messages=[ChatMessage(role="assistant", text="structured")]).to_dict()
serialized["value"] = {"answer": 42}
loaded = task._load_agent_response(serialized)
@@ -65,7 +65,7 @@ class TestAgentResponseHelpers:
assert loaded is not None
assert loaded.value == {"answer": 42}
loaded_dict = loaded.to_dict()
assert loaded_dict["type"] == "agent_run_response"
assert loaded_dict["type"] == "agent_response"
def test_load_agent_response_rejects_none(self) -> None:
task = self._create_agent_task()
@@ -86,7 +86,7 @@ class TestAgentResponseHelpers:
# Simulate successful entity task completion
entity_task.state = TaskState.SUCCEEDED
entity_task.result = AgentRunResponse(messages=[ChatMessage(role="assistant", text="Test response")]).to_dict()
entity_task.result = AgentResponse(messages=[ChatMessage(role="assistant", text="Test response")]).to_dict()
# Clear pending_tasks to simulate that parent has processed the child
task.pending_tasks.clear()
@@ -94,9 +94,9 @@ class TestAgentResponseHelpers:
# Call try_set_value
task.try_set_value(entity_task)
# Verify task completed successfully with AgentRunResponse
# Verify task completed successfully with AgentResponse
assert task.state == TaskState.SUCCEEDED
assert isinstance(task.result, AgentRunResponse)
assert isinstance(task.result, AgentResponse)
assert task.result.text == "Test response"
def test_try_set_value_failure(self) -> None:
@@ -128,9 +128,7 @@ class TestAgentResponseHelpers:
# Simulate successful entity task with JSON response
entity_task.state = TaskState.SUCCEEDED
entity_task.result = AgentRunResponse(
messages=[ChatMessage(role="assistant", text='{"answer": "42"}')]
).to_dict()
entity_task.result = AgentResponse(messages=[ChatMessage(role="assistant", text='{"answer": "42"}')]).to_dict()
# Clear pending_tasks to simulate that parent has processed the child
task.pending_tasks.clear()
@@ -140,7 +138,7 @@ class TestAgentResponseHelpers:
# Verify task completed and value was parsed
assert task.state == TaskState.SUCCEEDED
assert isinstance(task.result, AgentRunResponse)
assert isinstance(task.result, AgentResponse)
assert isinstance(task.result.value, TestSchema)
assert task.result.value.answer == "42"
@@ -152,7 +150,7 @@ class TestAgentResponseHelpers:
name: str
task = self._create_agent_task()
response = AgentRunResponse(messages=[ChatMessage(role="assistant", text='{"name": "test"}')])
response = AgentResponse(messages=[ChatMessage(role="assistant", text='{"name": "test"}')])
# Value should be None initially
assert response.value is None
@@ -173,7 +171,7 @@ class TestAgentResponseHelpers:
task = self._create_agent_task()
existing_value = SampleSchema(name="existing")
response = AgentRunResponse(
response = AgentResponse(
messages=[ChatMessage(role="assistant", text='{"name": "new"}')],
value=existing_value,
)
@@ -5,7 +5,7 @@ import logging
from collections.abc import Sequence
from agent_framework import (
AgentRunResponse,
AgentResponse,
ChatAgent,
FunctionCallContent,
FunctionResultContent,
@@ -39,7 +39,7 @@ async def main() -> None:
_log_response(response)
def _log_response(response: AgentRunResponse) -> None:
def _log_response(response: AgentResponse) -> None:
logging.info("\nConversation transcript:")
for idx, message in enumerate(response.messages, start=1):
tag = f"{idx}. {message.role.value if isinstance(message.role, Role) else message.role}"
+1 -1
View File
@@ -4,7 +4,7 @@ This package provides an integration layer between Microsoft Agent Framework
and [OpenAI ChatKit (Python)](https://github.com/openai/chatkit-python/).
Specifically, it mirrors the [Agent SDK integration](https://github.com/openai/chatkit-python/blob/main/docs/server.md#agents-sdk-integration), and provides the following helpers:
- `stream_agent_response`: A helper to convert a streamed `AgentRunResponseUpdate`
- `stream_agent_response`: A helper to convert a streamed `AgentResponseUpdate`
from a Microsoft Agent Framework agent that implements `AgentProtocol` to ChatKit events.
- `ThreadItemConverter`: A extendable helper class to convert ChatKit thread items to
`ChatMessage` objects that can be consumed by an Agent Framework agent.
@@ -6,7 +6,7 @@ import uuid
from collections.abc import AsyncIterable, AsyncIterator, Callable
from datetime import datetime
from agent_framework import AgentRunResponseUpdate, TextContent
from agent_framework import AgentResponseUpdate, TextContent
from chatkit.types import (
AssistantMessageContent,
AssistantMessageContentPartTextDelta,
@@ -19,13 +19,13 @@ from chatkit.types import (
async def stream_agent_response(
response_stream: AsyncIterable[AgentRunResponseUpdate],
response_stream: AsyncIterable[AgentResponseUpdate],
thread_id: str,
generate_id: Callable[[str], str] | None = None,
) -> AsyncIterator[ThreadStreamEvent]:
"""Convert a streamed AgentRunResponseUpdate from Agent Framework to ChatKit events.
"""Convert a streamed AgentResponseUpdate from Agent Framework to ChatKit events.
This helper function takes a stream of AgentRunResponseUpdate objects from
This helper function takes a stream of AgentResponseUpdate objects from
a Microsoft Agent Framework agent and converts them to ChatKit ThreadStreamEvent
objects that can be consumed by the ChatKit UI.
@@ -34,7 +34,7 @@ async def stream_agent_response(
text chunk as it arrives from the agent.
Args:
response_stream: An async iterable of AgentRunResponseUpdate objects
response_stream: An async iterable of AgentResponseUpdate objects
from an Agent Framework agent.
thread_id: The ChatKit thread ID for the conversation.
generate_id: Optional function to generate IDs for ChatKit items.
@@ -4,7 +4,7 @@
from unittest.mock import Mock
from agent_framework import AgentRunResponseUpdate, Role, TextContent
from agent_framework import AgentResponseUpdate, Role, TextContent
from chatkit.types import (
ThreadItemAddedEvent,
ThreadItemDoneEvent,
@@ -34,7 +34,7 @@ class TestStreamAgentResponse:
"""Test streaming single text update."""
async def single_update_stream():
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Hello world")])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Hello world")])
events = []
async for event in stream_agent_response(single_update_stream(), thread_id="test_thread"):
@@ -59,8 +59,8 @@ class TestStreamAgentResponse:
"""Test streaming multiple text updates."""
async def multiple_updates_stream():
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Hello ")])
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="world!")])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Hello ")])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="world!")])
events = []
async for event in stream_agent_response(multiple_updates_stream(), thread_id="test_thread"):
@@ -91,7 +91,7 @@ class TestStreamAgentResponse:
return f"custom_{item_type}_123"
async def single_update_stream():
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Test")])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[TextContent(text="Test")])
events = []
async for event in stream_agent_response(
@@ -107,8 +107,8 @@ class TestStreamAgentResponse:
"""Test streaming updates with empty content."""
async def empty_content_stream():
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[])
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=None)
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=None)
events = []
async for event in stream_agent_response(empty_content_stream(), thread_id="test_thread"):
@@ -130,7 +130,7 @@ class TestStreamAgentResponse:
del non_text_content.text
async def non_text_stream():
yield AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[non_text_content])
yield AgentResponseUpdate(role=Role.ASSISTANT, contents=[non_text_content])
events = []
async for event in stream_agent_response(non_text_stream(), thread_id="test_thread"):
@@ -5,8 +5,8 @@ from typing import Any, ClassVar
from agent_framework import (
AgentMiddlewareTypes,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -210,15 +210,15 @@ class CopilotStudioAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Get a response from the agent.
This method returns the final result of the agent's execution
as a single AgentRunResponse object. The caller is blocked until
as a single AgentResponse object. The caller is blocked until
the final result is available.
Note: For streaming responses, use the run_stream method, which returns
intermediate steps and the final result as a stream of AgentRunResponseUpdate
intermediate steps and the final result as a stream of AgentResponseUpdate
objects. Streaming only the final result is not feasible because the timing of
the final result's availability is unknown, and blocking the caller until then
is undesirable in streaming scenarios.
@@ -248,7 +248,7 @@ class CopilotStudioAgent(BaseAgent):
response_messages = [message async for message in self._process_activities(activities, streaming=False)]
response_id = response_messages[0].message_id if response_messages else None
return AgentRunResponse(messages=response_messages, response_id=response_id)
return AgentResponse(messages=response_messages, response_id=response_id)
async def run_stream(
self,
@@ -256,13 +256,13 @@ class CopilotStudioAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Run the agent as a stream.
This method will return the intermediate steps and final results of the
agent's execution as a stream of AgentRunResponseUpdate objects to the caller.
agent's execution as a stream of AgentResponseUpdate objects to the caller.
Note: An AgentRunResponseUpdate object contains a chunk of a message.
Note: An AgentResponseUpdate object contains a chunk of a message.
Args:
messages: The message(s) to send to the agent.
@@ -285,7 +285,7 @@ class CopilotStudioAgent(BaseAgent):
activities = self.client.ask_question(question, thread.service_thread_id)
async for message in self._process_activities(activities, streaming=True):
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
role=message.role,
contents=message.contents,
author_name=message.author_name,
@@ -5,8 +5,8 @@ from unittest.mock import MagicMock, patch
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatMessage,
Role,
@@ -133,7 +133,7 @@ class TestCopilotStudioAgent:
response = await agent.run("test message")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
content = response.messages[0].contents[0]
assert isinstance(content, TextContent)
@@ -153,7 +153,7 @@ class TestCopilotStudioAgent:
chat_message = ChatMessage(role=Role.USER, contents=[TextContent("test message")])
response = await agent.run(chat_message)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
content = response.messages[0].contents[0]
assert isinstance(content, TextContent)
@@ -173,7 +173,7 @@ class TestCopilotStudioAgent:
response = await agent.run("test message", thread=thread)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
assert thread.service_thread_id == "test-conversation-id"
@@ -204,7 +204,7 @@ class TestCopilotStudioAgent:
response_count = 0
async for response in agent.run_stream("test message"):
assert isinstance(response, AgentRunResponseUpdate)
assert isinstance(response, AgentResponseUpdate)
content = response.contents[0]
assert isinstance(content, TextContent)
assert content.text == "Streaming response"
@@ -231,7 +231,7 @@ class TestCopilotStudioAgent:
response_count = 0
async for response in agent.run_stream("test message", thread=thread):
assert isinstance(response, AgentRunResponseUpdate)
assert isinstance(response, AgentResponseUpdate)
content = response.contents[0]
assert isinstance(content, TextContent)
assert content.text == "Streaming response"
@@ -285,7 +285,7 @@ class TestCopilotStudioAgent:
response = await agent.run("test message")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 2
async def test_run_list_of_messages(self, mock_copilot_client: MagicMock, mock_activity: MagicMock) -> None:
@@ -301,7 +301,7 @@ class TestCopilotStudioAgent:
messages = ["Hello", "How are you?"]
response = await agent.run(messages)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert len(response.messages) == 1
async def test_run_stream_start_conversation_failure(self, mock_copilot_client: MagicMock) -> None:
+25 -25
View File
@@ -33,8 +33,8 @@ from ._serialization import SerializationMixin
from ._threads import AgentThread, ChatMessageStoreProtocol
from ._tools import FUNCTION_INVOKING_CHAT_CLIENT_MARKER, AIFunction, ToolProtocol
from ._types import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
@@ -177,16 +177,16 @@ class AgentProtocol(Protocol):
async def run(self, messages=None, *, thread=None, **kwargs):
# Your custom implementation
from agent_framework import AgentRunResponse
from agent_framework import AgentResponse
return AgentRunResponse(messages=[], response_id="custom-response")
return AgentResponse(messages=[], response_id="custom-response")
def run_stream(self, messages=None, *, thread=None, **kwargs):
# Your custom streaming implementation
async def _stream():
from agent_framework import AgentRunResponseUpdate
from agent_framework import AgentResponseUpdate
yield AgentRunResponseUpdate()
yield AgentResponseUpdate()
return _stream()
@@ -210,15 +210,15 @@ class AgentProtocol(Protocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Get a response from the agent.
This method returns the final result of the agent's execution
as a single AgentRunResponse object. The caller is blocked until
as a single AgentResponse object. The caller is blocked until
the final result is available.
Note: For streaming responses, use the run_stream method, which returns
intermediate steps and the final result as a stream of AgentRunResponseUpdate
intermediate steps and the final result as a stream of AgentResponseUpdate
objects. Streaming only the final result is not feasible because the timing of
the final result's availability is unknown, and blocking the caller until then
is undesirable in streaming scenarios.
@@ -241,13 +241,13 @@ class AgentProtocol(Protocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Run the agent as a stream.
This method will return the intermediate steps and final results of the
agent's execution as a stream of AgentRunResponseUpdate objects to the caller.
agent's execution as a stream of AgentResponseUpdate objects to the caller.
Note: An AgentRunResponseUpdate object contains a chunk of a message.
Note: An AgentResponseUpdate object contains a chunk of a message.
Args:
messages: The message(s) to send to the agent.
@@ -283,19 +283,19 @@ class BaseAgent(SerializationMixin):
Examples:
.. code-block:: python
from agent_framework import BaseAgent, AgentThread, AgentRunResponse
from agent_framework import BaseAgent, AgentThread, AgentResponse
# Create a concrete subclass that implements the protocol
class SimpleAgent(BaseAgent):
async def run(self, messages=None, *, thread=None, **kwargs):
# Custom implementation
return AgentRunResponse(messages=[], response_id="simple-response")
return AgentResponse(messages=[], response_id="simple-response")
def run_stream(self, messages=None, *, thread=None, **kwargs):
async def _stream():
# Custom streaming implementation
yield AgentRunResponseUpdate()
yield AgentResponseUpdate()
return _stream()
@@ -412,8 +412,8 @@ class BaseAgent(SerializationMixin):
description: str | None = None,
arg_name: str = "task",
arg_description: str | None = None,
stream_callback: Callable[[AgentRunResponseUpdate], None]
| Callable[[AgentRunResponseUpdate], Awaitable[None]]
stream_callback: Callable[[AgentResponseUpdate], None]
| Callable[[AgentResponseUpdate], Awaitable[None]]
| None = None,
) -> AIFunction[BaseModel, str]:
"""Create an AIFunction tool that wraps this agent.
@@ -478,7 +478,7 @@ class BaseAgent(SerializationMixin):
return (await self.run(input_text, **forwarded_kwargs)).text
# Use streaming mode - accumulate updates and create final response
response_updates: list[AgentRunResponseUpdate] = []
response_updates: list[AgentResponseUpdate] = []
async for update in self.run_stream(input_text, **forwarded_kwargs):
response_updates.append(update)
if is_async_callback:
@@ -487,7 +487,7 @@ class BaseAgent(SerializationMixin):
stream_callback(update)
# Create final text from accumulated updates
return AgentRunResponse.from_agent_run_response_updates(response_updates).text
return AgentResponse.from_agent_run_response_updates(response_updates).text
agent_tool: AIFunction[BaseModel, str] = AIFunction(
name=tool_name,
@@ -766,7 +766,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| None = None,
options: TOptions_co | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Run the agent with the given messages and options.
Note:
@@ -789,7 +789,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
Will only be passed to functions that are called.
Returns:
An AgentRunResponse containing the agent's response.
An AgentResponse containing the agent's response.
"""
# Build options dict from provided options
opts = dict(options) if options else {}
@@ -872,7 +872,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
response.messages,
**{k: v for k, v in kwargs.items() if k != "thread"},
)
return AgentRunResponse(
return AgentResponse(
messages=response.messages,
response_id=response.response_id,
created_at=response.created_at,
@@ -894,7 +894,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| None = None,
options: TOptions_co | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Stream the agent with the given messages and options.
Note:
@@ -917,7 +917,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
Will only be passed to functions that are called.
Yields:
AgentRunResponseUpdate objects containing chunks of the agent's response.
AgentResponseUpdate objects containing chunks of the agent's response.
"""
# Build options dict from provided options
opts = dict(options) if options else {}
@@ -989,7 +989,7 @@ class ChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
if update.author_name is None:
update.author_name = agent_name
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=update.contents,
role=update.role,
author_name=update.author_name,
@@ -8,7 +8,7 @@ from functools import update_wrapper
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeAlias, TypedDict, TypeVar
from ._serialization import SerializationMixin
from ._types import AgentRunResponse, AgentRunResponseUpdate, ChatMessage, prepare_messages
from ._types import AgentResponse, AgentResponseUpdate, ChatMessage, prepare_messages
from .exceptions import MiddlewareException
if TYPE_CHECKING:
@@ -67,8 +67,8 @@ class AgentRunContext(SerializationMixin):
metadata: Metadata dictionary for sharing data between agent middleware.
result: Agent execution result. Can be observed after calling ``next()``
to see the actual execution result or can be set to override the execution result.
For non-streaming: should be AgentRunResponse.
For streaming: should be AsyncIterable[AgentRunResponseUpdate].
For non-streaming: should be AgentResponse.
For streaming: should be AsyncIterable[AgentResponseUpdate].
terminate: A flag indicating whether to terminate execution after current middleware.
When set to True, execution will stop as soon as control returns to framework.
kwargs: Additional keyword arguments passed to the agent run method.
@@ -105,7 +105,7 @@ class AgentRunContext(SerializationMixin):
thread: "AgentThread | None" = None,
is_streaming: bool = False,
metadata: dict[str, Any] | None = None,
result: AgentRunResponse | AsyncIterable[AgentRunResponseUpdate] | None = None,
result: AgentResponse | AsyncIterable[AgentResponseUpdate] | None = None,
terminate: bool = False,
kwargs: dict[str, Any] | None = None,
) -> None:
@@ -321,8 +321,8 @@ class AgentMiddleware(ABC):
Use context.is_streaming to determine if this is a streaming call.
Middleware can set context.result to override execution, or observe
the actual execution result after calling next().
For non-streaming: AgentRunResponse
For streaming: AsyncIterable[AgentRunResponseUpdate]
For non-streaming: AgentResponse
For streaming: AsyncIterable[AgentResponseUpdate]
next: Function to call the next middleware or final agent execution.
Does not return anything - all data flows through the context.
@@ -773,8 +773,8 @@ class AgentMiddlewarePipeline(BaseMiddlewarePipeline):
agent: "AgentProtocol",
messages: list[ChatMessage],
context: AgentRunContext,
final_handler: Callable[[AgentRunContext], Awaitable[AgentRunResponse]],
) -> AgentRunResponse | None:
final_handler: Callable[[AgentRunContext], Awaitable[AgentResponse]],
) -> AgentResponse | None:
"""Execute the agent middleware pipeline for non-streaming.
Args:
@@ -795,15 +795,15 @@ class AgentMiddlewarePipeline(BaseMiddlewarePipeline):
return await final_handler(context)
# Store the final result
result_container: dict[str, AgentRunResponse | None] = {"result": None}
result_container: dict[str, AgentResponse | None] = {"result": None}
# Custom final handler that handles termination and result override
async def agent_final_handler(c: AgentRunContext) -> AgentRunResponse:
async def agent_final_handler(c: AgentRunContext) -> AgentResponse:
# If terminate was set, return the result (which might be None)
if c.terminate:
if c.result is not None and isinstance(c.result, AgentRunResponse):
if c.result is not None and isinstance(c.result, AgentResponse):
return c.result
return AgentRunResponse()
return AgentResponse()
# Execute actual handler and populate context for observability
return await final_handler(c)
@@ -811,13 +811,13 @@ class AgentMiddlewarePipeline(BaseMiddlewarePipeline):
await first_handler(context)
# Return the result from result container or overridden result
if context.result is not None and isinstance(context.result, AgentRunResponse):
if context.result is not None and isinstance(context.result, AgentResponse):
return context.result
# If no result was set (next() not called), return empty AgentRunResponse
# If no result was set (next() not called), return empty AgentResponse
response = result_container.get("result")
if response is None:
return AgentRunResponse()
return AgentResponse()
return response
async def execute_stream(
@@ -825,8 +825,8 @@ class AgentMiddlewarePipeline(BaseMiddlewarePipeline):
agent: "AgentProtocol",
messages: list[ChatMessage],
context: AgentRunContext,
final_handler: Callable[[AgentRunContext], AsyncIterable[AgentRunResponseUpdate]],
) -> AsyncIterable[AgentRunResponseUpdate]:
final_handler: Callable[[AgentRunContext], AsyncIterable[AgentResponseUpdate]],
) -> AsyncIterable[AgentResponseUpdate]:
"""Execute the agent middleware pipeline for streaming.
Args:
@@ -849,7 +849,7 @@ class AgentMiddlewarePipeline(BaseMiddlewarePipeline):
return
# Store the final result
result_container: dict[str, AsyncIterable[AgentRunResponseUpdate] | None] = {"result_stream": None}
result_container: dict[str, AsyncIterable[AgentResponseUpdate] | None] = {"result_stream": None}
first_handler = self._create_streaming_handler_chain(final_handler, result_container, "result_stream")
await first_handler(context)
@@ -1210,7 +1210,7 @@ def use_agent_middleware(agent_class: type[TAgent]) -> type[TAgent]:
thread: Any = None,
middleware: Sequence[Middleware] | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Middleware-enabled run method."""
# Build fresh middleware pipelines from current middleware collection and run-level middleware
agent_middleware = getattr(self, "middleware", None)
@@ -1237,7 +1237,7 @@ def use_agent_middleware(agent_class: type[TAgent]) -> type[TAgent]:
kwargs=kwargs,
)
async def _execute_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def _execute_handler(ctx: AgentRunContext) -> AgentResponse:
return await original_run(self, ctx.messages, thread=thread, **ctx.kwargs) # type: ignore
result = await agent_pipeline.execute(
@@ -1247,7 +1247,7 @@ def use_agent_middleware(agent_class: type[TAgent]) -> type[TAgent]:
_execute_handler,
)
return result if result else AgentRunResponse()
return result if result else AgentResponse()
# No middleware, execute directly
return await original_run(self, normalized_messages, thread=thread, **kwargs) # type: ignore[return-value]
@@ -1259,7 +1259,7 @@ def use_agent_middleware(agent_class: type[TAgent]) -> type[TAgent]:
thread: Any = None,
middleware: Sequence[Middleware] | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Middleware-enabled run_stream method."""
# Build fresh middleware pipelines from current middleware collection and run-level middleware
agent_middleware = getattr(self, "middleware", None)
@@ -1285,11 +1285,11 @@ def use_agent_middleware(agent_class: type[TAgent]) -> type[TAgent]:
kwargs=kwargs,
)
async def _execute_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def _execute_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
async for update in original_run_stream(self, ctx.messages, thread=thread, **ctx.kwargs): # type: ignore[misc]
yield update
async def _stream_generator() -> AsyncIterable[AgentRunResponseUpdate]:
async def _stream_generator() -> AsyncIterable[AgentResponseUpdate]:
async for update in agent_pipeline.execute_stream(
self, # type: ignore[arg-type]
normalized_messages,
+31 -31
View File
@@ -29,8 +29,8 @@ else:
__all__ = [
"AgentRunResponse",
"AgentRunResponseUpdate",
"AgentResponse",
"AgentResponseUpdate",
"AnnotatedRegions",
"Annotations",
"BaseAnnotation",
@@ -188,7 +188,7 @@ _T = TypeVar("_T")
TEmbedding = TypeVar("TEmbedding")
TChatResponse = TypeVar("TChatResponse", bound="ChatResponse")
TToolMode = TypeVar("TToolMode", bound="ToolMode")
TAgentRunResponse = TypeVar("TAgentRunResponse", bound="AgentRunResponse")
TAgentRunResponse = TypeVar("TAgentRunResponse", bound="AgentResponse")
CreatedAtT = str # Use a datetimeoffset type? Or a more specific type like datetime.datetime?
@@ -2541,7 +2541,7 @@ def prepend_instructions_to_messages(
def _process_update(
response: "ChatResponse | AgentRunResponse", update: "ChatResponseUpdate | AgentRunResponseUpdate"
response: "ChatResponse | AgentResponse", update: "ChatResponseUpdate | AgentResponseUpdate"
) -> None:
"""Processes a single update and modifies the response in place."""
is_new_message = False
@@ -2645,7 +2645,7 @@ def _coalesce_text_content(
contents.extend(coalesced_contents)
def _finalize_response(response: "ChatResponse | AgentRunResponse") -> None:
def _finalize_response(response: "ChatResponse | AgentResponse") -> None:
"""Finalizes the response by performing any necessary post-processing."""
for msg in response.messages:
_coalesce_text_content(msg.contents, TextContent)
@@ -3067,10 +3067,10 @@ class ChatResponseUpdate(SerializationMixin):
return self.text
# region AgentRunResponse
# region AgentResponse
class AgentRunResponse(SerializationMixin):
class AgentResponse(SerializationMixin):
"""Represents the response to an Agent run request.
Provides one or more response messages and metadata about the response.
@@ -3080,11 +3080,11 @@ class AgentRunResponse(SerializationMixin):
Examples:
.. code-block:: python
from agent_framework import AgentRunResponse, ChatMessage
from agent_framework import AgentResponse, ChatMessage
# Create agent response
msg = ChatMessage(role="assistant", text="Task completed successfully.")
response = AgentRunResponse(messages=[msg], response_id="run_123")
response = AgentResponse(messages=[msg], response_id="run_123")
print(response.text) # "Task completed successfully."
# Access user input requests
@@ -3092,20 +3092,20 @@ class AgentRunResponse(SerializationMixin):
print(len(user_requests)) # 0
# Combine streaming updates
updates = [...] # List of AgentRunResponseUpdate objects
response = AgentRunResponse.from_agent_run_response_updates(updates)
updates = [...] # List of AgentResponseUpdate objects
response = AgentResponse.from_agent_run_response_updates(updates)
# Serialization - to_dict and from_dict
response_dict = response.to_dict()
# {'type': 'agent_run_response', 'messages': [...], 'response_id': 'run_123',
# {'type': 'agent_response', 'messages': [...], 'response_id': 'run_123',
# 'additional_properties': {}}
restored_response = AgentRunResponse.from_dict(response_dict)
restored_response = AgentResponse.from_dict(response_dict)
print(restored_response.response_id) # "run_123"
# Serialization - to_json and from_json
response_json = response.to_json()
# '{"type": "agent_run_response", "messages": [...], "response_id": "run_123", ...}'
restored_from_json = AgentRunResponse.from_json(response_json)
# '{"type": "agent_response", "messages": [...], "response_id": "run_123", ...}'
restored_from_json = AgentResponse.from_json(response_json)
print(restored_from_json.text) # "Task completed successfully."
"""
@@ -3127,7 +3127,7 @@ class AgentRunResponse(SerializationMixin):
additional_properties: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
"""Initialize an AgentRunResponse.
"""Initialize an AgentResponse.
Keyword Args:
messages: The list of chat messages in the response.
@@ -3185,14 +3185,14 @@ class AgentRunResponse(SerializationMixin):
@classmethod
def from_agent_run_response_updates(
cls: type[TAgentRunResponse],
updates: Sequence["AgentRunResponseUpdate"],
updates: Sequence["AgentResponseUpdate"],
*,
output_format_type: type[BaseModel] | None = None,
) -> TAgentRunResponse:
"""Joins multiple updates into a single AgentRunResponse.
"""Joins multiple updates into a single AgentResponse.
Args:
updates: A sequence of AgentRunResponseUpdate objects to combine.
updates: A sequence of AgentResponseUpdate objects to combine.
Keyword Args:
output_format_type: Optional Pydantic model type to parse the response text into structured data.
@@ -3208,14 +3208,14 @@ class AgentRunResponse(SerializationMixin):
@classmethod
async def from_agent_response_generator(
cls: type[TAgentRunResponse],
updates: AsyncIterable["AgentRunResponseUpdate"],
updates: AsyncIterable["AgentResponseUpdate"],
*,
output_format_type: type[BaseModel] | None = None,
) -> TAgentRunResponse:
"""Joins multiple updates into a single AgentRunResponse.
"""Joins multiple updates into a single AgentResponse.
Args:
updates: An async iterable of AgentRunResponseUpdate objects to combine.
updates: An async iterable of AgentResponseUpdate objects to combine.
Keyword Args:
output_format_type: Optional Pydantic model type to parse the response text into structured data
@@ -3240,19 +3240,19 @@ class AgentRunResponse(SerializationMixin):
logger.debug("Failed to parse value from agent run response text: %s", ex)
# region AgentRunResponseUpdate
# region AgentResponseUpdate
class AgentRunResponseUpdate(SerializationMixin):
class AgentResponseUpdate(SerializationMixin):
"""Represents a single streaming response chunk from an Agent.
Examples:
.. code-block:: python
from agent_framework import AgentRunResponseUpdate, TextContent
from agent_framework import AgentResponseUpdate, TextContent
# Create an agent run update
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="Processing...")],
role="assistant",
response_id="run_123",
@@ -3264,15 +3264,15 @@ class AgentRunResponseUpdate(SerializationMixin):
# Serialization - to_dict and from_dict
update_dict = update.to_dict()
# {'type': 'agent_run_response_update', 'contents': [{'type': 'text', 'text': 'Processing...'}],
# {'type': 'agent_response_update', 'contents': [{'type': 'text', 'text': 'Processing...'}],
# 'role': {'type': 'role', 'value': 'assistant'}, 'response_id': 'run_123'}
restored_update = AgentRunResponseUpdate.from_dict(update_dict)
restored_update = AgentResponseUpdate.from_dict(update_dict)
print(restored_update.response_id) # "run_123"
# Serialization - to_json and from_json
update_json = update.to_json()
# '{"type": "agent_run_response_update", "contents": [{"type": "text", "text": "Processing..."}], ...}'
restored_from_json = AgentRunResponseUpdate.from_json(update_json)
# '{"type": "agent_response_update", "contents": [{"type": "text", "text": "Processing..."}], ...}'
restored_from_json = AgentResponseUpdate.from_json(update_json)
print(restored_from_json.text) # "Processing..."
"""
@@ -3292,7 +3292,7 @@ class AgentRunResponseUpdate(SerializationMixin):
raw_representation: Any | None = None,
**kwargs: Any,
) -> None:
"""Initialize an AgentRunResponseUpdate.
"""Initialize an AgentResponseUpdate.
Keyword Args:
contents: Optional list of BaseContent items or dicts to include in the update.
@@ -9,8 +9,8 @@ from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
BaseContent,
@@ -126,7 +126,7 @@ class WorkflowAgent(BaseAgent):
checkpoint_id: str | None = None,
checkpoint_storage: CheckpointStorage | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Get a response from the workflow agent (non-streaming).
This method collects all streaming updates and merges them into a single response.
@@ -146,10 +146,10 @@ class WorkflowAgent(BaseAgent):
and ai_function tools.
Returns:
The final workflow response as an AgentRunResponse.
The final workflow response as an AgentResponse.
"""
# Collect all streaming updates
response_updates: list[AgentRunResponseUpdate] = []
response_updates: list[AgentResponseUpdate] = []
input_messages = normalize_messages_input(messages)
thread = thread or self.get_new_thread()
response_id = str(uuid.uuid4())
@@ -175,7 +175,7 @@ class WorkflowAgent(BaseAgent):
checkpoint_id: str | None = None,
checkpoint_storage: CheckpointStorage | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Stream response updates from the workflow agent.
Args:
@@ -193,11 +193,11 @@ class WorkflowAgent(BaseAgent):
and ai_function tools.
Yields:
AgentRunResponseUpdate objects representing the workflow execution progress.
AgentResponseUpdate objects representing the workflow execution progress.
"""
input_messages = normalize_messages_input(messages)
thread = thread or self.get_new_thread()
response_updates: list[AgentRunResponseUpdate] = []
response_updates: list[AgentResponseUpdate] = []
response_id = str(uuid.uuid4())
async for update in self._run_stream_impl(
@@ -220,7 +220,7 @@ class WorkflowAgent(BaseAgent):
checkpoint_id: str | None = None,
checkpoint_storage: CheckpointStorage | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Internal implementation of streaming execution.
Args:
@@ -233,7 +233,7 @@ class WorkflowAgent(BaseAgent):
workflow and ai_function tools.
Yields:
AgentRunResponseUpdate objects representing the workflow execution progress.
AgentResponseUpdate objects representing the workflow execution progress.
"""
# Determine the event stream based on whether we have function responses
if bool(self.pending_requests):
@@ -289,8 +289,8 @@ class WorkflowAgent(BaseAgent):
self,
response_id: str,
event: WorkflowEvent,
) -> AgentRunResponseUpdate | None:
"""Convert a workflow event to an AgentRunResponseUpdate.
) -> AgentResponseUpdate | None:
"""Convert a workflow event to an AgentResponseUpdate.
AgentRunUpdateEvent, RequestInfoEvent, and WorkflowOutputEvent are processed.
Other workflow events are ignored as they are workflow-internal.
@@ -319,17 +319,17 @@ class WorkflowAgent(BaseAgent):
# Convert workflow output to an agent response update.
# Handle different data types appropriately.
# Skip AgentRunResponse from AgentExecutor with output_response=True
# Skip AgentResponse from AgentExecutor with output_response=True
# since streaming events already surfaced the content.
if isinstance(data, AgentRunResponse):
if isinstance(data, AgentResponse):
executor = self.workflow.executors.get(source_executor_id)
if isinstance(executor, AgentExecutor) and executor.output_response:
return None
if isinstance(data, AgentRunResponseUpdate):
if isinstance(data, AgentResponseUpdate):
return data
if isinstance(data, ChatMessage):
return AgentRunResponseUpdate(
return AgentResponseUpdate(
contents=list(data.contents),
role=data.role,
author_name=data.author_name or source_executor_id,
@@ -341,7 +341,7 @@ class WorkflowAgent(BaseAgent):
contents = self._extract_contents(data)
if not contents:
return None
return AgentRunResponseUpdate(
return AgentResponseUpdate(
contents=contents,
role=Role.ASSISTANT,
author_name=source_executor_id,
@@ -367,7 +367,7 @@ class WorkflowAgent(BaseAgent):
function_call=function_call,
additional_properties={"request_id": request_id},
)
return AgentRunResponseUpdate(
return AgentResponseUpdate(
contents=[function_call, approval_request],
role=Role.ASSISTANT,
author_name=self.name,
@@ -441,30 +441,30 @@ class WorkflowAgent(BaseAgent):
class _ResponseState(TypedDict):
"""State for grouping response updates by message_id."""
by_msg: dict[str, list[AgentRunResponseUpdate]]
dangling: list[AgentRunResponseUpdate]
by_msg: dict[str, list[AgentResponseUpdate]]
dangling: list[AgentResponseUpdate]
@staticmethod
def merge_updates(updates: list[AgentRunResponseUpdate], response_id: str) -> AgentRunResponse:
"""Merge streaming updates into a single AgentRunResponse.
def merge_updates(updates: list[AgentResponseUpdate], response_id: str) -> AgentResponse:
"""Merge streaming updates into a single AgentResponse.
Behavior:
- Group updates by response_id; within each response_id, group by message_id and keep a dangling bucket for
updates without message_id.
- Convert each group (per message and dangling) into an intermediate AgentRunResponse via
AgentRunResponse.from_agent_run_response_updates, then sort by created_at and merge.
- Convert each group (per message and dangling) into an intermediate AgentResponse via
AgentResponse.from_agent_run_response_updates, then sort by created_at and merge.
- Append messages from updates without any response_id at the end (global dangling), while aggregating metadata.
Args:
updates: The list of AgentRunResponseUpdate objects to merge.
response_id: The response identifier to set on the returned AgentRunResponse.
updates: The list of AgentResponseUpdate objects to merge.
response_id: The response identifier to set on the returned AgentResponse.
Returns:
An AgentRunResponse with messages in processing order and aggregated metadata.
An AgentResponse with messages in processing order and aggregated metadata.
"""
# PHASE 1: GROUP UPDATES BY RESPONSE_ID AND MESSAGE_ID
states: dict[str, WorkflowAgent._ResponseState] = {}
global_dangling: list[AgentRunResponseUpdate] = []
global_dangling: list[AgentResponseUpdate] = []
for u in updates:
if u.response_id:
@@ -497,7 +497,7 @@ class WorkflowAgent(BaseAgent):
return a
return a + b
def _merge_responses(current: AgentRunResponse | None, incoming: AgentRunResponse) -> AgentRunResponse:
def _merge_responses(current: AgentResponse | None, incoming: AgentResponse) -> AgentResponse:
if current is None:
return incoming
raw_list: list[object] = []
@@ -512,7 +512,7 @@ class WorkflowAgent(BaseAgent):
_add_raw(current.raw_representation)
if incoming.raw_representation is not None:
_add_raw(incoming.raw_representation)
return AgentRunResponse(
return AgentResponse(
messages=(current.messages or []) + (incoming.messages or []),
response_id=current.response_id or incoming.response_id,
created_at=incoming.created_at or current.created_at,
@@ -533,16 +533,16 @@ class WorkflowAgent(BaseAgent):
by_msg = state["by_msg"]
dangling = state["dangling"]
per_message_responses: list[AgentRunResponse] = []
per_message_responses: list[AgentResponse] = []
for _, msg_updates in by_msg.items():
if msg_updates:
per_message_responses.append(AgentRunResponse.from_agent_run_response_updates(msg_updates))
per_message_responses.append(AgentResponse.from_agent_run_response_updates(msg_updates))
if dangling:
per_message_responses.append(AgentRunResponse.from_agent_run_response_updates(dangling))
per_message_responses.append(AgentResponse.from_agent_run_response_updates(dangling))
per_message_responses.sort(key=lambda r: _parse_dt(r.created_at))
aggregated: AgentRunResponse | None = None
aggregated: AgentResponse | None = None
for resp in per_message_responses:
if resp.response_id and grouped_response_id and resp.response_id != grouped_response_id:
resp.response_id = grouped_response_id
@@ -570,7 +570,7 @@ class WorkflowAgent(BaseAgent):
# PHASE 3: HANDLE GLOBAL DANGLING UPDATES (NO RESPONSE_ID)
if global_dangling:
flattened = AgentRunResponse.from_agent_run_response_updates(global_dangling)
flattened = AgentResponse.from_agent_run_response_updates(global_dangling)
final_messages.extend(flattened.messages)
if flattened.usage_details:
merged_usage = _sum_usage(merged_usage, flattened.usage_details)
@@ -591,7 +591,7 @@ class WorkflowAgent(BaseAgent):
raw_representations.append(cast_flat)
# PHASE 4: CONSTRUCT FINAL RESPONSE WITH INPUT RESPONSE_ID
return AgentRunResponse(
return AgentResponse(
messages=final_messages,
response_id=response_id,
created_at=latest_created_at,
@@ -9,7 +9,7 @@ from agent_framework import FunctionApprovalRequestContent, FunctionApprovalResp
from .._agents import AgentProtocol, ChatAgent
from .._threads import AgentThread
from .._types import AgentRunResponse, AgentRunResponseUpdate, ChatMessage
from .._types import AgentResponse, AgentResponseUpdate, ChatMessage
from ._agent_utils import resolve_agent_id
from ._checkpoint_encoding import decode_checkpoint_value, encode_checkpoint_value
from ._const import WORKFLOW_RUN_KWARGS_KEY
@@ -51,14 +51,14 @@ class AgentExecutorResponse:
Attributes:
executor_id: The ID of the executor that generated the response.
agent_run_response: The underlying agent run response (unaltered from client).
agent_response: The underlying agent run response (unaltered from client).
full_conversation: The full conversation context (prior inputs + all assistant/tool outputs) that
should be used when chaining to another AgentExecutor. This prevents downstream agents losing
user prompts while keeping the emitted AgentRunEvent text faithful to the raw agent output.
"""
executor_id: str
agent_run_response: AgentRunResponse
agent_response: AgentResponse
full_conversation: list[ChatMessage] | None = None
@@ -85,7 +85,7 @@ class AgentExecutor(Executor):
Args:
agent: The agent to be wrapped by this executor.
agent_thread: The thread to use for running the agent. If None, a new thread will be created.
output_response: Whether to yield an AgentRunResponse as a workflow output when the agent completes.
output_response: Whether to yield an AgentResponse as a workflow output when the agent completes.
id: A unique identifier for the executor. If None, the agent's name will be used if available.
"""
# Prefer provided id; else use agent.name if present; else generate deterministic prefix
@@ -106,14 +106,14 @@ class AgentExecutor(Executor):
@property
def output_response(self) -> bool:
"""Whether this executor yields AgentRunResponse as workflow output when complete."""
"""Whether this executor yields AgentResponse as workflow output when complete."""
return self._output_response
@property
def workflow_output_types(self) -> list[type[Any]]:
# Override to declare AgentRunResponse as a possible output type only if enabled.
# Override to declare AgentResponse as a possible output type only if enabled.
if self._output_response:
return [AgentRunResponse]
return [AgentResponse]
return []
@property
@@ -123,7 +123,7 @@ class AgentExecutor(Executor):
@handler
async def run(
self, request: AgentExecutorRequest, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]
self, request: AgentExecutorRequest, ctx: WorkflowContext[AgentExecutorResponse, AgentResponse]
) -> None:
"""Handle an AgentExecutorRequest (canonical input).
@@ -136,22 +136,22 @@ class AgentExecutor(Executor):
@handler
async def from_response(
self, prior: AgentExecutorResponse, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]
self, prior: AgentExecutorResponse, ctx: WorkflowContext[AgentExecutorResponse, AgentResponse]
) -> None:
"""Enable seamless chaining: accept a prior AgentExecutorResponse as input.
Strategy: treat the prior response's messages as the conversation state and
immediately run the agent to produce a new response.
"""
# Replace cache with full conversation if available, else fall back to agent_run_response messages.
# Replace cache with full conversation if available, else fall back to agent_response messages.
if prior.full_conversation is not None:
self._cache = list(prior.full_conversation)
else:
self._cache = list(prior.agent_run_response.messages)
self._cache = list(prior.agent_response.messages)
await self._run_agent_and_emit(ctx)
@handler
async def from_str(self, text: str, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]) -> None:
async def from_str(self, text: str, ctx: WorkflowContext[AgentExecutorResponse, AgentResponse]) -> None:
"""Accept a raw user prompt string and run the agent (one-shot)."""
self._cache = normalize_messages_input(text)
await self._run_agent_and_emit(ctx)
@@ -160,7 +160,7 @@ class AgentExecutor(Executor):
async def from_message(
self,
message: ChatMessage,
ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse],
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse],
) -> None:
"""Accept a single ChatMessage as input."""
self._cache = normalize_messages_input(message)
@@ -170,7 +170,7 @@ class AgentExecutor(Executor):
async def from_messages(
self,
messages: list[str | ChatMessage],
ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse],
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse],
) -> None:
"""Accept a list of chat inputs (strings or ChatMessage) as conversation context."""
self._cache = normalize_messages_input(messages)
@@ -181,7 +181,7 @@ class AgentExecutor(Executor):
self,
original_request: FunctionApprovalRequestContent,
response: FunctionApprovalResponseContent,
ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse],
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse],
) -> None:
"""Handle user input responses for function approvals during agent execution.
@@ -292,7 +292,7 @@ class AgentExecutor(Executor):
logger.debug("AgentExecutor %s: Resetting cache", self.id)
self._cache.clear()
async def _run_agent_and_emit(self, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]) -> None:
async def _run_agent_and_emit(self, ctx: WorkflowContext[AgentExecutorResponse, AgentResponse]) -> None:
"""Execute the underlying agent, emit events, and enqueue response.
Checks ctx.is_streaming() to determine whether to emit incremental AgentRunUpdateEvent
@@ -306,7 +306,7 @@ class AgentExecutor(Executor):
response = await self._run_agent(cast(WorkflowContext, ctx))
# Always extend full conversation with cached messages plus agent outputs
# (agent_run_response.messages) after each run. This is to avoid losing context
# (agent_response.messages) after each run. This is to avoid losing context
# when agent did not complete and the cache is cleared when responses come back.
# Do not mutate response.messages so AgentRunEvent remains faithful to the raw output.
self._full_conversation.extend(list(self._cache) + (list(response.messages) if response else []))
@@ -323,14 +323,14 @@ class AgentExecutor(Executor):
await ctx.send_message(agent_response)
self._cache.clear()
async def _run_agent(self, ctx: WorkflowContext) -> AgentRunResponse | None:
async def _run_agent(self, ctx: WorkflowContext) -> AgentResponse | None:
"""Execute the underlying agent in non-streaming mode.
Args:
ctx: The workflow context for emitting events.
Returns:
The complete AgentRunResponse, or None if waiting for user input.
The complete AgentResponse, or None if waiting for user input.
"""
run_kwargs: dict[str, Any] = await ctx.get_shared_state(WORKFLOW_RUN_KWARGS_KEY)
@@ -350,18 +350,18 @@ class AgentExecutor(Executor):
return response
async def _run_agent_streaming(self, ctx: WorkflowContext) -> AgentRunResponse | None:
async def _run_agent_streaming(self, ctx: WorkflowContext) -> AgentResponse | None:
"""Execute the underlying agent in streaming mode and collect the full response.
Args:
ctx: The workflow context for emitting events.
Returns:
The complete AgentRunResponse, or None if waiting for user input.
The complete AgentResponse, or None if waiting for user input.
"""
run_kwargs: dict[str, Any] = await ctx.get_shared_state(WORKFLOW_RUN_KWARGS_KEY)
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
user_input_requests: list[FunctionApprovalRequestContent] = []
async for update in self._agent.run_stream(
self._cache,
@@ -374,15 +374,15 @@ class AgentExecutor(Executor):
if update.user_input_requests:
user_input_requests.extend(update.user_input_requests)
# Build the final AgentRunResponse from the collected updates
# Build the final AgentResponse from the collected updates
if isinstance(self._agent, ChatAgent):
response_format = self._agent.default_options.get("response_format")
response = AgentRunResponse.from_agent_run_response_updates(
response = AgentResponse.from_agent_run_response_updates(
updates,
output_format_type=response_format,
)
else:
response = AgentRunResponse.from_agent_run_response_updates(updates)
response = AgentResponse.from_agent_run_response_updates(updates)
# Handle any user input requests after the streaming completes
if user_input_requests:
@@ -346,7 +346,7 @@ class BaseGroupChatOrchestrator(Executor, ABC):
List of ChatMessages extracted from the response
"""
if isinstance(response, AgentExecutorResponse):
return response.agent_run_response.messages
return response.agent_response.messages
if isinstance(response, GroupChatResponseMessage):
return [response.message]
raise TypeError(f"Unsupported response type: {type(response)}")
@@ -79,7 +79,7 @@ class _AggregateAgentConversations(Executor):
[ single_user_prompt?, agent1_final_assistant, agent2_final_assistant, ... ]
- Extracts a single user prompt (first user message seen across results).
- For each result, selects the final assistant message (prefers agent_run_response.messages).
- For each result, selects the final assistant message (prefers agent_response.messages).
- Avoids duplicating the same user message per agent.
"""
@@ -107,7 +107,7 @@ class _AggregateAgentConversations(Executor):
assistant_replies: list[ChatMessage] = []
for r in results:
resp_messages = list(getattr(r.agent_run_response, "messages", []) or [])
resp_messages = list(getattr(r.agent_response, "messages", []) or [])
conv = r.full_conversation if r.full_conversation is not None else resp_messages
logger.debug(
@@ -213,7 +213,7 @@ class ConcurrentBuilder:
# Custom aggregator via callback (sync or async). The callback receives
# list[AgentExecutorResponse] and its return value becomes the workflow's output.
def summarize(results: list[AgentExecutorResponse]) -> str:
return " | ".join(r.agent_run_response.messages[-1].text for r in results)
return " | ".join(r.agent_response.messages[-1].text for r in results)
workflow = ConcurrentBuilder().participants([agent1, agent2, agent3]).with_aggregator(summarize).build()
@@ -223,7 +223,7 @@ class ConcurrentBuilder:
class MyAggregator(Executor):
@handler
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
await ctx.yield_output(" | ".join(r.agent_run_response.messages[-1].text for r in results))
await ctx.yield_output(" | ".join(r.agent_response.messages[-1].text for r in results))
workflow = (
@@ -416,7 +416,7 @@ class ConcurrentBuilder:
class CustomAggregator(Executor):
@handler
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext) -> None:
await ctx.yield_output(" | ".join(r.agent_run_response.messages[-1].text for r in results))
await ctx.yield_output(" | ".join(r.agent_response.messages[-1].text for r in results))
wf = ConcurrentBuilder().participants([a1, a2, a3]).with_aggregator(CustomAggregator()).build()
@@ -424,7 +424,7 @@ class ConcurrentBuilder:
# Callback-based aggregator (string result)
async def summarize(results: list[AgentExecutorResponse]) -> str:
return " | ".join(r.agent_run_response.messages[-1].text for r in results)
return " | ".join(r.agent_response.messages[-1].text for r in results)
wf = ConcurrentBuilder().participants([a1, a2, a3]).with_aggregator(summarize).build()
@@ -432,7 +432,7 @@ class ConcurrentBuilder:
# Callback-based aggregator (yield result)
async def summarize(results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
await ctx.yield_output(" | ".join(r.agent_run_response.messages[-1].text for r in results))
await ctx.yield_output(" | ".join(r.agent_response.messages[-1].text for r in results))
wf = ConcurrentBuilder().participants([a1, a2, a3]).with_aggregator(summarize).build()
@@ -8,7 +8,7 @@ from dataclasses import dataclass
from enum import Enum
from typing import Any, TypeAlias
from agent_framework import AgentRunResponse, AgentRunResponseUpdate
from agent_framework import AgentResponse, AgentResponseUpdate
from ._checkpoint_encoding import decode_checkpoint_value, encode_checkpoint_value
from ._typing_utils import deserialize_type, serialize_type
@@ -367,9 +367,9 @@ class ExecutorFailedEvent(ExecutorEvent):
class AgentRunUpdateEvent(ExecutorEvent):
"""Event triggered when an agent is streaming messages."""
data: AgentRunResponseUpdate
data: AgentResponseUpdate
def __init__(self, executor_id: str, data: AgentRunResponseUpdate):
def __init__(self, executor_id: str, data: AgentResponseUpdate):
"""Initialize the agent streaming event."""
super().__init__(executor_id, data)
@@ -381,9 +381,9 @@ class AgentRunUpdateEvent(ExecutorEvent):
class AgentRunEvent(ExecutorEvent):
"""Event triggered when an agent run is completed."""
data: AgentRunResponse
data: AgentResponse
def __init__(self, executor_id: str, data: AgentRunResponse):
def __init__(self, executor_id: str, data: AgentResponse):
"""Initialize the agent run event."""
super().__init__(executor_id, data)
@@ -42,7 +42,7 @@ from .._agents import AgentProtocol, ChatAgent
from .._middleware import FunctionInvocationContext, FunctionMiddleware
from .._threads import AgentThread
from .._tools import AIFunction, ai_function
from .._types import AgentRunResponse, ChatMessage, Role
from .._types import AgentResponse, ChatMessage, Role
from ._agent_executor import AgentExecutor, AgentExecutorRequest, AgentExecutorResponse
from ._agent_utils import resolve_agent_id
from ._base_group_chat_orchestrator import TerminationCondition
@@ -155,7 +155,7 @@ class HandoffAgentUserRequest:
agent_response: The response generated by the agent at the most recent turn
"""
agent_response: AgentRunResponse
agent_response: AgentResponse
@staticmethod
def create_response(response: str | list[str] | ChatMessage | list[ChatMessage]) -> list[ChatMessage]:
@@ -361,7 +361,7 @@ class HandoffAgentExecutor(AgentExecutor):
return _handoff_tool
@override
async def _run_agent_and_emit(self, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]) -> None:
async def _run_agent_and_emit(self, ctx: WorkflowContext[AgentExecutorResponse, AgentResponse]) -> None:
"""Override to support handoff."""
# When the full conversation is empty, it means this is the first run.
# Broadcast the initial cache to all other agents. Subsequent runs won't
@@ -436,7 +436,7 @@ class HandoffAgentExecutor(AgentExecutor):
self,
original_request: HandoffAgentUserRequest,
response: list[ChatMessage],
ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse],
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse],
) -> None:
"""Handle user response for a request that is issued after agent runs.
@@ -477,7 +477,7 @@ class HandoffAgentExecutor(AgentExecutor):
# Since all agents are connected via fan-out, we can directly send the message
await ctx.send_message(agent_executor_request)
def _is_handoff_requested(self, response: AgentRunResponse) -> str | None:
def _is_handoff_requested(self, response: AgentResponse) -> str | None:
"""Determine if the agent response includes a handoff request.
If a handoff tool is invoked, the middleware will short-circuit execution
@@ -16,7 +16,7 @@ from typing_extensions import Never
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentResponse,
ChatMessage,
Role,
)
@@ -594,7 +594,7 @@ class StandardMagenticManager(MagenticManagerBase):
The agent's run method is called which applies the agent's configured options
(temperature, seed, instructions, etc.).
"""
response: AgentRunResponse = await self._agent.run(messages)
response: AgentResponse = await self._agent.run(messages)
if not response.messages:
raise RuntimeError("Agent returned no messages in response.")
if len(response.messages) > 1:
@@ -223,7 +223,7 @@ class WorkflowBuilder:
Args:
candidate: The executor or agent to wrap.
agent_thread: The thread to use for running the agent. If None, a new thread will be created.
output_response: Whether to yield an AgentRunResponse as a workflow output when the agent completes.
output_response: Whether to yield an AgentResponse as a workflow output when the agent completes.
executor_id: A unique identifier for the executor. If None, the agent's name will be used if available.
"""
try: # Local import to avoid hard dependency at import time
@@ -352,7 +352,7 @@ class WorkflowBuilder:
the agent's internal name. But it must be unique within the workflow.
agent_thread: The thread to use for running the agent. If None, a new thread will be created when
the agent is instantiated.
output_response: Whether to yield an AgentRunResponse as a workflow output when the agent completes.
output_response: Whether to yield an AgentResponse as a workflow output when the agent completes.
Example:
.. code-block:: python
@@ -411,7 +411,7 @@ class WorkflowBuilder:
Args:
agent: The agent to add to the workflow.
agent_thread: The thread to use for running the agent. If None, a new thread will be created.
output_response: Whether to yield an AgentRunResponse as a workflow output when the agent completes.
output_response: Whether to yield an AgentResponse as a workflow output when the agent completes.
id: A unique identifier for the executor. If None, the agent's name will be used if available.
Returns:
@@ -35,8 +35,8 @@ if TYPE_CHECKING: # pragma: no cover
from ._threads import AgentThread
from ._tools import AIFunction
from ._types import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
@@ -1315,10 +1315,10 @@ def use_instrumentation(
def _trace_agent_run(
run_func: Callable[..., Awaitable["AgentRunResponse"]],
run_func: Callable[..., Awaitable["AgentResponse"]],
provider_name: str,
capture_usage: bool = True,
) -> Callable[..., Awaitable["AgentRunResponse"]]:
) -> Callable[..., Awaitable["AgentResponse"]]:
"""Decorator to trace chat completion activities.
Args:
@@ -1334,7 +1334,7 @@ def _trace_agent_run(
*,
thread: "AgentThread | None" = None,
**kwargs: Any,
) -> "AgentRunResponse":
) -> "AgentResponse":
global OBSERVABILITY_SETTINGS
if not OBSERVABILITY_SETTINGS.ENABLED:
@@ -1384,10 +1384,10 @@ def _trace_agent_run(
def _trace_agent_run_stream(
run_streaming_func: Callable[..., AsyncIterable["AgentRunResponseUpdate"]],
run_streaming_func: Callable[..., AsyncIterable["AgentResponseUpdate"]],
provider_name: str,
capture_usage: bool,
) -> Callable[..., AsyncIterable["AgentRunResponseUpdate"]]:
) -> Callable[..., AsyncIterable["AgentResponseUpdate"]]:
"""Decorator to trace streaming agent run activities.
Args:
@@ -1403,7 +1403,7 @@ def _trace_agent_run_stream(
*,
thread: "AgentThread | None" = None,
**kwargs: Any,
) -> AsyncIterable["AgentRunResponseUpdate"]:
) -> AsyncIterable["AgentResponseUpdate"]:
global OBSERVABILITY_SETTINGS
if not OBSERVABILITY_SETTINGS.ENABLED:
@@ -1412,9 +1412,9 @@ def _trace_agent_run_stream(
yield streaming_agent_response
return
from ._types import AgentRunResponse, merge_chat_options
from ._types import AgentResponse, merge_chat_options
all_updates: list["AgentRunResponseUpdate"] = []
all_updates: list["AgentResponseUpdate"] = []
default_options = getattr(self, "default_options", {})
options = merge_chat_options(default_options, kwargs.get("options", {}))
@@ -1444,7 +1444,7 @@ def _trace_agent_run_stream(
capture_exception(span=span, exception=exception, timestamp=time_ns())
raise
else:
response = AgentRunResponse.from_agent_run_response_updates(all_updates)
response = AgentResponse.from_agent_run_response_updates(all_updates)
attributes = _get_response_attributes(attributes, response, capture_usage=capture_usage)
_capture_response(span=span, attributes=attributes)
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages:
@@ -1784,7 +1784,7 @@ def _to_otel_part(content: "Contents") -> dict[str, Any] | None:
def _get_response_attributes(
attributes: dict[str, Any],
response: "ChatResponse | AgentRunResponse",
response: "ChatResponse | AgentResponse",
duration: float | None = None,
*,
capture_usage: bool = True,
@@ -9,8 +9,8 @@ from azure.identity import AzureCliCredential
from pydantic import Field
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatAgent,
ChatClientProtocol,
@@ -403,7 +403,7 @@ async def test_azure_assistants_agent_basic_run():
response = await agent.run("Hello! Please respond with 'Hello World' exactly.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
assert "Hello World" in response.text
@@ -420,7 +420,7 @@ async def test_azure_assistants_agent_basic_run_streaming():
full_message: str = ""
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
assert chunk is not None
assert isinstance(chunk, AgentRunResponseUpdate)
assert isinstance(chunk, AgentResponseUpdate)
if chunk.text:
full_message += chunk.text
@@ -444,14 +444,14 @@ async def test_azure_assistants_agent_thread_persistence():
first_response = await agent.run(
"Remember this number: 42. What number did I just tell you to remember?", thread=thread
)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert "42" in first_response.text
# Second message - test conversation memory
second_response = await agent.run(
"What number did I tell you to remember in my previous message?", thread=thread
)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert "42" in second_response.text
# Verify thread has been populated with conversation ID
@@ -475,7 +475,7 @@ async def test_azure_assistants_agent_existing_thread_id():
response1 = await agent.run("What's the weather in Paris?", thread=thread)
# Validate first response
assert isinstance(response1, AgentRunResponse)
assert isinstance(response1, AgentResponse)
assert response1.text is not None
assert any(word in response1.text.lower() for word in ["weather", "paris"])
@@ -497,7 +497,7 @@ async def test_azure_assistants_agent_existing_thread_id():
response2 = await agent.run("What was the last city I asked about?", thread=thread)
# Validate that the agent remembers the previous conversation
assert isinstance(response2, AgentRunResponse)
assert isinstance(response2, AgentResponse)
assert response2.text is not None
# Should reference Paris from the previous conversation
assert "paris" in response2.text.lower()
@@ -517,7 +517,7 @@ async def test_azure_assistants_agent_code_interpreter():
response = await agent.run("Write Python code to calculate the factorial of 5 and show the result.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
# Factorial of 5 is 120
assert "120" in response.text or "factorial" in response.text.lower()
@@ -536,7 +536,7 @@ async def test_azure_assistants_client_agent_level_tool_persistence():
# First run - agent-level tool should be available
first_response = await agent.run("What's the weather like in Chicago?")
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the agent-level weather tool
assert any(term in first_response.text.lower() for term in ["chicago", "sunny", "72"])
@@ -544,7 +544,7 @@ async def test_azure_assistants_client_agent_level_tool_persistence():
# Second run - agent-level tool should still be available (persistence test)
second_response = await agent.run("What's the weather in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should use the agent-level weather tool again
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@@ -17,8 +17,8 @@ from openai.types.chat.chat_completion_chunk import ChoiceDelta as ChunkChoiceDe
from openai.types.chat.chat_completion_message import ChatCompletionMessage
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
BaseChatClient,
ChatAgent,
ChatClientProtocol,
@@ -731,7 +731,7 @@ async def test_azure_openai_chat_client_agent_basic_run():
# Test basic run
response = await agent.run("Please respond with exactly: 'This is a response test.'")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
assert "response test" in response.text.lower()
@@ -747,7 +747,7 @@ async def test_azure_openai_chat_client_agent_basic_run_streaming():
# Test streaming run
full_text = ""
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
assert isinstance(chunk, AgentRunResponseUpdate)
assert isinstance(chunk, AgentResponseUpdate)
if chunk.text:
full_text += chunk.text
@@ -769,13 +769,13 @@ async def test_azure_openai_chat_client_agent_thread_persistence():
# First interaction
response1 = await agent.run("My name is Alice. Remember this.", thread=thread)
assert isinstance(response1, AgentRunResponse)
assert isinstance(response1, AgentResponse)
assert response1.text is not None
# Second interaction - test memory
response2 = await agent.run("What is my name?", thread=thread)
assert isinstance(response2, AgentRunResponse)
assert isinstance(response2, AgentResponse)
assert response2.text is not None
assert "alice" in response2.text.lower()
@@ -795,7 +795,7 @@ async def test_azure_openai_chat_client_agent_existing_thread():
thread = first_agent.get_new_thread()
first_response = await first_agent.run("My name is Alice. Remember this.", thread=thread)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Preserve the thread for reuse
@@ -810,7 +810,7 @@ async def test_azure_openai_chat_client_agent_existing_thread():
# Reuse the preserved thread
second_response = await second_agent.run("What is my name?", thread=preserved_thread)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
assert "alice" in second_response.text.lower()
@@ -828,7 +828,7 @@ async def test_azure_chat_client_agent_level_tool_persistence():
# First run - agent-level tool should be available
first_response = await agent.run("What's the weather like in Chicago?")
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the agent-level weather tool
assert any(term in first_response.text.lower() for term in ["chicago", "sunny", "72"])
@@ -836,7 +836,7 @@ async def test_azure_chat_client_agent_level_tool_persistence():
# Second run - agent-level tool should still be available (persistence test)
second_response = await agent.run("What's the weather in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should use the agent-level weather tool again
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@@ -10,7 +10,7 @@ from pydantic import BaseModel
from pytest import param
from agent_framework import (
AgentRunResponse,
AgentResponse,
ChatAgent,
ChatClientProtocol,
ChatMessage,
@@ -432,7 +432,7 @@ async def test_integration_client_agent_existing_thread():
thread = first_agent.get_new_thread()
first_response = await first_agent.run("My hobby is photography. Remember this.", thread=thread, store=True)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Preserve the thread for reuse
@@ -447,6 +447,6 @@ async def test_integration_client_agent_existing_thread():
# Reuse the preserved thread
second_response = await second_agent.run("What is my hobby?", thread=preserved_thread)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
assert "photography" in second_response.text.lower()
+6 -6
View File
@@ -13,8 +13,8 @@ from pytest import fixture
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseChatClient,
ChatMessage,
@@ -231,9 +231,9 @@ class MockAgent(AgentProtocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
logger.debug(f"Running mock agent, with: {messages=}, {thread=}, {kwargs=}")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent("Response")])])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent("Response")])])
async def run_stream(
self,
@@ -241,9 +241,9 @@ class MockAgent(AgentProtocol):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
logger.debug(f"Running mock agent stream, with: {messages=}, {thread=}, {kwargs=}")
yield AgentRunResponseUpdate(contents=[TextContent("Response")])
yield AgentResponseUpdate(contents=[TextContent("Response")])
def get_new_thread(self) -> AgentThread:
return MockAgentThread()
@@ -10,8 +10,8 @@ from pytest import raises
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatAgent,
ChatClientProtocol,
@@ -45,7 +45,7 @@ async def test_agent_run(agent: AgentProtocol) -> None:
async def test_agent_run_streaming(agent: AgentProtocol) -> None:
async def collect_updates(updates: AsyncIterable[AgentRunResponseUpdate]) -> list[AgentRunResponseUpdate]:
async def collect_updates(updates: AsyncIterable[AgentResponseUpdate]) -> list[AgentResponseUpdate]:
return [u async for u in updates]
updates = await collect_updates(agent.run_stream(messages="test"))
@@ -87,7 +87,7 @@ async def test_chat_client_agent_run(chat_client: ChatClientProtocol) -> None:
async def test_chat_client_agent_run_streaming(chat_client: ChatClientProtocol) -> None:
agent = ChatAgent(chat_client=chat_client)
result = await AgentRunResponse.from_agent_response_generator(agent.run_stream("Hello"))
result = await AgentResponse.from_agent_response_generator(agent.run_stream("Hello"))
assert result.text == "test streaming response another update"
@@ -329,7 +329,7 @@ async def test_chat_agent_run_stream_context_providers(chat_client: ChatClientPr
agent = ChatAgent(chat_client=chat_client, context_provider=mock_provider)
# Collect all stream updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Hello"):
updates.append(update)
@@ -440,9 +440,9 @@ async def test_chat_agent_as_tool_with_stream_callback(chat_client: ChatClientPr
agent = ChatAgent(chat_client=chat_client, name="StreamingAgent")
# Collect streaming updates
collected_updates: list[AgentRunResponseUpdate] = []
collected_updates: list[AgentResponseUpdate] = []
def stream_callback(update: AgentRunResponseUpdate) -> None:
def stream_callback(update: AgentResponseUpdate) -> None:
collected_updates.append(update)
tool = agent.as_tool(stream_callback=stream_callback)
@@ -474,9 +474,9 @@ async def test_chat_agent_as_tool_with_async_stream_callback(chat_client: ChatCl
agent = ChatAgent(chat_client=chat_client, name="AsyncStreamingAgent")
# Collect streaming updates using an async callback
collected_updates: list[AgentRunResponseUpdate] = []
collected_updates: list[AgentResponseUpdate] = []
async def async_stream_callback(update: AgentRunResponseUpdate) -> None:
async def async_stream_callback(update: AgentResponseUpdate) -> None:
collected_updates.append(update)
tool = agent.as_tool(stream_callback=async_stream_callback)
@@ -8,8 +8,8 @@ import pytest
from pydantic import BaseModel
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatAgent,
HostedCodeInterpreterTool,
@@ -46,7 +46,7 @@ async def test_openai_responses_client_agent_basic_run_streaming():
# Test streaming run
full_text = ""
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
assert isinstance(chunk, AgentRunResponseUpdate)
assert isinstance(chunk, AgentResponseUpdate)
if chunk.text:
full_text += chunk.text
@@ -68,13 +68,13 @@ async def test_openai_responses_client_agent_thread_persistence():
# First interaction
first_response = await agent.run("My favorite programming language is Python. Remember this.", thread=thread)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Second interaction - test memory
second_response = await agent.run("What is my favorite programming language?", thread=thread)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
@@ -100,7 +100,7 @@ async def test_openai_responses_client_agent_thread_storage_with_store_true():
)
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
@@ -125,7 +125,7 @@ async def test_openai_responses_client_agent_existing_thread():
thread = first_agent.get_new_thread()
first_response = await first_agent.run("My hobby is photography. Remember this.", thread=thread)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Preserve the thread for reuse
@@ -140,7 +140,7 @@ async def test_openai_responses_client_agent_existing_thread():
# Reuse the preserved thread
second_response = await second_agent.run("What is my hobby?", thread=preserved_thread)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
assert "photography" in second_response.text.lower()
@@ -157,7 +157,7 @@ async def test_openai_responses_client_agent_hosted_code_interpreter_tool():
# Test code interpreter functionality
response = await agent.run("Calculate the sum of numbers from 1 to 10 using Python code.")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
# Should contain calculation result (sum of 1-10 = 55) or code execution content
@@ -179,7 +179,7 @@ async def test_openai_responses_client_agent_image_generation_tool():
# Test image generation functionality
response = await agent.run("Generate an image of a cute red panda sitting on a tree branch in a forest.")
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.messages
# Verify we got image content - look for ImageGenerationToolResultContent
@@ -209,7 +209,7 @@ async def test_openai_responses_client_agent_level_tool_persistence():
# First run - agent-level tool should be available
first_response = await agent.run("What's the weather like in Chicago?")
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the agent-level weather tool
assert any(term in first_response.text.lower() for term in ["chicago", "sunny", "72"])
@@ -217,7 +217,7 @@ async def test_openai_responses_client_agent_level_tool_persistence():
# Second run - agent-level tool should still be available (persistence test)
second_response = await agent.run("What's the weather in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should use the agent-level weather tool again
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@@ -249,7 +249,7 @@ async def test_openai_responses_client_run_level_tool_isolation():
tools=[get_weather_with_counter], # Run-level tool
)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the run-level weather tool (call count should be 1)
assert call_count == 1
@@ -258,7 +258,7 @@ async def test_openai_responses_client_run_level_tool_isolation():
# Second run - run-level tool should NOT persist (key isolation test)
second_response = await agent.run("What's the weather like in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should NOT use the weather tool since it was only run-level in previous call
# Call count should still be 1 (no additional calls)
@@ -286,7 +286,7 @@ async def test_openai_responses_client_agent_chat_options_agent_level() -> None:
"Provide a brief, helpful response.",
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
@@ -312,7 +312,7 @@ async def test_openai_responses_client_agent_hosted_mcp_tool() -> None:
options={"max_tokens": 5000},
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text
# Should contain Azure-related content since it's asking about Azure CLI
assert any(term in response.text.lower() for term in ["azure", "storage", "account", "cli"])
@@ -338,7 +338,7 @@ async def test_openai_responses_client_agent_local_mcp_tool() -> None:
options={"max_tokens": 200},
)
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
# Should contain Azure-related content since it's asking about Azure CLI
@@ -375,7 +375,7 @@ async def test_openai_responses_client_agent_with_response_format_pydantic() ->
)
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.value is not None
assert isinstance(response.value, ReleaseBrief)
@@ -422,7 +422,7 @@ async def test_openai_responses_client_agent_with_runtime_json_schema() -> None:
)
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
# Parse JSON and validate structure
@@ -9,8 +9,8 @@ from pydantic import BaseModel, Field
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
@@ -172,9 +172,9 @@ class TestAgentMiddlewarePipeline:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
expected_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
expected_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
return expected_response
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -200,9 +200,9 @@ class TestAgentMiddlewarePipeline:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
expected_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
expected_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
execution_order.append("handler")
return expected_response
@@ -216,11 +216,11 @@ class TestAgentMiddlewarePipeline:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk2")])
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk2")])
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -248,13 +248,13 @@ class TestAgentMiddlewarePipeline:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
execution_order.append("handler_start")
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk2")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk2")])
execution_order.append("handler_end")
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -271,10 +271,10 @@ class TestAgentMiddlewarePipeline:
context = AgentRunContext(agent=mock_agent, messages=messages)
execution_order: list[str] = []
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
# Handler should not be executed when terminated before next()
execution_order.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
response = await pipeline.execute(mock_agent, messages, context, final_handler)
assert response is not None
@@ -291,9 +291,9 @@ class TestAgentMiddlewarePipeline:
context = AgentRunContext(agent=mock_agent, messages=messages)
execution_order: list[str] = []
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
execution_order.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
response = await pipeline.execute(mock_agent, messages, context, final_handler)
assert response is not None
@@ -310,14 +310,14 @@ class TestAgentMiddlewarePipeline:
context = AgentRunContext(agent=mock_agent, messages=messages)
execution_order: list[str] = []
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
# Handler should not be executed when terminated before next()
execution_order.append("handler_start")
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk2")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk2")])
execution_order.append("handler_end")
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -334,13 +334,13 @@ class TestAgentMiddlewarePipeline:
context = AgentRunContext(agent=mock_agent, messages=messages)
execution_order: list[str] = []
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
execution_order.append("handler_start")
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk2")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk2")])
execution_order.append("handler_end")
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -370,9 +370,9 @@ class TestAgentMiddlewarePipeline:
thread = AgentThread()
context = AgentRunContext(agent=mock_agent, messages=messages, thread=thread)
expected_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
expected_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
return expected_response
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -396,9 +396,9 @@ class TestAgentMiddlewarePipeline:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages, thread=None)
expected_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
expected_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
return expected_response
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -767,9 +767,9 @@ class TestClassBasedMiddleware:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
metadata_updates.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -830,9 +830,9 @@ class TestFunctionBasedMiddleware:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
execution_order.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -893,9 +893,9 @@ class TestMixedMiddleware:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
execution_order.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -1004,9 +1004,9 @@ class TestMultipleMiddlewareOrdering:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
execution_order.append("handler")
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -1142,10 +1142,10 @@ class TestContextContentValidation:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
# Verify metadata was set by middleware
assert ctx.metadata.get("validated") is True
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
assert result is not None
@@ -1253,20 +1253,20 @@ class TestStreamingScenarios:
# Test non-streaming
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
streaming_flags.append(ctx.is_streaming)
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response")])
await pipeline.execute(mock_agent, messages, context, final_handler)
# Test streaming
context_stream = AgentRunContext(agent=mock_agent, messages=messages)
async def final_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
streaming_flags.append(ctx.is_streaming)
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk")])
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context_stream, final_stream_handler):
updates.append(update)
@@ -1290,11 +1290,11 @@ class TestStreamingScenarios:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_stream_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
chunks_processed.append("stream_start")
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk1")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk1")])
chunks_processed.append("chunk1_yielded")
yield AgentRunResponseUpdate(contents=[TextContent(text="chunk2")])
yield AgentResponseUpdate(contents=[TextContent(text="chunk2")])
chunks_processed.append("chunk2_yielded")
chunks_processed.append("stream_end")
@@ -1452,16 +1452,16 @@ class TestMiddlewareExecutionControl:
handler_called = False
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
nonlocal handler_called
handler_called = True
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="should not execute")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="should not execute")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
# Verify no execution happened - should return empty AgentRunResponse
# Verify no execution happened - should return empty AgentResponse
assert result is not None
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.messages == [] # Empty response
assert not handler_called
assert context.result is None
@@ -1483,13 +1483,13 @@ class TestMiddlewareExecutionControl:
handler_called = False
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
nonlocal handler_called
handler_called = True
yield AgentRunResponseUpdate(contents=[TextContent(text="should not execute")])
yield AgentResponseUpdate(contents=[TextContent(text="should not execute")])
# When middleware doesn't call next(), streaming should yield no updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -1556,17 +1556,17 @@ class TestMiddlewareExecutionControl:
handler_called = False
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
nonlocal handler_called
handler_called = True
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="should not execute")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="should not execute")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
# Verify only first middleware was called and empty response returned
assert execution_order == ["first"]
assert result is not None
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert result.messages == [] # Empty response
assert not handler_called
@@ -9,8 +9,8 @@ from pydantic import BaseModel, Field
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
ChatAgent,
ChatMessage,
Role,
@@ -40,7 +40,7 @@ class TestResultOverrideMiddleware:
async def test_agent_middleware_response_override_non_streaming(self, mock_agent: AgentProtocol) -> None:
"""Test that agent middleware can override response for non-streaming execution."""
override_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="overridden response")])
override_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="overridden response")])
class ResponseOverrideMiddleware(AgentMiddleware):
async def process(
@@ -57,10 +57,10 @@ class TestResultOverrideMiddleware:
handler_called = False
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
nonlocal handler_called
handler_called = True
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="original response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="original response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -74,9 +74,9 @@ class TestResultOverrideMiddleware:
async def test_agent_middleware_response_override_streaming(self, mock_agent: AgentProtocol) -> None:
"""Test that agent middleware can override response for streaming execution."""
async def override_stream() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(contents=[TextContent(text="overridden")])
yield AgentRunResponseUpdate(contents=[TextContent(text=" stream")])
async def override_stream() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(contents=[TextContent(text="overridden")])
yield AgentResponseUpdate(contents=[TextContent(text=" stream")])
class StreamResponseOverrideMiddleware(AgentMiddleware):
async def process(
@@ -91,10 +91,10 @@ class TestResultOverrideMiddleware:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(contents=[TextContent(text="original")])
async def final_handler(ctx: AgentRunContext) -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(contents=[TextContent(text="original")])
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in pipeline.execute_stream(mock_agent, messages, context, final_handler):
updates.append(update)
@@ -148,7 +148,7 @@ class TestResultOverrideMiddleware:
await next(context)
# Then conditionally override based on content
if any("special" in msg.text for msg in context.messages if msg.text):
context.result = AgentRunResponse(
context.result = AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="Special response from middleware!")]
)
@@ -174,10 +174,10 @@ class TestResultOverrideMiddleware:
"""Test streaming result override functionality with ChatAgent integration."""
mock_chat_client = MockChatClient()
async def custom_stream() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(contents=[TextContent(text="Custom")])
yield AgentRunResponseUpdate(contents=[TextContent(text=" streaming")])
yield AgentRunResponseUpdate(contents=[TextContent(text=" response!")])
async def custom_stream() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(contents=[TextContent(text="Custom")])
yield AgentResponseUpdate(contents=[TextContent(text=" streaming")])
yield AgentResponseUpdate(contents=[TextContent(text=" response!")])
class ChatAgentStreamOverrideMiddleware(AgentMiddleware):
async def process(
@@ -195,7 +195,7 @@ class TestResultOverrideMiddleware:
# Test streaming override case
override_messages = [ChatMessage(role=Role.USER, text="Give me a custom stream")]
override_updates: list[AgentRunResponseUpdate] = []
override_updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream(override_messages):
override_updates.append(update)
@@ -206,7 +206,7 @@ class TestResultOverrideMiddleware:
# Test normal streaming case
normal_messages = [ChatMessage(role=Role.USER, text="Normal streaming request")]
normal_updates: list[AgentRunResponseUpdate] = []
normal_updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream(normal_messages):
normal_updates.append(update)
@@ -231,19 +231,19 @@ class TestResultOverrideMiddleware:
handler_called = False
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
nonlocal handler_called
handler_called = True
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="executed response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="executed response")])
# Test case where next() is NOT called
no_execute_messages = [ChatMessage(role=Role.USER, text="Don't run this")]
no_execute_context = AgentRunContext(agent=mock_agent, messages=no_execute_messages)
no_execute_result = await pipeline.execute(mock_agent, no_execute_messages, no_execute_context, final_handler)
# When middleware doesn't call next(), result should be empty AgentRunResponse
# When middleware doesn't call next(), result should be empty AgentResponse
assert no_execute_result is not None
assert isinstance(no_execute_result, AgentRunResponse)
assert isinstance(no_execute_result, AgentResponse)
assert no_execute_result.messages == [] # Empty response
assert not handler_called
assert no_execute_context.result is None
@@ -313,7 +313,7 @@ class TestResultObservability:
async def test_agent_middleware_response_observability(self, mock_agent: AgentProtocol) -> None:
"""Test that middleware can observe response after execution."""
observed_responses: list[AgentRunResponse] = []
observed_responses: list[AgentResponse] = []
class ObservabilityMiddleware(AgentMiddleware):
async def process(
@@ -327,7 +327,7 @@ class TestResultObservability:
# Context should now contain the response for observability
assert context.result is not None
assert isinstance(context.result, AgentRunResponse)
assert isinstance(context.result, AgentResponse)
observed_responses.append(context.result)
middleware = ObservabilityMiddleware()
@@ -335,8 +335,8 @@ class TestResultObservability:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="executed response")])
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="executed response")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -392,11 +392,11 @@ class TestResultObservability:
# Now observe and conditionally override
assert context.result is not None
assert isinstance(context.result, AgentRunResponse)
assert isinstance(context.result, AgentResponse)
if "modify" in context.result.messages[0].text:
# Override after observing
context.result = AgentRunResponse(
context.result = AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="modified after execution")]
)
@@ -405,8 +405,8 @@ class TestResultObservability:
messages = [ChatMessage(role=Role.USER, text="test")]
context = AgentRunContext(agent=mock_agent, messages=messages)
async def final_handler(ctx: AgentRunContext) -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response to modify")])
async def final_handler(ctx: AgentRunContext) -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="response to modify")])
result = await pipeline.execute(mock_agent, messages, context, final_handler)
@@ -6,7 +6,7 @@ from typing import Any
import pytest
from agent_framework import (
AgentRunResponseUpdate,
AgentResponseUpdate,
ChatAgent,
ChatContext,
ChatMessage,
@@ -372,7 +372,7 @@ class TestChatAgentStreamingMiddleware:
# Execute streaming
messages = [ChatMessage(role=Role.USER, text="test message")]
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream(messages):
updates.append(update)
@@ -878,7 +878,7 @@ class TestMiddlewareDynamicRebuild:
agent = ChatAgent(chat_client=chat_client, middleware=[middleware1])
# First streaming execution
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Test stream message 1"):
updates.append(update)
@@ -1085,7 +1085,7 @@ class TestRunLevelMiddleware:
run_middleware = StreamingTrackingMiddleware("run_stream")
# Execute streaming with run middleware
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Test streaming", middleware=[run_middleware]):
updates.append(update)
@@ -1711,7 +1711,7 @@ class TestChatAgentChatMiddleware:
# Execute streaming
messages = [ChatMessage(role=Role.USER, text="test message")]
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream(messages):
updates.append(update)
@@ -13,7 +13,7 @@ from opentelemetry.trace import StatusCode
from agent_framework import (
AGENT_FRAMEWORK_USER_AGENT,
AgentProtocol,
AgentRunResponse,
AgentResponse,
AgentThread,
BaseChatClient,
ChatMessage,
@@ -407,7 +407,7 @@ def mock_chat_agent():
self.default_options: dict[str, Any] = {"model_id": "TestModel"}
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="Agent response")],
usage_details=UsageDetails(input_token_count=15, output_token_count=25),
response_id="test_response_id",
@@ -415,10 +415,10 @@ def mock_chat_agent():
)
async def run_stream(self, messages=None, *, thread=None, **kwargs):
from agent_framework import AgentRunResponseUpdate
from agent_framework import AgentResponseUpdate
yield AgentRunResponseUpdate(text="Hello", role=Role.ASSISTANT)
yield AgentRunResponseUpdate(text=" from agent", role=Role.ASSISTANT)
yield AgentResponseUpdate(text="Hello", role=Role.ASSISTANT)
yield AgentResponseUpdate(text=" from agent", role=Role.ASSISTANT)
return MockChatClientAgent
+43 -43
View File
@@ -10,8 +10,8 @@ from pydantic import BaseModel
from pytest import fixture, mark, raises
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
BaseContent,
ChatMessage,
ChatOptions,
@@ -1026,90 +1026,90 @@ def text_content() -> TextContent:
@fixture
def agent_run_response(chat_message: ChatMessage) -> AgentRunResponse:
return AgentRunResponse(messages=chat_message)
def agent_response(chat_message: ChatMessage) -> AgentResponse:
return AgentResponse(messages=chat_message)
@fixture
def agent_run_response_update(text_content: TextContent) -> AgentRunResponseUpdate:
return AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[text_content])
def agent_response_update(text_content: TextContent) -> AgentResponseUpdate:
return AgentResponseUpdate(role=Role.ASSISTANT, contents=[text_content])
# region AgentRunResponse
# region AgentResponse
def test_agent_run_response_init_single_message(chat_message: ChatMessage) -> None:
response = AgentRunResponse(messages=chat_message)
response = AgentResponse(messages=chat_message)
assert response.messages == [chat_message]
def test_agent_run_response_init_list_messages(chat_message: ChatMessage) -> None:
response = AgentRunResponse(messages=[chat_message, chat_message])
response = AgentResponse(messages=[chat_message, chat_message])
assert len(response.messages) == 2
assert response.messages[0] == chat_message
def test_agent_run_response_init_none_messages() -> None:
response = AgentRunResponse()
response = AgentResponse()
assert response.messages == []
def test_agent_run_response_text_property(chat_message: ChatMessage) -> None:
response = AgentRunResponse(messages=[chat_message, chat_message])
response = AgentResponse(messages=[chat_message, chat_message])
assert response.text == "HelloHello"
def test_agent_run_response_text_property_empty() -> None:
response = AgentRunResponse()
response = AgentResponse()
assert response.text == ""
def test_agent_run_response_from_updates(agent_run_response_update: AgentRunResponseUpdate) -> None:
updates = [agent_run_response_update, agent_run_response_update]
response = AgentRunResponse.from_agent_run_response_updates(updates)
def test_agent_run_response_from_updates(agent_response_update: AgentResponseUpdate) -> None:
updates = [agent_response_update, agent_response_update]
response = AgentResponse.from_agent_run_response_updates(updates)
assert len(response.messages) > 0
assert response.text == "Test contentTest content"
def test_agent_run_response_str_method(chat_message: ChatMessage) -> None:
response = AgentRunResponse(messages=chat_message)
response = AgentResponse(messages=chat_message)
assert str(response) == "Hello"
# region AgentRunResponseUpdate
# region AgentResponseUpdate
def test_agent_run_response_update_init_content_list(text_content: TextContent) -> None:
update = AgentRunResponseUpdate(contents=[text_content, text_content])
update = AgentResponseUpdate(contents=[text_content, text_content])
assert len(update.contents) == 2
assert update.contents[0] == text_content
def test_agent_run_response_update_init_none_content() -> None:
update = AgentRunResponseUpdate()
update = AgentResponseUpdate()
assert update.contents == []
def test_agent_run_response_update_text_property(text_content: TextContent) -> None:
update = AgentRunResponseUpdate(contents=[text_content, text_content])
update = AgentResponseUpdate(contents=[text_content, text_content])
assert update.text == "Test contentTest content"
def test_agent_run_response_update_text_property_empty() -> None:
update = AgentRunResponseUpdate()
update = AgentResponseUpdate()
assert update.text == ""
def test_agent_run_response_update_str_method(text_content: TextContent) -> None:
update = AgentRunResponseUpdate(contents=[text_content])
update = AgentResponseUpdate(contents=[text_content])
assert str(update) == "Test content"
def test_agent_run_response_update_created_at() -> None:
"""Test that AgentRunResponseUpdate properly handles created_at timestamps."""
"""Test that AgentResponseUpdate properly handles created_at timestamps."""
# Test with a properly formatted UTC timestamp
utc_timestamp = "2024-12-01T00:31:30.000000Z"
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="test")],
role=Role.ASSISTANT,
created_at=utc_timestamp,
@@ -1120,7 +1120,7 @@ def test_agent_run_response_update_created_at() -> None:
# Verify that we can generate a proper UTC timestamp
now_utc = datetime.now(tz=timezone.utc)
formatted_utc = now_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
update_with_now = AgentRunResponseUpdate(
update_with_now = AgentResponseUpdate(
contents=[TextContent(text="test")],
role=Role.ASSISTANT,
created_at=formatted_utc,
@@ -1130,10 +1130,10 @@ def test_agent_run_response_update_created_at() -> None:
def test_agent_run_response_created_at() -> None:
"""Test that AgentRunResponse properly handles created_at timestamps."""
"""Test that AgentResponse properly handles created_at timestamps."""
# Test with a properly formatted UTC timestamp
utc_timestamp = "2024-12-01T00:31:30.000000Z"
response = AgentRunResponse(
response = AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="Hello")],
created_at=utc_timestamp,
)
@@ -1143,7 +1143,7 @@ def test_agent_run_response_created_at() -> None:
# Verify that we can generate a proper UTC timestamp
now_utc = datetime.now(tz=timezone.utc)
formatted_utc = now_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
response_with_now = AgentRunResponse(
response_with_now = AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="Hello")],
created_at=formatted_utc,
)
@@ -1285,20 +1285,20 @@ def test_chat_tool_mode_eq_with_string():
assert {"mode": "auto"} == {"mode": "auto"}
# region AgentRunResponse
# region AgentResponse
@fixture
def agent_run_response_async() -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role="user", text="Hello")])
def agent_run_response_async() -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role="user", text="Hello")])
async def test_agent_run_response_from_async_generator():
async def gen():
yield AgentRunResponseUpdate(contents=[TextContent("A")])
yield AgentRunResponseUpdate(contents=[TextContent("B")])
yield AgentResponseUpdate(contents=[TextContent("A")])
yield AgentResponseUpdate(contents=[TextContent("B")])
r = await AgentRunResponse.from_agent_response_generator(gen())
r = await AgentResponse.from_agent_response_generator(gen())
assert r.text == "AB"
@@ -1668,7 +1668,7 @@ def test_chat_response_update_all_content_types():
def test_agent_run_response_complex_serialization():
"""Test AgentRunResponse from_dict and to_dict with messages and usage_details."""
"""Test AgentResponse from_dict and to_dict with messages and usage_details."""
response_data = {
"messages": [
@@ -1683,7 +1683,7 @@ def test_agent_run_response_complex_serialization():
},
}
response = AgentRunResponse.from_dict(response_data)
response = AgentResponse.from_dict(response_data)
assert len(response.messages) == 2
assert isinstance(response.messages[0], ChatMessage)
assert isinstance(response.usage_details, UsageDetails)
@@ -1696,7 +1696,7 @@ def test_agent_run_response_complex_serialization():
def test_agent_run_response_update_all_content_types():
"""Test AgentRunResponseUpdate from_dict with all content types and role handling."""
"""Test AgentResponseUpdate from_dict with all content types and role handling."""
update_data = {
"contents": [
@@ -1725,7 +1725,7 @@ def test_agent_run_response_update_all_content_types():
"role": {"value": "assistant"}, # Test role as dict
}
update = AgentRunResponseUpdate.from_dict(update_data)
update = AgentResponseUpdate.from_dict(update_data)
assert len(update.contents) == 12 # unknown_type is logged and ignored
assert isinstance(update.role, Role)
assert update.role.value == "assistant"
@@ -1738,7 +1738,7 @@ def test_agent_run_response_update_all_content_types():
# Test role as string conversion
update_data_str_role = update_data.copy()
update_data_str_role["role"] = "user"
update_str = AgentRunResponseUpdate.from_dict(update_data_str_role)
update_str = AgentResponseUpdate.from_dict(update_data_str_role)
assert isinstance(update_str.role, Role)
assert update_str.role.value == "user"
@@ -1922,7 +1922,7 @@ def test_agent_run_response_update_all_content_types():
id="chat_response_update",
),
pytest.param(
AgentRunResponse,
AgentResponse,
{
"messages": [
{
@@ -1942,10 +1942,10 @@ def test_agent_run_response_update_all_content_types():
"total_token_count": 8,
},
},
id="agent_run_response",
id="agent_response",
),
pytest.param(
AgentRunResponseUpdate,
AgentResponseUpdate,
{
"contents": [
{"type": "text", "text": "Streaming"},
@@ -1956,7 +1956,7 @@ def test_agent_run_response_update_all_content_types():
"response_id": "run-123",
"author_name": "Agent",
},
id="agent_run_response_update",
id="agent_response_update",
),
],
)
@@ -11,8 +11,8 @@ from openai.types.beta.threads.runs import RunStep
from pydantic import Field
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatAgent,
ChatClientProtocol,
@@ -1118,7 +1118,7 @@ async def test_openai_assistants_agent_basic_run():
response = await agent.run("Hello! Please respond with 'Hello World' exactly.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
assert len(response.text) > 0
assert "Hello World" in response.text
@@ -1135,7 +1135,7 @@ async def test_openai_assistants_agent_basic_run_streaming():
full_message: str = ""
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
assert chunk is not None
assert isinstance(chunk, AgentRunResponseUpdate)
assert isinstance(chunk, AgentResponseUpdate)
if chunk.text:
full_message += chunk.text
@@ -1159,14 +1159,14 @@ async def test_openai_assistants_agent_thread_persistence():
first_response = await agent.run(
"Remember this number: 42. What number did I just tell you to remember?", thread=thread
)
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert "42" in first_response.text
# Second message - test conversation memory
second_response = await agent.run(
"What number did I tell you to remember in my previous message?", thread=thread
)
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert "42" in second_response.text
# Verify thread has been populated with conversation ID
@@ -1190,7 +1190,7 @@ async def test_openai_assistants_agent_existing_thread_id():
response1 = await agent.run("What's the weather in Paris?", thread=thread)
# Validate first response
assert isinstance(response1, AgentRunResponse)
assert isinstance(response1, AgentResponse)
assert response1.text is not None
assert any(word in response1.text.lower() for word in ["weather", "paris"])
@@ -1212,7 +1212,7 @@ async def test_openai_assistants_agent_existing_thread_id():
response2 = await agent.run("What was the last city I asked about?", thread=thread)
# Validate that the agent remembers the previous conversation
assert isinstance(response2, AgentRunResponse)
assert isinstance(response2, AgentResponse)
assert response2.text is not None
# Should reference Paris from the previous conversation
assert "paris" in response2.text.lower()
@@ -1232,7 +1232,7 @@ async def test_openai_assistants_agent_code_interpreter():
response = await agent.run("Write Python code to calculate the factorial of 5 and show the result.")
# Validate response
assert isinstance(response, AgentRunResponse)
assert isinstance(response, AgentResponse)
assert response.text is not None
# Factorial of 5 is 120
assert "120" in response.text or "factorial" in response.text.lower()
@@ -1251,7 +1251,7 @@ async def test_agent_level_tool_persistence():
# First run - agent-level tool should be available
first_response = await agent.run("What's the weather like in Chicago?")
assert isinstance(first_response, AgentRunResponse)
assert isinstance(first_response, AgentResponse)
assert first_response.text is not None
# Should use the agent-level weather tool
assert any(term in first_response.text.lower() for term in ["chicago", "sunny", "72"])
@@ -1259,7 +1259,7 @@ async def test_agent_level_tool_persistence():
# Second run - agent-level tool should still be available (persistence test)
second_response = await agent.run("What's the weather in Miami?")
assert isinstance(second_response, AgentRunResponse)
assert isinstance(second_response, AgentResponse)
assert second_response.text is not None
# Should use the agent-level weather tool again
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@@ -5,8 +5,8 @@ from typing import Any
from agent_framework import (
AgentExecutor,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -35,9 +35,9 @@ class _CountingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
self.call_count += 1
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text=f"Response #{self.call_count}: {self.name}")]
)
@@ -47,9 +47,9 @@ class _CountingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
self.call_count += 1
yield AgentRunResponseUpdate(contents=[TextContent(text=f"Response #{self.call_count}: {self.name}")])
yield AgentResponseUpdate(contents=[TextContent(text=f"Response #{self.call_count}: {self.name}")])
async def test_agent_executor_checkpoint_stores_and_restores_state() -> None:
@@ -10,8 +10,8 @@ from typing_extensions import Never
from agent_framework import (
AgentExecutor,
AgentExecutorResponse,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentRunUpdateEvent,
AgentThread,
BaseAgent,
@@ -46,9 +46,9 @@ class _ToolCallingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Non-streaming run - not used in this test."""
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="done")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="done")])
async def run_stream(
self,
@@ -56,16 +56,16 @@ class _ToolCallingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Simulate streaming with tool calls and results."""
# First update: some text
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="Let me search for that...")],
role=Role.ASSISTANT,
)
# Second update: tool call (no text!)
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[
FunctionCallContent(
call_id="call_123",
@@ -77,7 +77,7 @@ class _ToolCallingAgent(BaseAgent):
)
# Third update: tool result (no text!)
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[
FunctionResultContent(
call_id="call_123",
@@ -88,7 +88,7 @@ class _ToolCallingAgent(BaseAgent):
)
# Fourth update: final text response
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="The weather is sunny, 72°F.")],
role=Role.ASSISTANT,
)
@@ -223,7 +223,7 @@ class MockChatClient:
@executor(id="test_executor")
async def test_executor(agent_executor_response: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:
await ctx.yield_output(agent_executor_response.agent_run_response.text)
await ctx.yield_output(agent_executor_response.agent_response.text)
async def test_agent_executor_tool_call_with_approval() -> None:
@@ -2,26 +2,26 @@
"""Tests for AgentRunEvent and AgentRunUpdateEvent type annotations."""
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, ChatMessage, Role
from agent_framework import AgentResponse, AgentResponseUpdate, ChatMessage, Role
from agent_framework._workflows._events import AgentRunEvent, AgentRunUpdateEvent
def test_agent_run_event_data_type() -> None:
"""Verify AgentRunEvent.data is typed as AgentRunResponse | None."""
response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Hello")])
"""Verify AgentRunEvent.data is typed as AgentResponse | None."""
response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Hello")])
event = AgentRunEvent(executor_id="test", data=response)
# This assignment should pass type checking without a cast
data: AgentRunResponse | None = event.data
data: AgentResponse | None = event.data
assert data is not None
assert data.text == "Hello"
def test_agent_run_update_event_data_type() -> None:
"""Verify AgentRunUpdateEvent.data is typed as AgentRunResponseUpdate | None."""
update = AgentRunResponseUpdate()
"""Verify AgentRunUpdateEvent.data is typed as AgentResponseUpdate | None."""
update = AgentResponseUpdate()
event = AgentRunUpdateEvent(executor_id="test", data=update)
# This assignment should pass type checking without a cast
data: AgentRunResponseUpdate | None = event.data
data: AgentResponseUpdate | None = event.data
assert data is not None
@@ -3,7 +3,7 @@
from collections.abc import AsyncIterable
from typing import Any
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
from agent_framework import AgentResponse, AgentResponseUpdate, AgentThread, ChatMessage
from agent_framework._workflows._agent_utils import resolve_agent_id
@@ -38,7 +38,7 @@ class MockAgent:
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse: ...
) -> AgentResponse: ...
def run_stream(
self,
@@ -46,7 +46,7 @@ class MockAgent:
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]: ...
) -> AsyncIterable[AgentResponseUpdate]: ...
def get_new_thread(self, **kwargs: Any) -> AgentThread:
"""Creates a new conversation thread for the agent."""
@@ -8,7 +8,7 @@ from typing_extensions import Never
from agent_framework import (
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunResponse,
AgentResponse,
ChatMessage,
ConcurrentBuilder,
Executor,
@@ -36,7 +36,7 @@ class _FakeAgentExec(Executor):
@handler
async def run(self, request: AgentExecutorRequest, ctx: WorkflowContext[AgentExecutorResponse]) -> None:
response = AgentRunResponse(messages=ChatMessage(Role.ASSISTANT, text=self._reply_text))
response = AgentResponse(messages=ChatMessage(Role.ASSISTANT, text=self._reply_text))
full_conversation = list(request.messages) + list(response.messages)
await ctx.send_message(AgentExecutorResponse(self.id, response, full_conversation=full_conversation))
@@ -142,7 +142,7 @@ async def test_concurrent_custom_aggregator_callback_is_used() -> None:
async def summarize(results: list[AgentExecutorResponse]) -> str:
texts: list[str] = []
for r in results:
msgs: list[ChatMessage] = r.agent_run_response.messages
msgs: list[ChatMessage] = r.agent_response.messages
texts.append(msgs[-1].text if msgs else "")
return " | ".join(sorted(texts))
@@ -173,7 +173,7 @@ async def test_concurrent_custom_aggregator_sync_callback_is_used() -> None:
def summarize_sync(results: list[AgentExecutorResponse], _ctx: WorkflowContext[Any]) -> str: # type: ignore[unused-argument]
texts: list[str] = []
for r in results:
msgs: list[ChatMessage] = r.agent_run_response.messages
msgs: list[ChatMessage] = r.agent_response.messages
texts.append(msgs[-1].text if msgs else "")
return " | ".join(sorted(texts))
@@ -217,7 +217,7 @@ async def test_concurrent_with_aggregator_executor_instance() -> None:
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
texts: list[str] = []
for r in results:
msgs: list[ChatMessage] = r.agent_run_response.messages
msgs: list[ChatMessage] = r.agent_response.messages
texts.append(msgs[-1].text if msgs else "")
await ctx.yield_output(" & ".join(sorted(texts)))
@@ -251,7 +251,7 @@ async def test_concurrent_with_aggregator_executor_factory() -> None:
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
texts: list[str] = []
for r in results:
msgs: list[ChatMessage] = r.agent_run_response.messages
msgs: list[ChatMessage] = r.agent_response.messages
texts.append(msgs[-1].text if msgs else "")
await ctx.yield_output(" | ".join(sorted(texts)))
@@ -292,7 +292,7 @@ async def test_concurrent_with_aggregator_executor_factory_with_default_id() ->
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
texts: list[str] = []
for r in results:
msgs: list[ChatMessage] = r.agent_run_response.messages
msgs: list[ChatMessage] = r.agent_response.messages
texts.append(msgs[-1].text if msgs else "")
await ctx.yield_output(" | ".join(sorted(texts)))
@@ -9,8 +9,8 @@ from typing_extensions import Never
from agent_framework import (
AgentExecutor,
AgentExecutorResponse,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -39,8 +39,8 @@ class _SimpleAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
) -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
async def run_stream( # type: ignore[override]
self,
@@ -48,9 +48,9 @@ class _SimpleAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
# This agent does not support streaming; yield a single complete response
yield AgentRunResponseUpdate(contents=[TextContent(text=self._reply_text)])
yield AgentResponseUpdate(contents=[TextContent(text=self._reply_text)])
class _CaptureFullConversation(Executor):
@@ -108,7 +108,7 @@ class _CaptureAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
# Normalize and record messages for verification when running non-streaming
norm: list[ChatMessage] = []
if messages:
@@ -118,7 +118,7 @@ class _CaptureAgent(BaseAgent):
elif isinstance(m, str):
norm.append(ChatMessage(role=Role.USER, text=m))
self._last_messages = norm
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
async def run_stream( # type: ignore[override]
self,
@@ -126,7 +126,7 @@ class _CaptureAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
# Normalize and record messages for verification when running streaming
norm: list[ChatMessage] = []
if messages:
@@ -136,7 +136,7 @@ class _CaptureAgent(BaseAgent):
elif isinstance(m, str):
norm.append(ChatMessage(role=Role.USER, text=m))
self._last_messages = norm
yield AgentRunResponseUpdate(contents=[TextContent(text=self._reply_text)])
yield AgentResponseUpdate(contents=[TextContent(text=self._reply_text)])
async def test_sequential_adapter_uses_full_conversation() -> None:
@@ -8,8 +8,8 @@ import pytest
from agent_framework import (
AgentExecutorResponse,
AgentRequestInfoResponse,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
BaseGroupChatOrchestrator,
@@ -44,9 +44,9 @@ class StubAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
response = ChatMessage(role=Role.ASSISTANT, text=self._reply_text, author_name=self.name)
return AgentRunResponse(messages=[response])
return AgentResponse(messages=[response])
def run_stream( # type: ignore[override]
self,
@@ -54,9 +54,9 @@ class StubAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
async def _stream() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(
) -> AsyncIterable[AgentResponseUpdate]:
async def _stream() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(
contents=[TextContent(text=self._reply_text)], role=Role.ASSISTANT, author_name=self.name
)
@@ -88,12 +88,12 @@ class StubManagerAgent(ChatAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
if self._call_count == 0:
self._call_count += 1
# First call: select the agent (using AgentOrchestrationOutput format)
payload = {"terminate": False, "reason": "Selecting agent", "next_speaker": "agent", "final_message": None}
return AgentRunResponse(
return AgentResponse(
messages=[
ChatMessage(
role=Role.ASSISTANT,
@@ -114,7 +114,7 @@ class StubManagerAgent(ChatAgent):
"next_speaker": None,
"final_message": "agent manager final",
}
return AgentRunResponse(
return AgentResponse(
messages=[
ChatMessage(
role=Role.ASSISTANT,
@@ -134,12 +134,12 @@ class StubManagerAgent(ChatAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
if self._call_count == 0:
self._call_count += 1
async def _stream_initial() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(
async def _stream_initial() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(
contents=[
TextContent(
text=(
@@ -154,8 +154,8 @@ class StubManagerAgent(ChatAgent):
return _stream_initial()
async def _stream_final() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(
async def _stream_final() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(
contents=[
TextContent(
text=(
@@ -341,14 +341,14 @@ class TestGroupChatBuilder:
def __init__(self) -> None:
super().__init__(name="", description="test")
async def run(self, messages: Any = None, *, thread: Any = None, **kwargs: Any) -> AgentRunResponse:
return AgentRunResponse(messages=[])
async def run(self, messages: Any = None, *, thread: Any = None, **kwargs: Any) -> AgentResponse:
return AgentResponse(messages=[])
def run_stream(
self, messages: Any = None, *, thread: Any = None, **kwargs: Any
) -> AsyncIterable[AgentRunResponseUpdate]:
async def _stream() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(contents=[])
) -> AsyncIterable[AgentResponseUpdate]:
async def _stream() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(contents=[])
return _stream()
@@ -9,8 +9,8 @@ import pytest
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentRunUpdateEvent,
AgentThread,
BaseAgent,
@@ -158,9 +158,9 @@ class StubAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
response = ChatMessage(role=Role.ASSISTANT, text=self._reply_text, author_name=self.name)
return AgentRunResponse(messages=[response])
return AgentResponse(messages=[response])
def run_stream( # type: ignore[override]
self,
@@ -168,9 +168,9 @@ class StubAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
async def _stream() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(
) -> AsyncIterable[AgentResponseUpdate]:
async def _stream() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(
contents=[TextContent(text=self._reply_text)], role=Role.ASSISTANT, author_name=self.name
)
@@ -424,8 +424,8 @@ class StubManagerAgent(BaseAgent):
*,
thread: Any = None,
**kwargs: Any,
) -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="ok")])
) -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="ok")])
def run_stream(
self,
@@ -433,9 +433,9 @@ class StubManagerAgent(BaseAgent):
*,
thread: Any = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
async def _gen() -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(message_deltas=[ChatMessage(role=Role.ASSISTANT, text="ok")])
) -> AsyncIterable[AgentResponseUpdate]:
async def _gen() -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(message_deltas=[ChatMessage(role=Role.ASSISTANT, text="ok")])
return _gen()
@@ -538,14 +538,14 @@ class StubThreadAgent(BaseAgent):
super().__init__(name=name or "agentA")
async def run_stream(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="thread-ok")],
author_name=self.name,
role=Role.ASSISTANT,
)
async def run(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="thread-ok", author_name=self.name)])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="thread-ok", author_name=self.name)])
class StubAssistantsClient:
@@ -560,16 +560,14 @@ class StubAssistantsAgent(BaseAgent):
self.chat_client = StubAssistantsClient() # type name contains 'AssistantsClient'
async def run_stream(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="assistants-ok")],
author_name=self.name,
role=Role.ASSISTANT,
)
async def run(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
return AgentRunResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="assistants-ok", author_name=self.name)]
)
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="assistants-ok", author_name=self.name)])
async def _collect_agent_responses_setup(participant: AgentProtocol) -> list[ChatMessage]:
@@ -10,8 +10,8 @@ import pytest
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
ChatMessage,
Role,
@@ -114,10 +114,10 @@ class TestAgentRequestInfoExecutor:
"""Test that request_info handler calls ctx.request_info."""
executor = AgentRequestInfoExecutor(id="test_executor")
agent_run_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Agent response")])
agent_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Agent response")])
agent_response = AgentExecutorResponse(
executor_id="test_agent",
agent_run_response=agent_run_response,
agent_response=agent_response,
)
ctx = MagicMock(spec=WorkflowContext)
@@ -132,10 +132,10 @@ class TestAgentRequestInfoExecutor:
"""Test response handler when user provides additional messages."""
executor = AgentRequestInfoExecutor(id="test_executor")
agent_run_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Original")])
agent_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Original")])
original_request = AgentExecutorResponse(
executor_id="test_agent",
agent_run_response=agent_run_response,
agent_response=agent_response,
)
response = AgentRequestInfoResponse.from_strings(["Additional input"])
@@ -158,10 +158,10 @@ class TestAgentRequestInfoExecutor:
"""Test response handler when user approves (no additional messages)."""
executor = AgentRequestInfoExecutor(id="test_executor")
agent_run_response = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Original")])
agent_response = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Original")])
original_request = AgentExecutorResponse(
executor_id="test_agent",
agent_run_response=agent_run_response,
agent_response=agent_response,
)
response = AgentRequestInfoResponse.approve()
@@ -205,9 +205,9 @@ class _TestAgent:
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Dummy run method."""
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Test response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Test response")])
def run_stream(
self,
@@ -215,11 +215,11 @@ class _TestAgent:
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Dummy run_stream method."""
async def generator():
yield AgentRunResponseUpdate(messages=[ChatMessage(role=Role.ASSISTANT, text="Test response stream")])
yield AgentResponseUpdate(messages=[ChatMessage(role=Role.ASSISTANT, text="Test response stream")])
return generator()
@@ -7,7 +7,7 @@ import pytest
from agent_framework import (
AgentExecutorResponse,
AgentRunResponse,
AgentResponse,
Executor,
WorkflowContext,
WorkflowEvent,
@@ -158,7 +158,7 @@ async def test_runner_emits_runner_completion_for_agent_response_without_targets
await ctx.send_message(
Message(
data=AgentExecutorResponse("agent", AgentRunResponse()),
data=AgentExecutorResponse("agent", AgentResponse()),
source_id="agent",
)
)
@@ -7,8 +7,8 @@ import pytest
from agent_framework import (
AgentExecutorResponse,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -35,8 +35,8 @@ class _EchoAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=f"{self.name} reply")])
) -> AgentResponse:
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=f"{self.name} reply")])
async def run_stream( # type: ignore[override]
self,
@@ -44,9 +44,9 @@ class _EchoAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
# Minimal async generator with one assistant update
yield AgentRunResponseUpdate(contents=[TextContent(text=f"{self.name} reply")])
yield AgentResponseUpdate(contents=[TextContent(text=f"{self.name} reply")])
class _SummarizerExec(Executor):
@@ -11,9 +11,9 @@ import pytest
from agent_framework import (
AgentExecutor,
AgentResponse,
AgentResponseUpdate,
AgentRunEvent,
AgentRunResponse,
AgentRunResponseUpdate,
AgentRunUpdateEvent,
AgentThread,
BaseAgent,
@@ -831,9 +831,9 @@ class _StreamingTestAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
"""Non-streaming run - returns complete response."""
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=self._reply_text)])
async def run_stream(
self,
@@ -841,11 +841,11 @@ class _StreamingTestAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
"""Streaming run - yields incremental updates."""
# Simulate streaming by yielding character by character
for char in self._reply_text:
yield AgentRunResponseUpdate(contents=[TextContent(text=char)])
yield AgentResponseUpdate(contents=[TextContent(text=char)])
async def test_agent_streaming_vs_non_streaming() -> None:
@@ -8,8 +8,8 @@ import pytest
from agent_framework import (
AgentProtocol,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentRunUpdateEvent,
AgentThread,
ChatMessage,
@@ -52,7 +52,7 @@ class SimpleExecutor(Executor):
response_message = ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=response_text)])
# Emit update event.
streaming_update = AgentRunResponseUpdate(
streaming_update = AgentResponseUpdate(
contents=[TextContent(text=response_text)], role=Role.ASSISTANT, message_id=str(uuid.uuid4())
)
await ctx.add_event(AgentRunUpdateEvent(executor_id=self.id, data=streaming_update))
@@ -74,7 +74,7 @@ class RequestingExecutor(Executor):
self, original_request: str, response: str, ctx: WorkflowContext[ChatMessage]
) -> None:
# Handle the response and emit completion response
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="Request completed successfully")],
role=Role.ASSISTANT,
message_id=str(uuid.uuid4()),
@@ -100,7 +100,7 @@ class ConversationHistoryCapturingExecutor(Executor):
response_message = ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=response_text)])
streaming_update = AgentRunResponseUpdate(
streaming_update = AgentResponseUpdate(
contents=[TextContent(text=response_text)], role=Role.ASSISTANT, message_id=str(uuid.uuid4())
)
await ctx.add_event(AgentRunUpdateEvent(executor_id=self.id, data=streaming_update))
@@ -124,7 +124,7 @@ class TestWorkflowAgent:
result = await agent.run("Hello World")
# Verify we got responses from both executors
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) >= 2, f"Expected at least 2 messages, got {len(result.messages)}"
# Find messages from each executor
@@ -162,7 +162,7 @@ class TestWorkflowAgent:
agent = WorkflowAgent(workflow=workflow, name="Streaming Test Agent")
# Execute workflow streaming to capture streaming events
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Test input"):
updates.append(update)
@@ -191,13 +191,13 @@ class TestWorkflowAgent:
agent = WorkflowAgent(workflow=workflow, name="Request Test Agent")
# Execute workflow streaming to get request info event
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Start request"):
updates.append(update)
# Should have received an approval request for the request info
assert len(updates) > 0
approval_update: AgentRunResponseUpdate | None = None
approval_update: AgentResponseUpdate | None = None
for update in updates:
if any(isinstance(content, FunctionApprovalRequestContent) for content in update.contents):
approval_update = update
@@ -248,7 +248,7 @@ class TestWorkflowAgent:
continuation_result = await agent.run(response_message)
# Should complete successfully
assert isinstance(continuation_result, AgentRunResponse)
assert isinstance(continuation_result, AgentResponse)
# Verify cleanup - pending requests should be cleared after function response handling
assert len(agent.pending_requests) == 0
@@ -293,7 +293,7 @@ class TestWorkflowAgent:
"""Test that ctx.yield_output() in a workflow executor surfaces as agent output when using .as_agent().
This validates the fix for issue #2813: WorkflowOutputEvent should be converted to
AgentRunResponseUpdate when the workflow is wrapped via .as_agent().
AgentResponseUpdate when the workflow is wrapped via .as_agent().
"""
@executor
@@ -314,12 +314,12 @@ class TestWorkflowAgent:
agent = workflow.as_agent("test-agent")
agent_result = await agent.run("hello")
assert isinstance(agent_result, AgentRunResponse)
assert isinstance(agent_result, AgentResponse)
assert len(agent_result.messages) == 1
assert agent_result.messages[0].text == "processed: hello"
async def test_workflow_as_agent_yield_output_surfaces_in_run_stream(self) -> None:
"""Test that ctx.yield_output() surfaces as AgentRunResponseUpdate when streaming."""
"""Test that ctx.yield_output() surfaces as AgentResponseUpdate when streaming."""
@executor
async def yielding_executor(messages: list[ChatMessage], ctx: WorkflowContext) -> None:
@@ -329,7 +329,7 @@ class TestWorkflowAgent:
workflow = WorkflowBuilder().set_start_executor(yielding_executor).build()
agent = workflow.as_agent("test-agent")
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("hello"):
updates.append(update)
@@ -353,7 +353,7 @@ class TestWorkflowAgent:
result = await agent.run("test")
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 3
# Verify each content type is preserved
@@ -410,7 +410,7 @@ class TestWorkflowAgent:
workflow = WorkflowBuilder().set_start_executor(raw_yielding_executor).build()
agent = workflow.as_agent("raw-test-agent")
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("test"):
updates.append(update)
@@ -448,7 +448,7 @@ class TestWorkflowAgent:
agent = workflow.as_agent("list-msg-agent")
# Verify streaming returns the update with all 4 contents before coalescing
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("test"):
updates.append(update)
@@ -460,7 +460,7 @@ class TestWorkflowAgent:
# Verify run() coalesces text contents (expected behavior)
result = await agent.run("test")
assert isinstance(result, AgentRunResponse)
assert isinstance(result, AgentResponse)
assert len(result.messages) == 1
# TextContent items are coalesced into one
assert len(result.messages[0].contents) == 1
@@ -587,17 +587,17 @@ class TestWorkflowAgent:
def get_new_thread(self) -> AgentThread:
return AgentThread()
async def run(self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any) -> AgentRunResponse:
return AgentRunResponse(
async def run(self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any) -> AgentResponse:
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text=self._response_text)],
text=self._response_text,
)
async def run_stream(
self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
for word in self._response_text.split():
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text=word + " ")],
role=Role.ASSISTANT,
author_name=self._name,
@@ -661,16 +661,16 @@ class TestWorkflowAgent:
def get_new_thread(self) -> AgentThread:
return AgentThread()
async def run(self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any) -> AgentRunResponse:
return AgentRunResponse(
async def run(self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any) -> AgentResponse:
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text=self._response_text)],
text=self._response_text,
)
async def run_stream(
self, messages: Any, *, thread: AgentThread | None = None, **kwargs: Any
) -> AsyncIterable[AgentRunResponseUpdate]:
yield AgentRunResponseUpdate(
) -> AsyncIterable[AgentResponseUpdate]:
yield AgentResponseUpdate(
contents=[TextContent(text=self._response_text)],
role=Role.ASSISTANT,
author_name=self._name,
@@ -717,7 +717,7 @@ class TestWorkflowAgentAuthorName:
agent = WorkflowAgent(workflow=workflow, name="Test Agent")
# Collect streaming updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Hello"):
updates.append(update)
@@ -736,7 +736,7 @@ class TestWorkflowAgentAuthorName:
@handler
async def handle_message(self, message: list[ChatMessage], ctx: WorkflowContext[list[ChatMessage]]) -> None:
# Emit update with explicit author_name
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="Response with author")],
role=Role.ASSISTANT,
author_name="custom_author_name", # Explicitly set
@@ -749,7 +749,7 @@ class TestWorkflowAgentAuthorName:
agent = WorkflowAgent(workflow=workflow, name="Test Agent")
# Collect streaming updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Hello"):
updates.append(update)
@@ -767,7 +767,7 @@ class TestWorkflowAgentAuthorName:
agent = WorkflowAgent(workflow=workflow, name="Multi-Executor Agent")
# Collect streaming updates
updates: list[AgentRunResponseUpdate] = []
updates: list[AgentResponseUpdate] = []
async for update in agent.run_stream("Hello"):
updates.append(update)
@@ -788,7 +788,7 @@ class TestWorkflowAgentMergeUpdates:
# Create updates with different response_ids and message_ids in non-chronological order
updates = [
# Response B, Message 2 (latest in resp B)
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text="RespB-Msg2")],
role=Role.ASSISTANT,
response_id="resp-b",
@@ -796,7 +796,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:02:00Z",
),
# Response A, Message 1 (earliest overall)
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text="RespA-Msg1")],
role=Role.ASSISTANT,
response_id="resp-a",
@@ -804,7 +804,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:00:00Z",
),
# Response B, Message 1 (earlier in resp B)
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text="RespB-Msg1")],
role=Role.ASSISTANT,
response_id="resp-b",
@@ -812,7 +812,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:01:00Z",
),
# Response A, Message 2 (later in resp A)
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text="RespA-Msg2")],
role=Role.ASSISTANT,
response_id="resp-a",
@@ -820,7 +820,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:00:30Z",
),
# Global dangling update (no response_id) - should go at end
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[TextContent(text="Global-Dangling")],
role=Role.ASSISTANT,
response_id=None,
@@ -891,7 +891,7 @@ class TestWorkflowAgentMergeUpdates:
"""Test that merge_updates correctly aggregates usage details, timestamps, and additional properties."""
# Create updates with various metadata including usage details
updates = [
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[
TextContent(text="First"),
UsageContent(
@@ -904,7 +904,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:00:00Z",
additional_properties={"source": "executor1", "priority": "high"},
),
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[
TextContent(text="Second"),
UsageContent(
@@ -917,7 +917,7 @@ class TestWorkflowAgentMergeUpdates:
created_at="2024-01-01T12:01:00Z", # Later timestamp
additional_properties={"source": "executor2", "category": "analysis"},
),
AgentRunResponseUpdate(
AgentResponseUpdate(
contents=[
TextContent(text="Third"),
UsageContent(details=UsageDetails(input_token_count=5, output_token_count=3, total_token_count=8)),
@@ -7,8 +7,8 @@ import pytest
from agent_framework import (
AgentExecutor,
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -29,11 +29,11 @@ class DummyAgent(BaseAgent):
norm.append(m)
elif isinstance(m, str):
norm.append(ChatMessage(role=Role.USER, text=m))
return AgentRunResponse(messages=norm)
return AgentResponse(messages=norm)
async def run_stream(self, messages=None, *, thread: AgentThread | None = None, **kwargs): # type: ignore[override]
# Minimal async generator
yield AgentRunResponseUpdate()
yield AgentResponseUpdate()
def test_builder_accepts_agents_directly():
@@ -6,8 +6,8 @@ from typing import Annotated, Any
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
@@ -55,9 +55,9 @@ class _KwargsCapturingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
self.captured_kwargs.append(dict(kwargs))
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=f"{self.name} response")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=f"{self.name} response")])
async def run_stream(
self,
@@ -65,9 +65,9 @@ class _KwargsCapturingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
self.captured_kwargs.append(dict(kwargs))
yield AgentRunResponseUpdate(contents=[TextContent(text=f"{self.name} response")])
yield AgentResponseUpdate(contents=[TextContent(text=f"{self.name} response")])
# region Sequential Builder Tests
@@ -12,7 +12,7 @@ from collections.abc import AsyncGenerator
from typing import Any, cast
from agent_framework import get_logger
from agent_framework._types import AgentRunResponse, ChatMessage
from agent_framework._types import AgentResponse, ChatMessage
from ._handlers import (
ActionContext,
@@ -348,7 +348,7 @@ async def handle_invoke_azure_agent(ctx: ActionContext) -> AsyncGenerator[Workfl
tool_calls.extend(chunk.tool_calls)
# Build consolidated response from updates
response = AgentRunResponse.from_agent_run_response_updates(updates)
response = AgentResponse.from_agent_run_response_updates(updates)
text = response.text
response_messages = response.messages
@@ -581,7 +581,7 @@ async def handle_invoke_prompt_agent(ctx: ActionContext) -> AsyncGenerator[Workf
)
# Build consolidated response from updates
response = AgentRunResponse.from_agent_run_response_updates(updates)
response = AgentResponse.from_agent_run_response_updates(updates)
text = response.text
response_messages = response.messages
@@ -145,7 +145,7 @@ class MessageMapper:
"""Convert a single Agent Framework event to OpenAI events.
Args:
raw_event: Agent Framework event (AgentRunResponseUpdate, WorkflowEvent, etc.)
raw_event: Agent Framework event (AgentResponseUpdate, WorkflowEvent, etc.)
request: Original request for context
Returns:
@@ -178,26 +178,26 @@ class MessageMapper:
# Import Agent Framework types for proper isinstance checks
try:
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, WorkflowEvent
from agent_framework import AgentResponse, AgentResponseUpdate, WorkflowEvent
from agent_framework._workflows._events import AgentRunUpdateEvent
# Handle AgentRunUpdateEvent - workflow event wrapping AgentRunResponseUpdate
# Handle AgentRunUpdateEvent - workflow event wrapping AgentResponseUpdate
# This must be checked BEFORE generic WorkflowEvent check
if isinstance(raw_event, AgentRunUpdateEvent):
# Extract the AgentRunResponseUpdate from the event's data attribute
if raw_event.data and isinstance(raw_event.data, AgentRunResponseUpdate):
# Extract the AgentResponseUpdate from the event's data attribute
if raw_event.data and isinstance(raw_event.data, AgentResponseUpdate):
# Preserve executor_id in context for proper output routing
context["current_executor_id"] = raw_event.executor_id
return await self._convert_agent_update(raw_event.data, context)
# If no data, treat as generic workflow event
return await self._convert_workflow_event(raw_event, context)
# Handle complete agent response (AgentRunResponse) - for non-streaming agent execution
if isinstance(raw_event, AgentRunResponse):
# Handle complete agent response (AgentResponse) - for non-streaming agent execution
if isinstance(raw_event, AgentResponse):
return await self._convert_agent_response(raw_event, context)
# Handle agent updates (AgentRunResponseUpdate) - for direct agent execution
if isinstance(raw_event, AgentRunResponseUpdate):
# Handle agent updates (AgentResponseUpdate) - for direct agent execution
if isinstance(raw_event, AgentResponseUpdate):
return await self._convert_agent_update(raw_event, context)
# Handle workflow events (any class that inherits from WorkflowEvent)
@@ -686,13 +686,13 @@ class MessageMapper:
return events
async def _convert_agent_response(self, response: Any, context: dict[str, Any]) -> Sequence[Any]:
"""Convert complete AgentRunResponse to OpenAI events.
"""Convert complete AgentResponse to OpenAI events.
This handles non-streaming agent execution where agent.run() returns
a complete AgentRunResponse instead of streaming AgentRunResponseUpdate objects.
a complete AgentResponse instead of streaming AgentResponseUpdate objects.
Args:
response: Agent run response (AgentRunResponse)
response: Agent run response (AgentResponse)
context: Conversion context
Returns:
@@ -1047,7 +1047,7 @@ class MessageMapper:
# Create ExecutorActionItem with completed status
# ExecutorCompletedEvent uses 'data' field, not 'result'
# Serialize the result data to ensure it's JSON-serializable
# (AgentExecutorResponse contains AgentRunResponse/ChatMessage which are SerializationMixin)
# (AgentExecutorResponse contains AgentResponse/ChatMessage which are SerializationMixin)
raw_result = getattr(event, "data", None)
serialized_result = self._serialize_value(raw_result) if raw_result is not None else None
executor_item = ExecutorActionItem(
@@ -208,7 +208,7 @@ export interface UsageDetails {
}
// Agent run response update (streaming)
export interface AgentRunResponseUpdate {
export interface AgentResponseUpdate {
contents: Contents[];
role?: Role;
author_name?: string;
@@ -222,7 +222,7 @@ export interface AgentRunResponseUpdate {
}
// Agent run response (final)
export interface AgentRunResponse {
export interface AgentResponse {
messages: ChatMessage[];
response_id?: string;
created_at?: CreatedAtT;
@@ -302,11 +302,11 @@ export interface ExecutorEvent extends WorkflowEvent {
}
export interface AgentRunUpdateEvent extends ExecutorEvent {
data?: AgentRunResponseUpdate;
data?: AgentResponseUpdate;
}
export interface AgentRunEvent extends ExecutorEvent {
data?: AgentRunResponse;
data?: AgentResponse;
}
// Span event structure (from OpenTelemetry)
@@ -7,7 +7,7 @@ import tempfile
from pathlib import Path
import pytest
from agent_framework import AgentRunResponse, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, ChatMessage, Role, TextContent
from agent_framework_devui import register_cleanup
from agent_framework_devui._discovery import EntityDiscovery
@@ -35,7 +35,7 @@ class MockAgent:
async def run_stream(self, messages=None, *, thread=None, **kwargs):
"""Mock streaming run method."""
yield AgentRunResponse(
yield AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text="Test response")])],
)
@@ -259,7 +259,7 @@ async def test_cleanup_with_file_based_discovery():
# Write agent module with cleanup registration
agent_file = agent_dir / "__init__.py"
agent_file.write_text("""
from agent_framework import AgentRunResponse, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, ChatMessage, Role, TextContent
from agent_framework_devui import register_cleanup
class MockCredential:
@@ -278,7 +278,7 @@ class TestAgent:
description = "Test agent with cleanup"
async def run_stream(self, messages=None, *, thread=None, **kwargs):
yield AgentRunResponse(
yield AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, content=[TextContent(text="Test")])],
inner_messages=[],
)
@@ -84,7 +84,7 @@ async def test_discovery_accepts_agents_with_only_run():
init_file = agent_dir / "__init__.py"
init_file.write_text("""
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, AgentThread, ChatMessage, Role, TextContent
class NonStreamingAgent:
id = "non_streaming"
@@ -92,7 +92,7 @@ class NonStreamingAgent:
description = "Agent without run_stream"
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(
role=Role.ASSISTANT,
contents=[TextContent(text="response")]
@@ -203,13 +203,13 @@ workflow = builder.build()
agent_dir = temp_path / "my_agent"
agent_dir.mkdir()
(agent_dir / "agent.py").write_text("""
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, AgentThread, ChatMessage, Role, TextContent
class TestAgent:
name = "Test Agent"
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text="test")])],
response_id="test"
)
@@ -287,7 +287,7 @@ async def test_full_pipeline_agent_events_are_json_serializable(executor_with_re
2. Each event is converted by the mapper
3. Server calls model_dump_json() on each event for SSE
If any event contains non-serializable objects (like AgentRunResponse),
If any event contains non-serializable objects (like AgentResponse),
this test will fail - catching the bug before it hits production.
"""
executor, entity_id, mock_client = executor_with_real_agent
@@ -327,7 +327,7 @@ async def test_full_pipeline_workflow_events_are_json_serializable():
This is particularly important for workflows with AgentExecutor because:
- AgentExecutor produces ExecutorCompletedEvent with AgentExecutorResponse
- AgentExecutorResponse contains AgentRunResponse and ChatMessage objects
- AgentExecutorResponse contains AgentResponse and ChatMessage objects
- These are SerializationMixin objects, not Pydantic, which caused the original bug
This test ensures the ENTIRE streaming pipeline works end-to-end.
@@ -566,7 +566,7 @@ def test_extract_workflow_hil_responses_handles_stringified_json():
async def test_executor_handles_non_streaming_agent():
"""Test executor can handle agents with only run() method (no run_stream)."""
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, AgentThread, ChatMessage, Role, TextContent
class NonStreamingAgent:
"""Agent with only run() method - does NOT satisfy full AgentProtocol."""
@@ -576,7 +576,7 @@ async def test_executor_handles_non_streaming_agent():
description = "Test agent without run_stream()"
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=f"Processed: {messages}")])],
response_id="test_123",
)
+18 -18
View File
@@ -18,8 +18,8 @@ from collections.abc import AsyncIterable, MutableSequence
from typing import Any, Generic
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
BaseChatClient,
@@ -172,9 +172,9 @@ class MockAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
self.call_count += 1
return AgentRunResponse(
return AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=self.response_text)])]
)
@@ -184,10 +184,10 @@ class MockAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
self.call_count += 1
for chunk in self.streaming_chunks:
yield AgentRunResponseUpdate(contents=[TextContent(text=chunk)], role=Role.ASSISTANT)
yield AgentResponseUpdate(contents=[TextContent(text=chunk)], role=Role.ASSISTANT)
class MockToolCallingAgent(BaseAgent):
@@ -203,9 +203,9 @@ class MockToolCallingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentRunResponse:
) -> AgentResponse:
self.call_count += 1
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="done")])
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="done")])
async def run_stream(
self,
@@ -213,15 +213,15 @@ class MockToolCallingAgent(BaseAgent):
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentRunResponseUpdate]:
) -> AsyncIterable[AgentResponseUpdate]:
self.call_count += 1
# First: text
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="Let me search for that...")],
role=Role.ASSISTANT,
)
# Second: tool call
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[
FunctionCallContent(
call_id="call_123",
@@ -232,7 +232,7 @@ class MockToolCallingAgent(BaseAgent):
role=Role.ASSISTANT,
)
# Third: tool result
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[
FunctionResultContent(
call_id="call_123",
@@ -242,7 +242,7 @@ class MockToolCallingAgent(BaseAgent):
role=Role.TOOL,
)
# Fourth: final text
yield AgentRunResponseUpdate(
yield AgentResponseUpdate(
contents=[TextContent(text="The weather is sunny, 72°F.")],
role=Role.ASSISTANT,
)
@@ -295,9 +295,9 @@ def create_mock_tool_agent(id: str = "tool_agent", name: str = "ToolAgent") -> M
return MockToolCallingAgent(id=id, name=name)
def create_agent_run_response(text: str = "Test response") -> AgentRunResponse:
"""Create an AgentRunResponse with the given text."""
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=text)])])
def create_agent_run_response(text: str = "Test response") -> AgentResponse:
"""Create an AgentResponse with the given text."""
return AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=text)])])
def create_agent_executor_response(
@@ -308,7 +308,7 @@ def create_agent_executor_response(
agent_response = create_agent_run_response(response_text)
return AgentExecutorResponse(
executor_id=executor_id,
agent_run_response=agent_response,
agent_response=agent_response,
full_conversation=[
ChatMessage(role=Role.USER, contents=[TextContent(text="User input")]),
ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=response_text)]),
@@ -324,7 +324,7 @@ def create_executor_completed_event(
This creates the exact data structure that caused the serialization bug:
ExecutorCompletedEvent.data contains AgentExecutorResponse which contains
AgentRunResponse and ChatMessage objects (SerializationMixin, not Pydantic).
AgentResponse and ChatMessage objects (SerializationMixin, not Pydantic).
"""
data = create_agent_executor_response(executor_id) if with_agent_response else {"simple": "dict"}
return ExecutorCompletedEvent(executor_id=executor_id, data=data)
+16 -18
View File
@@ -13,7 +13,7 @@ import pytest
# Import Agent Framework types
from agent_framework._types import (
AgentRunResponseUpdate,
AgentResponseUpdate,
ErrorContent,
FunctionCallContent,
FunctionResultContent,
@@ -83,11 +83,9 @@ def create_test_content(content_type: str, **kwargs: Any) -> Any:
raise ValueError(f"Unknown content type: {content_type}")
def create_test_agent_update(contents: list[Any]) -> AgentRunResponseUpdate:
"""Create test AgentRunResponseUpdate."""
return AgentRunResponseUpdate(
contents=contents, role=Role.ASSISTANT, message_id="test_msg", response_id="test_resp"
)
def create_test_agent_update(contents: list[Any]) -> AgentResponseUpdate:
"""Create test AgentResponseUpdate."""
return AgentResponseUpdate(contents=contents, role=Role.ASSISTANT, message_id="test_msg", response_id="test_resp")
# =============================================================================
@@ -105,7 +103,7 @@ async def test_critical_isinstance_bug_detection(mapper: MessageMapper, test_req
assert not hasattr(update, "response") # Fake attribute should not exist
# Test isinstance works with real types
assert isinstance(update, AgentRunResponseUpdate)
assert isinstance(update, AgentResponseUpdate)
# Test mapper conversion - should NOT produce "Unknown event"
events = await mapper.convert_event(update, test_request)
@@ -264,7 +262,7 @@ async def test_agent_lifecycle_events(mapper: MessageMapper, test_request: Agent
async def test_agent_run_response_mapping(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test that mapper handles complete AgentRunResponse (non-streaming)."""
"""Test that mapper handles complete AgentResponse (non-streaming)."""
response = create_agent_run_response("Complete response from run()")
events = await mapper.convert_event(response, test_request)
@@ -325,14 +323,14 @@ async def test_executor_completed_event_with_agent_response(
This is a REGRESSION TEST for the serialization bug where
ExecutorCompletedEvent.data contained AgentExecutorResponse with nested
AgentRunResponse and ChatMessage objects (SerializationMixin) that
AgentResponse and ChatMessage objects (SerializationMixin) that
Pydantic couldn't serialize.
"""
# Create event with realistic nested data - the exact structure that caused the bug
event = create_executor_completed_event(executor_id="exec_agent", with_agent_response=True)
# Verify the data has the problematic structure
assert hasattr(event.data, "agent_run_response")
assert hasattr(event.data, "agent_response")
assert hasattr(event.data, "full_conversation")
# First invoke the executor
@@ -380,7 +378,7 @@ async def test_executor_completed_event_serialization_to_json(
done_event = events[0]
# This is the critical test - model_dump_json() should NOT raise
# "Unable to serialize unknown type: <class 'agent_framework._types.AgentRunResponse'>"
# "Unable to serialize unknown type: <class 'agent_framework._types.AgentResponse'>"
try:
json_str = done_event.model_dump_json()
assert json_str is not None
@@ -453,11 +451,11 @@ async def test_magentic_agent_run_update_event_with_agent_delta_metadata(
This tests the ACTUAL event format Magentic emits - not a fake MagenticAgentDeltaEvent class.
Magentic uses AgentRunUpdateEvent with additional_properties containing magentic_event_type.
"""
from agent_framework._types import AgentRunResponseUpdate, Role, TextContent
from agent_framework._types import AgentResponseUpdate, Role, TextContent
from agent_framework._workflows._events import AgentRunUpdateEvent
# Create the REAL event format that Magentic emits
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="Hello from agent")],
role=Role.ASSISTANT,
author_name="Writer",
@@ -484,11 +482,11 @@ async def test_magentic_orchestrator_message_event(mapper: MessageMapper, test_r
Magentic emits orchestrator planning/instruction messages using AgentRunUpdateEvent
with additional_properties containing magentic_event_type='orchestrator_message'.
"""
from agent_framework._types import AgentRunResponseUpdate, Role, TextContent
from agent_framework._types import AgentResponseUpdate, Role, TextContent
from agent_framework._workflows._events import AgentRunUpdateEvent
# Create orchestrator message event (REAL format from Magentic)
update = AgentRunResponseUpdate(
update = AgentResponseUpdate(
contents=[TextContent(text="Planning: First, the writer will create content...")],
role=Role.ASSISTANT,
author_name="Orchestrator",
@@ -520,19 +518,19 @@ async def test_magentic_events_use_same_event_class_as_other_workflows(
additional_properties. Any mapper code checking for 'MagenticAgentDeltaEvent'
class names is dead code.
"""
from agent_framework._types import AgentRunResponseUpdate, Role, TextContent
from agent_framework._types import AgentResponseUpdate, Role, TextContent
from agent_framework._workflows._events import AgentRunUpdateEvent
# Create events the way different workflows do it
# 1. Regular workflow (no additional_properties)
regular_update = AgentRunResponseUpdate(
regular_update = AgentResponseUpdate(
contents=[TextContent(text="Regular workflow response")],
role=Role.ASSISTANT,
)
regular_event = AgentRunUpdateEvent(executor_id="regular_executor", data=regular_update)
# 2. Magentic workflow (with additional_properties)
magentic_update = AgentRunResponseUpdate(
magentic_update = AgentResponseUpdate(
contents=[TextContent(text="Magentic workflow response")],
role=Role.ASSISTANT,
additional_properties={"magentic_event_type": "agent_delta"},
@@ -18,7 +18,7 @@ import string
from typing import TypedDict, cast
import sympy # type: ignore[import-untyped,reportMissingImports]
from agent_framework import AgentRunResponse, ChatAgent, MCPStdioTool
from agent_framework import AgentResponse, ChatAgent, MCPStdioTool
from agent_framework.lab.lightning import AgentFrameworkTracer
from agent_framework.openai import OpenAIChatClient
from agentlightning import LLM, Dataset, Trainer, rollout
@@ -102,7 +102,7 @@ def _is_result_correct(prediction: str, ground_truth: str) -> float:
return float(_scalar_are_results_same(prediction, ground_truth, 1e-2))
def evaluate(result: AgentRunResponse, ground_truth: str) -> float:
def evaluate(result: AgentResponse, ground_truth: str) -> float:
"""Main evaluation function that extracts the agent's answer and compares with ground truth.
This function:
@@ -7,7 +7,7 @@ from agent_framework import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunResponse,
AgentResponse,
ChatAgent,
ChatClientProtocol,
ChatMessage,
@@ -124,7 +124,7 @@ class TaskRunner:
f"{'<blue>assistant</blue>' if is_from_agent else '<green>user</green>'}</bold>, "
f"routing to {'<green>user</green>' if is_from_agent else '<blue>assistant</blue>'}:"
)
log_messages(response.agent_run_response.messages)
log_messages(response.agent_response.messages)
if self.step_count >= self.max_steps:
logger.info(f"Max steps ({self.max_steps}) reached - terminating conversation")
@@ -132,7 +132,7 @@ class TaskRunner:
# Terminate the workflow
return False
response_text = response.agent_run_response.text
response_text = response.agent_response.text
if is_from_agent and self._is_agent_stop(response_text):
logger.info("Agent requested stop - terminating conversation")
self.termination_reason = TerminationReason.AGENT_STOP
@@ -144,7 +144,7 @@ class TaskRunner:
# The final user message won't appear in the assistant's message store,
# because it will never arrive there.
# We need to store it because it's needed for evaluation.
self._final_user_message = flip_messages(response.agent_run_response.messages)
self._final_user_message = flip_messages(response.agent_response.messages)
return False
return True
@@ -255,7 +255,7 @@ class TaskRunner:
"""
# Flip message roles for proper conversation flow
# Assistant messages become user messages and vice versa
flipped = flip_messages(response.agent_run_response.messages)
flipped = flip_messages(response.agent_response.messages)
# Determine source to route to correct target
is_from_agent = response.executor_id == ASSISTANT_AGENT_ID
@@ -342,7 +342,7 @@ class TaskRunner:
first_message = ChatMessage(Role.ASSISTANT, text=DEFAULT_FIRST_AGENT_MESSAGE)
initial_greeting = AgentExecutorResponse(
executor_id=ASSISTANT_AGENT_ID,
agent_run_response=AgentRunResponse(messages=[first_message]),
agent_response=AgentResponse(messages=[first_message]),
full_conversation=[ChatMessage(Role.ASSISTANT, text=DEFAULT_FIRST_AGENT_MESSAGE)],
)
@@ -57,9 +57,9 @@ class PurviewPolicyMiddleware(AgentMiddleware):
context.messages, Activity.UPLOAD_TEXT
)
if should_block_prompt:
from agent_framework import AgentRunResponse, ChatMessage, Role
from agent_framework import AgentResponse, ChatMessage, Role
context.result = AgentRunResponse(
context.result = AgentResponse(
messages=[ChatMessage(role=Role.SYSTEM, text=self._settings.blocked_prompt_message)]
)
context.terminate = True
@@ -76,7 +76,7 @@ class PurviewPolicyMiddleware(AgentMiddleware):
await next(context)
try:
# Post (response) check only if we have a normal AgentRunResponse
# Post (response) check only if we have a normal AgentResponse
# Use the same user_id from the request for the response evaluation
if context.result and not context.is_streaming:
should_block_response, _ = await self._processor.process_messages(
@@ -85,9 +85,9 @@ class PurviewPolicyMiddleware(AgentMiddleware):
user_id=resolved_user_id,
)
if should_block_response:
from agent_framework import AgentRunResponse, ChatMessage, Role
from agent_framework import AgentResponse, ChatMessage, Role
context.result = AgentRunResponse(
context.result = AgentResponse(
messages=[ChatMessage(role=Role.SYSTEM, text=self._settings.blocked_response_message)]
)
else:
@@ -5,7 +5,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from agent_framework import AgentRunContext, AgentRunResponse, ChatMessage, Role
from agent_framework import AgentResponse, AgentRunContext, ChatMessage, Role
from azure.core.credentials import AccessToken
from agent_framework_purview import PurviewPolicyMiddleware, PurviewSettings
@@ -57,7 +57,7 @@ class TestPurviewPolicyMiddleware:
async def mock_next(ctx: AgentRunContext) -> None:
nonlocal next_called
next_called = True
ctx.result = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="I'm good, thanks!")])
ctx.result = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="I'm good, thanks!")])
await middleware.process(context, mock_next)
@@ -104,7 +104,7 @@ class TestPurviewPolicyMiddleware:
with patch.object(middleware._processor, "process_messages", side_effect=mock_process_messages):
async def mock_next(ctx: AgentRunContext) -> None:
ctx.result = AgentRunResponse(
ctx.result = AgentResponse(
messages=[ChatMessage(role=Role.ASSISTANT, text="Here's some sensitive information")]
)
@@ -145,7 +145,7 @@ class TestPurviewPolicyMiddleware:
with patch.object(middleware._processor, "process_messages", return_value=(False, "user-123")) as mock_process:
async def mock_next(ctx: AgentRunContext) -> None:
ctx.result = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
ctx.result = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
await middleware.process(context, mock_next)
@@ -167,7 +167,7 @@ class TestPurviewPolicyMiddleware:
) as mock_process:
async def mock_next(ctx: AgentRunContext) -> None:
ctx.result = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
ctx.result = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
await middleware.process(context, mock_next)
@@ -199,7 +199,7 @@ class TestPurviewPolicyMiddleware:
with patch.object(middleware._processor, "process_messages", side_effect=mock_process_messages):
async def mock_next(ctx: AgentRunContext) -> None:
ctx.result = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
ctx.result = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
await middleware.process(context, mock_next)
@@ -225,7 +225,7 @@ class TestPurviewPolicyMiddleware:
with patch.object(middleware._processor, "process_messages", side_effect=mock_process_messages):
async def mock_next(ctx):
ctx.result = AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
ctx.result = AgentResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="Response")])
# Should not raise, just log
await middleware.process(context, mock_next)