XML adapter with DSPy's field dialect and current output-completion contract.
One XML-only dialect end to end: the system message renders the interaction
structure as <field>\n{field}\n</field> blocks (no [[ ## ]] markers and
no completed sentinel), user turns wrap every input value in its field tag,
demo/history assistant turns wrap every output value in its field tag, and
the main request ends with DSPy's exact output requirement sentence
("Respond with the corresponding output fields wrapped in XML tags ...").
Parse fills declared output defaults and omitted nullable fields, then returns
an Imp.AdapterParseError of kind :missing_fields if any required output
remains absent. Typed objects, arrays, mappings, and unions use recursive XML. Saxy
parses completed responses; declarations, doctypes, and custom entities are
rejected before parsing.
A reply with none of the requested output tags (prose, a JSON object, Chat
marker sections) is an Imp.AdapterParseError of kind :missing_fields
naming every output, even when every output is optional or has a default, so
the caller's fallback or retry runs instead of a prediction made only of
defaults. A signature that names an output in metadata[:text_field] is
read as Imp.Adapter.Chat reads it: prose is that field, trimmed, a blank
reply is a step that said nothing, and the other outputs take their
defaults. A reply with an output's tag, a [[ ## field ## ]] line, or a
JSON object with an output's key is not prose: it is parsed by tags, and
with no requested tag it is a parse error that the JSON fallback reads. A reply with some requested tags and not others is read as
Chat reads a partial reply: absent outputs take their defaults or nil when
optional, and a required one that is still absent is reported missing.
Rendering is byte-verified against real DSPy 3.2.1 by the golden-trace
differential (test/fixtures/golden_trace/cases.json, xml_* cases).