-
Notifications
You must be signed in to change notification settings - Fork 3k
/
function_result_content.py
122 lines (100 loc) · 4.42 KB
/
function_result_content.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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)