diff --git a/python/semantic_kernel/agents/open_ai/assistant_content_generation.py b/python/semantic_kernel/agents/open_ai/assistant_content_generation.py index 872978adbdd4..1c4a79f3e5eb 100644 --- a/python/semantic_kernel/agents/open_ai/assistant_content_generation.py +++ b/python/semantic_kernel/agents/open_ai/assistant_content_generation.py @@ -87,17 +87,36 @@ def get_message_contents(message: "ChatMessageContent") -> list[dict[str, Any]]: for content in message.items: match content: case TextContent(): - contents.append({"type": "text", "text": content.text}) + # Make sure text is a string + final_text = content.text + if not isinstance(final_text, str): + if isinstance(final_text, (list, tuple)): + final_text = " ".join(map(str, final_text)) + else: + final_text = str(final_text) + + contents.append({"type": "text", "text": final_text}) + case ImageContent(): if content.uri: contents.append(content.to_dict()) + case FileReferenceContent(): contents.append({ "type": "image_file", "image_file": {"file_id": content.file_id}, }) + case FunctionResultContent(): - contents.append({"type": "text", "text": content.result}) + final_result = content.result + match final_result: + case str(): + contents.append({"type": "text", "text": final_result}) + case list() | tuple(): + contents.append({"type": "text", "text": " ".join(map(str, final_result))}) + case _: + contents.append({"type": "text", "text": str(final_result)}) + return contents diff --git a/python/semantic_kernel/contents/function_result_content.py b/python/semantic_kernel/contents/function_result_content.py index 821cc46615d1..536fb4ff19ce 100644 --- a/python/semantic_kernel/contents/function_result_content.py +++ b/python/semantic_kernel/contents/function_result_content.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from semantic_kernel.contents.chat_message_content import ChatMessageContent from semantic_kernel.contents.function_call_content import FunctionCallContent + from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.functions.function_result import FunctionResult TAG_CONTENT_MAP = { @@ -157,6 +158,12 @@ def to_chat_message_content(self) -> "ChatMessageContent": return ChatMessageContent(role=AuthorRole.TOOL, items=[self]) + def to_streaming_chat_message_content(self) -> "StreamingChatMessageContent": + """Convert the instance to a StreamingChatMessageContent.""" + from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent + + return StreamingChatMessageContent(role=AuthorRole.TOOL, choice_index=0, items=[self]) + def to_dict(self) -> dict[str, str]: """Convert the instance to a dictionary.""" return { @@ -187,4 +194,12 @@ def serialize_result(self, value: Any) -> str: def __hash__(self) -> int: """Return the hash of the function result content.""" - return hash((self.tag, self.id, self.result, self.name, self.function_name, self.plugin_name, self.encoding)) + return hash(( + self.tag, + self.id, + tuple(self.result) if isinstance(self.result, list) else self.result, + self.name, + self.function_name, + self.plugin_name, + self.encoding, + )) diff --git a/python/semantic_kernel/functions/kernel_function_from_prompt.py b/python/semantic_kernel/functions/kernel_function_from_prompt.py index ecba3e9aa96c..1e301da4fa17 100644 --- a/python/semantic_kernel/functions/kernel_function_from_prompt.py +++ b/python/semantic_kernel/functions/kernel_function_from_prompt.py @@ -6,7 +6,7 @@ from html import unescape from typing import TYPE_CHECKING, Any -import yaml +import yaml # type: ignore from pydantic import Field, ValidationError, model_validator from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase diff --git a/python/tests/unit/agents/test_open_ai_assistant_base.py b/python/tests/unit/agents/test_open_ai_assistant_base.py index 7e0658b252d6..411102270286 100644 --- a/python/tests/unit/agents/test_open_ai_assistant_base.py +++ b/python/tests/unit/agents/test_open_ai_assistant_base.py @@ -1487,6 +1487,8 @@ def test_get_message_contents(azure_openai_assistant_agent: AzureAssistantAgent, ImageContent(role=AuthorRole.ASSISTANT, content="test message", uri="http://image.url"), TextContent(role=AuthorRole.ASSISTANT, text="test message"), FileReferenceContent(role=AuthorRole.ASSISTANT, file_id="test_file_id"), + TextContent(role=AuthorRole.USER, text="test message"), + FunctionResultContent(role=AuthorRole.ASSISTANT, result=["test result"], id="test_id"), ] result = get_message_contents(message) diff --git a/python/tests/unit/contents/test_function_result_content.py b/python/tests/unit/contents/test_function_result_content.py index 4b013d8a83dd..5bb549924d81 100644 --- a/python/tests/unit/contents/test_function_result_content.py +++ b/python/tests/unit/contents/test_function_result_content.py @@ -1,7 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from typing import Any from unittest.mock import Mock import pytest @@ -15,6 +14,26 @@ from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata +class CustomResultClass: + """Custom class for testing.""" + + def __init__(self, result): + self.result = result + + def __str__(self) -> str: + return self.result + + +class CustomObjectWithList: + """Custom class for testing.""" + + def __init__(self, items): + self.items = items + + def __str__(self): + return f"CustomObjectWithList({self.items})" + + def test_init(): frc = FunctionResultContent(id="test", name="test-function", result="test-result", metadata={"test": "test"}) assert frc.name == "test-function" @@ -50,6 +69,11 @@ def test_init_from_names(): ChatMessageContent(role="user", content="Hello world!"), ChatMessageContent(role="user", items=[ImageContent(uri="https://example.com")]), ChatMessageContent(role="user", items=[FunctionResultContent(id="test", name="test", result="Hello world!")]), + [1, 2, 3], + [{"key": "value"}, {"another": "item"}], + {"a", "b"}, + CustomResultClass("test"), + CustomObjectWithList(["one", "two", "three"]), ], ids=[ "str", @@ -60,9 +84,14 @@ def test_init_from_names(): "ChatMessageContent", "ChatMessageContent-ImageContent", "ChatMessageContent-FunctionResultContent", + "list", + "list_of_dicts", + "set", + "CustomResultClass", + "CustomObjectWithList", ], ) -def test_from_fcc_and_result(result: Any): +def test_from_fcc_and_result(result: any): fcc = FunctionCallContent( id="test", name="test-function", arguments='{"input": "world"}', metadata={"test": "test"} )