mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Raise clear handler registration error for unresolved TypeVar annotations (#4944)
* Raise clear handler registration error for unresolved TypeVar (#4943) Detect unresolved TypeVar in message parameter annotations during handler registration in both _validate_handler_signature (Executor) and _validate_function_signature (FunctionExecutor). Raises a ValueError with an actionable message recommending @handler(input=..., output=...) or @executor(input=..., output=...) instead of letting TypeVar leak through to a confusing TypeCompatibilityError during workflow edge validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback for #4943: reorder checks and harden function executor - Move TypeVar check before validate_workflow_context_annotation in _executor.py so users see the more actionable error first - Wrap get_type_hints in try/except in _function_executor.py matching the defensive pattern in _executor.py - Repurpose duplicate test to cover bounded TypeVar rejection - Add test_function_executor_allows_concrete_types for test symmetry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Narrow get_type_hints except clause and add missing tests (#4943) - Narrow `except Exception` to `except (NameError, AttributeError, RecursionError)` in both _executor.py and _function_executor.py so unexpected failures in get_type_hints are not silently swallowed. - Add test_handler_unresolvable_annotation_raises to test_function_executor_future.py exercising the except branch of get_type_hints in the function executor path. - Add test_function_executor_rejects_bounded_typevar_in_message_annotation to test_function_executor.py for parity with the Executor bounded TypeVar test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add error ordering test for TypeVar vs WorkflowContext priority (#4943) Add test_handler_typevar_error_takes_priority_over_context_error to verify that when a handler has both a TypeVar message and an unannotated ctx, the TypeVar error is raised first (the more actionable issue). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Fix image content serialization sending null file_id to Foundry API Omit file_id from input_image dict when not present instead of including it as null, which Azure AI Foundry's stricter schema validation rejects. * Python: Fix Foundry API rejecting rich content in function_call_output Azure AI Foundry does not support list-format output in function_call_output items. Add SUPPORTS_RICH_FUNCTION_OUTPUT flag (default True) to RawOpenAIChatClient, set to False in RawFoundryChatClient so Foundry falls back to string output for tool results with images/files. Also omit file_id from input_image dicts when not set, since Foundry rejects explicit nulls. * Python: Surface rich tool content as user message when Foundry lacks support When SUPPORTS_RICH_FUNCTION_OUTPUT is False, image/file items from tool results are injected as a follow-up user message so the model can still process the visual content via Foundry's supported user message format. * Xfail Foundry image integration test for the meantime --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
942cb04ccb
commit
36cafe4e5a
@@ -265,6 +265,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
|
||||
INJECTABLE: ClassVar[set[str]] = {"client"}
|
||||
STORES_BY_DEFAULT: ClassVar[bool] = True # type: ignore[reportIncompatibleVariableOverride, misc]
|
||||
SUPPORTS_RICH_FUNCTION_OUTPUT: ClassVar[bool] = True
|
||||
|
||||
FILE_SEARCH_MAX_RESULTS: int = 50
|
||||
|
||||
@@ -1353,16 +1354,17 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
return ret
|
||||
case "data" | "uri":
|
||||
if content.has_top_level_media_type("image"):
|
||||
return {
|
||||
result: dict[str, Any] = {
|
||||
"type": "input_image",
|
||||
"image_url": content.uri,
|
||||
"detail": content.additional_properties.get("detail", "auto")
|
||||
if content.additional_properties
|
||||
else "auto",
|
||||
"file_id": content.additional_properties.get("file_id", None)
|
||||
if content.additional_properties
|
||||
else None,
|
||||
}
|
||||
file_id = content.additional_properties.get("file_id") if content.additional_properties else None
|
||||
if file_id:
|
||||
result["file_id"] = file_id
|
||||
return result
|
||||
if content.has_top_level_media_type("audio"):
|
||||
if content.media_type and "wav" in content.media_type:
|
||||
format = "wav"
|
||||
@@ -1444,7 +1446,11 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
}
|
||||
# call_id for the result needs to be the same as the call_id for the function call
|
||||
output: str | list[dict[str, Any]] = content.result or ""
|
||||
if content.items and any(item.type in ("data", "uri") for item in content.items):
|
||||
if (
|
||||
self.SUPPORTS_RICH_FUNCTION_OUTPUT
|
||||
and content.items
|
||||
and any(item.type in ("data", "uri") for item in content.items)
|
||||
):
|
||||
output_parts: list[dict[str, Any]] = []
|
||||
for item in content.items:
|
||||
if item.type == "text":
|
||||
|
||||
@@ -2577,7 +2577,7 @@ def test_prepare_content_for_openai_image_content() -> None:
|
||||
result = client._prepare_content_for_openai("user", image_content_basic)
|
||||
assert result["type"] == "input_image"
|
||||
assert result["detail"] == "auto"
|
||||
assert result["file_id"] is None
|
||||
assert "file_id" not in result
|
||||
|
||||
|
||||
def test_prepare_content_for_openai_audio_content() -> None:
|
||||
|
||||
Reference in New Issue
Block a user