From 133857125135fa464dacf32a087a61a6da9f2ab1 Mon Sep 17 00:00:00 2001 From: greymoth-jp Date: Thu, 25 Jun 2026 22:15:46 +0900 Subject: [PATCH] fix(safe_json_dumps): clear seen id for non-serializable objects safe_dumps tracks visited container ids in a seen set to detect circular references, removing each id after the branch returns. The fallback branch for non-serializable objects added the id but never removed it, so the same leaf object appearing twice as siblings (e.g. one tool-call payload reused across two log fields) was wrongly reported as "CircularReference Detected" on its second occurrence Remove the id in the fallback branch to match the other branches, and add a regression test covering repeated sibling occurrences while keeping genuine self-cycle detection --- litellm/litellm_core_utils/safe_json_dumps.py | 1 + .../test_safe_json_dumps.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/litellm/litellm_core_utils/safe_json_dumps.py b/litellm/litellm_core_utils/safe_json_dumps.py index 154306d01b8..ab8aa478be8 100644 --- a/litellm/litellm_core_utils/safe_json_dumps.py +++ b/litellm/litellm_core_utils/safe_json_dumps.py @@ -58,6 +58,7 @@ def safe_dumps(data: Any, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH) -> str: return result else: # Fall back to string conversion for non-serializable objects. + seen.remove(id(obj)) try: return strip_null_bytes(str(obj)) except Exception: diff --git a/tests/test_litellm/litellm_core_utils/test_safe_json_dumps.py b/tests/test_litellm/litellm_core_utils/test_safe_json_dumps.py index 74574370e46..5b54cf5aee0 100644 --- a/tests/test_litellm/litellm_core_utils/test_safe_json_dumps.py +++ b/tests/test_litellm/litellm_core_utils/test_safe_json_dumps.py @@ -80,6 +80,27 @@ def test_unserializable_object(): assert result == "Unserializable Object" +def test_repeated_unserializable_sibling_is_not_circular(): + # The same non-serializable object appearing twice as siblings is not a + # circular reference and both occurrences must be serialized. + class Stringable: + def __str__(self): + return "value" + + obj = Stringable() + + assert json.loads(safe_dumps([obj, obj])) == ["value", "value"] + assert json.loads(safe_dumps({"a": obj, "b": obj})) == { + "a": "value", + "b": "value", + } + + # A genuine self-cycle must still be reported. + cycle = {} + cycle["self"] = cycle + assert json.loads(safe_dumps(cycle))["self"] == "CircularReference Detected" + + def test_non_standard_dict_keys(): try: # Test handling of dictionaries with non-standard keys