-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(adapter): rewrite XMLAdapter for nested-data support #8482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(adapter): rewrite XMLAdapter for nested-data support #8482
Conversation
Refactors the to provide robust support for complex data structures, including nested Pydantic models and lists. The original adapter was limited to flat key-value pairs and used a brittle regex-based parsing approach. This commit replaces that implementation with a more resilient one based on Python's . Key changes: - **Recursive XML Parsing:** Implemented to recursively parse nested XML into a Python dictionary, correctly handling repeated tags for lists. - **Recursive XML Formatting:** Implemented to serialize nested dictionaries and Pydantic models into well-formed XML strings, ensuring correct formatting for few-shot examples. - **Pydantic Validation:** The method now uses for robust validation and type casting of the parsed XML against the . - **Comprehensive Testing:** Added new unit tests for deeply nested models, empty lists, malformed XML, and a corrected end-to-end test with a to validate the full workflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
This is essentially proposing a different approach of using XML to get structured output, right now we just wrap fields by XML tags, while the field values are still in JSON. The parse
logic you wrote here deals with the case where the values are structured in XML.
There are two problems here:
- The PR change doesn't include the instruction to prompt LM to generate XML for nested fields.
- I don't really know if LM is good at following detailed XML rule, but I am fairly sure that it does a better job at producing JSON value according to JSON schema than XML, because that's what they are trained for.
With that, I don't feel we should proceed in the full XML path, but I will let @okhat to make the call since he has done some research on this before.
@chenmoneygithub : Thanks for your quick response! Regarding performance of XML vs JSON
Regarding Instruction to Prompt LM to generate nested XML.
Thanks once again for your time in going through the PR and recommending changes. @okhat : Could you please give a green light for me to work further on this PR? I believe that a proper XML handling adapter would be a great value add for many usecases including ours. Thank you for DSPy! |
@Bhuvanesh09 This is something pretty interesting while complex, if you are interested in this path, here are some guidelines:
We have seen that mixing XML and JSON doing all right, so to avoid causing regression we need to collect numerical evidence that strict XML is beneficial. |
This is very interesting, @Bhuvanesh09 . Thanks @chenmoneygithub for discussing it with @Bhuvanesh09 . Q: How will this handle Lists? |
Hi @okhat! I'm currently working on collecting evidence to support my claims by curating a small custom dataset. Trying for minimal problems that focus on extracting structured information from natural language. How lists are handled in this case.Our XMLAdapter handles lists through repeated XML elements, which is the standard XML approach: 1. List Definition in Signatureclass TaskList(dspy.Signature):
topic: str = dspy.InputField(desc="Topic to generate tasks for")
tasks: list[str] = dspy.OutputField(desc="List of 5 specific tasks") 2. Expected XML Output Structure<tasks>Task 1 description</tasks>
<tasks>Task 2 description</tasks>
<tasks>Task 3 description</tasks>
... 3. XML Attributes Don't Affect ParsingOur parser also correctly handles XML attributes like <tasks id="1">Task 1 description</tasks>
<tasks id="2">Task 2 description</tasks>
<tasks id="3">Task 3 description</tasks> Side Note: Earlier when we didn't use DSPy, making the LLM add attributes to the tags like above actually helped the model to adhere to the number of outputs we ask it, since it is implictly able to keep track. For instance, when we ask it to generate 5 summary points, then even weaker models are able to be more consistent in giving 5 summary points. It might be in future scope to add this to parser's prompt to suggest models to do the same within DSPy. Since the fruitful discussion with @chenmoneygithub, I've made changes to the code and included better instructions for output formatting. In my very early experiments, the results look good for this new parser but I'm yet to compare it with the older one. Hoping to wrap these experiments this weekend and share updates soon! |
Hi @chenmoneygithub and @okhat, Thank you for the discussion on this PR. I've conducted a series of experiments to provide data-driven evidence for the proposed changes, focusing on how different models interact with the adapters. TL;DR: The current The full experiment notebook and dataset are available for complete reproducibility: <gist_link> The Experiment: Testing Adapter RobustnessMy experiment was designed to test how effectively each adapter could elicit correct, structured output from various language models.
The design rationale was to decouple the model's core NLU capabilities from its ability to adhere to a specific formatting schema. This allows us to see if a failure is due to the model not understanding the text or the adapter not providing clear instructions. Experimental ResultsThe results clearly show that the Parsing Accuracy (%)The
Exact Accuracy (%)The improved adapter's clarity also leads to higher final accuracy for the smaller models.
The "Why": Prompt Complexity vs. ClarityThe difference in performance comes down to prompt complexity. Current Legacy Adapter's behaviour:The legacy adapter requires a high level of instruction-following capability by asking for a JSON object inside an XML tag. Only the strongest model tested (Qwen 4B) could handle this reliably. <address>
{address} # note: the value you produce must adhere to the JSON schema: {"type": "object", ...}
</address> Example Failure:
The prompt itself to the Old Adapter was of the form: New Adapter's behaviour:The ImprovedXMLParser lowers the barrier to entry. It provides simple, direct instructions to generate pure, nested XML, a task that smaller models can easily accomplish. Example inspect_history of even the weakest model i.e. Qwen 0.6B being able to follow it: ConclusionThe ImprovedXMLParser makes the XML feature more robust and accessible, especially for developers using smaller, more efficient models. By simplifying the instructions, it ensures reliable structured output without requiring a 4B+ parameter model. This change solves a key usability issue and makes the feature work as expected across a broader ecosystem of LLMs. I'm happy to discuss this further and make any additional changes. Thanks a lot for taking the time to go through my PR. |
Closes #8481
TL;DR
xml.etree.ElementTree
.List
, mixed data types.Motivation
XMLAdapter
failed on any hierarchical XML (see #8481). Users were forced to switch toJSONAdapter
, losing the readability benefits of XML. This PR brings feature-parity withJSONAdapter
.What changed
1. Parsing & Formatting
<(\w+)>(.*?)</\1>
ElementTree
traversalAdapterParseError
with context2. New helpers
_xml_to_dict(element) → Any
_dict_to_xml(data, tag) → str
3. Removed
_parse_field_value()
– superseded by full XML mapping.Backwards compatibility
Example (was failing, now passes)
Tests added
Risks / limitations
(<tag attr="…">)
Happy to engage in conversations, and grateful for this chance to contribute to DSPy.