From 6aed4265b79536acb041ccd4ba214c26338e32b6 Mon Sep 17 00:00:00 2001 From: dxtime Date: Wed, 25 Feb 2026 20:58:59 +0800 Subject: [PATCH 1/2] Fix: The base64 images are stored in the session history, causing context overflow. --- nanobot/agent/loop.py | 5 ++++- nanobot/utils/helpers.py | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 8be8e51..5d3c492 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -427,12 +427,15 @@ class AgentLoop: def _save_turn(self, session: Session, messages: list[dict], skip: int) -> None: """Save new-turn messages into session, truncating large tool results.""" from datetime import datetime + from nanobot.utils import helpers for m in messages[skip:]: entry = {k: v for k, v in m.items() if k != "reasoning_content"} if entry.get("role") == "tool" and isinstance(entry.get("content"), str): content = entry["content"] if len(content) > self._TOOL_RESULT_MAX_CHARS: - entry["content"] = content[:self._TOOL_RESULT_MAX_CHARS] + "\n... (truncated)" + entry["content"] = content[: self._TOOL_RESULT_MAX_CHARS] + "\n... (truncated)" + if entry.get("role") == "user": + entry["content"] = helpers.strip_base64_images(entry["content"]) entry.setdefault("timestamp", datetime.now().isoformat()) session.messages.append(entry) session.updated_at = datetime.now() diff --git a/nanobot/utils/helpers.py b/nanobot/utils/helpers.py index 62f80ac..c977473 100644 --- a/nanobot/utils/helpers.py +++ b/nanobot/utils/helpers.py @@ -2,6 +2,7 @@ from pathlib import Path from datetime import datetime +from typing import Any def ensure_dir(path: Path) -> Path: @@ -78,3 +79,28 @@ def parse_session_key(key: str) -> tuple[str, str]: if len(parts) != 2: raise ValueError(f"Invalid session key: {key}") return parts[0], parts[1] + +def strip_base64_images(content: str | list[dict[str, Any]]) -> str | list[dict[str, Any]]: + """Strip base64 image data from message content, replacing with text placeholder.""" + if not isinstance(content, list): + return content + + new_content = [] + for item in content: + if not isinstance(item, dict): + new_content.append(item) + continue + + if item.get("type") == "image_url": + url = item.get("image_url", {}).get("url", "") + if url.startswith("data:image/") and ";base64," in url: + new_content.append({"type": "text", "text": "[image]"}) + continue + new_content.append(item) + + text_parts = [c["text"] for c in new_content if isinstance(c, dict) and c.get("type") == "text"] + if len(new_content) == 1 and not text_parts: + return new_content[0] if new_content else "" + if text_parts and len(new_content) == len(text_parts): + return "\n".join(text_parts) + return new_content \ No newline at end of file From a1440cf4cbe1296836d4debe883bd173690503d4 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Thu, 26 Feb 2026 02:43:45 +0000 Subject: [PATCH 2/2] refactor: inline base64 image stripping in _save_turn --- nanobot/agent/loop.py | 13 +++++++++---- nanobot/utils/helpers.py | 29 +---------------------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 51f965d..b402ea0 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -463,15 +463,20 @@ class AgentLoop: def _save_turn(self, session: Session, messages: list[dict], skip: int) -> None: """Save new-turn messages into session, truncating large tool results.""" from datetime import datetime - from nanobot.utils import helpers for m in messages[skip:]: entry = {k: v for k, v in m.items() if k != "reasoning_content"} if entry.get("role") == "tool" and isinstance(entry.get("content"), str): content = entry["content"] if len(content) > self._TOOL_RESULT_MAX_CHARS: - entry["content"] = content[: self._TOOL_RESULT_MAX_CHARS] + "\n... (truncated)" - if entry.get("role") == "user": - entry["content"] = helpers.strip_base64_images(entry["content"]) + entry["content"] = content[:self._TOOL_RESULT_MAX_CHARS] + "\n... (truncated)" + if entry.get("role") == "user" and isinstance(entry.get("content"), list): + entry["content"] = [ + {"type": "text", "text": "[image]"} if ( + c.get("type") == "image_url" + and c.get("image_url", {}).get("url", "").startswith("data:image/") + ) else c + for c in entry["content"] + ] entry.setdefault("timestamp", datetime.now().isoformat()) session.messages.append(entry) session.updated_at = datetime.now() diff --git a/nanobot/utils/helpers.py b/nanobot/utils/helpers.py index c977473..06d8fd5 100644 --- a/nanobot/utils/helpers.py +++ b/nanobot/utils/helpers.py @@ -2,8 +2,6 @@ from pathlib import Path from datetime import datetime -from typing import Any - def ensure_dir(path: Path) -> Path: """Ensure a directory exists, creating it if necessary.""" @@ -78,29 +76,4 @@ def parse_session_key(key: str) -> tuple[str, str]: parts = key.split(":", 1) if len(parts) != 2: raise ValueError(f"Invalid session key: {key}") - return parts[0], parts[1] - -def strip_base64_images(content: str | list[dict[str, Any]]) -> str | list[dict[str, Any]]: - """Strip base64 image data from message content, replacing with text placeholder.""" - if not isinstance(content, list): - return content - - new_content = [] - for item in content: - if not isinstance(item, dict): - new_content.append(item) - continue - - if item.get("type") == "image_url": - url = item.get("image_url", {}).get("url", "") - if url.startswith("data:image/") and ";base64," in url: - new_content.append({"type": "text", "text": "[image]"}) - continue - new_content.append(item) - - text_parts = [c["text"] for c in new_content if isinstance(c, dict) and c.get("type") == "text"] - if len(new_content) == 1 and not text_parts: - return new_content[0] if new_content else "" - if text_parts and len(new_content) == len(text_parts): - return "\n".join(text_parts) - return new_content \ No newline at end of file + return parts[0], parts[1] \ No newline at end of file