fix(providers): sanitize thinking_blocks by provider and harden content normalization
This commit is contained in:
@@ -78,6 +78,12 @@ class LLMProvider(ABC):
|
|||||||
result.append(clean)
|
result.append(clean)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(content, dict):
|
||||||
|
clean = dict(msg)
|
||||||
|
clean["content"] = [content]
|
||||||
|
result.append(clean)
|
||||||
|
continue
|
||||||
|
|
||||||
result.append(msg)
|
result.append(msg)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ from litellm import acompletion
|
|||||||
from nanobot.providers.base import LLMProvider, LLMResponse, ToolCallRequest
|
from nanobot.providers.base import LLMProvider, LLMResponse, ToolCallRequest
|
||||||
from nanobot.providers.registry import find_by_model, find_gateway
|
from nanobot.providers.registry import find_by_model, find_gateway
|
||||||
|
|
||||||
# Standard OpenAI chat-completion message keys plus reasoning_content for
|
# Standard chat-completion message keys.
|
||||||
# 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"})
|
_ANTHROPIC_EXTRA_KEYS = frozenset({"thinking_blocks"})
|
||||||
_ALNUM = string.ascii_letters + string.digits
|
_ALNUM = string.ascii_letters + string.digits
|
||||||
|
|
||||||
def _short_tool_id() -> str:
|
def _short_tool_id() -> str:
|
||||||
@@ -158,11 +158,20 @@ class LiteLLMProvider(LLMProvider):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
def _extra_msg_keys(original_model: str, resolved_model: str) -> frozenset[str]:
|
||||||
|
"""Return provider-specific extra keys to preserve in request messages."""
|
||||||
|
spec = find_by_model(original_model) or find_by_model(resolved_model)
|
||||||
|
if (spec and spec.name == "anthropic") or "claude" in original_model.lower() or resolved_model.startswith("anthropic/"):
|
||||||
|
return _ANTHROPIC_EXTRA_KEYS
|
||||||
|
return frozenset()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_messages(messages: list[dict[str, Any]], extra_keys: frozenset[str] = frozenset()) -> list[dict[str, Any]]:
|
||||||
"""Strip non-standard keys and ensure assistant messages have a content key."""
|
"""Strip non-standard keys and ensure assistant messages have a content key."""
|
||||||
|
allowed = _ALLOWED_MSG_KEYS | extra_keys
|
||||||
sanitized = []
|
sanitized = []
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
clean = {k: v for k, v in msg.items() if k in _ALLOWED_MSG_KEYS}
|
clean = {k: v for k, v in msg.items() if k in allowed}
|
||||||
# Strict providers require "content" even when assistant only has tool_calls
|
# Strict providers require "content" even when assistant only has tool_calls
|
||||||
if clean.get("role") == "assistant" and "content" not in clean:
|
if clean.get("role") == "assistant" and "content" not in clean:
|
||||||
clean["content"] = None
|
clean["content"] = None
|
||||||
@@ -193,6 +202,7 @@ class LiteLLMProvider(LLMProvider):
|
|||||||
"""
|
"""
|
||||||
original_model = model or self.default_model
|
original_model = model or self.default_model
|
||||||
model = self._resolve_model(original_model)
|
model = self._resolve_model(original_model)
|
||||||
|
extra_msg_keys = self._extra_msg_keys(original_model, model)
|
||||||
|
|
||||||
if self._supports_cache_control(original_model):
|
if self._supports_cache_control(original_model):
|
||||||
messages, tools = self._apply_cache_control(messages, tools)
|
messages, tools = self._apply_cache_control(messages, tools)
|
||||||
@@ -203,7 +213,7 @@ class LiteLLMProvider(LLMProvider):
|
|||||||
|
|
||||||
kwargs: dict[str, Any] = {
|
kwargs: dict[str, Any] = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"messages": self._sanitize_messages(self._sanitize_empty_content(messages)),
|
"messages": self._sanitize_messages(self._sanitize_empty_content(messages), extra_keys=extra_msg_keys),
|
||||||
"max_tokens": max_tokens,
|
"max_tokens": max_tokens,
|
||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user