Repository navigation
fix(mem_os): strip only anchored markdown fences in clean_json_response (#2420) - #2489
RerankerGuo wants to merge 1 commit into
Conversation
…se (MemTensor#2420) clean_json_response previously used a global `response.replace("```json", "").replace("```", "")`, which removed every triple-backtick sequence in the response — including ones that lived inside JSON string values. Any LLM response whose payload contained a code snippet (a fenced Python block, an unclosed partial fence, etc.) came out silently corrupted before json.loads, so suggestions and chat metadata carrying markdown snippets were returned to callers with mangled content. Both api/handlers/chat_handler.py and api/handlers/suggestion_handler.py pipe responses through clean_json_response, so the bug surface was the entire suggestion and chat pipeline. The fix replaces the global string replace with two anchored regex substitutions that only strip the outer fences: - opening fence (^\`\`\`[^\\n`]*\\n?) — covers the optional info string (`json` or otherwise) and trailing newline - closing fence (\\n?\`\`\`\\s*$) — at the very end only Interior \`\`\` sequences inside a JSON string value are preserved verbatim. As a side effect the function now correctly handles responses that have only an opening or only a closing fence (the original implementation would leave those stranded mid-string). - Add module-level _JSON_FENCE_OPEN_RE / _JSON_FENCE_CLOSE_RE - Rewrite clean_json_response body to use anchored re.sub - Four new tests in tests/mem_os/test_format_utils.py pin the contract: the issue reproducer (interior fenced code preserved), unclosed-fence-in-value, opening-only response, and closing-only response Fixes MemTensor#2420.
🤖 Open Code ReviewTarget: PR #2489 🔍 OpenCodeReview found 1 issue(s) in this PR. 1.
|
✅ Automated Test Results: PASSEDAll tests passed (12/12 executed). memos_python_core/changed-repo-python: 12/12. Duration: 13s Branch: |
Summary
clean_json_responseused a globalresponse.replace("```json", "").replace("```", ""), which removed every triple-backtick sequence — including ones inside JSON string values. Any LLM response whose payload contained a code snippet came out silently corrupted beforejson.loads.Root cause
src/memos/mem_os/utils/format_utils.py:1416(pre-fix):Reproducer from the issue:
Both
api/handlers/chat_handler.pyandapi/handlers/suggestion_handler.pypipe responses through this function, so the bug surface was the entire chat-metadata and suggestion pipeline.Fix
Replace the global string replace with two anchored regex substitutions that only strip the outer fences:
re.compile(r"^```[^\n`]*\n?")— covers the optional info string (jsonor otherwise) and trailing newlinere.compile(r"\n?```\s*$")— at the very end onlyInterior
```sequences inside a JSON string value are preserved verbatim. As a side effect the function now correctly handles responses that have only an opening or only a closing fence (the original implementation would leave those stranded mid-string).Tests
Added four cases to
tests/mem_os/test_format_utils.py:test_clean_json_response_preserves_internal_fenced_code— the issue reproducer (red against unpatchedmain)test_clean_json_response_preserves_unclosed_fence_in_value— a value containing a single unclosed fencetest_clean_json_response_handles_only_opening_fence— best-effort handling when the closing fence is missingtest_clean_json_response_handles_only_closing_fence— best-effort handling when the opening fence is missingExisting tests (single backticks in JSON content, plain JSON, complex JSON, etc.) all continue to pass —
test_clean_json_response_preserves_internal_backtickswas a good seed for the new tests.Verification
Ruff:
ruff format --checkandruff checkboth clean on touched files (the 2 pre-existing findings were auto-fixed byruff check --fixduring this PR — both wereE501 line-too-longinclean_json_responseitself, not introduced by this change).Out of scope
Closes #2420