From 83ccdf61862ffcd665fb096922087b4924b15d9a Mon Sep 17 00:00:00 2001 From: muskliu <862259098@qq.com> Date: Sun, 22 Feb 2026 00:14:22 +0800 Subject: [PATCH] fix(provider): filter empty text content blocks causing API 400 When MCP tools return empty content, messages may contain empty-string text blocks. OpenAI-compatible providers reject these with HTTP 400. Changes: - Add _prevent_empty_text_blocks() to filter empty text items from content lists and handle empty string content - For assistant messages with tool_calls, set content to None (valid) - For other messages, replace with '(empty)' placeholder - Only copy message dict when modification is needed (zero-copy path for normal messages) Co-Authored-By: nanobot --- nanobot/providers/custom_provider.py | 52 ++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/nanobot/providers/custom_provider.py b/nanobot/providers/custom_provider.py index f190ccf..ec5d48b 100644 --- a/nanobot/providers/custom_provider.py +++ b/nanobot/providers/custom_provider.py @@ -19,8 +19,12 @@ class CustomProvider(LLMProvider): async def chat(self, messages: list[dict[str, Any]], tools: list[dict[str, Any]] | None = None, model: str | None = None, max_tokens: int = 4096, temperature: float = 0.7) -> LLMResponse: - kwargs: dict[str, Any] = {"model": model or self.default_model, "messages": messages, - "max_tokens": max(1, max_tokens), "temperature": temperature} + kwargs: dict[str, Any] = { + "model": model or self.default_model, + "messages": self._prevent_empty_text_blocks(messages), + "max_tokens": max(1, max_tokens), + "temperature": temperature, + } if tools: kwargs.update(tools=tools, tool_choice="auto") try: @@ -45,3 +49,47 @@ class CustomProvider(LLMProvider): def get_default_model(self) -> str: return self.default_model + + @staticmethod + def _prevent_empty_text_blocks(messages: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Filter empty text content blocks that cause provider 400 errors. + + When MCP tools return empty content, messages may contain empty-string + text blocks. Most providers (OpenAI-compatible) reject these with 400. + This method filters them out before sending to the API. + """ + patched: list[dict[str, Any]] = [] + for msg in messages: + content = msg.get("content") + + # Empty string content + if isinstance(content, str) and content == "": + clean = dict(msg) + if msg.get("role") == "assistant" and msg.get("tool_calls"): + clean["content"] = None + else: + clean["content"] = "(empty)" + patched.append(clean) + continue + + # List content — filter out empty text items + if isinstance(content, list): + filtered = [ + item for item in content + if not (isinstance(item, dict) + and item.get("type") in {"text", "input_text", "output_text"} + and item.get("text") == "") + ] + if filtered != content: + clean = dict(msg) + if filtered: + clean["content"] = filtered + elif msg.get("role") == "assistant" and msg.get("tool_calls"): + clean["content"] = None + else: + clean["content"] = "(empty)" + patched.append(clean) + continue + + patched.append(msg) + return patched