Files
Evan MattsonandChris 91090f8cf8 Python: Update chat history channel to produce the correct messages with a visibility bool (#7619)
### 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]>
2024-08-05 16:23:40 +00:00

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