mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 11:19:56 +00:00
### Motivation and Context To align with the dotnet ChatHistoryChannel, there needs to be an update to the Python ChatHistoryChannel that properly handles the invoked message via a queue. This allows the final messages to be produced (as they sometimes may contain function call content or function result content). The messages are also produced with an `is_visibile` bool. <!-- 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. --> ### Description This PR: - updates the chat history channel to implement the queue that handles ChatMessageContent types and the `is_visible` bool to the caller. - This change requires the use of a set and thus an implementation of a `__hash__` method for some content types so we know we're not adding/handling duplicate messages. - Upgrades OpenAI package to 1.37.1 (supersedes a previous Dependabot PR doing the same) - Updates unit tests for ChatHistoryChannel based on the new code to maintain 100% coverage. - Adds a missing `parallel_tool_calls` bool in the Run object of agent unit tests related to the upgrade to OpenAI 1.37.1. <!-- 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 - [ ] I didn't break anyone 😄 --------- Co-authored-by: Chris <[email protected]>
64 lines
2.3 KiB
Python
64 lines
2.3 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
from html import unescape
|
|
from typing import ClassVar, Literal, TypeVar
|
|
from xml.etree.ElementTree import Element # nosec
|
|
|
|
from pydantic import Field
|
|
|
|
from semantic_kernel.contents.const import TEXT_CONTENT_TAG, ContentTypes
|
|
from semantic_kernel.contents.kernel_content import KernelContent
|
|
from semantic_kernel.exceptions.content_exceptions import ContentInitializationError
|
|
|
|
_T = TypeVar("_T", bound="TextContent")
|
|
|
|
|
|
class TextContent(KernelContent):
|
|
"""This represents text response content.
|
|
|
|
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.
|
|
"""
|
|
|
|
content_type: Literal[ContentTypes.TEXT_CONTENT] = Field(TEXT_CONTENT_TAG, init=False) # type: ignore
|
|
tag: ClassVar[str] = TEXT_CONTENT_TAG
|
|
text: str
|
|
encoding: str | None = None
|
|
|
|
def __str__(self) -> str:
|
|
"""Return the text of the response."""
|
|
return self.text
|
|
|
|
def to_element(self) -> Element:
|
|
"""Convert the instance to an Element."""
|
|
element = Element(self.tag)
|
|
element.text = self.text
|
|
if self.encoding:
|
|
element.set("encoding", self.encoding)
|
|
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(text=unescape(element.text) if element.text else "", encoding=element.get("encoding", None))
|
|
|
|
def to_dict(self) -> dict[str, str]:
|
|
"""Convert the instance to a dictionary."""
|
|
return {"type": "text", "text": self.text}
|
|
|
|
def __hash__(self) -> int:
|
|
"""Return the hash of the text content."""
|
|
return hash((self.tag, self.text, self.encoding))
|