Skip to content

Strip a Markdown code fence before json.loads on model replies - #79

Open
rogermsc wants to merge 1 commit into
BAI-LAB:mainfrom
rogermsc:fix/strip-code-fence-before-json-loads
Open

Strip a Markdown code fence before json.loads on model replies#79
rogermsc wants to merge 1 commit into
BAI-LAB:mainfrom
rogermsc:fix/strip-code-fence-before-json-loads

Conversation

@rogermsc

Copy link
Copy Markdown

Hi — following up on the email exchange with Prof. Bai, who kindly pointed me
here to submit security-related fixes. This is the one of my three findings that
has a patch to submit; the other two are design trade-offs in a mechanism
working as intended, and I am not filing those as issues.

The problem

gpt_generate_multi_summary parses the model's reply with a bare json.loads.
Many current models wrap JSON in a ```json ... ``` fence by default, even
when the prompt asks for bare JSON — the prompt here does ask ("Please directly
output the JSON array, without adding any other content"). Such a reply is a
correct answer, but json.loads rejects it, the except branch fires, and the
caller loses the structure it asked for.

Why it is worth more than the warning line

When the multi-summary returns no themes, updater.py files the entire batch as
one session under a constant fallback:

fallback_summary = "General conversation segment from short-term memory."
fallback_keywords = []  # Use empty keywords since multi-summary failed

Those two fields are the basis of two later decisions:

  • search_sessions scores a session on semantic_sim_score + keyword_alpha * s_topic_keywords
  • insert_pages_into_session picks which existing session a new batch joins with the same shape

So every batch that hits the fallback shares one identical summary embedding and
carries no keywords. It is both harder to retrieve and liable to be merged by a
similarity it did not earn — and a deployment can lose the topic structure of
its mid-term memory for a whole run without anything failing. The trigger is an
ordinary model default, not an adversarial input.

The change

Adds strip_code_fence() and applies it at all six bare json.loads sites
on model output, across all five copies of utils.py (memoryos-pypi,
memoryos-playground, memoryos-mcp, memoryos-chromadb, eval). Fixing one
copy would leave four shipped variants with the same defect. The extra site in
memoryos-chromadb is the profile-analysis parse, which has the same exposure.

Text that is not fenced is returned unchanged, so a deployment whose model
does not fence sees no behaviour change at all.

Checking it

The helper carries six doctests, so no test infrastructure is needed:

python -m doctest memoryos-pypi/utils.py -v

I verified before pushing: all five files compile, 30 doctests pass, the helper
is byte-identical across the five copies, and eval/utils.py's CRLF line
endings are preserved so the diff stays at the changed lines.

Happy to split this per-directory, drop the eval or chromadb copies, or
rename the helper if you would prefer any of those.

gpt_generate_multi_summary parses the model's reply with a bare
json.loads. Many current models wrap JSON in a ```json ... ``` fence by
default, even when the prompt asks for bare JSON. That reply is a correct
answer, but json.loads rejects it, the except branch fires, and the
caller loses the structure it asked for.

For the multi-summary path the cost is more than the warning. When it
returns no themes, updater.py files the whole batch as one session under
a constant fallback summary with an empty keyword list. Those two fields
are the basis of two later decisions -- search_sessions scores a session
on semantic_sim + keyword_alpha * s_topic_keywords, and
insert_pages_into_session picks which existing session a new batch joins
with the same shape -- so every batch that hits the fallback shares one
identical summary embedding and carries no keywords. It becomes both
harder to retrieve and liable to be merged by a similarity it did not
earn, and nothing fails while it happens.

Adds strip_code_fence() and applies it at all six bare json.loads sites
on model output, across all five copies of utils.py (memoryos-pypi,
memoryos-playground, memoryos-mcp, memoryos-chromadb, eval). Fixing one
copy would leave four shipped variants with the same defect.

Text that is not fenced is returned unchanged, so a deployment whose
model does not fence sees no behaviour change at all. The helper carries
six doctests:

    python -m doctest memoryos-pypi/utils.py -v
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant