diff --git a/libs/agno/tests/integration/models/azure/ai_foundry/test_basic.py b/libs/agno/tests/integration/models/azure/ai_foundry/test_basic.py new file mode 100644 index 000000000..a57cd7f8f --- /dev/null +++ b/libs/agno/tests/integration/models/azure/ai_foundry/test_basic.py @@ -0,0 +1,156 @@ +import pytest +from pydantic import BaseModel, Field + +from agno.agent import Agent, RunResponse +from agno.models.azure import AzureAIFoundry +from agno.storage.agent.postgres import PostgresAgentStorage + + +def _assert_metrics(response: RunResponse): + input_tokens = response.metrics.get("input_tokens", []) + output_tokens = response.metrics.get("output_tokens", []) + total_tokens = response.metrics.get("total_tokens", []) + + assert sum(input_tokens) > 0 + assert sum(output_tokens) > 0 + assert sum(total_tokens) > 0 + assert sum(total_tokens) == sum(input_tokens) + sum(output_tokens) + + +def test_basic(): + agent = Agent(model=AzureAIFoundry(id="Phi-4"), markdown=True, telemetry=False, monitoring=False) + + # Print the response in the terminal + response: RunResponse = agent.run("Share a 2 sentence horror story") + + assert response.content is not None + assert len(response.messages) == 3 + assert [m.role for m in response.messages] == ["system", "user", "assistant"] + + _assert_metrics(response) + + +def test_basic_stream(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + instructions="You tell ghost stories", + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = agent.run("Share a 2 sentence horror story", stream=True) + + # Verify it's an iterator + assert hasattr(response_stream, "__iter__") + + responses = list(response_stream) + assert len(responses) > 0 + for response in responses: + assert isinstance(response, RunResponse) + assert response.content is not None + + _assert_metrics(agent.run_response) + + +@pytest.mark.asyncio +async def test_async_basic(): + agent = Agent(model=AzureAIFoundry(id="Phi-4"), markdown=True, telemetry=False, monitoring=False) + + response = await agent.arun("Share a 2 sentence horror story") + + assert response.content is not None + assert len(response.messages) == 3 + assert [m.role for m in response.messages] == ["system", "user", "assistant"] + _assert_metrics(response) + + +@pytest.mark.asyncio +async def test_async_basic_stream(): + agent = Agent(model=AzureAIFoundry(id="Phi-4"), markdown=True, telemetry=False, monitoring=False) + + response_stream = await agent.arun("Share a 2 sentence horror story", stream=True) + + async for response in response_stream: + assert isinstance(response, RunResponse) + assert response.content is not None + + _assert_metrics(agent.run_response) + + +def test_with_memory(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + add_history_to_messages=True, + num_history_responses=5, + markdown=True, + telemetry=False, + monitoring=False, + ) + + # First interaction + response1 = agent.run("My name is John Smith") + assert response1.content is not None + + # Second interaction should remember the name + response2 = agent.run("What's my name?") + assert "John Smith" in response2.content + + # Verify memories were created + assert len(agent.memory.messages) == 5 + assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"] + + # Test metrics structure and types + input_tokens = response2.metrics["input_tokens"] + output_tokens = response2.metrics["output_tokens"] + total_tokens = response2.metrics["total_tokens"] + + assert isinstance(input_tokens[0], int) + assert input_tokens[0] > 0 + assert isinstance(output_tokens[0], int) + assert output_tokens[0] > 0 + assert isinstance(total_tokens[0], int) + assert total_tokens[0] > 0 + assert total_tokens[0] == input_tokens[0] + output_tokens[0] + + +def test_response_model(): + class MovieScript(BaseModel): + title: str = Field(..., description="Movie title") + genre: str = Field(..., description="Movie genre") + plot: str = Field(..., description="Brief plot summary") + + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + response_model=MovieScript, + telemetry=False, + monitoring=False, + ) + + response = agent.run("Create a movie about time travel") + + # Verify structured output + assert isinstance(response.content, MovieScript) + assert response.content.title is not None + assert response.content.genre is not None + assert response.content.plot is not None + + +def test_history(): + db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai" + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url), + add_history_to_messages=True, + telemetry=False, + monitoring=False, + markdown=True, + ) + agent.run("Hello") + assert len(agent.run_response.messages) == 2 + agent.run("Hello 2") + assert len(agent.run_response.messages) == 4 + agent.run("Hello 3") + assert len(agent.run_response.messages) == 6 + agent.run("Hello 4") + assert len(agent.run_response.messages) == 8 diff --git a/libs/agno/tests/integration/models/azure/ai_foundry/test_multimodal.py b/libs/agno/tests/integration/models/azure/ai_foundry/test_multimodal.py new file mode 100644 index 000000000..d334d3f4d --- /dev/null +++ b/libs/agno/tests/integration/models/azure/ai_foundry/test_multimodal.py @@ -0,0 +1,17 @@ +from agno.agent.agent import Agent +from agno.media import Image +from agno.models.azure import AzureAIFoundry + + +def test_image_input(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), markdown=True, telemetry=False, monitoring=False + ) + + response = agent.run( + "Tell me about this image.", + images=[Image(url="https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg")], + ) + + assert "golden" in response.content.lower() + assert "bridge" in response.content.lower() diff --git a/libs/agno/tests/integration/models/azure/ai_foundry/test_tool_use.py b/libs/agno/tests/integration/models/azure/ai_foundry/test_tool_use.py new file mode 100644 index 000000000..8fcb0acf4 --- /dev/null +++ b/libs/agno/tests/integration/models/azure/ai_foundry/test_tool_use.py @@ -0,0 +1,226 @@ +from typing import Optional + +import pytest + +from agno.agent import Agent, RunResponse +from agno.models.azure import AzureAIFoundry +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.tools.exa import ExaTools +from agno.tools.yfinance import YFinanceTools + + +def test_tool_use(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "TSLA" in response.content + + +def test_tool_use_stream(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = agent.run("What is the current price of TSLA?", stream=True) + + responses = [] + tool_call_seen = False + + for chunk in response_stream: + assert isinstance(chunk, RunResponse) + responses.append(chunk) + if chunk.tools: + if any(tc.get("tool_name") for tc in chunk.tools): + tool_call_seen = True + + assert len(responses) > 0 + assert tool_call_seen, "No tool calls observed in stream" + assert any("TSLA" in r.content for r in responses if r.content) + + +@pytest.mark.asyncio +async def test_async_tool_use(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = await agent.arun("What is the current price of TSLA?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages if msg.role == "assistant") + assert response.content is not None + assert "TSLA" in response.content + + +@pytest.mark.asyncio +async def test_async_tool_use_stream(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = await agent.arun("What is the current price of TSLA?", stream=True) + + responses = [] + tool_call_seen = False + + async for chunk in response_stream: + assert isinstance(chunk, RunResponse) + responses.append(chunk) + if chunk.tools: + if any(tc.get("tool_name") for tc in chunk.tools): + tool_call_seen = True + + assert len(responses) > 0 + assert tool_call_seen, "No tool calls observed in stream" + assert any("TSLA" in r.content for r in responses if r.content) + + +def test_parallel_tool_calls(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA and AAPL?") + + # Verify tool usage + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + assert len([call for call in tool_calls if call.get("type", "") == "function"]) == 2 # Total of 2 tool calls made + assert response.content is not None + assert "TSLA" in response.content and "AAPL" in response.content + + +def test_multiple_tool_calls(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[YFinanceTools(), DuckDuckGoTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA and what is the latest news about it?") + + # Verify tool usage + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + assert len([call for call in tool_calls if call.get("type", "") == "function"]) == 2 # Total of 2 tool calls made + assert response.content is not None + assert "TSLA" in response.content and "latest news" in response.content.lower() + + +def test_tool_call_custom_tool_no_parameters(): + def get_the_weather_in_tokyo(): + """ + Get the weather in Tokyo + """ + return "It is currently 70 degrees and cloudy in Tokyo" + + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[get_the_weather_in_tokyo], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the weather in Tokyo?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "70" in response.content + + +def test_tool_call_custom_tool_optional_parameters(): + def get_the_weather(city: Optional[str] = None): + """ + Get the weather in a city + + Args: + city: The city to get the weather for + """ + if city is None: + return "It is currently 70 degrees and cloudy in Tokyo" + else: + return f"It is currently 70 degrees and cloudy in {city}" + + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[get_the_weather], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the weather in Paris?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "70" in response.content + + +def test_tool_call_list_parameters(): + agent = Agent( + model=AzureAIFoundry(id="Phi-4"), + tools=[ExaTools()], + instructions="Use a single tool call if possible", + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run( + "What are the papers at https://arxiv.org/pdf/2307.06435 and https://arxiv.org/pdf/2502.09601 about?" + ) + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + for call in tool_calls: + if call.get("type", "") == "function": + assert call["function"]["name"] == "get_contents" + assert response.content is not None diff --git a/libs/agno/tests/integration/models/azure/openai/test_basic.py b/libs/agno/tests/integration/models/azure/openai/test_basic.py new file mode 100644 index 000000000..d381d04b3 --- /dev/null +++ b/libs/agno/tests/integration/models/azure/openai/test_basic.py @@ -0,0 +1,156 @@ +import pytest +from pydantic import BaseModel, Field + +from agno.agent import Agent, RunResponse # noqa +from agno.models.azure import AzureOpenAI +from agno.storage.agent.postgres import PostgresAgentStorage + + +def _assert_metrics(response: RunResponse): + input_tokens = response.metrics.get("input_tokens", []) + output_tokens = response.metrics.get("output_tokens", []) + total_tokens = response.metrics.get("total_tokens", []) + + assert sum(input_tokens) > 0 + assert sum(output_tokens) > 0 + assert sum(total_tokens) > 0 + assert sum(total_tokens) == sum(input_tokens) + sum(output_tokens) + + +def test_basic(): + agent = Agent(model=AzureOpenAI(id="gpt-4o-mini"), markdown=True, telemetry=False, monitoring=False) + + # Print the response in the terminal + response: RunResponse = agent.run("Share a 2 sentence horror story") + + assert response.content is not None + assert len(response.messages) == 3 + assert [m.role for m in response.messages] == ["system", "user", "assistant"] + + _assert_metrics(response) + + +def test_basic_stream(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + instructions="You tell ghost stories", + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = agent.run("Share a 2 sentence horror story", stream=True) + + # Verify it's an iterator + assert hasattr(response_stream, "__iter__") + + responses = list(response_stream) + assert len(responses) > 0 + for response in responses: + assert isinstance(response, RunResponse) + assert response.content is not None + + _assert_metrics(agent.run_response) + + +@pytest.mark.asyncio +async def test_async_basic(): + agent = Agent(model=AzureOpenAI(id="gpt-4o-mini"), markdown=True, telemetry=False, monitoring=False) + + response = await agent.arun("Share a 2 sentence horror story") + + assert response.content is not None + assert len(response.messages) == 3 + assert [m.role for m in response.messages] == ["system", "user", "assistant"] + _assert_metrics(response) + + +@pytest.mark.asyncio +async def test_async_basic_stream(): + agent = Agent(model=AzureOpenAI(id="gpt-4o-mini"), markdown=True, telemetry=False, monitoring=False) + + response_stream = await agent.arun("Share a 2 sentence horror story", stream=True) + + async for response in response_stream: + assert isinstance(response, RunResponse) + assert response.content is not None + + _assert_metrics(agent.run_response) + + +def test_with_memory(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + add_history_to_messages=True, + num_history_responses=5, + markdown=True, + telemetry=False, + monitoring=False, + ) + + # First interaction + response1 = agent.run("My name is John Smith") + assert response1.content is not None + + # Second interaction should remember the name + response2 = agent.run("What's my name?") + assert "John Smith" in response2.content + + # Verify memories were created + assert len(agent.memory.messages) == 5 + assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"] + + # Test metrics structure and types + input_tokens = response2.metrics["input_tokens"] + output_tokens = response2.metrics["output_tokens"] + total_tokens = response2.metrics["total_tokens"] + + assert isinstance(input_tokens[0], int) + assert input_tokens[0] > 0 + assert isinstance(output_tokens[0], int) + assert output_tokens[0] > 0 + assert isinstance(total_tokens[0], int) + assert total_tokens[0] > 0 + assert total_tokens[0] == input_tokens[0] + output_tokens[0] + + +def test_response_model(): + class MovieScript(BaseModel): + title: str = Field(..., description="Movie title") + genre: str = Field(..., description="Movie genre") + plot: str = Field(..., description="Brief plot summary") + + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + response_model=MovieScript, + telemetry=False, + monitoring=False, + ) + + response = agent.run("Create a movie about time travel") + + # Verify structured output + assert isinstance(response.content, MovieScript) + assert response.content.title is not None + assert response.content.genre is not None + assert response.content.plot is not None + + +def test_history(): + db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai" + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url), + add_history_to_messages=True, + telemetry=False, + monitoring=False, + markdown=True, + ) + agent.run("Hello") + assert len(agent.run_response.messages) == 2 + agent.run("Hello 2") + assert len(agent.run_response.messages) == 4 + agent.run("Hello 3") + assert len(agent.run_response.messages) == 6 + agent.run("Hello 4") + assert len(agent.run_response.messages) == 8 diff --git a/libs/agno/tests/integration/models/azure/openai/test_multimodal.py b/libs/agno/tests/integration/models/azure/openai/test_multimodal.py new file mode 100644 index 000000000..4d1c02168 --- /dev/null +++ b/libs/agno/tests/integration/models/azure/openai/test_multimodal.py @@ -0,0 +1,17 @@ +from agno.agent.agent import Agent +from agno.media import Image +from agno.models.azure import AzureOpenAI + + +def test_image_input(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), markdown=True, telemetry=False, monitoring=False + ) + + response = agent.run( + "Tell me about this image.", + images=[Image(url="https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg")], + ) + + assert "golden" in response.content.lower() + assert "bridge" in response.content.lower() diff --git a/libs/agno/tests/integration/models/azure/openai/test_tool_use.py b/libs/agno/tests/integration/models/azure/openai/test_tool_use.py new file mode 100644 index 000000000..3df390580 --- /dev/null +++ b/libs/agno/tests/integration/models/azure/openai/test_tool_use.py @@ -0,0 +1,226 @@ +from typing import Optional + +import pytest + +from agno.agent import Agent, RunResponse # noqa +from agno.models.azure import AzureOpenAI +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.tools.exa import ExaTools +from agno.tools.yfinance import YFinanceTools + + +def test_tool_use(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "TSLA" in response.content + + +def test_tool_use_stream(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = agent.run("What is the current price of TSLA?", stream=True) + + responses = [] + tool_call_seen = False + + for chunk in response_stream: + assert isinstance(chunk, RunResponse) + responses.append(chunk) + if chunk.tools: + if any(tc.get("tool_name") for tc in chunk.tools): + tool_call_seen = True + + assert len(responses) > 0 + assert tool_call_seen, "No tool calls observed in stream" + assert any("TSLA" in r.content for r in responses if r.content) + + +@pytest.mark.asyncio +async def test_async_tool_use(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = await agent.arun("What is the current price of TSLA?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages if msg.role == "assistant") + assert response.content is not None + assert "TSLA" in response.content + + +@pytest.mark.asyncio +async def test_async_tool_use_stream(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response_stream = await agent.arun("What is the current price of TSLA?", stream=True) + + responses = [] + tool_call_seen = False + + async for chunk in response_stream: + assert isinstance(chunk, RunResponse) + responses.append(chunk) + if chunk.tools: + if any(tc.get("tool_name") for tc in chunk.tools): + tool_call_seen = True + + assert len(responses) > 0 + assert tool_call_seen, "No tool calls observed in stream" + assert any("TSLA" in r.content for r in responses if r.content) + + +def test_parallel_tool_calls(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA and AAPL?") + + # Verify tool usage + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + assert len([call for call in tool_calls if call.get("type", "") == "function"]) == 2 # Total of 2 tool calls made + assert response.content is not None + assert "TSLA" in response.content and "AAPL" in response.content + + +def test_multiple_tool_calls(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[YFinanceTools(), DuckDuckGoTools()], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the current price of TSLA and what is the latest news about it?") + + # Verify tool usage + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + assert len([call for call in tool_calls if call.get("type", "") == "function"]) == 2 # Total of 2 tool calls made + assert response.content is not None + assert "TSLA" in response.content and "latest news" in response.content.lower() + + +def test_tool_call_custom_tool_no_parameters(): + def get_the_weather_in_tokyo(): + """ + Get the weather in Tokyo + """ + return "It is currently 70 degrees and cloudy in Tokyo" + + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[get_the_weather_in_tokyo], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the weather in Tokyo?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "70" in response.content + + +def test_tool_call_custom_tool_optional_parameters(): + def get_the_weather(city: Optional[str] = None): + """ + Get the weather in a city + + Args: + city: The city to get the weather for + """ + if city is None: + return "It is currently 70 degrees and cloudy in Tokyo" + else: + return f"It is currently 70 degrees and cloudy in {city}" + + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[get_the_weather], + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run("What is the weather in Paris?") + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + assert response.content is not None + assert "70" in response.content + + +def test_tool_call_list_parameters(): + agent = Agent( + model=AzureOpenAI(id="gpt-4o-mini"), + tools=[ExaTools()], + instructions="Use a single tool call if possible", + show_tool_calls=True, + markdown=True, + telemetry=False, + monitoring=False, + ) + + response = agent.run( + "What are the papers at https://arxiv.org/pdf/2307.06435 and https://arxiv.org/pdf/2502.09601 about?" + ) + + # Verify tool usage + assert any(msg.tool_calls for msg in response.messages) + tool_calls = [] + for msg in response.messages: + if msg.tool_calls: + tool_calls.extend(msg.tool_calls) + for call in tool_calls: + if call.get("type", "") == "function": + assert call["function"]["name"] == "get_contents" + assert response.content is not None