-
Notifications
You must be signed in to change notification settings - Fork 20k
xai[patch]: Fix 400 error for AIMessages with tool_calls but no content #34229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…tead of None
xAI's API is stricter than OpenAI's and requires all messages to have at least
an empty content field. When using LangGraph agents with tool calling, AIMessages
can have tool_calls but no content, which causes xAI to return a 400 error:
'Invalid request content: Each message must have at least one content element.'
This fix overrides the message conversion logic in ChatXAI to ensure that
AIMessages with tool_calls or function_call always have at least an empty string
for content, rather than None (which is acceptable for OpenAI but not xAI).
Changes:
- Added _convert_message_to_dict_xai() function that wraps the OpenAI converter
and ensures content is never None for AIMessages with tool calls
- Overrode _get_request_payload() to use the xAI-specific converter
- Added necessary imports from langchain_openai.chat_models.base
Fixes langchain-ai#34140
CodSpeed Performance ReportMerging #34229 will not alter performanceComparing Summary
Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a 400 error that occurs when using xAI's API with AIMessages that have tool_calls but no content. The xAI API differs from OpenAI in that it requires the content field to be an empty string rather than None for such messages.
Key Changes:
- Introduces
_convert_message_to_dict_xaifunction to wrap OpenAI's message converter and ensure content is empty string instead of None for AIMessages with tool_calls/function_call - Overrides
_get_request_payloadto use the xAI-specific message converter for the chat/completions API path
| if self._use_responses_api(payload): | ||
| if self.use_previous_response_id: | ||
| last_messages, previous_response_id = _get_last_messages(messages) | ||
| payload_to_use = last_messages if previous_response_id else messages | ||
| if previous_response_id: | ||
| payload["previous_response_id"] = previous_response_id | ||
| payload = _construct_responses_api_payload(payload_to_use, payload) | ||
| else: | ||
| payload = _construct_responses_api_payload(messages, payload) |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The responses API path doesn't use the xAI-specific message converter. When _construct_responses_api_payload is called, it internally uses _construct_responses_api_input which calls _convert_message_to_dict from langchain_openai, not _convert_message_to_dict_xai. This means AIMessages with tool_calls will still have content=None instead of content="" when using the responses API, potentially causing the same 400 error this PR aims to fix.
Consider either:
- Creating an xAI-specific version of
_construct_responses_api_payloadthat uses_convert_message_to_dict_xai, or - Overriding
_construct_responses_api_inputto use the xAI converter, or - If xAI doesn't support the responses API, explicitly disable it by overriding
_use_responses_apito return False
| def _convert_message_to_dict_xai( | ||
| message: BaseMessage, | ||
| api: Literal["chat/completions", "responses"] = "chat/completions", | ||
| ) -> dict: | ||
| """Convert a LangChain message to dictionary format expected by xAI. | ||
| xAI's API requires that all messages have at least an empty content field, | ||
| unlike OpenAI which allows None for messages with tool_calls. | ||
| Args: | ||
| message: The LangChain message to convert | ||
| api: The API format to use (default: "chat/completions") | ||
| Returns: | ||
| Dictionary representation of the message compatible with xAI's API | ||
| """ | ||
| message_dict = _openai_convert_message_to_dict(message, api=api) | ||
|
|
||
| # xAI requires content to be at least an empty string, not None | ||
| # This is especially important for AIMessages with tool_calls but no content | ||
| if isinstance(message, AIMessage) and message_dict.get("content") is None: | ||
| if "tool_calls" in message_dict or "function_call" in message_dict: | ||
| message_dict["content"] = "" | ||
|
|
||
| return message_dict |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the new _convert_message_to_dict_xai function. There should be unit tests that verify:
- AIMessage with tool_calls but no content gets
content=""instead ofcontent=None - AIMessage with function_call but no content gets
content=""instead ofcontent=None - AIMessage with both tool_calls and content preserves the content
- Other message types are not affected
The existing tests in test_chat_models.py import _convert_message_to_dict from langchain_openai, but don't test the xAI-specific converter. Consider adding tests similar to the existing message conversion tests but using the xAI version.
| xAI's API requires that all messages have at least an empty content field, | ||
| unlike OpenAI which allows None for messages with tool_calls. | ||
| Args: | ||
| message: The LangChain message to convert | ||
| api: The API format to use (default: "chat/completions") | ||
| Returns: | ||
| Dictionary representation of the message compatible with xAI's API |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring is missing type information in the Args and Returns sections. Following Google-style docstring conventions, the Args should include types and the Returns section should specify the dict structure or reference the type hint.
Example:
Args:
message: The LangChain message to convert.
api: The API format to use. Defaults to "chat/completions".
Returns:
Dictionary representation of the message compatible with xAI's API.Note: Types should be in the function signature (which they are), so the docstring can focus on describing the parameters rather than repeating types.
| xAI's API requires that all messages have at least an empty content field, | |
| unlike OpenAI which allows None for messages with tool_calls. | |
| Args: | |
| message: The LangChain message to convert | |
| api: The API format to use (default: "chat/completions") | |
| Returns: | |
| Dictionary representation of the message compatible with xAI's API | |
| xAI's API requires that all messages have at least an empty content field, | |
| unlike OpenAI which allows None for messages with tool_calls. | |
| Args: | |
| message: The LangChain message to convert. | |
| api: The API format to use. Defaults to "chat/completions". | |
| Returns: | |
| Dictionary representation of the message compatible with xAI's API. |
| Overrides the base implementation to use xAI-specific message conversion | ||
| that ensures all messages have at least an empty content field. |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring is incomplete. It's missing the Args and Returns sections that are present in similar functions throughout the codebase. Following Google-style docstring conventions:
"""Prepare the request payload for xAI's API.
Overrides the base implementation to use xAI-specific message conversion
that ensures all messages have at least an empty content field.
Args:
input_: The input to convert into messages.
stop: List of stop sequences. Defaults to None.
**kwargs: Additional keyword arguments to pass to the API.
Returns:
Dictionary containing the request payload for xAI's API.| Overrides the base implementation to use xAI-specific message conversion | |
| that ensures all messages have at least an empty content field. | |
| Overrides the base implementation to use xAI-specific message conversion | |
| that ensures all messages have at least an empty content field. | |
| Args: | |
| input_: The input to convert into messages. | |
| stop: List of stop sequences. Defaults to None. | |
| **kwargs: Additional keyword arguments to pass to the API. | |
| Returns: | |
| Dictionary containing the request payload for xAI's API. |
| _convert_from_v1_to_chat_completions, | ||
| _convert_message_to_dict as _openai_convert_message_to_dict, | ||
| _get_last_messages, | ||
| _use_responses_api, |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of '_use_responses_api' is not used.
| _use_responses_api, |
Fixes #34140
xAI API requires all messages to have at least an empty content field. When using LangGraph agents with tool calling, AIMessage objects can have tool_calls but no content, causing 400 errors.
This fix ensures content is set to empty string instead of None for AIMessages with tool_calls.