Skip to content

Commit be287e7

Browse files
committed
fix: Use convert_tooldef_to_openai_tool for function tool serialization
The Responses API function tool conversion used model_dump() to build the ChatCompletionToolParam function dict, which leaked the "type" field (and would leak any future fields like defer_loading) into the FunctionDefinition. OpenAI silently ignores extra fields, but stricter endpoints (e.g. Gemini) reject them. Instead of a fragile denylist (exclude={"type"}), reuse convert_tooldef_to_openai_tool which explicitly picks only the fields that belong in a FunctionDefinition. Added strict parameter support to convert_tooldef_to_openai_tool so function tools can pass it through. Signed-off-by: Dan Prince <dprince@redhat.com>
1 parent e2e82ed commit be287e7

3 files changed

Lines changed: 52 additions & 2 deletions

File tree

src/ogx/providers/inline/responses/builtin/responses/streaming.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,8 +1569,13 @@ def make_openai_tool(tool_name: str, tool: ToolDef) -> ChatCompletionToolParam:
15691569
for input_tool in tools:
15701570
if input_tool.type == "function":
15711571
self.ctx.chat_tools.append(
1572-
ChatCompletionToolParam(type="function", function=input_tool.model_dump(exclude_none=True)) # type: ignore[typeddict-item,arg-type] # Dict compatible with FunctionDefinition
1573-
)
1572+
convert_tooldef_to_openai_tool(
1573+
tool_name=input_tool.name,
1574+
description=input_tool.description,
1575+
input_schema=input_tool.parameters,
1576+
strict=input_tool.strict,
1577+
)
1578+
) # type: ignore[arg-type] # Returns dict but ChatCompletionToolParam expects TypedDict
15741579
elif input_tool.type in WebSearchToolTypes:
15751580
tool_name = "web_search"
15761581
# Need to access tool_groups_api from tool_executor

src/ogx/providers/utils/inference/openai_compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def convert_tooldef_to_openai_tool(
1818
tool_name: str,
1919
description: str | None = None,
2020
input_schema: dict[str, Any] | None = None,
21+
strict: bool | None = None,
2122
) -> dict[str, Any]:
2223
"""
2324
Convert tool parameters to an OpenAI API-compatible dictionary.
@@ -26,6 +27,7 @@ def convert_tooldef_to_openai_tool(
2627
tool_name: Tool name as string
2728
description: Optional tool description
2829
input_schema: Optional JSON Schema for tool parameters
30+
strict: Optional flag for strict parameter validation
2931
3032
Returns:
3133
OpenAI-compatible tool dictionary:
@@ -35,6 +37,7 @@ def convert_tooldef_to_openai_tool(
3537
"name": tool_name,
3638
"description": description,
3739
"parameters": {<JSON Schema>},
40+
"strict": strict,
3841
},
3942
}
4043
@@ -54,6 +57,9 @@ def convert_tooldef_to_openai_tool(
5457
if input_schema:
5558
function["parameters"] = input_schema
5659

60+
if strict is not None:
61+
function["strict"] = strict
62+
5763
return out
5864

5965

tests/unit/providers/responses/builtin/test_openai_responses_tools.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,42 @@ async def test_function_tool_strict_false_included(openai_responses_impl, mock_i
927927
tool_function = params.tools[0]["function"]
928928
assert "strict" in tool_function, "strict field should be included when explicitly set to False"
929929
assert tool_function["strict"] is False, "strict field should be False"
930+
931+
932+
async def test_function_tool_type_field_excluded_from_function_dict(openai_responses_impl, mock_inference_api):
933+
"""Test that the 'type' field from the input tool is excluded from the function dict.
934+
935+
The function dict inside ChatCompletionToolParam should only contain function-specific
936+
fields (name, description, parameters, strict) and not the top-level 'type' field,
937+
which belongs on the outer tool param.
938+
"""
939+
input_text = "What is the weather?"
940+
model = "meta-llama/Llama-3.1-8B-Instruct"
941+
942+
mock_inference_api.openai_chat_completion.return_value = fake_stream()
943+
944+
await openai_responses_impl.create_openai_response(
945+
input=input_text,
946+
model=model,
947+
stream=False,
948+
tools=[
949+
OpenAIResponseInputToolFunction(
950+
type="function",
951+
name="get_weather",
952+
description="Get weather information",
953+
parameters={"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]},
954+
)
955+
],
956+
)
957+
958+
assert mock_inference_api.openai_chat_completion.call_count == 1
959+
params = mock_inference_api.openai_chat_completion.call_args[0][0]
960+
961+
assert len(params.tools) == 1
962+
# The outer tool param should have type="function"
963+
assert params.tools[0]["type"] == "function"
964+
# The inner function dict should NOT contain 'type'
965+
tool_function = params.tools[0]["function"]
966+
assert "type" not in tool_function, "The input tool 'type' field should be excluded from the function dict"
967+
assert tool_function["name"] == "get_weather"
968+
assert tool_function["description"] == "Get weather information"

0 commit comments

Comments
 (0)