Skip to content

Commit 844658a

Browse files
authored
Add agent tools tests (#44125)
Add agent tools tests for azure-ai-projects Introduces extensive test coverage for agent tools functionality, validating various tool types and their combinations across different scenarios. - Individual tool tests: file search, code interpreter, function tools, AI search, web search, bing grounding, MCP, and image generation - Multi-tool integration tests: combinations of file search, code interpreter, and functions - Conversation-based tool tests: multi-turn interactions with various tools - Async test support: parallel execution testing for AI search - tests/agents/tools/test_agent_*.py: Individual tool validation - tests/agents/tools/multitool/*: Multi-tool integration scenarios - Image model deployment is now configurable via AZURE_AI_PROJECTS_TESTS_IMAGE_MODEL_DEPLOYMENT_NAME
1 parent 2fa7519 commit 844658a

20 files changed

+4113
-1
lines changed

sdk/ai/azure-ai-projects/.env.template

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ AZURE_AI_PROJECTS_TESTS_CONTAINER_PROJECT_ENDPOINT=
4444
AZURE_AI_PROJECTS_TESTS_CONTAINER_APP_RESOURCE_ID=
4545
AZURE_AI_PROJECTS_TESTS_CONTAINER_INGRESS_SUBDOMAIN_SUFFIX=
4646

47+
# Connection IDs and settings used in agent tool tests
48+
AZURE_AI_PROJECTS_TESTS_BING_PROJECT_CONNECTION_ID=
49+
AZURE_AI_PROJECTS_TESTS_AI_SEARCH_PROJECT_CONNECTION_ID=
50+
AZURE_AI_PROJECTS_TESTS_AI_SEARCH_INDEX_NAME=
51+
AZURE_AI_PROJECTS_TESTS_MCP_PROJECT_CONNECTION_ID=
52+
53+
# Used in Image generation agent tools tests
54+
AZURE_AI_PROJECTS_TESTS_IMAGE_MODEL_DEPLOYMENT_NAME=
55+
4756
# Used in tools
4857
BING_PROJECT_CONNECTION_ID=
4958
MCP_PROJECT_CONNECTION_ID=

sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in
2323
the "Models + endpoints" tab in your Microsoft Foundry project.
2424
3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys
25-
with key equals to "Authorization" and value to be "Bear <your GitHub PAT token>".
25+
with key equals to "Authorization" and value to be "Bearer <your GitHub PAT token>".
2626
Token can be created in https://github.com/settings/personal-access-tokens/new
2727
"""
2828

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# pylint: disable=too-many-lines,line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
# cSpell:disable
7+
8+
"""
9+
Multi-Tool Tests: Code Interpreter + Function Tool
10+
11+
Tests various scenarios using an agent with Code Interpreter and Function Tool.
12+
All tests use the same tool combination but different inputs and workflows.
13+
"""
14+
15+
import os
16+
import json
17+
import pytest
18+
from test_base import TestBase, servicePreparer
19+
from devtools_testutils import is_live_and_not_recording
20+
from azure.ai.projects.models import PromptAgentDefinition, CodeInterpreterTool, CodeInterpreterToolAuto, FunctionTool
21+
from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam
22+
23+
24+
class TestAgentCodeInterpreterAndFunction(TestBase):
25+
"""Tests for agents using Code Interpreter + Function Tool combination."""
26+
27+
@servicePreparer()
28+
@pytest.mark.skipif(
29+
condition=(not is_live_and_not_recording()),
30+
reason="Skipped because we cannot record network calls with OpenAI client",
31+
)
32+
def test_calculate_and_save(self, **kwargs):
33+
"""
34+
Test calculation with Code Interpreter and saving with Function Tool.
35+
"""
36+
37+
model = self.test_agents_params["model_deployment_name"]
38+
39+
# Setup
40+
project_client = self.create_client(operation_group="agents", **kwargs)
41+
openai_client = project_client.get_openai_client()
42+
43+
# Define function tool
44+
func_tool = FunctionTool(
45+
name="save_result",
46+
description="Save analysis result",
47+
parameters={
48+
"type": "object",
49+
"properties": {
50+
"result": {"type": "string", "description": "The result"},
51+
},
52+
"required": ["result"],
53+
"additionalProperties": False,
54+
},
55+
strict=True,
56+
)
57+
58+
# Create agent
59+
agent = project_client.agents.create_version(
60+
agent_name="code-func-agent",
61+
definition=PromptAgentDefinition(
62+
model=model,
63+
instructions="Run calculations and save results.",
64+
tools=[
65+
CodeInterpreterTool(container=CodeInterpreterToolAuto()),
66+
func_tool,
67+
],
68+
),
69+
description="Agent with Code Interpreter and Function Tool.",
70+
)
71+
print(f"Agent created (id: {agent.id})")
72+
73+
# Use the agent
74+
response = openai_client.responses.create(
75+
input="Calculate 5 + 3 and save the result.",
76+
extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
77+
)
78+
print(f"Response received (id: {response.id})")
79+
80+
assert response.id is not None
81+
print("✓ Code Interpreter + Function Tool works!")
82+
83+
# Cleanup
84+
project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
85+
86+
@servicePreparer()
87+
@pytest.mark.skipif(
88+
condition=(not is_live_and_not_recording()),
89+
reason="Skipped because we cannot record network calls with OpenAI client",
90+
)
91+
def test_generate_data_and_report(self, **kwargs):
92+
"""
93+
Test generating data with Code Interpreter and reporting with Function.
94+
"""
95+
96+
model = self.test_agents_params["model_deployment_name"]
97+
98+
# Setup
99+
project_client = self.create_client(operation_group="agents", **kwargs)
100+
openai_client = project_client.get_openai_client()
101+
102+
# Define function tool
103+
report_function = FunctionTool(
104+
name="generate_report",
105+
description="Generate a report with the provided data",
106+
parameters={
107+
"type": "object",
108+
"properties": {
109+
"title": {"type": "string", "description": "Report title"},
110+
"summary": {"type": "string", "description": "Report summary"},
111+
},
112+
"required": ["title", "summary"],
113+
"additionalProperties": False,
114+
},
115+
strict=True,
116+
)
117+
118+
# Create agent
119+
agent = project_client.agents.create_version(
120+
agent_name="code-func-report-agent",
121+
definition=PromptAgentDefinition(
122+
model=model,
123+
instructions="Generate data using code and create reports with the generate_report function.",
124+
tools=[
125+
CodeInterpreterTool(container=CodeInterpreterToolAuto()),
126+
report_function,
127+
],
128+
),
129+
description="Agent for data generation and reporting.",
130+
)
131+
print(f"Agent created (id: {agent.id})")
132+
133+
# Request data generation and report
134+
response = openai_client.responses.create(
135+
input="Generate a list of 10 random numbers between 1 and 100, calculate their average, and create a report.",
136+
extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
137+
)
138+
139+
print(f"Response received (id: {response.id})")
140+
assert response.id is not None
141+
print("✓ Data generation and reporting works!")
142+
143+
# Cleanup
144+
project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# pylint: disable=too-many-lines,line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
# cSpell:disable
7+
8+
"""
9+
Multi-Tool Tests: File Search + Code Interpreter
10+
11+
Tests various scenarios using an agent with File Search and Code Interpreter.
12+
All tests use the same tool combination but different inputs and workflows.
13+
"""
14+
15+
import os
16+
import pytest
17+
from io import BytesIO
18+
from test_base import TestBase, servicePreparer
19+
from devtools_testutils import is_live_and_not_recording
20+
from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool, CodeInterpreterTool, CodeInterpreterToolAuto
21+
22+
23+
class TestAgentFileSearchAndCodeInterpreter(TestBase):
24+
"""Tests for agents using File Search + Code Interpreter combination."""
25+
26+
@servicePreparer()
27+
@pytest.mark.skipif(
28+
condition=(not is_live_and_not_recording()),
29+
reason="Skipped because we cannot record network calls with OpenAI client",
30+
)
31+
def test_find_and_analyze_data(self, **kwargs):
32+
"""
33+
Test finding data with File Search and analyzing with Code Interpreter.
34+
"""
35+
36+
model = self.test_agents_params["model_deployment_name"]
37+
38+
# Setup
39+
project_client = self.create_client(operation_group="agents", **kwargs)
40+
openai_client = project_client.get_openai_client()
41+
42+
# Create data file
43+
txt_content = "Sample data: 10, 20, 30, 40, 50"
44+
vector_store = openai_client.vector_stores.create(name="DataStore")
45+
46+
txt_file = BytesIO(txt_content.encode("utf-8"))
47+
txt_file.name = "data.txt"
48+
49+
file = openai_client.vector_stores.files.upload_and_poll(
50+
vector_store_id=vector_store.id,
51+
file=txt_file,
52+
)
53+
print(f"File uploaded (id: {file.id})")
54+
55+
# Create agent
56+
agent = project_client.agents.create_version(
57+
agent_name="file-search-code-agent",
58+
definition=PromptAgentDefinition(
59+
model=model,
60+
instructions="Find data and analyze it.",
61+
tools=[
62+
FileSearchTool(vector_store_ids=[vector_store.id]),
63+
CodeInterpreterTool(container=CodeInterpreterToolAuto()),
64+
],
65+
),
66+
description="Agent with File Search and Code Interpreter.",
67+
)
68+
print(f"Agent created (id: {agent.id})")
69+
70+
# Use the agent
71+
response = openai_client.responses.create(
72+
input="Find the data file and calculate the average.",
73+
extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
74+
)
75+
print(f"Response received (id: {response.id})")
76+
77+
assert response.id is not None
78+
assert len(response.output_text) > 20
79+
print("✓ File Search + Code Interpreter works!")
80+
81+
# Cleanup
82+
project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
83+
openai_client.vector_stores.delete(vector_store.id)
84+
85+
@servicePreparer()
86+
@pytest.mark.skipif(
87+
condition=(not is_live_and_not_recording()),
88+
reason="Skipped because we cannot record network calls with OpenAI client",
89+
)
90+
def test_analyze_code_file(self, **kwargs):
91+
"""
92+
Test finding code file and analyzing it.
93+
"""
94+
95+
model = self.test_agents_params["model_deployment_name"]
96+
97+
# Setup
98+
project_client = self.create_client(operation_group="agents", **kwargs)
99+
openai_client = project_client.get_openai_client()
100+
101+
# Create Python code file
102+
python_code = """def fibonacci(n):
103+
if n <= 1:
104+
return n
105+
return fibonacci(n-1) + fibonacci(n-2)
106+
107+
result = fibonacci(10)
108+
print(f"Fibonacci(10) = {result}")
109+
"""
110+
111+
vector_store = openai_client.vector_stores.create(name="CodeAnalysisStore")
112+
113+
code_file = BytesIO(python_code.encode("utf-8"))
114+
code_file.name = "fibonacci.py"
115+
116+
file = openai_client.vector_stores.files.upload_and_poll(
117+
vector_store_id=vector_store.id,
118+
file=code_file,
119+
)
120+
print(f"Code file uploaded (id: {file.id})")
121+
122+
# Create agent
123+
agent = project_client.agents.create_version(
124+
agent_name="file-search-code-analysis-agent",
125+
definition=PromptAgentDefinition(
126+
model=model,
127+
instructions="Find code files and analyze them. You can run code to test it.",
128+
tools=[
129+
FileSearchTool(vector_store_ids=[vector_store.id]),
130+
CodeInterpreterTool(container=CodeInterpreterToolAuto()),
131+
],
132+
),
133+
description="Agent for code analysis.",
134+
)
135+
print(f"Agent created (id: {agent.id})")
136+
137+
# Request analysis
138+
response = openai_client.responses.create(
139+
input="Find the fibonacci code and explain what it does. What is the computational complexity?",
140+
extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
141+
)
142+
143+
response_text = response.output_text
144+
print(f"Response: {response_text[:300]}...")
145+
146+
assert len(response_text) > 50
147+
response_lower = response_text.lower()
148+
assert any(
149+
keyword in response_lower for keyword in ["fibonacci", "recursive", "complexity", "exponential"]
150+
), "Expected analysis of fibonacci algorithm"
151+
152+
print("✓ Code file analysis completed")
153+
154+
# Cleanup
155+
project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
156+
openai_client.vector_stores.delete(vector_store.id)

0 commit comments

Comments
 (0)