mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into flaky-test-report
This commit is contained in:
@@ -1296,6 +1296,12 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
"type": "function",
|
||||
"name": func_name,
|
||||
}
|
||||
elif mode == "auto" and (allowed := tool_mode.get("allowed_tools")) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "allowed_tools",
|
||||
"mode": "auto",
|
||||
"tools": [{"type": "function", "name": name} for name in allowed],
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
else:
|
||||
|
||||
@@ -662,6 +662,12 @@ class RawOpenAIChatCompletionClient( # type: ignore[misc]
|
||||
"type": "function",
|
||||
"function": {"name": func_name},
|
||||
}
|
||||
elif mode in ("auto", "required") and tool_mode.get("allowed_tools") is not None:
|
||||
logger.warning(
|
||||
"allowed_tools is not supported by the Chat Completions API; "
|
||||
"the setting will be ignored. Use OpenAIChatClient (Responses API) instead."
|
||||
)
|
||||
run_options["tool_choice"] = mode
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
|
||||
|
||||
@@ -4272,6 +4272,12 @@ def test_with_callable_api_key() -> None:
|
||||
True,
|
||||
id="tool_choice_required",
|
||||
),
|
||||
param(
|
||||
"tool_choice",
|
||||
{"mode": "auto", "allowed_tools": ["get_weather"]},
|
||||
True,
|
||||
id="tool_choice_allowed_tools",
|
||||
),
|
||||
param("response_format", OutputStruct, True, id="response_format_pydantic"),
|
||||
param(
|
||||
"response_format",
|
||||
@@ -4822,6 +4828,90 @@ async def test_prepare_options_excludes_continuation_token() -> None:
|
||||
assert run_options["background"] is True
|
||||
|
||||
|
||||
async def test_prepare_options_allowed_tools() -> None:
|
||||
"""Test that _prepare_options converts allowed_tools to OpenAI API format."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
|
||||
@tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a city."""
|
||||
return f"Sunny in {city}"
|
||||
|
||||
@tool
|
||||
def search_docs(query: str) -> str:
|
||||
"""Search documentation."""
|
||||
return f"Results for {query}"
|
||||
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
options: dict[str, Any] = {
|
||||
"model": "test-model",
|
||||
"tools": [get_weather, search_docs],
|
||||
"tool_choice": {"mode": "auto", "allowed_tools": ["get_weather"]},
|
||||
}
|
||||
|
||||
run_options = await client._prepare_options(messages, options)
|
||||
|
||||
assert run_options["tool_choice"] == {
|
||||
"type": "allowed_tools",
|
||||
"mode": "auto",
|
||||
"tools": [{"type": "function", "name": "get_weather"}],
|
||||
}
|
||||
|
||||
|
||||
async def test_prepare_options_allowed_tools_multiple() -> None:
|
||||
"""Test that _prepare_options converts multiple allowed_tools correctly."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
|
||||
@tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a city."""
|
||||
return f"Sunny in {city}"
|
||||
|
||||
@tool
|
||||
def search_docs(query: str) -> str:
|
||||
"""Search documentation."""
|
||||
return f"Results for {query}"
|
||||
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
options: dict[str, Any] = {
|
||||
"model": "test-model",
|
||||
"tools": [get_weather, search_docs],
|
||||
"tool_choice": {"mode": "auto", "allowed_tools": ["get_weather", "search_docs"]},
|
||||
}
|
||||
|
||||
run_options = await client._prepare_options(messages, options)
|
||||
|
||||
assert run_options["tool_choice"] == {
|
||||
"type": "allowed_tools",
|
||||
"mode": "auto",
|
||||
"tools": [
|
||||
{"type": "function", "name": "get_weather"},
|
||||
{"type": "function", "name": "search_docs"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_prepare_options_auto_without_allowed_tools() -> None:
|
||||
"""Test that auto mode without allowed_tools still returns plain 'auto' string."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
|
||||
@tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a city."""
|
||||
return f"Sunny in {city}"
|
||||
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
options: dict[str, Any] = {
|
||||
"model": "test-model",
|
||||
"tools": [get_weather],
|
||||
"tool_choice": {"mode": "auto"},
|
||||
}
|
||||
|
||||
run_options = await client._prepare_options(messages, options)
|
||||
|
||||
assert run_options["tool_choice"] == "auto"
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
|
||||
@@ -1430,6 +1430,57 @@ def test_tool_choice_required_with_function_name(
|
||||
assert prepared_options["tool_choice"]["function"]["name"] == "get_weather"
|
||||
|
||||
|
||||
def test_tool_choice_allowed_tools_falls_back_to_mode(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
) -> None:
|
||||
"""Test that tool_choice with allowed_tools falls back to plain mode (Chat Completions API unsupported)."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {
|
||||
"tools": [get_weather],
|
||||
"tool_choice": {"mode": "auto", "allowed_tools": ["get_weather"]},
|
||||
}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
|
||||
assert prepared_options["tool_choice"] == "auto"
|
||||
|
||||
|
||||
def test_tool_choice_allowed_tools_required_mode_falls_back(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
) -> None:
|
||||
"""Test that tool_choice with allowed_tools and required mode falls back to 'required'."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {
|
||||
"tools": [get_weather],
|
||||
"tool_choice": {"mode": "required", "allowed_tools": ["get_weather"]},
|
||||
}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
|
||||
assert prepared_options["tool_choice"] == "required"
|
||||
|
||||
|
||||
def test_tool_choice_auto_dict_without_allowed_tools(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
) -> None:
|
||||
"""Test that tool_choice dict with mode auto and no allowed_tools falls through to plain 'auto'."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {
|
||||
"tools": [get_weather],
|
||||
"tool_choice": {"mode": "auto"},
|
||||
}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
|
||||
assert prepared_options["tool_choice"] == "auto"
|
||||
|
||||
|
||||
def test_response_format_dict_passthrough(openai_unit_test_env: dict[str, str]) -> None:
|
||||
"""Test that response_format as dict is passed through directly."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
@@ -1590,6 +1641,12 @@ class OutputStruct(BaseModel):
|
||||
False,
|
||||
id="tool_choice_required",
|
||||
),
|
||||
param(
|
||||
"tool_choice",
|
||||
{"mode": "auto", "allowed_tools": ["get_weather"]},
|
||||
False,
|
||||
id="tool_choice_allowed_tools",
|
||||
),
|
||||
param("response_format", OutputStruct, True, id="response_format_pydantic"),
|
||||
param(
|
||||
"response_format",
|
||||
|
||||
Reference in New Issue
Block a user