Skip to content

fix: handle multiple root-level XML elements from non-conformant LLMs#409

Open
octo-patch wants to merge 1 commit intoNevaMind-AI:mainfrom
octo-patch:fix/issue-407-xml-multiple-root-elements
Open

fix: handle multiple root-level XML elements from non-conformant LLMs#409
octo-patch wants to merge 1 commit intoNevaMind-AI:mainfrom
octo-patch:fix/issue-407-xml-multiple-root-elements

Conversation

@octo-patch
Copy link
Copy Markdown

Fixes #407

Problem

Some LLMs (e.g. llama3.2 via Ollama) do not follow the expected XML output format precisely. Instead of wrapping all extracted memories in a single <item> root element, they produce one <item> element per memory with natural-language numbering in between:

1. <item>
    <memory>...</memory>
</item>

2. <item>
    <memory>...</memory>
</item>

_find_xml_boundaries extracts the slice from the first <item> to the last </item>, which contains multiple top-level tags separated by text (2. , 3. , …). ET.fromstring then raises ParseError: junk after document element because the first root element closes before the end of the string.

Solution

Two minimal changes to _parse_memory_type_response_xml:

  1. Wrap-and-retry: catch ParseError from the first ET.fromstring call, wrap the content in a synthetic <_root_> element, and retry. This makes the multi-<item> layout valid XML without affecting well-formed single-root responses.

  2. findalliter: switch from root.findall("memory") to root.iter("memory") so that <memory> nodes are found regardless of depth—direct children of the root (normal case) or grandchildren via <item> wrappers (wrapped multi-root case).

Testing

Manually validated against the XML snippets from the issue report. The retry path successfully parses multiple top-level <item> elements and returns all contained <memory> items.

…fixes NevaMind-AI#407)

Some LLMs (e.g. llama3.2 via Ollama) return one <item> element per
memory instead of wrapping all memories in a single root element.
This produces "junk after document element" when the slice captured
by _find_xml_boundaries contains several top-level tags separated by
numbering text (e.g. "2. ").

Two changes to _parse_memory_type_response_xml:

1. Wrap-and-retry: if ET.fromstring raises ParseError on the first
   attempt, wrap the content in a synthetic <_root_> element and
   retry.  Valid single-root responses are unaffected.

2. findall to iter: use root.iter("memory") so that <memory> elements
   are located regardless of whether they are direct children of the
   root (normal case) or grandchildren via intermediate <item> tags
   (wrapped multi-root case).
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.

[BUG] Failed to parse XML

1 participant