diff --git a/letta/adapters/letta_llm_request_adapter.py b/letta/adapters/letta_llm_request_adapter.py index 17c3a77f..8ea95680 100644 --- a/letta/adapters/letta_llm_request_adapter.py +++ b/letta/adapters/letta_llm_request_adapter.py @@ -66,7 +66,13 @@ class LettaLLMRequestAdapter(LettaLLMAdapter): self.reasoning_content = [OmittedReasoningContent()] elif self.chat_completions_response.choices[0].message.content: # Reasoning placed into content for legacy reasons - self.reasoning_content = [TextContent(text=self.chat_completions_response.choices[0].message.content)] + # Carry thought_signature on TextContent when ReasoningContent doesn't exist to hold it + self.reasoning_content = [ + TextContent( + text=self.chat_completions_response.choices[0].message.content, + signature=self.chat_completions_response.choices[0].message.reasoning_content_signature, + ) + ] else: # logger.info("No reasoning content found.") self.reasoning_content = None diff --git a/letta/adapters/simple_llm_request_adapter.py b/letta/adapters/simple_llm_request_adapter.py index 7cf5b260..7cec9472 100644 --- a/letta/adapters/simple_llm_request_adapter.py +++ b/letta/adapters/simple_llm_request_adapter.py @@ -81,7 +81,12 @@ class SimpleLLMRequestAdapter(LettaLLMRequestAdapter): if self.chat_completions_response.choices[0].message.content: # NOTE: big difference - 'content' goes into 'content' # Reasoning placed into content for legacy reasons - self.content = [TextContent(text=self.chat_completions_response.choices[0].message.content)] + # Carry thought_signature on TextContent when ReasoningContent doesn't exist to hold it + # (e.g. Gemini 2.5 Flash with include_thoughts=False still returns thought_signature) + orphan_sig = ( + self.chat_completions_response.choices[0].message.reasoning_content_signature if not self.reasoning_content else None + ) + self.content = [TextContent(text=self.chat_completions_response.choices[0].message.content, signature=orphan_sig)] else: self.content = None diff --git a/letta/agents/letta_agent.py b/letta/agents/letta_agent.py index 246f6d3f..98e56205 100644 --- a/letta/agents/letta_agent.py +++ b/letta/agents/letta_agent.py @@ -370,8 +370,12 @@ class LettaAgent(BaseAgent): elif response.choices[0].message.omitted_reasoning_content: reasoning = [OmittedReasoningContent()] elif response.choices[0].message.content: + # Carry thought_signature on TextContent when ReasoningContent doesn't exist to hold it reasoning = [ - TextContent(text=response.choices[0].message.content) + TextContent( + text=response.choices[0].message.content, + signature=response.choices[0].message.reasoning_content_signature, + ) ] # reasoning placed into content for legacy reasons else: self.logger.info("No reasoning content found.") @@ -703,8 +707,12 @@ class LettaAgent(BaseAgent): ) ] elif response.choices[0].message.content: + # Carry thought_signature on TextContent when ReasoningContent doesn't exist to hold it reasoning = [ - TextContent(text=response.choices[0].message.content) + TextContent( + text=response.choices[0].message.content, + signature=response.choices[0].message.reasoning_content_signature, + ) ] # reasoning placed into content for legacy reasons elif response.choices[0].message.omitted_reasoning_content: reasoning = [OmittedReasoningContent()] diff --git a/letta/llm_api/google_vertex_client.py b/letta/llm_api/google_vertex_client.py index 56ed0ae1..ccbee0ca 100644 --- a/letta/llm_api/google_vertex_client.py +++ b/letta/llm_api/google_vertex_client.py @@ -593,6 +593,9 @@ class GoogleVertexClient(LLMClientBase): content=inner_thoughts, tool_calls=[tool_call], ) + if response_message.thought_signature: + thought_signature = base64.b64encode(response_message.thought_signature).decode("utf-8") + openai_response_message.reasoning_content_signature = thought_signature else: openai_response_message.content = inner_thoughts if openai_response_message.tool_calls is None: