Skip to content

Commit 13968cc

Browse files
tstadelsjrlanakin87
authored
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 <[email protected]> --------- Co-authored-by: Sebastian Husch Lee <[email protected]> Co-authored-by: Sebastian Husch Lee <[email protected]> Co-authored-by: Stefano Fiorucci <[email protected]>
1 parent 296e31c commit 13968cc

File tree

5 files changed

+50
-10
lines changed

5 files changed

+50
-10
lines changed

haystack/components/generators/chat/openai.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,13 @@ def _prepare_api_call( # noqa: PLR0913
373373

374374
openai_tools = {}
375375
if tools:
376-
tool_definitions = [
377-
{"type": "function", "function": {**t.tool_spec, **({"strict": tools_strict} if tools_strict else {})}}
378-
for t in tools
379-
]
376+
tool_definitions = []
377+
for t in tools:
378+
function_spec = {**t.tool_spec}
379+
if tools_strict:
380+
function_spec["strict"] = True
381+
function_spec["parameters"]["additionalProperties"] = False
382+
tool_definitions.append({"type": "function", "function": function_spec})
380383
openai_tools = {"tools": tool_definitions}
381384

382385
is_streaming = streaming_callback is not None
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
Make sure that `OpenAIChatGenerator` sets `additionalProperties: False` in the tool schema when `tool_strict` is set to `True`.

test/components/generators/chat/test_openai.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,10 @@ def test_run_with_tools(self, tools):
407407
response = component.run([ChatMessage.from_user("What's the weather like in Paris?")])
408408

409409
# ensure that the tools are passed to the OpenAI API
410-
assert mock_chat_completion_create.call_args[1]["tools"] == [
411-
{"type": "function", "function": {**tools[0].tool_spec, "strict": True}}
412-
]
410+
function_spec = {**tools[0].tool_spec}
411+
function_spec["strict"] = True
412+
function_spec["parameters"]["additionalProperties"] = False
413+
assert mock_chat_completion_create.call_args[1]["tools"] == [{"type": "function", "function": function_spec}]
413414

414415
assert len(response["replies"]) == 1
415416
message = response["replies"][0]

test/components/generators/chat/test_openai_async.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,10 @@ async def test_run_with_tools_async(self, tools):
216216
response = await component.run_async([ChatMessage.from_user("What's the weather like in Paris?")])
217217

218218
# ensure that the tools are passed to the OpenAI API
219-
assert mock_chat_completion_create.call_args[1]["tools"] == [
220-
{"type": "function", "function": {**tools[0].tool_spec, "strict": True}}
221-
]
219+
function_spec = {**tools[0].tool_spec}
220+
function_spec["strict"] = True
221+
function_spec["parameters"]["additionalProperties"] = False
222+
assert mock_chat_completion_create.call_args[1]["tools"] == [{"type": "function", "function": function_spec}]
222223

223224
assert len(response["replies"]) == 1
224225
message = response["replies"][0]

test/tools/test_component_tool.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,37 @@ def test_component_tool_in_pipeline(self):
346346
assert "Vladimir" in tool_message.tool_call_result.result
347347
assert not tool_message.tool_call_result.error
348348

349+
@pytest.mark.skipif(not os.environ.get("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
350+
@pytest.mark.integration
351+
def test_component_tool_in_pipeline_openai_tools_strict(self):
352+
# Create component and convert it to tool
353+
component = SimpleComponent()
354+
tool = ComponentTool(
355+
component=component, name="hello_tool", description="A tool that generates a greeting message for the user"
356+
)
357+
358+
# Create pipeline with OpenAIChatGenerator and ToolInvoker
359+
pipeline = Pipeline()
360+
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini", tools=[tool], tools_strict=True))
361+
pipeline.add_component("tool_invoker", ToolInvoker(tools=[tool]))
362+
363+
# Connect components
364+
pipeline.connect("llm.replies", "tool_invoker.messages")
365+
366+
message = ChatMessage.from_user(text="Vladimir")
367+
368+
# Run pipeline
369+
result = pipeline.run({"llm": {"messages": [message]}})
370+
371+
# Check results
372+
tool_messages = result["tool_invoker"]["tool_messages"]
373+
assert len(tool_messages) == 1
374+
375+
tool_message = tool_messages[0]
376+
assert tool_message.is_from(ChatRole.TOOL)
377+
assert "Vladimir" in tool_message.tool_call_result.result
378+
assert not tool_message.tool_call_result.error
379+
349380
@pytest.mark.skipif(not os.environ.get("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
350381
@pytest.mark.integration
351382
def test_user_greeter_in_pipeline(self):

0 commit comments

Comments
 (0)