From 295fa715f9f9d671567ff896545b65e15f87b833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81?= Date: Wed, 3 Jun 2026 01:22:16 +0800 Subject: [PATCH] fix: handle reasoning_content in OpenRouter thinking model responses When using OpenRouter with thinking models (Anthropic Claude, Google Gemini), the API returns a reasoning_content field alongside content in streaming deltas and response messages. The OpenAI completion handlers were only checking the content field, causing empty responses and ValueError when reasoning_content contained the actual response text. This fix adds fallback to reasoning_content in both streaming and non-streaming paths (sync and async), gated with isinstance(str) checks for robustness with mock objects. Fixes #5537 Co-Authored-By: Claude Opus 4.8 --- .../llms/providers/openai/completion.py | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/openai/completion.py b/lib/crewai/src/crewai/llms/providers/openai/completion.py index 0adcd82d6a..9327facfce 100644 --- a/lib/crewai/src/crewai/llms/providers/openai/completion.py +++ b/lib/crewai/src/crewai/llms/providers/openai/completion.py @@ -1661,7 +1661,11 @@ def _handle_completion( if result is not None: return result - content = message.content or "" + content = message.content if isinstance(message.content, str) else None + if content is None: + reasoning = getattr(message, "reasoning_content", None) + content = reasoning if isinstance(reasoning, str) else None + content = content or "" if self.response_format and isinstance(self.response_format, type): try: @@ -1898,10 +1902,17 @@ def _handle_streaming_completion( choice = completion_chunk.choices[0] chunk_delta: ChoiceDelta = choice.delta - if chunk_delta.content: - full_response += chunk_delta.content + delta_text: str | None = None + if isinstance(chunk_delta.content, str): + delta_text = chunk_delta.content + elif isinstance( + getattr(chunk_delta, "reasoning_content", None), str + ): + delta_text = chunk_delta.reasoning_content + if delta_text: + full_response += delta_text self._emit_stream_chunk_event( - chunk=chunk_delta.content, + chunk=delta_text, from_task=from_task, from_agent=from_agent, response_id=response_id_stream, @@ -2051,7 +2062,11 @@ async def _ahandle_completion( if result is not None: return result - content = message.content or "" + content = message.content if isinstance(message.content, str) else None + if content is None: + reasoning = getattr(message, "reasoning_content", None) + content = reasoning if isinstance(reasoning, str) else None + content = content or "" if self.response_format and isinstance(self.response_format, type): try: @@ -2199,10 +2214,17 @@ async def _ahandle_streaming_completion( choice = chunk.choices[0] chunk_delta: ChoiceDelta = choice.delta - if chunk_delta.content: - full_response += chunk_delta.content + delta_text: str | None = None + if isinstance(chunk_delta.content, str): + delta_text = chunk_delta.content + elif isinstance( + getattr(chunk_delta, "reasoning_content", None), str + ): + delta_text = chunk_delta.reasoning_content + if delta_text: + full_response += delta_text self._emit_stream_chunk_event( - chunk=chunk_delta.content, + chunk=delta_text, from_task=from_task, from_agent=from_agent, response_id=response_id_stream,