mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [Breaking] Simplified Content types to a single class with classmethod constructors. (#3252)
* ported Content to a new model * fixed linting * fixes * fixed data format handling * fix for 3.10 mypy * fix * fix int test
This commit is contained in:
committed by
GitHub
Unverified
parent
73761aa4a3
commit
83e6229c11
+61
-71
@@ -36,18 +36,8 @@ from typing import Any, cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentResponse,
|
||||
BaseContent,
|
||||
ChatMessage,
|
||||
DataContent,
|
||||
ErrorContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedFileContent,
|
||||
HostedVectorStoreContent,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
UriContent,
|
||||
UsageContent,
|
||||
Content,
|
||||
UsageDetails,
|
||||
get_logger,
|
||||
)
|
||||
@@ -290,25 +280,25 @@ class DurableAgentStateContent:
|
||||
The corresponding DurableAgentStateContent subclass instance
|
||||
"""
|
||||
# Map AI content type to appropriate DurableAgentStateContent subclass
|
||||
if isinstance(content, DataContent):
|
||||
if isinstance(content, Content) and content.type == "data":
|
||||
return DurableAgentStateDataContent.from_data_content(content)
|
||||
if isinstance(content, ErrorContent):
|
||||
if isinstance(content, Content) and content.type == "error":
|
||||
return DurableAgentStateErrorContent.from_error_content(content)
|
||||
if isinstance(content, FunctionCallContent):
|
||||
if isinstance(content, Content) and content.type == "function_call":
|
||||
return DurableAgentStateFunctionCallContent.from_function_call_content(content)
|
||||
if isinstance(content, FunctionResultContent):
|
||||
if isinstance(content, Content) and content.type == "function_result":
|
||||
return DurableAgentStateFunctionResultContent.from_function_result_content(content)
|
||||
if isinstance(content, HostedFileContent):
|
||||
if isinstance(content, Content) and content.type == "hosted_file":
|
||||
return DurableAgentStateHostedFileContent.from_hosted_file_content(content)
|
||||
if isinstance(content, HostedVectorStoreContent):
|
||||
if isinstance(content, Content) and content.type == "hosted_vector_store":
|
||||
return DurableAgentStateHostedVectorStoreContent.from_hosted_vector_store_content(content)
|
||||
if isinstance(content, TextContent):
|
||||
if isinstance(content, Content) and content.type == "text":
|
||||
return DurableAgentStateTextContent.from_text_content(content)
|
||||
if isinstance(content, TextReasoningContent):
|
||||
if isinstance(content, Content) and content.type == "text_reasoning":
|
||||
return DurableAgentStateTextReasoningContent.from_text_reasoning_content(content)
|
||||
if isinstance(content, UriContent):
|
||||
if isinstance(content, Content) and content.type == "uri":
|
||||
return DurableAgentStateUriContent.from_uri_content(content)
|
||||
if isinstance(content, UsageContent):
|
||||
if isinstance(content, Content) and content.type == "usage":
|
||||
return DurableAgentStateUsageContent.from_usage_content(content)
|
||||
return DurableAgentStateUnknownContent.from_unknown_content(content)
|
||||
|
||||
@@ -699,7 +689,7 @@ class DurableAgentStateResponse(DurableAgentStateEntry):
|
||||
correlation_id=correlation_id,
|
||||
created_at=_parse_created_at(response.created_at),
|
||||
messages=[DurableAgentStateMessage.from_chat_message(m) for m in response.messages],
|
||||
usage=DurableAgentStateUsage.from_usage(response.usage_details),
|
||||
usage=DurableAgentStateUsage.from_usage(response.usage_details), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
@@ -868,11 +858,11 @@ class DurableAgentStateDataContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_data_content(content: DataContent) -> DurableAgentStateDataContent:
|
||||
return DurableAgentStateDataContent(uri=content.uri, media_type=content.media_type)
|
||||
def from_data_content(content: Content) -> DurableAgentStateDataContent:
|
||||
return DurableAgentStateDataContent(uri=content.uri, media_type=content.media_type) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> DataContent:
|
||||
return DataContent(uri=self.uri, media_type=self.media_type)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_uri(uri=self.uri, media_type=self.media_type)
|
||||
|
||||
|
||||
class DurableAgentStateErrorContent(DurableAgentStateContent):
|
||||
@@ -907,13 +897,13 @@ class DurableAgentStateErrorContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_error_content(content: ErrorContent) -> DurableAgentStateErrorContent:
|
||||
def from_error_content(content: Content) -> DurableAgentStateErrorContent:
|
||||
return DurableAgentStateErrorContent(
|
||||
message=content.message, error_code=content.error_code, details=content.details
|
||||
message=content.message, error_code=content.error_code, details=content.error_details
|
||||
)
|
||||
|
||||
def to_ai_content(self) -> ErrorContent:
|
||||
return ErrorContent(message=self.message, error_code=self.error_code, details=self.details)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_error(message=self.message, error_code=self.error_code, error_details=self.details)
|
||||
|
||||
|
||||
class DurableAgentStateFunctionCallContent(DurableAgentStateContent):
|
||||
@@ -949,7 +939,7 @@ class DurableAgentStateFunctionCallContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_function_call_content(content: FunctionCallContent) -> DurableAgentStateFunctionCallContent:
|
||||
def from_function_call_content(content: Content) -> DurableAgentStateFunctionCallContent:
|
||||
# Ensure arguments is a dict; parse string if needed
|
||||
arguments: dict[str, Any] = {}
|
||||
if content.arguments:
|
||||
@@ -962,10 +952,10 @@ class DurableAgentStateFunctionCallContent(DurableAgentStateContent):
|
||||
except json.JSONDecodeError:
|
||||
arguments = {}
|
||||
|
||||
return DurableAgentStateFunctionCallContent(call_id=content.call_id, name=content.name, arguments=arguments)
|
||||
return DurableAgentStateFunctionCallContent(call_id=content.call_id, name=content.name, arguments=arguments) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> FunctionCallContent:
|
||||
return FunctionCallContent(call_id=self.call_id, name=self.name, arguments=self.arguments)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_function_call(call_id=self.call_id, name=self.name, arguments=self.arguments)
|
||||
|
||||
|
||||
class DurableAgentStateFunctionResultContent(DurableAgentStateContent):
|
||||
@@ -997,11 +987,11 @@ class DurableAgentStateFunctionResultContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_function_result_content(content: FunctionResultContent) -> DurableAgentStateFunctionResultContent:
|
||||
return DurableAgentStateFunctionResultContent(call_id=content.call_id, result=content.result)
|
||||
def from_function_result_content(content: Content) -> DurableAgentStateFunctionResultContent:
|
||||
return DurableAgentStateFunctionResultContent(call_id=content.call_id, result=content.result) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> FunctionResultContent:
|
||||
return FunctionResultContent(call_id=self.call_id, result=self.result)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_function_result(call_id=self.call_id, result=self.result)
|
||||
|
||||
|
||||
class DurableAgentStateHostedFileContent(DurableAgentStateContent):
|
||||
@@ -1025,11 +1015,11 @@ class DurableAgentStateHostedFileContent(DurableAgentStateContent):
|
||||
return {DurableStateFields.TYPE_DISCRIMINATOR: self.type, DurableStateFields.FILE_ID: self.file_id}
|
||||
|
||||
@staticmethod
|
||||
def from_hosted_file_content(content: HostedFileContent) -> DurableAgentStateHostedFileContent:
|
||||
return DurableAgentStateHostedFileContent(file_id=content.file_id)
|
||||
def from_hosted_file_content(content: Content) -> DurableAgentStateHostedFileContent:
|
||||
return DurableAgentStateHostedFileContent(file_id=content.file_id) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> HostedFileContent:
|
||||
return HostedFileContent(file_id=self.file_id)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_hosted_file(file_id=self.file_id)
|
||||
|
||||
|
||||
class DurableAgentStateHostedVectorStoreContent(DurableAgentStateContent):
|
||||
@@ -1058,12 +1048,12 @@ class DurableAgentStateHostedVectorStoreContent(DurableAgentStateContent):
|
||||
|
||||
@staticmethod
|
||||
def from_hosted_vector_store_content(
|
||||
content: HostedVectorStoreContent,
|
||||
content: Content,
|
||||
) -> DurableAgentStateHostedVectorStoreContent:
|
||||
return DurableAgentStateHostedVectorStoreContent(vector_store_id=content.vector_store_id)
|
||||
return DurableAgentStateHostedVectorStoreContent(vector_store_id=content.vector_store_id) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> HostedVectorStoreContent:
|
||||
return HostedVectorStoreContent(vector_store_id=self.vector_store_id)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_hosted_vector_store(vector_store_id=self.vector_store_id)
|
||||
|
||||
|
||||
class DurableAgentStateTextContent(DurableAgentStateContent):
|
||||
@@ -1085,11 +1075,11 @@ class DurableAgentStateTextContent(DurableAgentStateContent):
|
||||
return {DurableStateFields.TYPE_DISCRIMINATOR: self.type, DurableStateFields.TEXT: self.text}
|
||||
|
||||
@staticmethod
|
||||
def from_text_content(content: TextContent) -> DurableAgentStateTextContent:
|
||||
def from_text_content(content: Content) -> DurableAgentStateTextContent:
|
||||
return DurableAgentStateTextContent(text=content.text)
|
||||
|
||||
def to_ai_content(self) -> TextContent:
|
||||
return TextContent(text=self.text or "")
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_text(text=self.text or "")
|
||||
|
||||
|
||||
class DurableAgentStateTextReasoningContent(DurableAgentStateContent):
|
||||
@@ -1111,11 +1101,11 @@ class DurableAgentStateTextReasoningContent(DurableAgentStateContent):
|
||||
return {DurableStateFields.TYPE_DISCRIMINATOR: self.type, DurableStateFields.TEXT: self.text}
|
||||
|
||||
@staticmethod
|
||||
def from_text_reasoning_content(content: TextReasoningContent) -> DurableAgentStateTextReasoningContent:
|
||||
def from_text_reasoning_content(content: Content) -> DurableAgentStateTextReasoningContent:
|
||||
return DurableAgentStateTextReasoningContent(text=content.text)
|
||||
|
||||
def to_ai_content(self) -> TextReasoningContent:
|
||||
return TextReasoningContent(text=self.text or "")
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_text_reasoning(text=self.text)
|
||||
|
||||
|
||||
class DurableAgentStateUriContent(DurableAgentStateContent):
|
||||
@@ -1146,11 +1136,11 @@ class DurableAgentStateUriContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_uri_content(content: UriContent) -> DurableAgentStateUriContent:
|
||||
return DurableAgentStateUriContent(uri=content.uri, media_type=content.media_type)
|
||||
def from_uri_content(content: Content) -> DurableAgentStateUriContent:
|
||||
return DurableAgentStateUriContent(uri=content.uri, media_type=content.media_type) # type: ignore[arg-type]
|
||||
|
||||
def to_ai_content(self) -> UriContent:
|
||||
return UriContent(uri=self.uri, media_type=self.media_type)
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_uri(uri=self.uri, media_type=self.media_type)
|
||||
|
||||
|
||||
class DurableAgentStateUsage:
|
||||
@@ -1204,22 +1194,22 @@ class DurableAgentStateUsage:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_usage(usage: UsageDetails | None) -> DurableAgentStateUsage | None:
|
||||
def from_usage(usage: UsageDetails | dict[str, int] | None) -> DurableAgentStateUsage | None:
|
||||
if usage is None:
|
||||
return None
|
||||
return DurableAgentStateUsage(
|
||||
input_token_count=usage.input_token_count,
|
||||
output_token_count=usage.output_token_count,
|
||||
total_token_count=usage.total_token_count,
|
||||
input_token_count=usage.get("input_token_count"),
|
||||
output_token_count=usage.get("output_token_count"),
|
||||
total_token_count=usage.get("total_token_count"),
|
||||
)
|
||||
|
||||
def to_usage_details(self) -> UsageDetails:
|
||||
# Convert back to AI SDK UsageDetails
|
||||
return UsageDetails(
|
||||
input_token_count=self.input_token_count,
|
||||
output_token_count=self.output_token_count,
|
||||
total_token_count=self.total_token_count,
|
||||
)
|
||||
return {
|
||||
"input_token_count": self.input_token_count,
|
||||
"output_token_count": self.output_token_count,
|
||||
"total_token_count": self.total_token_count,
|
||||
}
|
||||
|
||||
|
||||
class DurableAgentStateUsageContent(DurableAgentStateContent):
|
||||
@@ -1247,11 +1237,11 @@ class DurableAgentStateUsageContent(DurableAgentStateContent):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_usage_content(content: UsageContent) -> DurableAgentStateUsageContent:
|
||||
return DurableAgentStateUsageContent(usage=DurableAgentStateUsage.from_usage(content.details))
|
||||
def from_usage_content(content: Content) -> DurableAgentStateUsageContent:
|
||||
return DurableAgentStateUsageContent(usage=DurableAgentStateUsage.from_usage(content.usage_details))
|
||||
|
||||
def to_ai_content(self) -> UsageContent:
|
||||
return UsageContent(details=self.usage.to_usage_details())
|
||||
def to_ai_content(self) -> Content:
|
||||
return Content.from_usage(usage_details=self.usage.to_usage_details())
|
||||
|
||||
|
||||
class DurableAgentStateUnknownContent(DurableAgentStateContent):
|
||||
@@ -1279,7 +1269,7 @@ class DurableAgentStateUnknownContent(DurableAgentStateContent):
|
||||
def from_unknown_content(content: Any) -> DurableAgentStateUnknownContent:
|
||||
return DurableAgentStateUnknownContent(content=content)
|
||||
|
||||
def to_ai_content(self) -> BaseContent:
|
||||
def to_ai_content(self) -> Content:
|
||||
if not self.content:
|
||||
raise Exception("The content is missing and cannot be converted to valid AI content.")
|
||||
return BaseContent(content=self.content)
|
||||
return Content(type=self.type, additional_properties={"content": self.content}) # type: ignore
|
||||
|
||||
@@ -18,7 +18,7 @@ from agent_framework import (
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatMessage,
|
||||
ErrorContent,
|
||||
Content,
|
||||
Role,
|
||||
get_logger,
|
||||
)
|
||||
@@ -193,7 +193,7 @@ class AgentEntity:
|
||||
|
||||
# Create error message
|
||||
error_message = ChatMessage(
|
||||
role=Role.ASSISTANT, contents=[ErrorContent(message=str(exc), error_code=type(exc).__name__)]
|
||||
role=Role.ASSISTANT, contents=[Content.from_error(message=str(exc), error_code=type(exc).__name__)]
|
||||
)
|
||||
|
||||
error_response = AgentResponse(messages=[error_message])
|
||||
|
||||
@@ -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 AgentResponse, ChatMessage, ErrorContent
|
||||
from agent_framework import AgentResponse, ChatMessage
|
||||
|
||||
from agent_framework_azurefunctions import AgentFunctionApp
|
||||
from agent_framework_azurefunctions._app import WAIT_FOR_RESPONSE_FIELD, WAIT_FOR_RESPONSE_HEADER
|
||||
@@ -622,7 +622,7 @@ class TestErrorHandling:
|
||||
assert isinstance(result, AgentResponse)
|
||||
assert len(result.messages) == 1
|
||||
content = result.messages[0].contents[0]
|
||||
assert isinstance(content, ErrorContent)
|
||||
assert content.type == "error"
|
||||
assert "Agent error" in (content.message or "")
|
||||
assert content.error_code == "Exception"
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Any, TypeVar
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, ChatMessage, ErrorContent, Role
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, ChatMessage, Role
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agent_framework_azurefunctions._durable_agent_state import (
|
||||
@@ -608,7 +608,7 @@ class TestErrorHandling:
|
||||
assert isinstance(result, AgentResponse)
|
||||
assert len(result.messages) == 1
|
||||
content = result.messages[0].contents[0]
|
||||
assert isinstance(content, ErrorContent)
|
||||
assert content.type == "error"
|
||||
assert "Agent failed" in (content.message or "")
|
||||
assert content.error_code == "Exception"
|
||||
|
||||
@@ -627,7 +627,7 @@ class TestErrorHandling:
|
||||
assert isinstance(result, AgentResponse)
|
||||
assert len(result.messages) == 1
|
||||
content = result.messages[0].contents[0]
|
||||
assert isinstance(content, ErrorContent)
|
||||
assert content.type == "error"
|
||||
assert content.error_code == "ValueError"
|
||||
assert "Invalid input" in str(content.message)
|
||||
|
||||
@@ -646,7 +646,7 @@ class TestErrorHandling:
|
||||
assert isinstance(result, AgentResponse)
|
||||
assert len(result.messages) == 1
|
||||
content = result.messages[0].contents[0]
|
||||
assert isinstance(content, ErrorContent)
|
||||
assert content.type == "error"
|
||||
assert content.error_code == "TimeoutError"
|
||||
|
||||
def test_entity_function_handles_exception_in_operation(self) -> None:
|
||||
@@ -685,7 +685,7 @@ class TestErrorHandling:
|
||||
assert isinstance(result, AgentResponse)
|
||||
assert len(result.messages) == 1
|
||||
content = result.messages[0].contents[0]
|
||||
assert isinstance(content, ErrorContent)
|
||||
assert content.type == "error"
|
||||
|
||||
|
||||
class TestConversationHistory:
|
||||
|
||||
Reference in New Issue
Block a user