Files
semantic-kernel/python/semantic_kernel/contents/function_result_content.py
T
Eduard van Valkenburg 81cdde24fc Python: upgraded all files to 310 plus format and removed from future (#6353)
### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->
Now that we only support 310 and up, we could unify all files, to use
the new style typing and annotations.

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄
2024-05-21 16:33:08 +00:00

123 lines
4.4 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from functools import cached_property
from typing import TYPE_CHECKING, Any
from xml.etree.ElementTree import Element
from pydantic import field_validator
from semantic_kernel.contents.const import FUNCTION_RESULT_CONTENT_TAG, TEXT_CONTENT_TAG
from semantic_kernel.contents.kernel_content import KernelContent
from semantic_kernel.contents.text_content import TextContent
if TYPE_CHECKING:
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.functions.function_result import FunctionResult
TAG_CONTENT_MAP = {
TEXT_CONTENT_TAG: TextContent,
}
class FunctionResultContent(KernelContent):
"""This is the base class for text response content.
All Text Completion Services should return a instance of this class as response.
Or they can implement their own subclass of this class and return an instance.
Args:
inner_content: Any - The inner content of the response,
this should hold all the information from the response so even
when not creating a subclass a developer can leverage the full thing.
ai_model_id: str | None - The id of the AI model that generated this response.
metadata: dict[str, Any] - Any metadata that should be attached to the response.
text: str | None - The text of the response.
encoding: str | None - The encoding of the text.
Methods:
__str__: Returns the text of the response.
"""
id: str
name: str | None = None
result: str
encoding: str | None = None
@cached_property
def function_name(self) -> str:
"""Get the function name."""
return self.split_name()[1]
@cached_property
def plugin_name(self) -> str | None:
"""Get the plugin name."""
return self.split_name()[0]
@field_validator("result", mode="before")
@classmethod
def _validate_result(cls, result: Any):
if not isinstance(result, str):
result = str(result)
return result
def __str__(self) -> str:
return self.result
def to_element(self) -> Element:
"""Convert the instance to an Element."""
element = Element(FUNCTION_RESULT_CONTENT_TAG)
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, element: Element) -> "FunctionResultContent":
"""Create an instance from an Element."""
if element.tag != FUNCTION_RESULT_CONTENT_TAG:
raise ValueError(f"Element tag is not {FUNCTION_RESULT_CONTENT_TAG}")
return cls(id=element.get("id", ""), result=element.text, name=element.get("name", None)) # type: ignore
@classmethod
def from_function_call_content_and_result(
cls,
function_call_content: "FunctionCallContent",
result: "FunctionResult | TextContent | ChatMessageContent | Any",
metadata: dict[str, Any] = {},
) -> "FunctionResultContent":
"""Create an instance from a FunctionCallContent and a result."""
if function_call_content.metadata:
metadata.update(function_call_content.metadata)
return cls(
id=function_call_content.id,
result=result, # type: ignore
name=function_call_content.name,
ai_model_id=function_call_content.ai_model_id,
metadata=metadata,
)
def to_chat_message_content(self, unwrap: bool = False) -> "ChatMessageContent":
"""Convert the instance to a ChatMessageContent."""
from semantic_kernel.contents.chat_message_content import ChatMessageContent
if unwrap:
return ChatMessageContent(role="tool", items=[self.result]) # type: ignore
return ChatMessageContent(role="tool", items=[self]) # type: ignore
def to_dict(self) -> dict[str, str]:
"""Convert the instance to a dictionary."""
return {
"tool_call_id": self.id,
"content": self.result,
}
def split_name(self) -> list[str]:
"""Split the name into a plugin and function name."""
if not self.name:
raise ValueError("Name is not set.")
if "-" not in self.name:
return ["", self.name]
return self.name.split("-", maxsplit=1)