From 646fd537407d614ecde33ef3f361024569f6b174 Mon Sep 17 00:00:00 2001 From: shivam Date: Sat, 12 Sep 2026 18:17:52 +0000 Subject: [PATCH] fix(bedrock): hash-suffix tool ids whose chars were rewritten so they cannot collide Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/litellm_core_utils/prompt_templates/factory.py | 6 +++--- ...test_litellm_core_utils_prompt_templates_factory.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index c3591e62a20..f7f4a964c9b 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1522,11 +1522,11 @@ _BEDROCK_TOOL_USE_ID_HASH_LEN: Final = 8 def _sanitize_bedrock_tool_use_id(tool_use_id: str) -> str: """ Bedrock Converse requires toolUseId to match [a-zA-Z0-9_.:-]+ and be at most 64 chars. - Over-long ids are truncated and suffixed with a short hash of the original so two ids - that only differ past the cut still map to distinct values. + Ids that need rewriting get a short hash of the original appended so two ids that only + differ in a replaced char or past the cut still map to distinct values. """ sanitized: Final = re.sub(r"[^a-zA-Z0-9_.:-]", "_", tool_use_id) or "tool_use_id" - if len(sanitized) <= _BEDROCK_TOOL_USE_ID_MAX_LEN: + if sanitized == tool_use_id and len(sanitized) <= _BEDROCK_TOOL_USE_ID_MAX_LEN: return sanitized digest: Final = hashlib.sha256(tool_use_id.encode()).hexdigest()[:_BEDROCK_TOOL_USE_ID_HASH_LEN] return f"{sanitized[: _BEDROCK_TOOL_USE_ID_MAX_LEN - _BEDROCK_TOOL_USE_ID_HASH_LEN - 1]}_{digest}" diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index 32445b8b6ec..fe8a9bd5205 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -2264,6 +2264,16 @@ def test_bedrock_tool_use_id_truncation_keeps_distinct_ids_distinct(): assert all(len(i) == 64 for i in ids) +def test_bedrock_tool_use_id_replaced_chars_do_not_collide_with_existing_ids(): + ids = { + _convert_to_bedrock_tool_call_result({"tool_call_id": i, "role": "tool", "name": "f", "content": "ok"})[ + "toolResult" + ]["toolUseId"] + for i in ("call|x", "call_x") + } + assert len(ids) == 2 + + def test_bedrock_tool_call_invoke_concatenated_json_long_id_stays_within_limit(): long_id = "call_" + "q" * 62 result = _convert_to_bedrock_tool_call_invoke(