diff --git a/src/openai/types/chat/chat_completion_chunk.py b/src/openai/types/chat/chat_completion_chunk.py index ecbfd0a5aa..028ca925c8 100644 --- a/src/openai/types/chat/chat_completion_chunk.py +++ b/src/openai/types/chat/chat_completion_chunk.py @@ -13,6 +13,7 @@ "ChoiceDelta", "ChoiceDeltaFunctionCall", "ChoiceDeltaToolCall", + "ChoiceDeltaToolCallCustom", "ChoiceDeltaToolCallFunction", "ChoiceLogprobs", ] @@ -49,16 +50,26 @@ class ChoiceDeltaToolCallFunction(BaseModel): """The name of the function to call.""" +class ChoiceDeltaToolCallCustom(BaseModel): + input: Optional[str] = None + """The input for the custom tool call generated by the model.""" + + name: Optional[str] = None + """The name of the custom tool to call.""" + + class ChoiceDeltaToolCall(BaseModel): index: int id: Optional[str] = None """The ID of the tool call.""" + custom: Optional[ChoiceDeltaToolCallCustom] = None + function: Optional[ChoiceDeltaToolCallFunction] = None - type: Optional[Literal["function"]] = None - """The type of the tool. Currently, only `function` is supported.""" + type: Optional[Literal["function", "custom"]] = None + """The type of the tool.""" class ChoiceDelta(BaseModel): diff --git a/tests/lib/chat/test_completions_streaming.py b/tests/lib/chat/test_completions_streaming.py index eb3a0973ac..84383b4a52 100644 --- a/tests/lib/chat/test_completions_streaming.py +++ b/tests/lib/chat/test_completions_streaming.py @@ -1068,6 +1068,39 @@ def streamer(client: OpenAI) -> Iterator[ChatCompletionChunk]: ) +@pytest.mark.respx(base_url=base_url) +def test_stream_custom_tool_call_delta(client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/chat/completions").mock( + return_value=httpx.Response( + 200, + content=( + 'data: {"id":"chatcmpl-custom-tool-delta","object":"chat.completion.chunk","created":1727346161,' + '"model":"gpt-5","choices":[{"index":0,"delta":{"role":"assistant","tool_calls":[{"index":0,' + '"id":"call_custom","type":"custom","custom":{"name":"display_time","input":"August 7th 2025 at ' + '10AM"}}]},"finish_reason":null}],"usage":null}\n\n' + "data: [DONE]\n\n" + ), + headers={"content-type": "text/event-stream"}, + ) + ) + + stream = client.chat.completions.create( + model="gpt-5", + messages=[{"role": "user", "content": "what time is it?"}], + stream=True, + ) + + chunk = next(stream) + tool_calls = chunk.choices[0].delta.tool_calls + assert tool_calls is not None + tool_call = tool_calls[0] + assert tool_call.type == "custom" + assert tool_call.custom is not None + assert tool_call.custom.name == "display_time" + assert tool_call.custom.input == "August 7th 2025 at 10AM" + stream.close() + + @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) def test_stream_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: checking_client: OpenAI | AsyncOpenAI = client if sync else async_client