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 <noreply@anthropic.com>
This commit is contained in:
muskliu
2026-02-22 00:14:22 +08:00
parent 88ca2e0530
commit 83ccdf6186

View File

@@ -19,8 +19,12 @@ class CustomProvider(LLMProvider):
async def chat(self, messages: list[dict[str, Any]], tools: list[dict[str, Any]] | None = None, 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: 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, kwargs: dict[str, Any] = {
"max_tokens": max(1, max_tokens), "temperature": temperature} "model": model or self.default_model,
"messages": self._prevent_empty_text_blocks(messages),
"max_tokens": max(1, max_tokens),
"temperature": temperature,
}
if tools: if tools:
kwargs.update(tools=tools, tool_choice="auto") kwargs.update(tools=tools, tool_choice="auto")
try: try:
@@ -45,3 +49,47 @@ class CustomProvider(LLMProvider):
def get_default_model(self) -> str: def get_default_model(self) -> str:
return self.default_model 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