mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33d3e5bc6 | ||
|
|
097095c1ea | ||
|
|
0edd5f1b32 | ||
|
|
52589ab474 | ||
|
|
d2de5ba1b5 | ||
|
|
455c28da62 | ||
|
|
7ce27ddda3 | ||
|
|
acf24ea2e4 | ||
|
|
3ab3370a8e | ||
|
|
072123a8f1 | ||
|
|
2e6b999bd2 | ||
|
|
e2eba0bacc | ||
|
|
386e08ed64 | ||
|
|
374526515d | ||
|
|
a6e0ab5603 | ||
|
|
f6f87477c9 | ||
|
|
9316f2c2f8 | ||
|
|
dc64d63a2a | ||
|
|
733bfb9bfe | ||
|
|
101f50134c |
@@ -157,6 +157,8 @@ jobs:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CHAT_MODEL: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
|
||||
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
||||
OLLAMA_MODEL: qwen2.5:1.5b
|
||||
OLLAMA_EMBEDDING_MODEL: nomic-embed-text
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
@@ -171,6 +173,43 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.UV_PYTHON }}
|
||||
os: ${{ runner.os }}
|
||||
- name: Install Ollama
|
||||
run: curl -fsSL https://ollama.com/install.sh | sh
|
||||
working-directory: .
|
||||
- name: Cache Ollama models
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ollama/models
|
||||
key: ollama-models-qwen2.5-1.5b-nomic-embed-text-v1
|
||||
- name: Start Ollama and pull models
|
||||
run: |
|
||||
# Stop any Ollama instance auto-started by the install script
|
||||
pkill ollama || true
|
||||
sleep 2
|
||||
ollama serve &
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
# Pull models with retry for transient 429 rate limits
|
||||
for model in qwen2.5:1.5b nomic-embed-text; do
|
||||
pulled=false
|
||||
for attempt in 1 2 3; do
|
||||
if ollama pull "$model"; then
|
||||
pulled=true
|
||||
break
|
||||
fi
|
||||
echo "Retry $attempt for $model (waiting 15s)..."
|
||||
sleep 15
|
||||
done
|
||||
if [ "$pulled" != "true" ]; then
|
||||
echo "ERROR: Failed to pull $model after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
working-directory: .
|
||||
- name: Start local MCP server
|
||||
id: local-mcp
|
||||
uses: ./.github/actions/setup-local-mcp-server
|
||||
@@ -271,7 +310,7 @@ jobs:
|
||||
-m integration
|
||||
-n logical --dist worksteal
|
||||
-x
|
||||
--timeout=360 --session-timeout=900 --timeout_method thread
|
||||
--timeout=480 --session-timeout=900 --timeout_method thread
|
||||
--retries 2 --retry-delay 5
|
||||
--junitxml=pytest.xml
|
||||
- name: Upload test results
|
||||
@@ -435,9 +474,9 @@ jobs:
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Flaky test trend report (aggregates per-job JUnit XML results)
|
||||
python-flaky-test-report:
|
||||
name: Flaky Test Report
|
||||
# Integration test trend report (aggregates per-job JUnit XML results)
|
||||
python-integration-test-report:
|
||||
name: Integration Test Report
|
||||
if: >
|
||||
always() &&
|
||||
(contains(join(needs.*.result, ','), 'success') ||
|
||||
@@ -471,36 +510,36 @@ jobs:
|
||||
with:
|
||||
pattern: test-results-*
|
||||
path: test-results/
|
||||
- name: Restore flaky report history cache
|
||||
- name: Restore report history cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: python/flaky-report-history.json
|
||||
key: flaky-report-history-integration-${{ github.run_id }}
|
||||
path: python/integration-report-history.json
|
||||
key: integration-report-history-integration-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
flaky-report-history-integration-
|
||||
integration-report-history-integration-
|
||||
- name: Generate trend report
|
||||
run: >
|
||||
uv run python scripts/flaky_report/aggregate.py
|
||||
uv run python scripts/integration_test_report/aggregate.py
|
||||
../test-results/
|
||||
flaky-report-history.json
|
||||
flaky-test-report.md
|
||||
integration-report-history.json
|
||||
integration-test-report.md
|
||||
- name: Post to Job Summary
|
||||
if: always()
|
||||
run: cat flaky-test-report.md >> $GITHUB_STEP_SUMMARY
|
||||
- name: Save flaky report history cache
|
||||
run: cat integration-test-report.md >> $GITHUB_STEP_SUMMARY
|
||||
- name: Save report history cache
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: python/flaky-report-history.json
|
||||
key: flaky-report-history-integration-${{ github.run_id }}
|
||||
path: python/integration-report-history.json
|
||||
key: integration-report-history-integration-${{ github.run_id }}
|
||||
- name: Upload unified trend report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flaky-test-report
|
||||
name: integration-test-report
|
||||
path: |
|
||||
python/flaky-test-report.md
|
||||
python/flaky-report-history.json
|
||||
python/integration-test-report.md
|
||||
python/integration-report-history.json
|
||||
|
||||
python-integration-tests-check:
|
||||
if: always()
|
||||
|
||||
@@ -278,6 +278,8 @@ jobs:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CHAT_MODEL: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
|
||||
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
|
||||
OLLAMA_MODEL: qwen2.5:1.5b
|
||||
OLLAMA_EMBEDDING_MODEL: nomic-embed-text
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
@@ -289,6 +291,43 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.UV_PYTHON }}
|
||||
os: ${{ runner.os }}
|
||||
- name: Install Ollama
|
||||
run: curl -fsSL https://ollama.com/install.sh | sh
|
||||
working-directory: .
|
||||
- name: Cache Ollama models
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ollama/models
|
||||
key: ollama-models-qwen2.5-1.5b-nomic-embed-text-v1
|
||||
- name: Start Ollama and pull models
|
||||
run: |
|
||||
# Stop any Ollama instance auto-started by the install script
|
||||
pkill ollama || true
|
||||
sleep 2
|
||||
ollama serve &
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
# Pull models with retry for transient 429 rate limits
|
||||
for model in qwen2.5:1.5b nomic-embed-text; do
|
||||
pulled=false
|
||||
for attempt in 1 2 3; do
|
||||
if ollama pull "$model"; then
|
||||
pulled=true
|
||||
break
|
||||
fi
|
||||
echo "Retry $attempt for $model (waiting 15s)..."
|
||||
sleep 15
|
||||
done
|
||||
if [ "$pulled" != "true" ]; then
|
||||
echo "ERROR: Failed to pull $model after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
working-directory: .
|
||||
- name: Start local MCP server
|
||||
id: local-mcp
|
||||
uses: ./.github/actions/setup-local-mcp-server
|
||||
@@ -403,7 +442,7 @@ jobs:
|
||||
-m integration
|
||||
-n logical --dist worksteal
|
||||
-x
|
||||
--timeout=360 --session-timeout=900 --timeout_method thread
|
||||
--timeout=480 --session-timeout=900 --timeout_method thread
|
||||
--retries 2 --retry-delay 5
|
||||
--junitxml=pytest.xml
|
||||
working-directory: ./python
|
||||
@@ -619,9 +658,9 @@ jobs:
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Flaky test trend report (aggregates per-job JUnit XML results)
|
||||
python-flaky-test-report:
|
||||
name: Flaky Test Report
|
||||
# Integration test trend report (aggregates per-job JUnit XML results)
|
||||
python-integration-test-report:
|
||||
name: Integration Test Report
|
||||
if: >
|
||||
always() &&
|
||||
(contains(join(needs.*.result, ','), 'success') ||
|
||||
@@ -652,36 +691,36 @@ jobs:
|
||||
with:
|
||||
pattern: test-results-*
|
||||
path: test-results/
|
||||
- name: Restore flaky report history cache
|
||||
- name: Restore report history cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: python/flaky-report-history.json
|
||||
key: flaky-report-history-merge-${{ github.run_id }}
|
||||
path: python/integration-report-history.json
|
||||
key: integration-report-history-merge-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
flaky-report-history-merge-
|
||||
integration-report-history-merge-
|
||||
- name: Generate trend report
|
||||
run: >
|
||||
uv run python scripts/flaky_report/aggregate.py
|
||||
uv run python scripts/integration_test_report/aggregate.py
|
||||
../test-results/
|
||||
flaky-report-history.json
|
||||
flaky-test-report.md
|
||||
integration-report-history.json
|
||||
integration-test-report.md
|
||||
- name: Post to Job Summary
|
||||
if: always()
|
||||
run: cat flaky-test-report.md >> $GITHUB_STEP_SUMMARY
|
||||
- name: Save flaky report history cache
|
||||
run: cat integration-test-report.md >> $GITHUB_STEP_SUMMARY
|
||||
- name: Save report history cache
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: python/flaky-report-history.json
|
||||
key: flaky-report-history-merge-${{ github.run_id }}
|
||||
path: python/integration-report-history.json
|
||||
key: integration-report-history-merge-${{ github.run_id }}
|
||||
- name: Upload unified trend report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flaky-test-report
|
||||
name: integration-test-report
|
||||
path: |
|
||||
python/flaky-test-report.md
|
||||
python/flaky-report-history.json
|
||||
python/integration-test-report.md
|
||||
python/integration-report-history.json
|
||||
|
||||
python-integration-tests-check:
|
||||
if: always()
|
||||
|
||||
+3
-5
@@ -26,7 +26,6 @@ pytestmark = [
|
||||
pytest.mark.integration,
|
||||
pytest.mark.sample("03_reliable_streaming"),
|
||||
pytest.mark.usefixtures("function_app_for_test"),
|
||||
pytest.mark.skip(reason="Temp disabled to fix test instability - needs investigation into root cause"),
|
||||
]
|
||||
|
||||
|
||||
@@ -56,12 +55,11 @@ class TestSampleReliableStreaming:
|
||||
# Wait a moment for the agent to start writing to Redis
|
||||
time.sleep(2)
|
||||
|
||||
# Stream response from Redis with shorter timeout
|
||||
# Note: We use text/plain to avoid SSE parsing complexity
|
||||
# Stream response from Redis with longer timeout to account for LLM latency
|
||||
stream_response = requests.get(
|
||||
f"{self.stream_url}/{thread_id}",
|
||||
headers={"Accept": "text/plain"},
|
||||
timeout=30, # Shorter timeout for test
|
||||
timeout=60,
|
||||
)
|
||||
assert stream_response.status_code == 200
|
||||
|
||||
@@ -83,7 +81,7 @@ class TestSampleReliableStreaming:
|
||||
stream_response = requests.get(
|
||||
f"{self.stream_url}/{thread_id}",
|
||||
headers={"Accept": "text/event-stream"},
|
||||
timeout=30, # Shorter timeout
|
||||
timeout=60,
|
||||
)
|
||||
assert stream_response.status_code == 200
|
||||
content_type = stream_response.headers.get("content-type", "")
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestWorkflowParallel:
|
||||
self.base_url = base_url
|
||||
self.helper = sample_helper
|
||||
|
||||
@pytest.mark.skip(reason="Causes timeouts.")
|
||||
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
|
||||
def test_parallel_workflow_document_analysis(self) -> None:
|
||||
"""Test parallel workflow with a standard document."""
|
||||
payload = {
|
||||
@@ -71,7 +71,7 @@ class TestWorkflowParallel:
|
||||
assert status["runtimeStatus"] == "Completed"
|
||||
assert "output" in status
|
||||
|
||||
@pytest.mark.skip(reason="Causes timeouts.")
|
||||
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
|
||||
def test_parallel_workflow_short_document(self) -> None:
|
||||
"""Test parallel workflow with a short document."""
|
||||
payload = {
|
||||
@@ -91,7 +91,7 @@ class TestWorkflowParallel:
|
||||
assert status["runtimeStatus"] == "Completed"
|
||||
assert "output" in status
|
||||
|
||||
@pytest.mark.skip(reason="Causes timeouts.")
|
||||
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
|
||||
def test_parallel_workflow_technical_document(self) -> None:
|
||||
"""Test parallel workflow with a technical document."""
|
||||
payload = {
|
||||
@@ -115,7 +115,7 @@ class TestWorkflowParallel:
|
||||
status = self.helper.wait_for_orchestration_with_output(data["statusQueryGetUri"], max_wait=300)
|
||||
assert status["runtimeStatus"] == "Completed"
|
||||
|
||||
@pytest.mark.skip(reason="Causes timeouts.")
|
||||
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
|
||||
def test_workflow_status_endpoint(self) -> None:
|
||||
"""Test that the workflow status endpoint works correctly."""
|
||||
payload = {
|
||||
|
||||
-1
@@ -52,7 +52,6 @@ class TestMultiAgentOrchestrationConditionals:
|
||||
assert email_agent is not None
|
||||
assert email_agent.name == EMAIL_AGENT_NAME
|
||||
|
||||
@pytest.mark.skip(reason="Consistently fails due to orchestration timeouts - needs investigation")
|
||||
def test_conditional_branching(self):
|
||||
"""Test that conditional branching works correctly."""
|
||||
# Test with obvious spam
|
||||
|
||||
@@ -634,7 +634,6 @@ async def test_foundry_agent_configure_azure_monitor_import_error() -> None:
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_foundry_agent_integration_tests_disabled
|
||||
@pytest.mark.skip(reason="Test agent seems to have disappeared from the test environment; needs investigation.")
|
||||
async def test_foundry_agent_basic_run() -> None:
|
||||
"""Smoke-test FoundryAgent against a real configured agent."""
|
||||
async with FoundryAgent(credential=AzureCliCredential(), allow_preview=True) as agent:
|
||||
@@ -648,10 +647,11 @@ async def test_foundry_agent_basic_run() -> None:
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_foundry_agent_integration_tests_disabled
|
||||
@pytest.mark.skip(reason="Test agent seems to have disappeared from the test environment; needs investigation.")
|
||||
async def test_foundry_agent_custom_client_run() -> None:
|
||||
"""Smoke-test FoundryAgent against a real configured agent."""
|
||||
async with FoundryAgent(credential=AzureCliCredential(), client_type=RawFoundryAgentChatClient) as agent:
|
||||
async with FoundryAgent(
|
||||
credential=AzureCliCredential(), client_type=RawFoundryAgentChatClient, allow_preview=True
|
||||
) as agent:
|
||||
response = await agent.run("Please respond with exactly: 'This is a response test.'")
|
||||
|
||||
assert isinstance(response, AgentResponse)
|
||||
|
||||
@@ -559,25 +559,21 @@ class TestToolCalling:
|
||||
class TestOptions:
|
||||
"""Verify chat options are passed through to the model."""
|
||||
|
||||
@pytest.mark.skip(reason="Flaky in merge queue, blocking unrelated PRs. Tracked in #5553.")
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_foundry_hosting_integration_tests_disabled
|
||||
async def test_temperature_and_max_tokens(self, server: ResponsesHostServer) -> None:
|
||||
"""Set temperature and max_output_tokens and verify the response succeeds."""
|
||||
"""Set max_output_tokens and verify the response succeeds."""
|
||||
resp = await _post_json(
|
||||
server,
|
||||
{
|
||||
"input": "Say hello briefly.",
|
||||
"stream": False,
|
||||
"max_output_tokens": 50,
|
||||
"max_output_tokens": 200,
|
||||
},
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["status"] == "completed"
|
||||
output_messages = [o for o in body["output"] if o["type"] == "message"]
|
||||
assert len(output_messages) == 1
|
||||
output_text = output_messages[0]["content"][0]["text"]
|
||||
assert len(output_text) > 0
|
||||
assert len(body["output"]) > 0
|
||||
|
||||
@@ -150,6 +150,12 @@ def hello_world(arg1: str) -> str:
|
||||
return "Hello World"
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def greet() -> str:
|
||||
"""Say hello to the world. No-arg tool for integration tests to avoid argument parsing flakiness."""
|
||||
return "Hello World"
|
||||
|
||||
|
||||
def test_init(ollama_unit_test_env: dict[str, str]) -> None:
|
||||
# Test successful initialization
|
||||
ollama_chat_client = OllamaChatClient()
|
||||
@@ -500,10 +506,10 @@ async def test_cmc_with_invalid_content_type(
|
||||
async def test_cmc_integration_with_tool_call(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(contents=["Call the hello world function and repeat what it says"], role="user"))
|
||||
chat_history.append(Message(contents=["Call the greet function and repeat what it says"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history, options={"tools": [hello_world]})
|
||||
result = await ollama_client.get_response(messages=chat_history, options={"tools": [greet]})
|
||||
|
||||
assert "hello" in result.text.lower() and "world" in result.text.lower()
|
||||
assert result.messages[-2].contents[0].type == "function_result"
|
||||
@@ -531,11 +537,11 @@ async def test_cmc_integration_with_chat_completion(
|
||||
async def test_cmc_streaming_integration_with_tool_call(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(contents=["Call the hello world function and repeat what it says"], role="user"))
|
||||
chat_history.append(Message(contents=["Call the greet function and repeat what it says"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result: AsyncIterable[ChatResponseUpdate] = ollama_client.get_response(
|
||||
messages=chat_history, stream=True, options={"tools": [hello_world]}
|
||||
messages=chat_history, stream=True, options={"tools": [greet]}
|
||||
)
|
||||
|
||||
chunks: list[ChatResponseUpdate] = []
|
||||
@@ -549,7 +555,7 @@ async def test_cmc_streaming_integration_with_tool_call(
|
||||
assert tool_result.result == "Hello World"
|
||||
if c.contents[0].type == "function_call":
|
||||
tool_call = c.contents[0]
|
||||
assert tool_call.name == "hello_world"
|
||||
assert tool_call.name == "greet"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import inspect
|
||||
import json
|
||||
@@ -120,6 +121,15 @@ async def create_vector_store(
|
||||
if result.last_error is not None:
|
||||
raise Exception(f"Vector store file processing failed with status: {result.last_error.message}")
|
||||
|
||||
# Wait for the vector store index to be fully searchable.
|
||||
# create_and_poll confirms file processing, but the search index is eventually consistent.
|
||||
for _ in range(10):
|
||||
vs = await client.client.vector_stores.retrieve(vector_store.id)
|
||||
if vs.file_counts.completed >= 1 and vs.file_counts.in_progress == 0:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
return file.id, Content.from_hosted_vector_store(vector_store_id=vector_store.id)
|
||||
|
||||
|
||||
@@ -4385,10 +4395,6 @@ async def test_integration_web_search() -> None:
|
||||
assert response.text is not None
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="Unreliable due to OpenAI vector store indexing potential "
|
||||
"race condition. See https://github.com/microsoft/agent-framework/issues/1669"
|
||||
)
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
@@ -4398,31 +4404,29 @@ async def test_integration_file_search() -> None:
|
||||
assert isinstance(openai_responses_client, SupportsChatGetResponse)
|
||||
|
||||
file_id, vector_store = await create_vector_store(openai_responses_client)
|
||||
# Use static method for file search tool
|
||||
file_search_tool = OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])
|
||||
# Test that the client will use the file search tool
|
||||
response = await openai_responses_client.get_response(
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
"tool_choice": "auto",
|
||||
"tools": [file_search_tool],
|
||||
},
|
||||
)
|
||||
try:
|
||||
# Use static method for file search tool
|
||||
file_search_tool = OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])
|
||||
# Test that the client will use the file search tool
|
||||
response = await openai_responses_client.get_response(
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
"tool_choice": "auto",
|
||||
"tools": [file_search_tool],
|
||||
},
|
||||
)
|
||||
|
||||
await delete_vector_store(openai_responses_client, file_id, vector_store.vector_store_id)
|
||||
assert "sunny" in response.text.lower()
|
||||
assert "75" in response.text
|
||||
assert "sunny" in response.text.lower()
|
||||
assert "75" in response.text
|
||||
finally:
|
||||
await delete_vector_store(openai_responses_client, file_id, vector_store.vector_store_id)
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="Unreliable due to OpenAI vector store indexing "
|
||||
"potential race condition. See https://github.com/microsoft/agent-framework/issues/1669"
|
||||
)
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
@@ -4432,35 +4436,37 @@ async def test_integration_streaming_file_search() -> None:
|
||||
assert isinstance(openai_responses_client, SupportsChatGetResponse)
|
||||
|
||||
file_id, vector_store = await create_vector_store(openai_responses_client)
|
||||
# Use static method for file search tool
|
||||
file_search_tool = OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])
|
||||
# Test that the client will use the web search tool
|
||||
response = openai_responses_client.get_streaming_response(
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
"tool_choice": "auto",
|
||||
"tools": [file_search_tool],
|
||||
},
|
||||
)
|
||||
try:
|
||||
# Use static method for file search tool
|
||||
file_search_tool = OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])
|
||||
# Test that the client will use the file search tool
|
||||
response = openai_responses_client.get_response(
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
stream=True,
|
||||
options={
|
||||
"tool_choice": "auto",
|
||||
"tools": [file_search_tool],
|
||||
},
|
||||
)
|
||||
|
||||
assert response is not None
|
||||
full_message: str = ""
|
||||
async for chunk in response:
|
||||
assert chunk is not None
|
||||
assert isinstance(chunk, ChatResponseUpdate)
|
||||
for content in chunk.contents:
|
||||
if content.type == "text" and content.text:
|
||||
full_message += content.text
|
||||
assert response is not None
|
||||
full_message: str = ""
|
||||
async for chunk in response:
|
||||
assert chunk is not None
|
||||
assert isinstance(chunk, ChatResponseUpdate)
|
||||
for content in chunk.contents:
|
||||
if content.type == "text" and content.text:
|
||||
full_message += content.text
|
||||
|
||||
await delete_vector_store(openai_responses_client, file_id, vector_store.vector_store_id)
|
||||
|
||||
assert "sunny" in full_message.lower()
|
||||
assert "75" in full_message
|
||||
assert "sunny" in full_message.lower()
|
||||
assert "75" in full_message
|
||||
finally:
|
||||
await delete_vector_store(openai_responses_client, file_id, vector_store.vector_store_id)
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
@@ -77,6 +78,15 @@ async def create_vector_store(client: OpenAIChatClient) -> tuple[str, Content]:
|
||||
if result.last_error is not None:
|
||||
raise RuntimeError(f"Vector store file processing failed with status: {result.last_error.message}")
|
||||
|
||||
# Wait for the vector store index to be fully searchable.
|
||||
# create_and_poll confirms file processing, but the search index is eventually consistent.
|
||||
for _ in range(10):
|
||||
vs = await client.client.vector_stores.retrieve(vector_store.id)
|
||||
if vs.file_counts.completed >= 1 and vs.file_counts.in_progress == 0:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
return file.id, Content.from_hosted_vector_store(vector_store_id=vector_store.id)
|
||||
|
||||
|
||||
@@ -355,7 +365,6 @@ async def test_integration_web_search() -> None:
|
||||
@pytest.mark.integration
|
||||
@skip_if_azure_openai_integration_tests_disabled
|
||||
@_with_azure_openai_debug()
|
||||
@pytest.mark.skip(reason="Azure OpenAI with files raises 500 error. Needs investigation.")
|
||||
async def test_integration_client_file_search() -> None:
|
||||
async with AzureCliCredential() as credential:
|
||||
client = OpenAIChatClient(credential=credential)
|
||||
@@ -381,7 +390,6 @@ async def test_integration_client_file_search() -> None:
|
||||
@pytest.mark.integration
|
||||
@skip_if_azure_openai_integration_tests_disabled
|
||||
@_with_azure_openai_debug()
|
||||
@pytest.mark.skip(reason="Azure OpenAI with files raises 500 error. Needs investigation.")
|
||||
async def test_integration_client_file_search_streaming() -> None:
|
||||
async with AzureCliCredential() as credential:
|
||||
client = OpenAIChatClient(credential=credential)
|
||||
|
||||
@@ -363,7 +363,7 @@ def _create_workflow() -> Workflow:
|
||||
|
||||
chat_client = OpenAIChatCompletionClient(
|
||||
model=os.environ["AZURE_OPENAI_MODEL"],
|
||||
api_key=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
|
||||
credential=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
|
||||
)
|
||||
|
||||
# Create agents for parallel analysis
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ def create_spam_agent() -> "Agent":
|
||||
return Agent(
|
||||
client=OpenAIChatCompletionClient(
|
||||
model=os.environ["AZURE_OPENAI_MODEL"],
|
||||
api_key=get_async_bearer_token_provider(
|
||||
credential=get_async_bearer_token_provider(
|
||||
AsyncAzureCliCredential(), "https://cognitiveservices.azure.com/.default"
|
||||
),
|
||||
),
|
||||
@@ -88,7 +88,7 @@ def create_email_agent() -> "Agent":
|
||||
return Agent(
|
||||
client=OpenAIChatCompletionClient(
|
||||
model=os.environ["AZURE_OPENAI_MODEL"],
|
||||
api_key=get_async_bearer_token_provider(
|
||||
credential=get_async_bearer_token_provider(
|
||||
AsyncAzureCliCredential(), "https://cognitiveservices.azure.com/.default"
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""CLI entry point for the flaky test report tool.
|
||||
|
||||
Usage:
|
||||
uv run python -m scripts.flaky_report <reports-dir> <history-file> <output-file>
|
||||
|
||||
Example (from python/ directory):
|
||||
uv run python -m scripts.flaky_report \\
|
||||
../flaky-reports/ \\
|
||||
flaky-report-history.json \\
|
||||
flaky-test-report.md
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from scripts.flaky_report.aggregate import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Flaky test report aggregation and trend generation.
|
||||
"""Integration test report aggregation and trend generation.
|
||||
|
||||
Parses JUnit XML (``pytest.xml``) files produced by each CI job, merges
|
||||
them with historical data, and generates a markdown trend report showing
|
||||
per-test status across the last N runs.
|
||||
|
||||
Usage:
|
||||
uv run python -m scripts.flaky_report <reports-dir> <history-file> <output-file>
|
||||
uv run python -m scripts.integration_test_report <reports-dir> <history-file> <output-file>
|
||||
"""
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""CLI entry point for the integration test report tool.
|
||||
|
||||
Usage:
|
||||
uv run python -m scripts.integration_test_report <reports-dir> <history-file> <output-file>
|
||||
|
||||
Example (from python/ directory):
|
||||
uv run python -m scripts.integration_test_report \\
|
||||
../test-results/ \\
|
||||
integration-report-history.json \\
|
||||
integration-test-report.md
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from scripts.integration_test_report.aggregate import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+1
-1
@@ -247,7 +247,7 @@ def _short_name(nodeid: str) -> str:
|
||||
def generate_trend_report(runs: list[dict[str, Any]]) -> str:
|
||||
"""Generate a markdown trend report from run history."""
|
||||
lines = [
|
||||
"# 🔬 Flaky Test Report",
|
||||
"# 🔬 Integration Test Report",
|
||||
"",
|
||||
f"*Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}*",
|
||||
"",
|
||||
Reference in New Issue
Block a user