openai: Removed tool_calls from completion chunk after other chunks have already been sent. (#29649)

- **Description:** Before sending a completion chunk at the end of an
OpenAI stream, removing the tool_calls as those have already been sent
as chunks.
- **Issue:** -
- **Dependencies:** -
- **Twitter handle:** -

@ccurme as mentioned in another PR

---------

Co-authored-by: Chester Curme <chester.curme@gmail.com>
This commit is contained in:
Marc Ammann
2025-02-07 08:15:52 -07:00
committed by GitHub
parent 0d45ad57c1
commit 5690575f13
2 changed files with 28 additions and 0 deletions

View File

@@ -1483,6 +1483,9 @@ class BaseChatOpenAI(BaseChatModel):
chat_message = chat_result.generations[0].message
if isinstance(chat_message, AIMessage):
usage_metadata = chat_message.usage_metadata
# Skip tool_calls, already sent as chunks
if "tool_calls" in chat_message.additional_kwargs:
chat_message.additional_kwargs.pop("tool_calls")
else:
usage_metadata = None
message = AIMessageChunk(

View File

@@ -1213,3 +1213,28 @@ def test_multi_party_conversation() -> None:
]
response = llm.invoke(messages)
assert "Bob" in response.content
def test_structured_output_and_tools() -> None:
class ResponseFormat(BaseModel):
response: str
explanation: str
llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(
[GenerateUsername], strict=True, response_format=ResponseFormat
)
response = llm.invoke("What weighs more, a pound of feathers or a pound of gold?")
assert isinstance(response.additional_kwargs["parsed"], ResponseFormat)
# Test streaming tool calls
full: Optional[BaseMessageChunk] = None
for chunk in llm.stream(
"Generate a user name for Alice, black hair. Use the tool."
):
assert isinstance(chunk, AIMessageChunk)
full = chunk if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert len(full.tool_calls) == 1
tool_call = full.tool_calls[0]
assert tool_call["name"] == "GenerateUsername"