fix: handle chatty LLM responses in JSON parsing (#4525)

Co-authored-by: utkarsh240799 <utkarsh240799@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Utkarsh
2026-03-26 16:39:17 +05:30
committed by GitHub
co-authored by utkarsh240799 Claude Opus 4.6
parent 669ed184e4
commit 2a59c9fd99
3 changed files with 258 additions and 14 deletions
+18 -12
View File
@@ -516,15 +516,15 @@ class Memory(MemoryBase):
)
try:
response = remove_code_blocks(response)
if not response.strip():
cleaned_response = remove_code_blocks(response)
if not cleaned_response.strip():
new_retrieved_facts = []
else:
try:
# First try direct JSON parsing
new_retrieved_facts = json.loads(response, strict=False)["facts"]
new_retrieved_facts = json.loads(cleaned_response, strict=False)["facts"]
except json.JSONDecodeError:
# Try extracting JSON from response using built-in function
# Try extracting JSON from response (handles chatty LLM output)
extracted_json = extract_json(response)
new_retrieved_facts = json.loads(extracted_json, strict=False)["facts"]
new_retrieved_facts = normalize_facts(new_retrieved_facts)
@@ -589,8 +589,11 @@ class Memory(MemoryBase):
logger.warning("Empty response from LLM, no memories to extract")
new_memories_with_actions = {}
else:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response, strict=False)
try:
new_memories_with_actions = json.loads(remove_code_blocks(response), strict=False)
except json.JSONDecodeError:
extracted_json = extract_json(response)
new_memories_with_actions = json.loads(extracted_json, strict=False)
except Exception as e:
logger.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
@@ -1590,15 +1593,15 @@ class AsyncMemory(MemoryBase):
response_format={"type": "json_object"},
)
try:
response = remove_code_blocks(response)
if not response.strip():
cleaned_response = remove_code_blocks(response)
if not cleaned_response.strip():
new_retrieved_facts = []
else:
try:
# First try direct JSON parsing
new_retrieved_facts = json.loads(response, strict=False)["facts"]
new_retrieved_facts = json.loads(cleaned_response, strict=False)["facts"]
except json.JSONDecodeError:
# Try extracting JSON from response using built-in function
# Try extracting JSON from response (handles chatty LLM output)
extracted_json = extract_json(response)
new_retrieved_facts = json.loads(extracted_json, strict=False)["facts"]
new_retrieved_facts = normalize_facts(new_retrieved_facts)
@@ -1666,8 +1669,11 @@ class AsyncMemory(MemoryBase):
logger.warning("Empty response from LLM, no memories to extract")
new_memories_with_actions = {}
else:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response, strict=False)
try:
new_memories_with_actions = json.loads(remove_code_blocks(response), strict=False)
except json.JSONDecodeError:
extracted_json = extract_json(response)
new_memories_with_actions = json.loads(extracted_json, strict=False)
except Exception as e:
logger.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
+8 -2
View File
@@ -124,14 +124,20 @@ def remove_code_blocks(content: str) -> str:
def extract_json(text):
"""
Extracts JSON content from a string, removing enclosing triple backticks and optional 'json' tag if present.
If no code block is found, returns the text as-is.
If no code block is found, attempts to locate JSON by finding the first '{' and last '}'.
If that also fails, returns the text as-is.
"""
text = text.strip()
match = re.search(r"```(?:json)?\s*(.*?)\s*```", text, re.DOTALL)
if match:
json_str = match.group(1)
else:
json_str = text # assume it's raw JSON
start_idx = text.find("{")
end_idx = text.rfind("}")
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
json_str = text[start_idx : end_idx + 1]
else:
json_str = text
return json_str