Files
microsoft--semantic-kernel/python/semantic_kernel/contents/function_result_content.py
T
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

213 lines
8.5 KiB
Python

# 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,
))