chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# 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,
|
||||
))
|
||||
Reference in New Issue
Block a user