fix: in OpenAIChatGenerator set additionalProperties to False when tools_strict=True (#8913)

* fix: set ComponentTool addtionalProperties for OpenAI tools_strict=True

* add reno

* Move the additionalProperties into the OpenAIChatGenerator

* Remove

* Put additionalProperties into the correct place

* Fix test

* Update releasenotes/notes/fix-componenttool-for-openai-tools_strict-998e5cd7ebc6ec19.yaml

Co-authored-by: Stefano Fiorucci <stefanofiorucci@gmail.com>

---------

Co-authored-by: Sebastian Husch Lee <sebastian.lee@deepset.ai>
Co-authored-by: Sebastian Husch Lee <sjrl@users.noreply.github.com>
Co-authored-by: Stefano Fiorucci <stefanofiorucci@gmail.com>
This commit is contained in:
tstadel 2025-03-03 16:23:24 +01:00 committed by GitHub
parent 296e31c182
commit 13968cc15b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 10 deletions

View File

@ -373,10 +373,13 @@ class OpenAIChatGenerator:
openai_tools = {}
if tools:
tool_definitions = [
{"type": "function", "function": {**t.tool_spec, **({"strict": tools_strict} if tools_strict else {})}}
for t in tools
]
tool_definitions = []
for t in tools:
function_spec = {**t.tool_spec}
if tools_strict:
function_spec["strict"] = True
function_spec["parameters"]["additionalProperties"] = False
tool_definitions.append({"type": "function", "function": function_spec})
openai_tools = {"tools": tool_definitions}
is_streaming = streaming_callback is not None

View File

@ -0,0 +1,4 @@
---
fixes:
- |
Make sure that `OpenAIChatGenerator` sets `additionalProperties: False` in the tool schema when `tool_strict` is set to `True`.

View File

@ -407,9 +407,10 @@ class TestOpenAIChatGenerator:
response = component.run([ChatMessage.from_user("What's the weather like in Paris?")])
# ensure that the tools are passed to the OpenAI API
assert mock_chat_completion_create.call_args[1]["tools"] == [
{"type": "function", "function": {**tools[0].tool_spec, "strict": True}}
]
function_spec = {**tools[0].tool_spec}
function_spec["strict"] = True
function_spec["parameters"]["additionalProperties"] = False
assert mock_chat_completion_create.call_args[1]["tools"] == [{"type": "function", "function": function_spec}]
assert len(response["replies"]) == 1
message = response["replies"][0]

View File

@ -216,9 +216,10 @@ class TestOpenAIChatGeneratorAsync:
response = await component.run_async([ChatMessage.from_user("What's the weather like in Paris?")])
# ensure that the tools are passed to the OpenAI API
assert mock_chat_completion_create.call_args[1]["tools"] == [
{"type": "function", "function": {**tools[0].tool_spec, "strict": True}}
]
function_spec = {**tools[0].tool_spec}
function_spec["strict"] = True
function_spec["parameters"]["additionalProperties"] = False
assert mock_chat_completion_create.call_args[1]["tools"] == [{"type": "function", "function": function_spec}]
assert len(response["replies"]) == 1
message = response["replies"][0]

View File

@ -346,6 +346,37 @@ class TestToolComponentInPipelineWithOpenAI:
assert "Vladimir" in tool_message.tool_call_result.result
assert not tool_message.tool_call_result.error
@pytest.mark.skipif(not os.environ.get("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
@pytest.mark.integration
def test_component_tool_in_pipeline_openai_tools_strict(self):
# Create component and convert it to tool
component = SimpleComponent()
tool = ComponentTool(
component=component, name="hello_tool", description="A tool that generates a greeting message for the user"
)
# Create pipeline with OpenAIChatGenerator and ToolInvoker
pipeline = Pipeline()
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini", tools=[tool], tools_strict=True))
pipeline.add_component("tool_invoker", ToolInvoker(tools=[tool]))
# Connect components
pipeline.connect("llm.replies", "tool_invoker.messages")
message = ChatMessage.from_user(text="Vladimir")
# Run pipeline
result = pipeline.run({"llm": {"messages": [message]}})
# Check results
tool_messages = result["tool_invoker"]["tool_messages"]
assert len(tool_messages) == 1
tool_message = tool_messages[0]
assert tool_message.is_from(ChatRole.TOOL)
assert "Vladimir" in tool_message.tool_call_result.result
assert not tool_message.tool_call_result.error
@pytest.mark.skipif(not os.environ.get("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
@pytest.mark.integration
def test_user_greeter_in_pipeline(self):