mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] Types API Review improvements (#3647)
* Replace Role and FinishReason classes with NewType + Literal
- Remove EnumLike metaclass from _types.py
- Replace Role class with NewType('Role', str) + RoleLiteral
- Replace FinishReason class with NewType('FinishReason', str) + FinishReasonLiteral
- Update all usages across codebase to use string literals
- Remove .value access patterns (direct string comparison now works)
- Add backward compatibility for legacy dict serialization format
- Update tests to reflect new string-based types
Addresses #3591, #3615
* Simplify ChatResponse and AgentResponse type hints (#3592)
- Remove overloads from ChatResponse.__init__
- Remove text parameter from ChatResponse.__init__
- Remove | dict[str, Any] from finish_reason and usage_details params
- Remove **kwargs from AgentResponse.__init__
- Both now accept ChatMessage | Sequence[ChatMessage] | None for messages
- Update docstrings and examples to reflect changes
- Fix tests that were using removed kwargs
- Fix Role type hint usage in ag-ui utils
* Remove text parameter from ChatResponseUpdate and AgentResponseUpdate (#3597)
- Remove text parameter from ChatResponseUpdate.__init__
- Remove text parameter from AgentResponseUpdate.__init__
- Remove **kwargs from both update classes
- Simplify contents parameter type to Sequence[Content] | None
- Update all usages to use contents=[Content.from_text(...)] pattern
- Fix imports in test files
- Update docstrings and examples
* Rename from_chat_response_updates to from_updates (#3593)
- ChatResponse.from_chat_response_updates → ChatResponse.from_updates
- ChatResponse.from_chat_response_generator → ChatResponse.from_update_generator
- AgentResponse.from_agent_run_response_updates → AgentResponse.from_updates
* Remove try_parse_value method from ChatResponse and AgentResponse (#3595)
- Remove try_parse_value method from ChatResponse
- Remove try_parse_value method from AgentResponse
- Remove try_parse_value calls from from_updates and from_update_generator methods
- Update samples to use try/except with response.value instead
- Update tests to use response.value pattern
- Users should now use response.value with try/except for safe parsing
* Add agent_id to AgentResponse and clarify author_name documentation (#3596)
- Add agent_id parameter to AgentResponse class
- Document that author_name is on ChatMessage objects, not responses
- Update ChatResponse docstring with author_name note
- Update AgentResponse docstring with author_name note
* Simplify ChatMessage.__init__ signature (#3618)
- Make contents a positional argument accepting Sequence[Content | str]
- Auto-convert strings in contents to TextContent
- Remove overloads, keep text kwarg for backward compatibility with serialization
- Update _parse_content_list to handle string items
- Update all usages across codebase to use new format: ChatMessage("role", ["text"])
* Allow Content as input on run and get_response
- Update prepare_messages and normalize_messages to accept Content
- Update type signatures in _agents.py and _clients.py
- Add tests for Content input handling
* Fix ChatMessage usage across packages and samples
Update all remaining ChatMessage(role=..., text=...) to use new
ChatMessage('role', ['text']) signature.
* Fix Role string usage and response format parsing
- Fix redis provider: remove .value access on string literals
- Fix durabletask ensure_response_format: set _response_format before accessing .value
* Fix ollama .value and ai_model_id issues, handle None in content list
- Fix ollama _chat_client: remove .value on string literals
- Fix ollama _chat_client: rename ai_model_id to model_id
- Fix _parse_content_list: skip None values gracefully
* Fix A2AAgent type signature to include Content
* Fix Role/FinishReason NewType dict annotations and improve test coverage to 95%
* Fix mypy errors for Role/FinishReason NewType usage
* Fix Role.TOOL and Role.ASSISTANT usage in _orchestrator_helpers.py
* Fix Role NewType usage in durabletask _models.py
This commit is contained in:
@@ -334,7 +334,7 @@ class AGUIChatClient(BaseChatClient[TAGUIChatOptions], Generic[TAGUIChatOptions]
|
||||
Returns:
|
||||
ChatResponse object
|
||||
"""
|
||||
return await ChatResponse.from_chat_response_generator(
|
||||
return await ChatResponse.from_update_generator(
|
||||
self._inner_get_streaming_response(
|
||||
messages=messages,
|
||||
options=options,
|
||||
|
||||
@@ -7,8 +7,6 @@ from typing import Any
|
||||
from agent_framework import (
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
FinishReason,
|
||||
Role,
|
||||
)
|
||||
|
||||
|
||||
@@ -86,7 +84,7 @@ class AGUIEventConverter:
|
||||
self.run_id = event.get("runId")
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
contents=[],
|
||||
additional_properties={
|
||||
"thread_id": self.thread_id,
|
||||
@@ -98,7 +96,7 @@ class AGUIEventConverter:
|
||||
"""Handle TEXT_MESSAGE_START event."""
|
||||
self.current_message_id = event.get("messageId")
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
message_id=self.current_message_id,
|
||||
contents=[],
|
||||
)
|
||||
@@ -112,7 +110,7 @@ class AGUIEventConverter:
|
||||
self.current_message_id = message_id
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
message_id=self.current_message_id,
|
||||
contents=[Content.from_text(text=delta)],
|
||||
)
|
||||
@@ -128,7 +126,7 @@ class AGUIEventConverter:
|
||||
self.accumulated_tool_args = ""
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
call_id=self.current_tool_call_id or "",
|
||||
@@ -144,7 +142,7 @@ class AGUIEventConverter:
|
||||
self.accumulated_tool_args += delta
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
call_id=self.current_tool_call_id or "",
|
||||
@@ -165,7 +163,7 @@ class AGUIEventConverter:
|
||||
result = event.get("result") if event.get("result") is not None else event.get("content")
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.TOOL,
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
call_id=tool_call_id,
|
||||
@@ -177,8 +175,8 @@ class AGUIEventConverter:
|
||||
def _handle_run_finished(self, event: dict[str, Any]) -> ChatResponseUpdate:
|
||||
"""Handle RUN_FINISHED event."""
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
finish_reason=FinishReason.STOP,
|
||||
role="assistant",
|
||||
finish_reason="stop",
|
||||
contents=[],
|
||||
additional_properties={
|
||||
"thread_id": self.thread_id,
|
||||
@@ -191,8 +189,8 @@ class AGUIEventConverter:
|
||||
error_message = event.get("message", "Unknown error")
|
||||
|
||||
return ChatResponseUpdate(
|
||||
role=Role.ASSISTANT,
|
||||
finish_reason=FinishReason.CONTENT_FILTER,
|
||||
role="assistant",
|
||||
finish_reason="content_filter",
|
||||
contents=[
|
||||
Content.from_error(
|
||||
message=error_message,
|
||||
|
||||
@@ -9,7 +9,6 @@ from typing import Any, cast
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
Content,
|
||||
Role,
|
||||
prepare_function_call_results,
|
||||
)
|
||||
|
||||
@@ -269,7 +268,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
|
||||
def _find_matching_func_call(call_id: str) -> Content | None:
|
||||
for prev_msg in result:
|
||||
role_val = prev_msg.role.value if hasattr(prev_msg.role, "value") else str(prev_msg.role)
|
||||
role_val = prev_msg.role if hasattr(prev_msg.role, "value") else str(prev_msg.role)
|
||||
if role_val != "assistant":
|
||||
continue
|
||||
for content in prev_msg.contents or []:
|
||||
@@ -287,7 +286,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
return str(explicit_call_id)
|
||||
|
||||
for prev_msg in result:
|
||||
role_val = prev_msg.role.value if hasattr(prev_msg.role, "value") else str(prev_msg.role)
|
||||
role_val = prev_msg.role if hasattr(prev_msg.role, "value") else str(prev_msg.role)
|
||||
if role_val != "assistant":
|
||||
continue
|
||||
direct_call = None
|
||||
@@ -396,7 +395,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
m
|
||||
for m in result
|
||||
if not (
|
||||
(m.role.value if hasattr(m.role, "value") else str(m.role)) == "tool"
|
||||
(m.role if hasattr(m.role, "value") else str(m.role)) == "tool"
|
||||
and any(
|
||||
c.type == "function_result" and c.call_id == approval_call_id
|
||||
for c in (m.contents or [])
|
||||
@@ -473,14 +472,14 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
additional_properties={"ag_ui_state_args": state_args} if state_args else None,
|
||||
)
|
||||
chat_msg = ChatMessage(
|
||||
role=Role.USER,
|
||||
role="user",
|
||||
contents=[approval_response],
|
||||
)
|
||||
else:
|
||||
# No matching function call found - this is likely a confirm_changes approval
|
||||
# Keep the old behavior for backwards compatibility
|
||||
chat_msg = ChatMessage(
|
||||
role=Role.USER,
|
||||
role="user",
|
||||
contents=[Content.from_text(text=approval_payload_text)],
|
||||
additional_properties={"is_tool_result": True, "tool_call_id": str(tool_call_id or "")},
|
||||
)
|
||||
@@ -500,7 +499,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
else:
|
||||
func_result = str(result_content)
|
||||
chat_msg = ChatMessage(
|
||||
role=Role.TOOL,
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id=str(tool_call_id), result=func_result)],
|
||||
)
|
||||
if "id" in msg:
|
||||
@@ -516,7 +515,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
result_content = msg.get("result", msg.get("content", ""))
|
||||
|
||||
chat_msg = ChatMessage(
|
||||
role=Role.TOOL,
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id=str(tool_call_id), result=result_content)],
|
||||
)
|
||||
if "id" in msg:
|
||||
@@ -554,7 +553,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
arguments=arguments,
|
||||
)
|
||||
)
|
||||
chat_msg = ChatMessage(role=Role.ASSISTANT, contents=contents)
|
||||
chat_msg = ChatMessage("assistant", contents)
|
||||
if "id" in msg:
|
||||
chat_msg.message_id = msg["id"]
|
||||
result.append(chat_msg)
|
||||
@@ -562,7 +561,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
|
||||
# No special handling required for assistant/plain messages here
|
||||
|
||||
role = AGUI_TO_FRAMEWORK_ROLE.get(role_str, Role.USER)
|
||||
role = AGUI_TO_FRAMEWORK_ROLE.get(role_str, "user")
|
||||
|
||||
# Check if this message contains function approvals
|
||||
if "function_approvals" in msg and msg["function_approvals"]:
|
||||
@@ -584,14 +583,14 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
)
|
||||
approval_contents.append(approval_response)
|
||||
|
||||
chat_msg = ChatMessage(role=role, contents=approval_contents) # type: ignore[arg-type]
|
||||
chat_msg = ChatMessage(role, approval_contents) # type: ignore[arg-type]
|
||||
else:
|
||||
# Regular text message
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str):
|
||||
chat_msg = ChatMessage(role=role, contents=[Content.from_text(text=content)])
|
||||
chat_msg = ChatMessage(role, [Content.from_text(text=content)])
|
||||
else:
|
||||
chat_msg = ChatMessage(role=role, contents=[Content.from_text(text=str(content))])
|
||||
chat_msg = ChatMessage(role, [Content.from_text(text=str(content))])
|
||||
|
||||
if "id" in msg:
|
||||
chat_msg.message_id = msg["id"]
|
||||
|
||||
@@ -862,7 +862,7 @@ async def run_agent_stream(
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger.info(f"Processing structured output, update count: {len(all_updates)}")
|
||||
final_response = AgentResponse.from_agent_run_response_updates(all_updates, output_format_type=response_format)
|
||||
final_response = AgentResponse.from_updates(all_updates, output_format_type=response_format)
|
||||
|
||||
if final_response.value and isinstance(final_response.value, BaseModel):
|
||||
response_dict = final_response.value.model_dump(mode="json", exclude_none=True)
|
||||
|
||||
@@ -10,19 +10,19 @@ from dataclasses import asdict, is_dataclass
|
||||
from datetime import date, datetime
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import AgentResponseUpdate, ChatResponseUpdate, FunctionTool, Role, ToolProtocol
|
||||
from agent_framework import AgentResponseUpdate, ChatResponseUpdate, FunctionTool, ToolProtocol
|
||||
|
||||
# Role mapping constants
|
||||
AGUI_TO_FRAMEWORK_ROLE: dict[str, Role] = {
|
||||
"user": Role.USER,
|
||||
"assistant": Role.ASSISTANT,
|
||||
"system": Role.SYSTEM,
|
||||
AGUI_TO_FRAMEWORK_ROLE: dict[str, str] = {
|
||||
"user": "user",
|
||||
"assistant": "assistant",
|
||||
"system": "system",
|
||||
}
|
||||
|
||||
FRAMEWORK_TO_AGUI_ROLE: dict[Role, str] = {
|
||||
Role.USER: "user",
|
||||
Role.ASSISTANT: "assistant",
|
||||
Role.SYSTEM: "system",
|
||||
FRAMEWORK_TO_AGUI_ROLE: dict[str, str] = {
|
||||
"user": "user",
|
||||
"assistant": "assistant",
|
||||
"system": "system",
|
||||
}
|
||||
|
||||
ALLOWED_AGUI_ROLES: set[str] = {"user", "assistant", "system", "tool"}
|
||||
|
||||
Reference in New Issue
Block a user