fix(types): strip is_error in the existing cache_control pass for the pyright ceiling

The previous in-place strip left two basedpyright breaches over the
merge-base ceiling (base 8683f1418e): the bare second
`filter_value_from_dict(message, "is_error")` call added a
reportArgumentType (AllMessageValues is not assignable to the bare-dict
parameter), and `message.get("role")` on the AllMessageValues union added
two reportUnknownMemberType. Fold is_error into the existing
cache_control pass via a new `*extra_keys` parameter on
filter_value_from_dict (no new call site, so no new diagnostic); the
unconditional strip is behavior-equivalent because is_error only ever
exists as the litellm-internal carrier on tool messages, which real
OpenAI would reject as an unknown field anyway. The adapter side reads
is_error through a membership guard instead of a second unknown-typed
`.get`.

Verification: type_check_gate OK (every rule within ceiling, base
8683f1418e), type_discipline_gate OK, targeted per-file basedpyright
delta vs merge-base zero on all four touched files, adapter + gpt
transformation test suites 316 passed.

Generated-by: GLM-5.3-Flash (ZCode)
This commit is contained in:
ggbdpq 2026-10-09 12:34:55 +08:00
parent 016348c6c2
commit 328866c020
3 changed files with 11 additions and 14 deletions

View file

@ -1807,11 +1807,11 @@ def strip_litellm_internal_message_fields(message: AllMessageValues) -> AllMessa
)
def filter_value_from_dict(dictionary: dict, key: str, depth: int = 0) -> Any:
def filter_value_from_dict(dictionary: dict, key: str, *extra_keys: str, depth: int = 0) -> Any:
"""
Filters a value from a dictionary
Goes through the nested dict and removes the key if it exists
Goes through the nested dict and removes the key (plus any extra keys) if it exists
"""
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
@ -1822,14 +1822,14 @@ def filter_value_from_dict(dictionary: dict, key: str, depth: int = 0) -> Any:
keys: Final = list(dictionary.keys())
for k in keys:
v = dictionary[k]
if k == key:
if k == key or k in extra_keys:
del dictionary[k]
elif isinstance(v, dict):
filter_value_from_dict(v, key, depth + 1)
filter_value_from_dict(v, key, *extra_keys, depth=depth + 1)
elif isinstance(v, list):
for item in v:
if isinstance(item, dict):
filter_value_from_dict(item, key, depth + 1)
filter_value_from_dict(item, key, *extra_keys, depth=depth + 1)
return dictionary

View file

@ -529,7 +529,7 @@ class LiteLLMAnthropicMessagesAdapter:
role="tool",
tool_call_id=content.get("tool_use_id", ""),
content=self._tool_result_content(content.get("content")),
**({"is_error": True} if content.get("is_error") is True else {}),
**({"is_error": True} if "is_error" in content and content["is_error"] is True else {}),
)
self._add_cache_control_if_applicable(content, tool_result, model)
tool_message_list.append(tool_result)

View file

@ -410,17 +410,14 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig):
from litellm.types.llms.openai import ChatCompletionToolParam
for i, message in enumerate(messages):
# is_error: litellm's anthropic adapter carries tool_result.is_error
# on the intermediate tool message so error-aware translations can
# restore it; real OpenAI has no such field (#44979). Stripped in
# the same in-place pass as cache_control.
messages[i] = cast(
AllMessageValues,
filter_value_from_dict(message, "cache_control"),
filter_value_from_dict(message, "cache_control", "is_error"),
)
if message.get("role") == "tool":
# litellm's anthropic adapter carries tool_result.is_error on
# the intermediate tool message so error-aware translations
# can restore it; real OpenAI has no such field (#44979).
# filter_value_from_dict mutates the message dict in place, so
# the list slot keeps referencing the updated message.
filter_value_from_dict(message, "is_error")
if tools is not None:
for i, tool in enumerate(tools):
tools[i] = cast(