From 52222a9f8475b64879d50f8925587206a3ffc774 Mon Sep 17 00:00:00 2001 From: fengxiaohu <975326527@qq.com> Date: Sat, 28 Feb 2026 18:46:15 +0800 Subject: [PATCH 1/2] fix(providers): allow reasoning_content in message history for thinking models --- nanobot/providers/litellm_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nanobot/providers/litellm_provider.py b/nanobot/providers/litellm_provider.py index 7402a2b..03a6c4d 100644 --- a/nanobot/providers/litellm_provider.py +++ b/nanobot/providers/litellm_provider.py @@ -13,7 +13,7 @@ from nanobot.providers.registry import find_by_model, find_gateway # Standard OpenAI chat-completion message keys; extras (e.g. reasoning_content) are stripped for strict providers. -_ALLOWED_MSG_KEYS = frozenset({"role", "content", "tool_calls", "tool_call_id", "name"}) +_ALLOWED_MSG_KEYS = frozenset({"role", "content", "tool_calls", "tool_call_id", "name", "reasoning_content"}) class LiteLLMProvider(LLMProvider): From 5ca386ebf52f36441b44dacd85072d79aea0dd98 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Sat, 28 Feb 2026 17:37:12 +0000 Subject: [PATCH 2/2] fix: preserve reasoning_content and thinking_blocks in session history --- nanobot/agent/context.py | 3 +++ nanobot/agent/loop.py | 4 +++- nanobot/providers/base.py | 1 + nanobot/providers/litellm_provider.py | 4 +++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/nanobot/agent/context.py b/nanobot/agent/context.py index be0ec59..a469bc8 100644 --- a/nanobot/agent/context.py +++ b/nanobot/agent/context.py @@ -150,6 +150,7 @@ Reply directly with text for conversations. Only use the 'message' tool to send content: str | None, tool_calls: list[dict[str, Any]] | None = None, reasoning_content: str | None = None, + thinking_blocks: list[dict] | None = None, ) -> list[dict[str, Any]]: """Add an assistant message to the message list.""" msg: dict[str, Any] = {"role": "assistant", "content": content} @@ -157,5 +158,7 @@ Reply directly with text for conversations. Only use the 'message' tool to send msg["tool_calls"] = tool_calls if reasoning_content is not None: msg["reasoning_content"] = reasoning_content + if thinking_blocks: + msg["thinking_blocks"] = thinking_blocks messages.append(msg) return messages diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index b42c3ba..8da9fcb 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -218,6 +218,7 @@ class AgentLoop: messages = self.context.add_assistant_message( messages, response.content, tool_call_dicts, reasoning_content=response.reasoning_content, + thinking_blocks=response.thinking_blocks, ) for tool_call in response.tool_calls: @@ -238,6 +239,7 @@ class AgentLoop: break messages = self.context.add_assistant_message( messages, clean, reasoning_content=response.reasoning_content, + thinking_blocks=response.thinking_blocks, ) final_content = clean break @@ -451,7 +453,7 @@ class AgentLoop: """Save new-turn messages into session, truncating large tool results.""" from datetime import datetime for m in messages[skip:]: - entry = {k: v for k, v in m.items() if k != "reasoning_content"} + entry = dict(m) role, content = entry.get("role"), entry.get("content") if role == "assistant" and not content and not entry.get("tool_calls"): continue # skip empty assistant messages — they poison session context diff --git a/nanobot/providers/base.py b/nanobot/providers/base.py index 36e9938..25932a3 100644 --- a/nanobot/providers/base.py +++ b/nanobot/providers/base.py @@ -21,6 +21,7 @@ class LLMResponse: finish_reason: str = "stop" usage: dict[str, int] = field(default_factory=dict) reasoning_content: str | None = None # Kimi, DeepSeek-R1 etc. + thinking_blocks: list[dict] | None = None # Anthropic extended thinking @property def has_tool_calls(self) -> bool: diff --git a/nanobot/providers/litellm_provider.py b/nanobot/providers/litellm_provider.py index 0067ae8..aff2ac7 100644 --- a/nanobot/providers/litellm_provider.py +++ b/nanobot/providers/litellm_provider.py @@ -16,7 +16,7 @@ from nanobot.providers.registry import find_by_model, find_gateway # Standard OpenAI chat-completion message keys plus reasoning_content for # thinking-enabled models (Kimi k2.5, DeepSeek-R1, etc.). -_ALLOWED_MSG_KEYS = frozenset({"role", "content", "tool_calls", "tool_call_id", "name", "reasoning_content"}) +_ALLOWED_MSG_KEYS = frozenset({"role", "content", "tool_calls", "tool_call_id", "name", "reasoning_content", "thinking_blocks"}) _ALNUM = string.ascii_letters + string.digits def _short_tool_id() -> str: @@ -271,6 +271,7 @@ class LiteLLMProvider(LLMProvider): } reasoning_content = getattr(message, "reasoning_content", None) or None + thinking_blocks = getattr(message, "thinking_blocks", None) or None return LLMResponse( content=message.content, @@ -278,6 +279,7 @@ class LiteLLMProvider(LLMProvider): finish_reason=choice.finish_reason or "stop", usage=usage, reasoning_content=reasoning_content, + thinking_blocks=thinking_blocks, ) def get_default_model(self) -> str: