Skip to content

Commit 09e3ec1

Browse files
Python: Fix test-typing regressions from latest main merge
A fresh merge from main brought in new test code never run under the five-checker test-typing suite. Green up across the affected packages: - core: narrow Optional span.attributes with 'and' guards in span filters and assert+cast the json.loads(...attributes[...]) reads (test_observability); match the existing as_agent ignore on the protocol-typed fixture (test_clients). - openai: align new streaming tests with the established chat_options dict pattern (ChatOptions TypedDict isn't assignable to dict), route Optional .annotations[0] access through a small _first_annotation helper (mirrors the file's assert-not-None convention), and annotate a mapped ResponseStream. - foundry_hosting: annotate error: dict[str, Any] = body.get(...) or {} (zuban needs the annotation). - foundry: narrow ignores for the live AIProjectClient credential arg (pyrefly) and connections.get_default (zuban) SDK type gaps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e7b9b00 commit 09e3ec1

5 files changed

Lines changed: 49 additions & 34 deletions

File tree

python/packages/core/tests/core/test_clients.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_base_client_as_agent_rejects_function_invocation_configuration(
6969
TypeError,
7070
match=r"as_agent\(\) got an unexpected keyword argument 'function_invocation_configuration'",
7171
):
72-
chat_client_base.as_agent(function_invocation_configuration={"enabled": False})
72+
chat_client_base.as_agent(function_invocation_configuration={"enabled": False}) # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
7373

7474

7575
async def test_base_client_get_response_uses_explicit_client_kwargs(chat_client_base: SupportsChatGetResponse) -> None:

python/packages/core/tests/core/test_observability.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import logging
44
from collections.abc import AsyncIterable, Awaitable, Mapping, Sequence
5-
from typing import Any
5+
from typing import Any, cast
66
from unittest.mock import Mock, patch
77

