chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from typing import Any, Generic, Protocol, TypeVar
|
||||
|
||||
from semantic_kernel.agents.agent import Agent, AgentResponseItem, AgentThread
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
DEFAULT_MAX_ATTEMPTS = 3
|
||||
DEFAULT_BACKOFF_SECONDS = 1
|
||||
|
||||
|
||||
class ChatResponseProtocol(Protocol):
|
||||
"""Represents a single response item returned by the agent."""
|
||||
|
||||
@property
|
||||
def message(self) -> ChatMessageContent: ...
|
||||
|
||||
@property
|
||||
def thread(self) -> AgentThread | None: ...
|
||||
|
||||
|
||||
class ChatAgentProtocol(Protocol):
|
||||
"""Protocol describing the common agent interface used by the tests."""
|
||||
|
||||
async def get_response(
|
||||
self, messages: str | list[str] | None, thread: object | None = None
|
||||
) -> ChatResponseProtocol: ...
|
||||
|
||||
def invoke(
|
||||
self, messages: str | list[str] | None, thread: object | None = None
|
||||
) -> AsyncIterator[ChatResponseProtocol]: ...
|
||||
|
||||
def invoke_stream(
|
||||
self, messages: str | list[str] | None, thread: object | None = None
|
||||
) -> AsyncIterator[ChatResponseProtocol]: ...
|
||||
|
||||
|
||||
TAgent = TypeVar("TAgent", bound=ChatAgentProtocol)
|
||||
|
||||
|
||||
async def run_with_retry(
|
||||
coro: Callable[..., Awaitable[Any]],
|
||||
*args,
|
||||
attempts: int = DEFAULT_MAX_ATTEMPTS,
|
||||
backoff_seconds: float = DEFAULT_BACKOFF_SECONDS,
|
||||
**kwargs,
|
||||
) -> AgentResponseItem[ChatMessageContent]:
|
||||
"""
|
||||
Execute an async callable with retry/backoff logic.
|
||||
|
||||
Args:
|
||||
coro: The async function to call
|
||||
args: Positional args to pass to the function
|
||||
attempts: How many times to attempt before giving up
|
||||
backoff_seconds: The initial backoff in seconds, doubled after each failure
|
||||
kwargs: Keyword args to pass to the function
|
||||
|
||||
Returns:
|
||||
Whatever the async function returns
|
||||
|
||||
Raises:
|
||||
Exception: If the function fails after the specified number of attempts
|
||||
"""
|
||||
delay = backoff_seconds
|
||||
for attempt in range(1, attempts + 1):
|
||||
try:
|
||||
return await coro(*args, **kwargs)
|
||||
except Exception:
|
||||
if attempt == attempts:
|
||||
raise
|
||||
await asyncio.sleep(delay)
|
||||
delay *= 2
|
||||
raise RuntimeError("Unexpected error: run_with_retry exit.")
|
||||
|
||||
|
||||
class AgentTestBase(Generic[TAgent]):
|
||||
"""Common test base that wraps all agent invocation patterns with retry logic.
|
||||
|
||||
Each integration test can inherit from this or use its methods directly.
|
||||
"""
|
||||
|
||||
async def get_response_with_retry(
|
||||
self,
|
||||
agent: Agent,
|
||||
messages: str | list[str] | None,
|
||||
thread: Any | None = None,
|
||||
attempts: int = DEFAULT_MAX_ATTEMPTS,
|
||||
backoff_seconds: float = DEFAULT_BACKOFF_SECONDS,
|
||||
) -> AgentResponseItem[ChatMessageContent]:
|
||||
"""Wraps agent.get_response(...) in run_with_retry."""
|
||||
return await run_with_retry(
|
||||
agent.get_response, messages=messages, thread=thread, attempts=attempts, backoff_seconds=backoff_seconds
|
||||
)
|
||||
|
||||
async def get_invoke_with_retry(
|
||||
self,
|
||||
agent: Any,
|
||||
messages: str | list[str] | None,
|
||||
thread: Any | None = None,
|
||||
attempts: int = DEFAULT_MAX_ATTEMPTS,
|
||||
backoff_seconds: float = DEFAULT_BACKOFF_SECONDS,
|
||||
) -> list[AgentResponseItem[ChatMessageContent]]:
|
||||
"""Wraps agent.invoke(...) in run_with_retry.
|
||||
|
||||
Collects generator results in a list before returning them.
|
||||
"""
|
||||
return await run_with_retry(
|
||||
self._collect_from_invoke,
|
||||
agent,
|
||||
messages,
|
||||
thread=thread,
|
||||
attempts=attempts,
|
||||
backoff_seconds=backoff_seconds,
|
||||
)
|
||||
|
||||
async def get_invoke_stream_with_retry(
|
||||
self,
|
||||
agent: Any,
|
||||
messages: str | list[str] | None,
|
||||
thread: Any | None = None,
|
||||
attempts: int = DEFAULT_MAX_ATTEMPTS,
|
||||
backoff_seconds: float = DEFAULT_BACKOFF_SECONDS,
|
||||
) -> list[AgentResponseItem[ChatMessageContent]]:
|
||||
"""Wraps agent.invoke_stream(...) in run_with_retry.
|
||||
|
||||
Collects streaming results in a list before returning them."""
|
||||
return await run_with_retry(
|
||||
self._collect_from_invoke_stream,
|
||||
agent,
|
||||
messages,
|
||||
thread=thread,
|
||||
attempts=attempts,
|
||||
backoff_seconds=backoff_seconds,
|
||||
)
|
||||
|
||||
async def _collect_from_invoke(
|
||||
self, agent: Agent, messages: str | list[str] | None, thread: Any | None = None
|
||||
) -> list[AgentResponseItem[ChatMessageContent]]:
|
||||
results: list[AgentResponseItem[ChatMessageContent]] = []
|
||||
async for response in agent.invoke(messages=messages, thread=thread):
|
||||
results.append(response)
|
||||
return results
|
||||
|
||||
async def _collect_from_invoke_stream(
|
||||
self, agent: Agent, messages: str | list[str] | None, thread: Any | None = None
|
||||
) -> list[AgentResponseItem[ChatMessageContent]]:
|
||||
results: list[AgentResponseItem[ChatMessageContent]] = []
|
||||
async for response in agent.invoke_stream(messages=messages, thread=thread):
|
||||
results.append(response)
|
||||
return results
|
||||
@@ -0,0 +1,278 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
import pytest
|
||||
from azure.ai.agents.models import CodeInterpreterTool, FileInfo, FileSearchTool
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingChatMessageContent
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from tests.integration.agents.agent_test_base import AgentTestBase
|
||||
|
||||
|
||||
class WeatherPlugin:
|
||||
"""Mock weather plugin."""
|
||||
|
||||
@kernel_function(description="Get real-time weather information.")
|
||||
def current_weather(self, location: Annotated[str, "The location to get the weather"]) -> str:
|
||||
"""Returns the current weather."""
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
class TestAzureAIAgentIntegration:
|
||||
@pytest.fixture
|
||||
async def azureai_agent(self, request):
|
||||
ai_agent_settings = AzureAIAgentSettings()
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
tools, tool_resources, plugins = [], {}, []
|
||||
|
||||
params = getattr(request, "param", {})
|
||||
if params.get("enable_code_interpreter"):
|
||||
ci_tool = CodeInterpreterTool()
|
||||
tools.extend(ci_tool.definitions)
|
||||
tool_resources.update(ci_tool.resources)
|
||||
|
||||
if params.get("enable_file_search"):
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||
"resources",
|
||||
"employees.pdf",
|
||||
)
|
||||
file: FileInfo = await client.agents.files.upload_and_poll(
|
||||
file_path=pdf_file_path, purpose="assistants"
|
||||
)
|
||||
vector_store = await client.agents.vector_stores.create_and_poll(
|
||||
file_ids=[file.id], name="my_vectorstore"
|
||||
)
|
||||
fs_tool = FileSearchTool(vector_store_ids=[vector_store.id])
|
||||
tools.extend(fs_tool.definitions)
|
||||
tool_resources.update(fs_tool.resources)
|
||||
|
||||
if params.get("enable_kernel_function"):
|
||||
plugins.append(WeatherPlugin())
|
||||
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=ai_agent_settings.model_deployment_name,
|
||||
tools=tools,
|
||||
tool_resources=tool_resources,
|
||||
name="SKPythonIntegrationTestAgent",
|
||||
instructions="You are a helpful assistant that help users with their questions.",
|
||||
)
|
||||
|
||||
azureai_agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
plugins=plugins,
|
||||
)
|
||||
|
||||
yield azureai_agent # yield agent for test method to use
|
||||
|
||||
# cleanup
|
||||
await azureai_agent.client.agents.delete_agent(azureai_agent.id)
|
||||
|
||||
async def test_get_response(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test get response of the agent."""
|
||||
response = await agent_test_base.get_response_with_retry(azureai_agent, messages="Hello")
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
assert "thread_id" in response.message.metadata
|
||||
assert "run_id" in response.message.metadata
|
||||
|
||||
async def test_get_response_with_thread(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test get response of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
azureai_agent, messages=user_message, thread=thread
|
||||
)
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
async def test_invoke(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(azureai_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
async def test_invoke_with_thread(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_with_retry(azureai_agent, messages=user_message, thread=thread)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
async def test_invoke_stream(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke stream of the agent."""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(azureai_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_invoke_stream_with_thread(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke stream of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
azureai_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_code_interpreter_get_response(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = """
|
||||
Using Python, sum the number of animals for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
response = await agent_test_base.get_response_with_retry(azureai_agent, messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_code_interpreter_invoke(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = """
|
||||
Using Python, sum the number of animals for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
responses = await agent_test_base.get_invoke_with_retry(azureai_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_code_interpreter_invoke_stream(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = """
|
||||
Using Python, sum the number of animals for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(azureai_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_file_search": True}], indirect=True)
|
||||
async def test_file_search_get_response(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
response = await agent_test_base.get_response_with_retry(azureai_agent, messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_file_search": True}], indirect=True)
|
||||
async def test_file_search_invoke(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_with_retry(azureai_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_file_search": True}], indirect=True)
|
||||
async def test_file_search_invoke_stream(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(azureai_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_kernel_function": True}], indirect=True)
|
||||
async def test_function_calling_get_response(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling."""
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
azureai_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_kernel_function": True}], indirect=True)
|
||||
async def test_function_calling_invoke(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
azureai_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize("azureai_agent", [{"enable_kernel_function": True}], indirect=True)
|
||||
async def test_function_calling_stream(self, azureai_agent: AzureAIAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling streaming."""
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
azureai_agent, messages="What is the weather in Seattle?"
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert all(isinstance(item, StreamingTextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert "sunny" in full_message
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
from semantic_kernel.kernel import Kernel
|
||||
|
||||
|
||||
class WeatherPlugin:
|
||||
"""Mock weather plugin."""
|
||||
|
||||
@kernel_function(description="Get real-time weather information.")
|
||||
def current(self, location: Annotated[str, "The location to get the weather"]) -> str:
|
||||
"""Returns the current weather."""
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kernel_with_dummy_function() -> Kernel:
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(WeatherPlugin(), plugin_name="weather")
|
||||
|
||||
return kernel
|
||||
@@ -0,0 +1,141 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.agents.bedrock.bedrock_agent import BedrockAgent
|
||||
from semantic_kernel.contents.binary_content import BinaryContent
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
||||
from semantic_kernel.contents.utils.author_role import AuthorRole
|
||||
|
||||
|
||||
class TestBedrockAgentIntegration:
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_and_teardown(self, request):
|
||||
"""Setup and teardown for the test.
|
||||
|
||||
This is run for each test function, i.e. each test function will have its own instance of the agent.
|
||||
"""
|
||||
try:
|
||||
self.bedrock_agent = await BedrockAgent.create_and_prepare_agent(
|
||||
f"semantic-kernel-integration-test-agent-{uuid.uuid4()}",
|
||||
"You are a helpful assistant that help users with their questions.",
|
||||
)
|
||||
if hasattr(request, "param"):
|
||||
if "enable_code_interpreter" in request.param:
|
||||
await self.bedrock_agent.create_code_interpreter_action_group()
|
||||
if "kernel" in request.param:
|
||||
self.bedrock_agent.kernel = request.getfixturevalue(request.param.get("kernel"))
|
||||
if "enable_kernel_function" in request.param:
|
||||
await self.bedrock_agent.create_kernel_function_action_group()
|
||||
except Exception as e:
|
||||
pytest.fail("Failed to create agent")
|
||||
raise e
|
||||
# Yield control to the test
|
||||
yield
|
||||
# Clean up
|
||||
try:
|
||||
await self.bedrock_agent.delete_agent()
|
||||
except Exception as e:
|
||||
pytest.fail(f"Failed to delete agent: {e}")
|
||||
raise e
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invoke(self):
|
||||
"""Test invoke of the agent."""
|
||||
async for response in self.bedrock_agent.invoke(messages="Hello"):
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invoke_stream(self):
|
||||
"""Test invoke stream of the agent."""
|
||||
async for response in self.bedrock_agent.invoke_stream(messages="Hello"):
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("setup_and_teardown", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_code_interpreter(self):
|
||||
"""Test code interpreter."""
|
||||
input_text = """
|
||||
Create a bar chart for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
binary_item: BinaryContent | None = None
|
||||
async for response in self.bedrock_agent.invoke(messages=input_text):
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
if not binary_item:
|
||||
binary_item = next((item for item in response.message.items if isinstance(item, BinaryContent)), None)
|
||||
|
||||
assert binary_item
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("setup_and_teardown", [{"enable_code_interpreter": True}], indirect=True)
|
||||
async def test_code_interpreter_stream(self):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = """
|
||||
Create a bar chart for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
binary_item: BinaryContent | None = None
|
||||
async for response in self.bedrock_agent.invoke_stream(messages=input_text):
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
binary_item = next((item for item in response.message.items if isinstance(item, BinaryContent)), None)
|
||||
assert binary_item
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"setup_and_teardown",
|
||||
[
|
||||
{
|
||||
"enable_kernel_function": True,
|
||||
"kernel": "kernel_with_dummy_function",
|
||||
},
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_function_calling(self):
|
||||
"""Test function calling."""
|
||||
async for response in self.bedrock_agent.invoke(
|
||||
messages="What is the weather in Seattle?",
|
||||
):
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"setup_and_teardown",
|
||||
[
|
||||
{
|
||||
"enable_kernel_function": True,
|
||||
"kernel": "kernel_with_dummy_function",
|
||||
},
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_function_calling_stream(self):
|
||||
"""Test function calling streaming."""
|
||||
full_message: str = ""
|
||||
async for response in self.bedrock_agent.invoke_stream(
|
||||
messages="What is the weather in Seattle?",
|
||||
):
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert "sunny" in full_message
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import pytest
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingChatMessageContent
|
||||
from semantic_kernel.contents.image_content import ImageContent
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from tests.integration.agents.agent_test_base import AgentTestBase
|
||||
|
||||
|
||||
class WeatherPlugin:
|
||||
"""A sample Mock weather plugin."""
|
||||
|
||||
@kernel_function(description="Get real-time weather information.")
|
||||
def current_weather(self, location: Annotated[str, "The location to get the weather"]) -> str:
|
||||
"""Returns the current weather."""
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
class TestChatCompletionAgentIntegration:
|
||||
@pytest.fixture(params=["azure", "openai"])
|
||||
async def chat_completion_agent(self, request):
|
||||
raw_param = request.param
|
||||
|
||||
if isinstance(raw_param, str):
|
||||
agent_service, params = raw_param, {}
|
||||
elif isinstance(raw_param, tuple) and len(raw_param) == 2:
|
||||
agent_service, params = raw_param
|
||||
else:
|
||||
raise ValueError(f"Unsupported param format: {raw_param}")
|
||||
|
||||
plugins = []
|
||||
|
||||
service = (
|
||||
AzureChatCompletion(credential=AzureCliCredential()) if agent_service == "azure" else OpenAIChatCompletion()
|
||||
)
|
||||
|
||||
if params.get("enable_kernel_function"):
|
||||
plugins.append(WeatherPlugin())
|
||||
|
||||
agent = ChatCompletionAgent(
|
||||
service=service,
|
||||
name="SKPythonIntegrationTestChatCompletionAgent",
|
||||
instructions="You are a helpful assistant that help users with their questions.",
|
||||
plugins=plugins,
|
||||
)
|
||||
|
||||
yield agent # yield agent for test method to use
|
||||
|
||||
# region Simple 'Hello' messages tests
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response(self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase):
|
||||
"""Test get response of the agent."""
|
||||
response = await agent_test_base.get_response_with_retry(chat_completion_agent, messages="Hello")
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response_with_thread(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test get response of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
chat_completion_agent,
|
||||
messages=user_message,
|
||||
thread=thread,
|
||||
)
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke(self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(chat_completion_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
usage: CompletionUsage = CompletionUsage()
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
if response.metadata.get("usage"):
|
||||
usage += response.metadata["usage"]
|
||||
assert usage.prompt_tokens > 0
|
||||
assert usage.completion_tokens > 0
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_with_thread(self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
chat_completion_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream(self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke stream of the agent."""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(chat_completion_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
usage: CompletionUsage = CompletionUsage()
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
if response.metadata.get("usage"):
|
||||
usage += response.metadata["usage"]
|
||||
assert usage.prompt_tokens > 0
|
||||
assert usage.completion_tokens > 0
|
||||
|
||||
@pytest.mark.parametrize("chat_completion_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream_with_thread(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test invoke stream of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
chat_completion_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
# endregion
|
||||
|
||||
# region Function calling tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_completion_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["chat_completion_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_get_response(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling."""
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
chat_completion_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_completion_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["chat_completion_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_invoke(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
chat_completion_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_completion_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["chat_completion_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_stream(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling streaming."""
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
chat_completion_agent, messages="What is the weather in Seattle?"
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert all(isinstance(item, StreamingTextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert "sunny" in full_message
|
||||
|
||||
# endregion
|
||||
|
||||
# region Image Content tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_completion_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
pytest.param(
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
marks=pytest.mark.xfail(reason="OpenAI service raise error for downloading image from URL"),
|
||||
),
|
||||
],
|
||||
indirect=["chat_completion_agent"],
|
||||
ids=["azure-image-content-streaming", "openai-image-content-streaming"],
|
||||
)
|
||||
async def test_image_content_stream(
|
||||
self, chat_completion_agent: ChatCompletionAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling streaming."""
|
||||
IMAGE_URI = (
|
||||
"https://raw.githubusercontent.com/microsoft/semantic-kernel/main/python/tests/assets/sample_image.jpg"
|
||||
)
|
||||
image_content_remote = ImageContent(uri=IMAGE_URI)
|
||||
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
chat_completion_agent,
|
||||
messages=ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[TextContent(text="What is in this image?"), image_content_remote],
|
||||
),
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert all(isinstance(item, StreamingTextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert full_message is not None
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.integration.agents.agent_test_base import AgentTestBase, ChatAgentProtocol
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_test_base() -> AgentTestBase[ChatAgentProtocol]:
|
||||
"""Provides a single AgentTestBase instance that all tests can use.
|
||||
|
||||
Typed as a Generic over any ChatAgentProtocol.
|
||||
"""
|
||||
return AgentTestBase()
|
||||
+417
@@ -0,0 +1,417 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
import pytest
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAssistantAgent, OpenAIAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings, OpenAISettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingChatMessageContent
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from tests.integration.agents.agent_test_base import AgentTestBase
|
||||
|
||||
|
||||
class WeatherPlugin:
|
||||
"""A sample Mock weather plugin."""
|
||||
|
||||
@kernel_function(description="Get real-time weather information.")
|
||||
def current_weather(self, location: Annotated[str, "The location to get the weather"]) -> str:
|
||||
"""Returns the current weather."""
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
class TestOpenAIAssistantAgentIntegration:
|
||||
@pytest.fixture(params=["azure", "openai"])
|
||||
async def assistant_agent(self, request):
|
||||
raw_param = request.param
|
||||
|
||||
if isinstance(raw_param, str):
|
||||
agent_type, params = raw_param, {}
|
||||
elif isinstance(raw_param, tuple) and len(raw_param) == 2:
|
||||
agent_type, params = raw_param
|
||||
else:
|
||||
raise ValueError(f"Unsupported param format: {raw_param}")
|
||||
|
||||
tools, tool_resources, plugins = [], {}, []
|
||||
|
||||
if agent_type == "azure":
|
||||
client = AzureAssistantAgent.create_client(credential=AzureCliCredential())
|
||||
model = AzureOpenAISettings().chat_deployment_name
|
||||
AgentClass = AzureAssistantAgent
|
||||
else: # agent_type == "openai"
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
model = OpenAISettings().chat_model_id
|
||||
AgentClass = OpenAIAssistantAgent
|
||||
|
||||
if params.get("enable_code_interpreter"):
|
||||
code_interpreter_tool, code_interpreter_tool_resources = (
|
||||
AzureAssistantAgent.configure_code_interpreter_tool()
|
||||
if agent_type == "azure"
|
||||
else OpenAIAssistantAgent.configure_code_interpreter_tool()
|
||||
)
|
||||
tools.extend(code_interpreter_tool)
|
||||
tool_resources.update(code_interpreter_tool_resources)
|
||||
|
||||
if params.get("enable_file_search"):
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "employees.pdf"
|
||||
)
|
||||
with open(pdf_file_path, "rb") as file:
|
||||
file = await client.files.create(file=file, purpose="assistants")
|
||||
vector_store = await client.vector_stores.create(
|
||||
name="assistant_file_search_int_tests",
|
||||
file_ids=[file.id],
|
||||
)
|
||||
file_search_tool, file_search_tool_resources = (
|
||||
AzureAssistantAgent.configure_file_search_tool(vector_store.id)
|
||||
if agent_type == "azure"
|
||||
else OpenAIAssistantAgent.configure_file_search_tool(vector_store.id)
|
||||
)
|
||||
tools.extend(file_search_tool)
|
||||
tool_resources.update(file_search_tool_resources)
|
||||
|
||||
if params.get("enable_kernel_function"):
|
||||
plugins.append(WeatherPlugin())
|
||||
|
||||
definition = await client.beta.assistants.create(
|
||||
model=model,
|
||||
tools=tools,
|
||||
tool_resources=tool_resources,
|
||||
name="SKPythonIntegrationTestAssistantAgent",
|
||||
instructions="You are a helpful assistant that help users with their questions.",
|
||||
)
|
||||
|
||||
agent = AgentClass(
|
||||
client=client,
|
||||
definition=definition,
|
||||
plugins=plugins,
|
||||
)
|
||||
|
||||
yield agent # yield agent for test method to use
|
||||
|
||||
# cleanup
|
||||
await client.beta.assistants.delete(agent.id)
|
||||
|
||||
# region Simple 'Hello' messages tests
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test get response of the agent."""
|
||||
response = await agent_test_base.get_response_with_retry(assistant_agent, messages="Hello")
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
assert "thread_id" in response.message.metadata
|
||||
assert "run_id" in response.message.metadata
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response_with_thread(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test get response of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
assistant_agent, messages=user_message, thread=thread
|
||||
)
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(assistant_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_with_thread(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
assistant_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke stream of the agent."""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(assistant_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("assistant_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream_with_thread(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test invoke stream of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
assistant_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
# endregion
|
||||
|
||||
# region Code interpreter tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
pytest.param(
|
||||
("azure", {"enable_code_interpreter": True}), marks=pytest.mark.xfail(reason="Service outage")
|
||||
),
|
||||
pytest.param(
|
||||
("openai", {"enable_code_interpreter": True}),
|
||||
),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-code-interpreter", "openai-code-interpreter"],
|
||||
)
|
||||
async def test_code_interpreter_get_response(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter."""
|
||||
input_text = """
|
||||
Create a bar chart for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
response = await agent_test_base.get_response_with_retry(assistant_agent, messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
pytest.param(
|
||||
("azure", {"enable_code_interpreter": True}), marks=pytest.mark.xfail(reason="Service outage")
|
||||
),
|
||||
pytest.param(
|
||||
("openai", {"enable_code_interpreter": True}),
|
||||
),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-code-interpreter", "openai-code-interpreter"],
|
||||
)
|
||||
async def test_code_interpreter_invoke(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = """
|
||||
Create a bar chart for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
responses = await agent_test_base.get_invoke_with_retry(assistant_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
pytest.param(
|
||||
("azure", {"enable_code_interpreter": True}), marks=pytest.mark.xfail(reason="Service outage")
|
||||
),
|
||||
pytest.param(
|
||||
("openai", {"enable_code_interpreter": True}),
|
||||
),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-code-interpreter", "openai-code-interpreter"],
|
||||
)
|
||||
async def test_code_interpreter_invoke_stream(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = """
|
||||
Create a bar chart for the following data:
|
||||
Panda 5
|
||||
Tiger 8
|
||||
Lion 3
|
||||
Monkey 6
|
||||
Dolphin 2
|
||||
"""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(assistant_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
# endregion
|
||||
|
||||
# region File search tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-file-search", "openai-file-search"],
|
||||
)
|
||||
async def test_file_search_get_response(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
response = await agent_test_base.get_response_with_retry(assistant_agent, messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-file-search", "openai-file-search"],
|
||||
)
|
||||
async def test_file_search_invoke(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_with_retry(assistant_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-file-search", "openai-file-search"],
|
||||
)
|
||||
async def test_file_search_invoke_stream(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(assistant_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
# endregion
|
||||
|
||||
# region Function calling tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_get_response(
|
||||
self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling."""
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
assistant_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_invoke(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
assistant_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"assistant_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["assistant_agent"],
|
||||
ids=["azure-function-calling", "openai-function-calling"],
|
||||
)
|
||||
async def test_function_calling_stream(self, assistant_agent: OpenAIAssistantAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling streaming."""
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
assistant_agent, messages="What is the weather in Seattle?"
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert all(isinstance(item, StreamingTextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert "sunny" in full_message
|
||||
|
||||
# endregion
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
import pytest
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel.agents import AzureResponsesAgent, OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings, OpenAISettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingChatMessageContent
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from tests.integration.agents.agent_test_base import AgentTestBase
|
||||
|
||||
|
||||
class WeatherPlugin:
|
||||
"""A sample Mock weather plugin."""
|
||||
|
||||
@kernel_function(description="Get real-time weather information.")
|
||||
def current_weather(self, location: Annotated[str, "The location to get the weather"]) -> str:
|
||||
"""Returns the current weather."""
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
class Step(BaseModel):
|
||||
explanation: str
|
||||
output: str
|
||||
|
||||
|
||||
class Reasoning(BaseModel):
|
||||
steps: list[Step]
|
||||
final_answer: str
|
||||
|
||||
|
||||
class TestOpenAIResponsesAgentIntegration:
|
||||
@pytest.fixture(params=["azure", "openai"])
|
||||
async def responses_agent(self, request):
|
||||
raw_param = request.param
|
||||
|
||||
if isinstance(raw_param, str):
|
||||
agent_type, params = raw_param, {}
|
||||
elif isinstance(raw_param, tuple) and len(raw_param) == 2:
|
||||
agent_type, params = raw_param
|
||||
else:
|
||||
raise ValueError(f"Unsupported param format: {raw_param}")
|
||||
|
||||
tools, plugins, text = [], [], None
|
||||
|
||||
if agent_type == "azure":
|
||||
client = AzureResponsesAgent.create_client(credential=AzureCliCredential())
|
||||
model = AzureOpenAISettings().chat_deployment_name
|
||||
AgentClass = AzureResponsesAgent
|
||||
else: # agent_type == "openai"
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
model = OpenAISettings().chat_model_id
|
||||
AgentClass = OpenAIResponsesAgent
|
||||
|
||||
if params.get("enable_web_search"):
|
||||
web_search_tool = OpenAIResponsesAgent.configure_web_search_tool()
|
||||
tools.append(web_search_tool)
|
||||
|
||||
if params.get("enable_structured_outputs"):
|
||||
text = OpenAIResponsesAgent.configure_response_format(Reasoning)
|
||||
|
||||
if params.get("enable_file_search"):
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "employees.pdf"
|
||||
)
|
||||
with open(pdf_file_path, "rb") as file:
|
||||
file = await client.files.create(file=file, purpose="assistants")
|
||||
vector_store = await client.vector_stores.create(
|
||||
name="responses_file_search_int_tests",
|
||||
file_ids=[file.id],
|
||||
)
|
||||
file_search_tool = (
|
||||
AzureResponsesAgent.configure_file_search_tool(vector_store.id)
|
||||
if agent_type == "azure"
|
||||
else OpenAIResponsesAgent.configure_file_search_tool(vector_store.id)
|
||||
)
|
||||
tools.append(file_search_tool)
|
||||
|
||||
if params.get("enable_kernel_function"):
|
||||
plugins.append(WeatherPlugin())
|
||||
|
||||
agent = AgentClass(
|
||||
ai_model_id=model,
|
||||
client=client,
|
||||
name="SKPythonIntegrationTestResponsesAgent",
|
||||
instructions="You are a helpful agent that help users with their questions.",
|
||||
plugins=plugins,
|
||||
tools=tools,
|
||||
text=text,
|
||||
)
|
||||
|
||||
yield agent # yield agent for test method to use
|
||||
|
||||
# region Simple 'Hello' messages tests
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test get response of the agent."""
|
||||
response = await agent_test_base.get_response_with_retry(responses_agent, messages="Hello")
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
assert "thread_id" in response.message.metadata
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_get_response_with_thread(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test get response of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
responses_agent, messages=user_message, thread=thread
|
||||
)
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent."""
|
||||
async for response in responses_agent.invoke(messages="Hello"):
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_with_thread(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
responses_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test invoke stream of the agent."""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(responses_agent, messages="Hello")
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
@pytest.mark.parametrize("responses_agent", ["azure", "openai"], indirect=True, ids=["azure", "openai"])
|
||||
async def test_invoke_stream_with_thread(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test invoke stream of the agent with a thread."""
|
||||
thread = None
|
||||
user_messages = ["Hello, I am John Doe.", "What is my name?"]
|
||||
for user_message in user_messages:
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
responses_agent, messages=user_message, thread=thread
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
thread = response.thread
|
||||
assert thread is not None
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
await thread.delete() if thread else None
|
||||
|
||||
# endregion
|
||||
|
||||
# region Web Search tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
# Azure OpenAI Responses API doesn't yet support the web search tool
|
||||
("openai", {"enable_web_search": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["openai-web-search-get-response"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable when using the web search tool.")
|
||||
async def test_web_search_get_response(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Find articles about the latest AI trends."
|
||||
response = await responses_agent.get_response(messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert response.message.content is not None
|
||||
|
||||
# endregion
|
||||
|
||||
# region File search tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-file-search-get-response", "openai-file-search-get-response"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable and is throwing 500s.")
|
||||
async def test_file_search_get_response(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
response = await agent_test_base.get_response_with_retry(responses_agent, messages=input_text)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-file-search-invoke", "openai-file-search-invoke"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable and is throwing 500s.")
|
||||
async def test_file_search_invoke(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test code interpreter."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_with_retry(responses_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_file_search": True}),
|
||||
("openai", {"enable_file_search": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-file-search-invoke-stream", "openai-file-search-invoke-stream"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable and is throwing 500s.")
|
||||
async def test_file_search_invoke_stream(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test code interpreter streaming."""
|
||||
input_text = "Who is the youngest employee?"
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(responses_agent, messages=input_text)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
|
||||
# endregion
|
||||
|
||||
# region Function calling tests
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-function-calling-get-response", "openai-function-calling-get-response"],
|
||||
)
|
||||
async def test_function_calling_get_response(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test function calling."""
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
responses_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-function-calling-invoke", "openai-function-calling-invoke"],
|
||||
)
|
||||
async def test_function_calling_invoke(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
responses_agent,
|
||||
messages="What is the weather in Seattle?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert all(isinstance(item, TextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert "sunny" in response.message.content
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_kernel_function": True}),
|
||||
("openai", {"enable_kernel_function": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-function-calling-invoke-stream", "openai-function-calling-invoke-stream"],
|
||||
)
|
||||
async def test_function_calling_stream(self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase):
|
||||
"""Test function calling streaming."""
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
responses_agent, messages="What is the weather in Seattle?"
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert all(isinstance(item, StreamingTextContent) for item in response.items)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert "sunny" in full_message
|
||||
|
||||
# endregion
|
||||
|
||||
# region Structured Outputs
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_structured_outputs": True}),
|
||||
("openai", {"enable_structured_outputs": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-structured-outputs-get-response", "openai-structured-outputs-get-response"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable when configuring structured outputs.")
|
||||
async def test_structured_outputs_get_response(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test structured outputs get response."""
|
||||
response = await agent_test_base.get_response_with_retry(
|
||||
responses_agent,
|
||||
messages="how can I solve 8x + 7y = -23, and 4x=12?",
|
||||
)
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert Reasoning.model_validate_json(response.message.content)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_structured_outputs": True}),
|
||||
("openai", {"enable_structured_outputs": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-structured-outputs-invoke", "openai-structured-outputs-invoke"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable when configuring structured outputs.")
|
||||
async def test_structured_outputs_invoke(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test structured outputs invoke."""
|
||||
responses = await agent_test_base.get_invoke_with_retry(
|
||||
responses_agent,
|
||||
messages="how can I solve 8x + 7y = -23, and 4x=12?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, ChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
assert Reasoning.model_validate_json(response.message.content)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"responses_agent",
|
||||
[
|
||||
("azure", {"enable_structured_outputs": True}),
|
||||
("openai", {"enable_structured_outputs": True}),
|
||||
],
|
||||
indirect=["responses_agent"],
|
||||
ids=["azure-structured-outputs-invoke-stream", "openai-structured-outputs-invoke-stream"],
|
||||
)
|
||||
@pytest.mark.xfail(reason="The Responses API is unstable when configuring structured outputs.")
|
||||
async def test_structured_outputs_stream(
|
||||
self, responses_agent: OpenAIResponsesAgent, agent_test_base: AgentTestBase
|
||||
):
|
||||
"""Test structured outputs streaming."""
|
||||
full_message: str = ""
|
||||
responses = await agent_test_base.get_invoke_stream_with_retry(
|
||||
responses_agent,
|
||||
messages="how can I solve 8x + 7y = -23, and 4x=12?",
|
||||
)
|
||||
assert len(responses) > 0
|
||||
for response in responses:
|
||||
assert isinstance(response.message, StreamingChatMessageContent)
|
||||
assert response.message.role == AuthorRole.ASSISTANT
|
||||
full_message += response.message.content
|
||||
assert Reasoning.model_validate_json(full_message)
|
||||
|
||||
# endregion
|
||||
Binary file not shown.
Reference in New Issue
Block a user