diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index 0243141eda2..343f245c2ae 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -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 diff --git a/litellm/llms/anthropic/pass_through/adapters/transformation.py b/litellm/llms/anthropic/pass_through/adapters/transformation.py index ffdb424a1f3..b4a78993159 100644 --- a/litellm/llms/anthropic/pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/pass_through/adapters/transformation.py @@ -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) diff --git a/litellm/llms/openai/chat/gpt_transformation.py b/litellm/llms/openai/chat/gpt_transformation.py index 5692f385ebd..24323fc7b8a 100644 --- a/litellm/llms/openai/chat/gpt_transformation.py +++ b/litellm/llms/openai/chat/gpt_transformation.py @@ -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(