# Copyright (c) Microsoft. All rights reserved. from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar from xml.etree.ElementTree import Element # nosec from pydantic import Field, field_serializer from typing_extensions import deprecated from semantic_kernel.const import DEFAULT_FULLY_QUALIFIED_NAME_SEPARATOR from semantic_kernel.contents.const import FUNCTION_RESULT_CONTENT_TAG, TEXT_CONTENT_TAG, ContentTypes from semantic_kernel.contents.image_content import ImageContent from semantic_kernel.contents.kernel_content import KernelContent from semantic_kernel.contents.text_content import TextContent from semantic_kernel.contents.utils.author_role import AuthorRole from semantic_kernel.contents.utils.hashing import make_hashable from semantic_kernel.exceptions.content_exceptions import ContentInitializationError if TYPE_CHECKING: from semantic_kernel.contents.chat_message_content import ChatMessageContent from semantic_kernel.contents.function_call_content import FunctionCallContent from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.functions.function_result import FunctionResult TAG_CONTENT_MAP = { TEXT_CONTENT_TAG: TextContent, } _T = TypeVar("_T", bound="FunctionResultContent") class FunctionResultContent(KernelContent): """This class represents function result content.""" content_type: Literal[ContentTypes.FUNCTION_RESULT_CONTENT] = Field(FUNCTION_RESULT_CONTENT_TAG, init=False) # type: ignore tag: ClassVar[str] = FUNCTION_RESULT_CONTENT_TAG id: str | None = None call_id: str | None = None result: Any name: str | None = None function_name: str plugin_name: str | None = None encoding: str | None = None def __init__( self, inner_content: Any | None = None, ai_model_id: str | None = None, id: str | None = None, call_id: str | None = None, name: str | None = None, function_name: str | None = None, plugin_name: str | None = None, result: Any | None = None, encoding: str | None = None, metadata: dict[str, Any] | None = None, **kwargs: Any, ) -> None: """Create function result content. Args: inner_content (Any | None): The inner content. ai_model_id (str | None): The id of the AI model. id (str | None): The id of the function call that the result relates to. call_id (str | None): The call id of the function call from the Responses API. name (str | None): The name of the function. When not supplied function_name and plugin_name should be supplied. function_name (str | None): The function name. Not used when 'name' is supplied. plugin_name (str | None): The plugin name. Not used when 'name' is supplied. result (Any | None): The result of the function. encoding (str | None): The encoding of the result. metadata (dict[str, Any] | None): The metadata of the function call. kwargs (Any): Additional arguments. """ if function_name and plugin_name and not name: name = f"{plugin_name}{DEFAULT_FULLY_QUALIFIED_NAME_SEPARATOR}{function_name}" if name and not function_name and not plugin_name: if DEFAULT_FULLY_QUALIFIED_NAME_SEPARATOR in name: plugin_name, function_name = name.split(DEFAULT_FULLY_QUALIFIED_NAME_SEPARATOR, maxsplit=1) else: function_name = name args = { "inner_content": inner_content, "ai_model_id": ai_model_id, "id": id, "name": name, "function_name": function_name or "", "plugin_name": plugin_name, "result": result, "encoding": encoding, } if call_id: args["call_id"] = call_id if metadata: args["metadata"] = metadata super().__init__(**args) def __str__(self) -> str: """Return the text of the response.""" return str(self.result) def to_element(self) -> Element: """Convert the instance to an Element.""" element = Element(self.tag) if self.id: element.set("id", self.id) if self.name: element.set("name", self.name) element.text = str(self.result) return element @classmethod def from_element(cls: type[_T], element: Element) -> _T: """Create an instance from an Element.""" if element.tag != cls.tag: raise ContentInitializationError(f"Element tag is not {cls.tag}") # pragma: no cover return cls(id=element.get("id", ""), result=element.text, name=element.get("name", None)) @classmethod def from_function_call_content_and_result( cls: type[_T], function_call_content: "FunctionCallContent", result: "FunctionResult | TextContent | ChatMessageContent | Any", metadata: dict[str, Any] | None = None, ) -> _T: """Create an instance from a FunctionCallContent and a result.""" from semantic_kernel.contents.chat_message_content import ChatMessageContent from semantic_kernel.functions.function_result import FunctionResult metadata = metadata or {} metadata = metadata | (function_call_content.metadata or {}) metadata = metadata | getattr(result, "metadata", {}) inner_content = result if isinstance(result, FunctionResult): result = result.value if isinstance(result, TextContent): res = result.text elif isinstance(result, ChatMessageContent): if isinstance(result.items[0], TextContent): res = result.items[0].text elif isinstance(result.items[0], ImageContent): res = result.items[0].data_uri elif isinstance(result.items[0], FunctionResultContent): res = result.items[0].result res = str(result) else: res = result return cls( id=function_call_content.id or "unknown", call_id=function_call_content.call_id if hasattr(function_call_content, "call_id") else None, inner_content=inner_content, result=res, function_name=function_call_content.function_name, plugin_name=function_call_content.plugin_name, ai_model_id=function_call_content.ai_model_id, metadata=metadata, ) def to_chat_message_content(self) -> "ChatMessageContent": """Convert the instance to a ChatMessageContent.""" from semantic_kernel.contents.chat_message_content import ChatMessageContent return ChatMessageContent(role=AuthorRole.TOOL, items=[self]) def to_streaming_chat_message_content(self) -> "StreamingChatMessageContent": """Convert the instance to a StreamingChatMessageContent.""" from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent return StreamingChatMessageContent(role=AuthorRole.TOOL, choice_index=0, items=[self]) def to_dict(self) -> dict[str, str | Any]: """Convert the instance to a dictionary.""" return { "tool_call_id": self.id, "content": self.result, } @deprecated("The function_name and plugin_name attributes should be used instead.") def split_name(self) -> list[str]: """Split the name into a plugin and function name.""" return [self.plugin_name or "", self.function_name] def custom_fully_qualified_name(self, separator: str) -> str: """Get the fully qualified name of the function with a custom separator. Args: separator (str): The custom separator. Returns: The fully qualified name of the function with a custom separator. """ return f"{self.plugin_name}{separator}{self.function_name}" if self.plugin_name else self.function_name @field_serializer("result") def serialize_result(self, value: Any) -> str: """Serialize the result.""" return str(value) def __hash__(self) -> int: """Return the hash of the function result content.""" hashable_result = make_hashable(self.result) return hash(( self.tag, self.id, hashable_result, self.name, self.function_name, self.plugin_name, self.encoding, ))