mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: mypy coverage for contents folder (#5904)
### 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. --> Adds mypy typing coverage for the contents folder, includes some small changes to the types in it. ### 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 😄
This commit is contained in:
@@ -16,9 +16,6 @@ no_implicit_reexport = true
|
||||
[mypy-semantic_kernel.connectors.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-semantic_kernel.contents.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-semantic_kernel.core_plugins.*]
|
||||
ignore_errors = true
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from semantic_kernel.connectors.ai.open_ai.contents.open_ai_chat_message_content import OpenAIChatMessageContent
|
||||
from semantic_kernel.contents.const import AZURE_CHAT_MESSAGE_CONTENT
|
||||
from semantic_kernel.contents.types import AZURE_CHAT_MESSAGE_CONTENT
|
||||
|
||||
|
||||
class AzureChatMessageContent(OpenAIChatMessageContent):
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ from pydantic import field_validator
|
||||
from semantic_kernel.connectors.ai.open_ai.contents.function_call import FunctionCall
|
||||
from semantic_kernel.connectors.ai.open_ai.contents.tool_calls import ToolCall
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
from semantic_kernel.contents.const import OPENAI_CHAT_MESSAGE_CONTENT
|
||||
from semantic_kernel.contents.types import OPENAI_CHAT_MESSAGE_CONTENT
|
||||
|
||||
|
||||
class OpenAIChatMessageContent(ChatMessageContent):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Iterator, List
|
||||
from typing import Any, Generator
|
||||
from xml.etree.ElementTree import Element, tostring
|
||||
|
||||
from defusedxml.ElementTree import XML, ParseError
|
||||
@@ -12,10 +12,12 @@ from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.chat_message_content_base import ChatMessageContentBase
|
||||
from semantic_kernel.contents.chat_role import ChatRole
|
||||
from semantic_kernel.contents.const import (
|
||||
CHAT_MESSAGE_CONTENT,
|
||||
ROOT_KEY_HISTORY,
|
||||
ROOT_KEY_MESSAGE,
|
||||
TYPES_CHAT_MESSAGE_CONTENT,
|
||||
)
|
||||
from semantic_kernel.contents.types import (
|
||||
CHAT_MESSAGE_CONTENT,
|
||||
CHAT_MESSAGE_CONTENT_TYPE_NAMES,
|
||||
)
|
||||
from semantic_kernel.exceptions import ContentInitializationError, ContentSerializationError
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
@@ -36,7 +38,7 @@ class ChatHistory(KernelBaseModel):
|
||||
"""
|
||||
|
||||
messages: list[ChatMessageContent]
|
||||
message_type: TYPES_CHAT_MESSAGE_CONTENT = CHAT_MESSAGE_CONTENT
|
||||
message_type: CHAT_MESSAGE_CONTENT_TYPE_NAMES = "ChatMessageContent"
|
||||
|
||||
def __init__(self, **data: Any):
|
||||
"""
|
||||
@@ -78,10 +80,10 @@ class ChatHistory(KernelBaseModel):
|
||||
|
||||
@field_validator("messages", mode="before")
|
||||
@classmethod
|
||||
def _validate_messages(cls, messages: List[ChatMessageContent]) -> List[ChatMessageContent]:
|
||||
def _validate_messages(cls, messages: list[ChatMessageContent]) -> list[ChatMessageContent]:
|
||||
if not messages:
|
||||
return messages
|
||||
out_msgs: List[ChatMessageContent] = []
|
||||
out_msgs: list[ChatMessageContent] = []
|
||||
for message in messages:
|
||||
if isinstance(message, dict):
|
||||
out_msgs.append(ChatMessageContentBase.from_dict(message))
|
||||
@@ -109,7 +111,7 @@ class ChatHistory(KernelBaseModel):
|
||||
|
||||
def add_message(
|
||||
self,
|
||||
message: "ChatMessageContent" | dict[str, Any],
|
||||
message: ChatMessageContent | dict[str, Any],
|
||||
encoding: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
@@ -145,7 +147,7 @@ class ChatHistory(KernelBaseModel):
|
||||
kwargs["content"] = content
|
||||
return kwargs
|
||||
|
||||
def remove_message(self, message: "ChatMessageContent") -> bool:
|
||||
def remove_message(self, message: ChatMessageContent) -> bool:
|
||||
"""Remove a message from the history.
|
||||
|
||||
Args:
|
||||
@@ -164,7 +166,7 @@ class ChatHistory(KernelBaseModel):
|
||||
"""Return the number of messages in the history."""
|
||||
return len(self.messages)
|
||||
|
||||
def __getitem__(self, index: int) -> "ChatMessageContent":
|
||||
def __getitem__(self, index: int) -> ChatMessageContent:
|
||||
"""Get a message from the history using the [] operator.
|
||||
|
||||
Args:
|
||||
@@ -175,7 +177,7 @@ class ChatHistory(KernelBaseModel):
|
||||
"""
|
||||
return self.messages[index]
|
||||
|
||||
def __contains__(self, item: "ChatMessageContent") -> bool:
|
||||
def __contains__(self, item: ChatMessageContent) -> bool:
|
||||
"""Check if a message is in the history.
|
||||
|
||||
Args:
|
||||
@@ -193,9 +195,9 @@ class ChatHistory(KernelBaseModel):
|
||||
chat_history_xml.append(message.to_element(root_key=ROOT_KEY_MESSAGE))
|
||||
return tostring(chat_history_xml, encoding="unicode", short_empty_elements=True)
|
||||
|
||||
def __iter__(self) -> Iterator["ChatMessageContent"]:
|
||||
def __iter__(self) -> Generator[ChatMessageContent, None, None]: # type: ignore
|
||||
"""Return an iterator over the messages in the history."""
|
||||
return iter(self.messages)
|
||||
yield from self.messages
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""Check if two ChatHistory instances are equal."""
|
||||
@@ -205,7 +207,7 @@ class ChatHistory(KernelBaseModel):
|
||||
return self.messages == other.messages
|
||||
|
||||
@classmethod
|
||||
def from_rendered_prompt(cls, rendered_prompt: str, message_type: str = CHAT_MESSAGE_CONTENT) -> "ChatHistory":
|
||||
def from_rendered_prompt(cls, rendered_prompt: str, message_type: str = CHAT_MESSAGE_CONTENT) -> ChatHistory:
|
||||
"""
|
||||
Create a ChatHistory instance from a rendered prompt.
|
||||
|
||||
@@ -215,7 +217,7 @@ class ChatHistory(KernelBaseModel):
|
||||
Returns:
|
||||
ChatHistory: The ChatHistory instance created from the rendered prompt.
|
||||
"""
|
||||
messages: List[ChatMessageContent] = []
|
||||
messages: list[ChatMessageContent] = []
|
||||
prompt = rendered_prompt.strip()
|
||||
try:
|
||||
xml_prompt = XML(text=f"<prompt>{prompt}</prompt>")
|
||||
@@ -260,7 +262,7 @@ class ChatHistory(KernelBaseModel):
|
||||
raise ContentSerializationError(f"Unable to serialize ChatHistory to JSON: {e}") from e
|
||||
|
||||
@classmethod
|
||||
def restore_chat_history(cls, chat_history_json: str) -> "ChatHistory":
|
||||
def restore_chat_history(cls, chat_history_json: str) -> ChatHistory:
|
||||
"""
|
||||
Restores a ChatHistory instance from a JSON string.
|
||||
|
||||
@@ -292,7 +294,7 @@ class ChatHistory(KernelBaseModel):
|
||||
file.write(json_str)
|
||||
|
||||
@classmethod
|
||||
def load_chat_history_from_file(cls, file_path: str) -> "ChatHistory":
|
||||
def load_chat_history_from_file(cls, file_path: str) -> ChatHistory:
|
||||
"""
|
||||
Loads the ChatHistory from a file.
|
||||
|
||||
@@ -302,6 +304,6 @@ class ChatHistory(KernelBaseModel):
|
||||
Returns:
|
||||
ChatHistory: The deserialized ChatHistory instance.
|
||||
"""
|
||||
with open(file_path, "r") as file:
|
||||
with open(file_path) as file:
|
||||
json_str = file.read()
|
||||
return cls.restore_chat_history(json_str)
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Literal, Optional
|
||||
from typing import Literal
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from defusedxml import ElementTree
|
||||
|
||||
from semantic_kernel.contents.chat_message_content_base import DISCRIMINATOR_FIELD
|
||||
from semantic_kernel.contents.chat_role import ChatRole
|
||||
from semantic_kernel.contents.const import CHAT_MESSAGE_CONTENT
|
||||
from semantic_kernel.contents.const import DISCRIMINATOR_FIELD
|
||||
from semantic_kernel.contents.finish_reason import FinishReason
|
||||
from semantic_kernel.contents.kernel_content import KernelContent
|
||||
from semantic_kernel.contents.types import CHAT_MESSAGE_CONTENT
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
|
||||
|
||||
@@ -35,9 +37,9 @@ class ChatMessageContent(KernelContent):
|
||||
|
||||
type: Literal[CHAT_MESSAGE_CONTENT] = CHAT_MESSAGE_CONTENT # type: ignore
|
||||
role: ChatRole
|
||||
content: Optional[str] = None
|
||||
encoding: Optional[str] = None
|
||||
finish_reason: Optional[FinishReason] = None
|
||||
content: str | None = None
|
||||
encoding: str | None = None
|
||||
finish_reason: FinishReason | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.content or ""
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, Dict, Final
|
||||
from __future__ import annotations
|
||||
|
||||
from semantic_kernel.contents.const import ALL_CHAT_MESSAGE_CONTENTS, CHAT_MESSAGE_CONTENT
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from typing import Annotated
|
||||
@@ -13,10 +13,14 @@ from xml.etree.ElementTree import Element
|
||||
|
||||
from pydantic import Field, RootModel
|
||||
|
||||
from semantic_kernel.contents.const import DISCRIMINATOR_FIELD
|
||||
from semantic_kernel.contents.types import CHAT_MESSAGE_CONTENT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.connectors.ai.open_ai.contents import AzureChatMessageContent, OpenAIChatMessageContent
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
|
||||
DISCRIMINATOR_FIELD: Final[str] = "type"
|
||||
CHAT_MESSAGE_CONTENT_TYPES = Union["ChatMessageContent", "OpenAIChatMessageContent", "AzureChatMessageContent"]
|
||||
|
||||
|
||||
class ChatMessageContentBase(RootModel):
|
||||
@@ -33,10 +37,10 @@ class ChatMessageContentBase(RootModel):
|
||||
which is a instance of ChatMessageContent or the requested subclass.
|
||||
"""
|
||||
|
||||
root: Annotated[ALL_CHAT_MESSAGE_CONTENTS, Field(discriminator=DISCRIMINATOR_FIELD)]
|
||||
root: Annotated[CHAT_MESSAGE_CONTENT_TYPES, Field(discriminator=DISCRIMINATOR_FIELD)]
|
||||
|
||||
@classmethod
|
||||
def from_fields(cls, **kwargs: Any) -> "ChatMessageContent":
|
||||
def from_fields(cls, **kwargs: Any) -> ChatMessageContent:
|
||||
"""Create a new instance of ChatMessageContent from fields.
|
||||
|
||||
Args:
|
||||
@@ -54,10 +58,10 @@ class ChatMessageContentBase(RootModel):
|
||||
cls.model_rebuild()
|
||||
if DISCRIMINATOR_FIELD not in kwargs:
|
||||
kwargs[DISCRIMINATOR_FIELD] = CHAT_MESSAGE_CONTENT
|
||||
return cls(**kwargs).root
|
||||
return cls(**kwargs).root # type: ignore
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "ChatMessageContent":
|
||||
def from_dict(cls, data: dict[str, Any]) -> ChatMessageContent:
|
||||
"""Create a new instance of ChatMessageContent from a dictionary.
|
||||
|
||||
Args:
|
||||
@@ -69,7 +73,7 @@ class ChatMessageContentBase(RootModel):
|
||||
return cls.from_fields(**data)
|
||||
|
||||
@classmethod
|
||||
def from_element(cls, element: Element) -> "ChatMessageContent":
|
||||
def from_element(cls, element: Element) -> ChatMessageContent:
|
||||
"""Create a new instance of ChatMessageContent from a XML element.
|
||||
|
||||
Args:
|
||||
@@ -78,7 +82,7 @@ class ChatMessageContentBase(RootModel):
|
||||
Returns:
|
||||
ChatMessageContent - The new instance of ChatMessageContent or a subclass.
|
||||
"""
|
||||
kwargs: Dict[str, Any] = {"content": element.text}
|
||||
kwargs: dict[str, Any] = {"content": element.text}
|
||||
for key, value in element.items():
|
||||
kwargs[key] = value
|
||||
return cls.from_fields(**kwargs)
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Final, Literal, Union
|
||||
from typing import Final
|
||||
|
||||
ROOT_KEY_MESSAGE: Final[str] = "message"
|
||||
ROOT_KEY_HISTORY: Final[str] = "chat_history"
|
||||
AZURE_CHAT_MESSAGE_CONTENT: Final[str] = "AzureChatMessageContent"
|
||||
OPENAI_CHAT_MESSAGE_CONTENT: Final[str] = "OpenAIChatMessageContent"
|
||||
CHAT_MESSAGE_CONTENT: Final[str] = "ChatMessageContent"
|
||||
|
||||
ALL_CHAT_MESSAGE_CONTENTS = Union[CHAT_MESSAGE_CONTENT, OPENAI_CHAT_MESSAGE_CONTENT, AZURE_CHAT_MESSAGE_CONTENT]
|
||||
TYPES_CHAT_MESSAGE_CONTENT = Literal[CHAT_MESSAGE_CONTENT, OPENAI_CHAT_MESSAGE_CONTENT, AZURE_CHAT_MESSAGE_CONTENT]
|
||||
DISCRIMINATOR_FIELD: Final[str] = "type"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
@@ -10,9 +12,9 @@ from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
class KernelContent(KernelBaseModel, ABC):
|
||||
"""Base class for all kernel contents."""
|
||||
|
||||
inner_content: Optional[Any] = None
|
||||
ai_model_id: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
||||
inner_content: Any | None = None
|
||||
ai_model_id: str | None = None
|
||||
metadata: dict[str, Any] | None = Field(default_factory=dict)
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.streaming_content_mixin import StreamingContentMixin
|
||||
@@ -34,7 +34,7 @@ class StreamingChatMessageContent(StreamingContentMixin, ChatMessageContent):
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.content.encode(self.encoding if self.encoding else "utf-8") if self.content else b""
|
||||
|
||||
def __add__(self, other: "StreamingChatMessageContent") -> "StreamingChatMessageContent":
|
||||
def __add__(self, other: StreamingChatMessageContent) -> StreamingChatMessageContent:
|
||||
"""When combining two StreamingChatMessageContent instances, the content fields are combined.
|
||||
|
||||
The inner_content of the first one is used, ai_model_id and encoding should be the same,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import Self
|
||||
else:
|
||||
from typing_extensions import Self
|
||||
|
||||
from typing import Any
|
||||
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from semantic_kernel.contents.kernel_content import KernelContent
|
||||
|
||||
@@ -11,20 +11,20 @@ class TextContent(KernelContent):
|
||||
Or they can implement their own subclass of this class and return an instance.
|
||||
|
||||
Args:
|
||||
inner_content: Optional[Any] - The inner content of the response,
|
||||
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: Optional[str] - The id of the AI model that generated this response.
|
||||
metadata: Dict[str, Any] - Any metadata that should be attached to the response.
|
||||
text: Optional[str] - The text of the response.
|
||||
encoding: Optional[str] - The encoding of the text.
|
||||
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.
|
||||
"""
|
||||
|
||||
text: Optional[str] = None
|
||||
encoding: Optional[str] = None
|
||||
text: str | None = None
|
||||
encoding: str | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text or ""
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Literal, Union, get_args
|
||||
|
||||
AZURE_CHAT_MESSAGE_CONTENT_TYPE = Literal["AzureChatMessageContent"]
|
||||
OPENAI_CHAT_MESSAGE_CONTENT_TYPE = Literal["OpenAIChatMessageContent"]
|
||||
CHAT_MESSAGE_CONTENT_TYPE = Literal["ChatMessageContent"]
|
||||
|
||||
AZURE_CHAT_MESSAGE_CONTENT: AZURE_CHAT_MESSAGE_CONTENT_TYPE = get_args(AZURE_CHAT_MESSAGE_CONTENT_TYPE)[0]
|
||||
OPENAI_CHAT_MESSAGE_CONTENT: OPENAI_CHAT_MESSAGE_CONTENT_TYPE = get_args(OPENAI_CHAT_MESSAGE_CONTENT_TYPE)[0]
|
||||
CHAT_MESSAGE_CONTENT: CHAT_MESSAGE_CONTENT_TYPE = get_args(CHAT_MESSAGE_CONTENT_TYPE)[0]
|
||||
|
||||
CHAT_MESSAGE_CONTENT_TYPE_NAMES = Union[
|
||||
CHAT_MESSAGE_CONTENT_TYPE, OPENAI_CHAT_MESSAGE_CONTENT_TYPE, AZURE_CHAT_MESSAGE_CONTENT_TYPE
|
||||
]
|
||||
Reference in New Issue
Block a user