88
import pytest
@@ -3707,11 +3707,15 @@ async def before_run(
37073707

37083708
spans = span_exporter.get_finished_spans()
37093709
agent_spans = [
3710-
span for span in spans if span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.AGENT_INVOKE_OPERATION
3710+
span
3711+
for span in spans
3712+
if span.attributes and span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.AGENT_INVOKE_OPERATION
37113713
]
37123714
assert len(agent_spans) == 1
37133715

3714-
system_instructions = json.loads(agent_spans[0].attributes[OtelAttr.SYSTEM_INSTRUCTIONS])
3716+
agent_attributes = agent_spans[0].attributes
3717+
assert agent_attributes is not None
3718+
system_instructions = json.loads(cast(str, agent_attributes[OtelAttr.SYSTEM_INSTRUCTIONS]))
37153719
contents = [item["content"] for item in system_instructions]
37163720
assert any("You are a friendly assistant." in content for content in contents)
37173721
assert any("The user's name is Alice." in content for content in contents)
@@ -3767,15 +3771,21 @@ async def after_run(
37673771

37683772
spans = span_exporter.get_finished_spans()
37693773
agent_spans = [
3770-
span for span in spans if span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.AGENT_INVOKE_OPERATION
3774+
span
3775+
for span in spans
3776+
if span.attributes and span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.AGENT_INVOKE_OPERATION
37713777
]
37723778
assert len(agent_spans) == 1
37733779
chat_spans = [
3774-
span for span in spans if span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.CHAT_COMPLETION_OPERATION
3780+
span
3781+
for span in spans
3782+
if span.attributes and span.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.CHAT_COMPLETION_OPERATION
37753783
]
37763784
assert len(chat_spans) == 2
37773785

3778-
system_instructions = json.loads(agent_spans[0].attributes[OtelAttr.SYSTEM_INSTRUCTIONS])
3786+
agent_attributes = agent_spans[0].attributes
3787+
assert agent_attributes is not None
3788+
system_instructions = json.loads(cast(str, agent_attributes[OtelAttr.SYSTEM_INSTRUCTIONS]))
37793789
contents = [item["content"] for item in system_instructions]
37803790
assert any("Base agent instructions." in content for content in contents)
37813791
assert any("Context-provided instructions." in content for content in contents)

python/packages/foundry/tests/foundry/test_foundry_agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,11 +1073,11 @@ async def test_foundry_agent_azure_ai_search_streaming_citation_get_url() -> Non
10731073

10741074
project_client = AIProjectClient(
10751075
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
1076-
credential=credential,
1076+
credential=credential, # pyrefly: ignore[bad-argument-type]
10771077
allow_preview=True,
10781078
)
10791079
try:
1080-
search_connection = await project_client.connections.get_default(
1080+
search_connection = await project_client.connections.get_default( # type: ignore[attr-defined]
10811081
projects_models.ConnectionType.AZURE_AI_SEARCH
10821082
)
10831083
except Exception as exc:

python/packages/foundry_hosting/tests/test_responses.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,7 +2920,7 @@ async def test_approval_response_referencing_unknown_id_fails(self) -> None:
29202920
assert resp.status_code == 200
29212921
body = resp.json()
29222922
assert body["status"] == "failed"
2923-
error = body.get("error") or {}
2923+
error: dict[str, Any] = body.get("error") or {}
29242924
assert "apr_unknown" in (error.get("message") or "")
29252925

29262926

@@ -3500,7 +3500,7 @@ async def test_non_consent_error_during_entry_propagates(self) -> None:
35003500
body = resp.json()
35013501
assert body["status"] == "failed"
35023502
assert not any(it["type"] == "oauth_consent_request" for it in body.get("output", []))
3503-
error = body.get("error") or {}
3503+
error: dict[str, Any] = body.get("error") or {}
35043504
assert error.get("message") == "boom"
35053505
agent.run.assert_not_called()
35063506

@@ -3558,7 +3558,7 @@ async def _raise(*args: Any, **kwargs: Any) -> AgentResponse:
35583558
assert resp.status_code == 200
35593559
body = resp.json()
35603560
assert body["status"] == "failed"
3561-
error = body.get("error") or {}
3561+
error: dict[str, Any] = body.get("error") or {}
35623562
assert error.get("message") == "non-stream kaboom"
35633563

35643564
async def test_streaming_run_failure_emits_response_failed(self) -> None:
@@ -3593,8 +3593,8 @@ def run_streaming(*args: Any, **kwargs: Any) -> Any:
35933593

35943594
failed = [e for e in events if e["event"] == "response.failed"]
35953595
assert len(failed) == 1
3596-
response_payload = failed[0]["data"].get("response") or {}
3597-
error = response_payload.get("error") or {}
3596+
response_payload: dict[str, Any] = failed[0]["data"].get("response") or {}
3597+
error: dict[str, Any] = response_payload.get("error") or {}
35983598
assert error.get("message") == "stream kaboom"
35993599

36003600
async def test_streaming_run_failure_drains_pending_output_item(self) -> None:
@@ -3649,7 +3649,7 @@ async def _raise(*args: Any, **kwargs: Any) -> AgentResponse:
36493649
assert resp.status_code == 200
36503650
body = resp.json()
36513651
assert body["status"] == "failed"
3652-
error = body.get("error") or {}
3652+
error: dict[str, Any] = body.get("error") or {}
36533653
assert error.get("message") == "workflow kaboom"
36543654

36553655

python/packages/openai/tests/openai/test_openai_chat_client.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3615,6 +3615,11 @@ def test_streaming_annotation_added_with_url_citation() -> None:
36153615
assert region["end_index"] == 112
36163616

36173617

3618+
def _first_annotation(content: Any) -> Any:
3619+
assert content.annotations is not None
3620+
return content.annotations[0]
3621+
3622+
36183623
def _make_url_citation_event(
36193624
*,
36203625
title: str,
@@ -3667,7 +3672,7 @@ def _make_azure_ai_search_output_event(
36673672
def test_streaming_azure_ai_search_output_enriches_final_url_citation_get_url() -> None:
36683673
"""Azure AI Search get_urls are resolved onto doc_N citation annotations after streaming completes."""
36693674
client = OpenAIChatClient(model="test-model", api_key="test-key")
3670-
chat_options = ChatOptions()
3675+
chat_options: dict[str, Any] = {}
36713676
function_call_ids: dict[int, tuple[str, str]] = {}
36723677
get_url = "https://example.search.windows.net/indexes/my-index/docs/doc-123?api-version=2024-07-01"
36733678

@@ -3686,17 +3691,17 @@ def test_streaming_azure_ai_search_output_enriches_final_url_citation_get_url()
36863691

36873692
response = client._finalize_response_updates([citation_update, search_update])
36883693

3689-
annotation = response.messages[0].contents[0].annotations[0]
3694+
annotation = _first_annotation(response.messages[0].contents[0])
36903695
assert annotation["additional_properties"]["get_url"] == get_url
36913696

36923697

36933698
async def test_streaming_azure_ai_search_output_enriches_mapped_agent_response() -> None:
36943699
"""Finalization mutates collected chat updates so mapped agent streams receive the enriched citation too."""
3695-
from agent_framework import AgentResponse
3700+
from agent_framework import AgentResponse, AgentResponseUpdate
36963701
from agent_framework._types import ResponseStream, map_chat_to_agent_update
36973702

36983703
client = OpenAIChatClient(model="test-model", api_key="test-key")
3699-
chat_options = ChatOptions()
3704+
chat_options: dict[str, Any] = {}
37003705
function_call_ids: dict[int, tuple[str, str]] = {}
37013706
get_url = "https://example.search.windows.net/indexes/my-index/docs/doc-123?api-version=2024-07-01"
37023707
updates = [
@@ -3717,7 +3722,7 @@ async def _stream() -> AsyncGenerator[ChatResponseUpdate, None]:
37173722
yield update
37183723

37193724
chat_stream = ResponseStream(_stream(), finalizer=client._finalize_response_updates)
3720-
agent_stream = chat_stream.map(
3725+
agent_stream: ResponseStream[AgentResponseUpdate, AgentResponse] = chat_stream.map(
37213726
transform=lambda update: map_chat_to_agent_update(update, agent_name="test-agent"),
37223727
finalizer=AgentResponse.from_updates,
37233728
)
@@ -3726,14 +3731,14 @@ async def _stream() -> AsyncGenerator[ChatResponseUpdate, None]:
37263731
pass
37273732
response = await agent_stream.get_final_response()
37283733

3729-
annotation = response.messages[0].contents[0].annotations[0]
3734+
annotation = _first_annotation(response.messages[0].contents[0])
37303735
assert annotation["additional_properties"]["get_url"] == get_url
37313736

37323737

37333738
def test_streaming_azure_ai_search_output_does_not_overwrite_existing_get_url() -> None:
37343739
"""If the annotation already contains get_url, the later Search output does not replace it."""
37353740
client = OpenAIChatClient(model="test-model", api_key="test-key")
3736-
chat_options = ChatOptions()
3741+
chat_options: dict[str, Any] = {}
37373742
function_call_ids: dict[int, tuple[str, str]] = {}
37383743
existing_get_url = "https://example.search.windows.net/indexes/my-index/docs/existing?api-version=2024-07-01"
37393744

@@ -3752,14 +3757,14 @@ def test_streaming_azure_ai_search_output_does_not_overwrite_existing_get_url()
37523757

37533758
response = client._finalize_response_updates([citation_update, search_update])
37543759

3755-
annotation = response.messages[0].contents[0].annotations[0]
3760+
annotation = _first_annotation(response.messages[0].contents[0])
37563761
assert annotation["additional_properties"]["get_url"] == existing_get_url
37573762

37583763

37593764
def test_streaming_azure_ai_search_output_uses_global_doc_index_across_search_events() -> None:
37603765
"""Azure AI Search `doc_N` URLs are resolved against the concatenated stream order of all search events."""
37613766
client = OpenAIChatClient(model="test-model", api_key="test-key")
3762-
chat_options = ChatOptions()
3767+
chat_options: dict[str, Any] = {}
37633768
function_call_ids: dict[int, tuple[str, str]] = {}
37643769

37653770
citation_update = client._parse_chunk_from_openai(
@@ -3787,14 +3792,14 @@ def test_streaming_azure_ai_search_output_uses_global_doc_index_across_search_ev
37873792

37883793
response = client._finalize_response_updates([citation_update, search_update_one, search_update_two])
37893794

3790-
annotation = response.messages[0].contents[0].annotations[0]
3795+
annotation = _first_annotation(response.messages[0].contents[0])
37913796
assert annotation["additional_properties"]["get_url"] == "https://example.search.windows.net/docs/three"
37923797

37933798

37943799
def test_streaming_azure_ai_search_output_normalizes_non_dict_additional_properties() -> None:
37953800
"""Existing non-dict additional_properties should be normalized before enriching get_url."""
37963801
client = OpenAIChatClient(model="test-model", api_key="test-key")
3797-
chat_options = ChatOptions()
3802+
chat_options: dict[str, Any] = {}
37983803
function_call_ids: dict[int, tuple[str, str]] = {}
37993804
get_url = "https://example.search.windows.net/indexes/my-index/docs/doc-123?api-version=2024-07-01"
38003805

@@ -3803,7 +3808,7 @@ def test_streaming_azure_ai_search_output_normalizes_non_dict_additional_propert
38033808
chat_options,
38043809
function_call_ids,
38053810
)
3806-
citation_update.contents[0].annotations[0]["additional_properties"] = None
3811+
_first_annotation(citation_update.contents[0])["additional_properties"] = None
38073812
search_update = client._parse_chunk_from_openai(
38083813
_make_azure_ai_search_output_event(json.dumps({"get_urls": [get_url]})),
38093814
chat_options,
@@ -3812,7 +3817,7 @@ def test_streaming_azure_ai_search_output_normalizes_non_dict_additional_propert
38123817

38133818
response = client._finalize_response_updates([citation_update, search_update])
38143819

3815-
annotation = response.messages[0].contents[0].annotations[0]
3820+
annotation = _first_annotation(response.messages[0].contents[0])
38163821
assert annotation["additional_properties"] == {"get_url": get_url}
38173822

38183823

@@ -3832,7 +3837,7 @@ def test_streaming_azure_ai_search_output_does_not_create_additional_properties_
38323837

38333838
RawOpenAIChatClient._enrich_streamed_azure_ai_search_citations([update])
38343839

3835-
annotation = update.contents[0].annotations[0]
3840+
annotation = _first_annotation(update.contents[0])
38363841
assert annotation.get("additional_properties") is None
38373842

38383843

@@ -3851,7 +3856,7 @@ def test_extract_azure_ai_search_get_urls_accepts_dedicated_output_event() -> No
38513856
def test_parse_chunk_from_openai_ignores_dedicated_azure_ai_search_events() -> None:
38523857
"""Dedicated Azure AI Search events should be treated as intentional no-op updates."""
38533858
client = OpenAIChatClient(model="test-model", api_key="test-key")
3854-
chat_options = ChatOptions()
3859+
chat_options: dict[str, Any] = {}
38553860
function_call_ids: dict[int, tuple[str, str]] = {}
38563861
event = _make_azure_ai_search_output_event(
38573862
json.dumps({"get_urls": ["https://example.search.windows.net/indexes/my-index/docs/doc-0"]}),
@@ -3878,7 +3883,7 @@ def test_parse_chunk_from_openai_ignores_dedicated_azure_ai_search_events() -> N
38783883
def test_streaming_azure_ai_search_output_ignores_unusable_get_url_data(title: str, output: str) -> None:
38793884
"""Malformed or non-matching Azure AI Search output leaves citations unchanged."""
38803885
client = OpenAIChatClient(model="test-model", api_key="test-key")
3881-
chat_options = ChatOptions()
3886+
chat_options: dict[str, Any] = {}
38823887
function_call_ids: dict[int, tuple[str, str]] = {}
38833888

38843889
citation_update = client._parse_chunk_from_openai(
@@ -3894,14 +3899,14 @@ def test_streaming_azure_ai_search_output_ignores_unusable_get_url_data(title: s
38943899

38953900
response = client._finalize_response_updates([citation_update, search_update])
38963901

3897-
annotation = response.messages[0].contents[0].annotations[0]
3902+
annotation = _first_annotation(response.messages[0].contents[0])
38983903
assert "get_url" not in annotation["additional_properties"]
38993904

39003905

39013906
def test_streaming_mcp_searchindex_citation_enriched_from_mcp_output() -> None:
39023907
"""MCP search-index citations are enriched from retrieved document metadata in MCP output."""
39033908
client = OpenAIChatClient(model="test-model", api_key="test-key")
3904-
chat_options = ChatOptions()
3909+
chat_options: dict[str, Any] = {}
39053910
function_call_ids: dict[int, tuple[str, str]] = {}
39063911
document_id = "inspection_procedures_p1_c0"
39073912
mcp_output = f"""
@@ -3996,7 +4001,7 @@ def test_parse_response_enriches_mcp_searchindex_citation_from_mcp_output() -> N
39964001

39974002
response = client._parse_response_from_openai(mock_response, options={})
39984003

3999-
annotation = response.messages[0].contents[-1].annotations[0]
4004+
annotation = _first_annotation(response.messages[0].contents[-1])
40004005
assert annotation["additional_properties"]["mcp_document_id"] == document_id
40014006
assert annotation["additional_properties"]["document_title"] == "Ticket Management Policy"
40024007
assert annotation["additional_properties"]["source"] == "ticket_management_policy.pdf"

0 commit comments

Comments
 (0)