Choose an LLM output format by its consumer. Markdown suits a report a person will read. JSON suits software that must receive known fields and types. Neither format proves that the answer is true. Keep source support and content accuracy separate from whether the output parses.
Define the consumer before the prompt
| Consumer | Format to test | Acceptance criterion |
|---|---|---|
| A human reviewing a report | Markdown | Clear sections, readable evidence and useful links |
| A program ingesting records | JSON | Expected fields, types and allowed values |
| A person plus an application | JSON record with a Markdown text field | Both the schema and rendered report pass |
| A citation-review workflow | Either, with source-to-claim mapping | Every material assertion has traceable support |
The task here is output formatting. Converting an input webpage to Markdown is a separate operation, and input token savings are a separate experiment. Do not use an HTML-to-Markdown result to claim that a model’s Markdown answer is inherently more accurate.
Represent the same report in two forms
For an illustrative report, Markdown might contain a short answer followed by a Sources section. The equivalent JSON object can hold answer and sources, with each source identifying its URL and the assertion it supports. Keep the content the same while testing each consumer, rather than comparing a rich report to a thin object.
Validate the machine contract
This standard-library example requires exactly the two chosen fields, checks types and rejects an absent source-to-claim description. That is a local application contract, not a model-provider feature guarantee. It does not certify that a source actually supports the assertion.
import json
def consume_report(raw):
obj = json.loads(raw)
if not isinstance(obj, dict) or set(obj) != {"answer", "sources"}:
raise ValueError("Expected exactly answer and sources")
if not isinstance(obj["answer"], str) or not obj["answer"].strip():
raise ValueError("Nonempty answer required")
if not isinstance(obj["sources"], list):
raise ValueError("Sources must be an array")
for source in obj["sources"]:
if not isinstance(source, dict) or set(source) != {"url", "supports"}:
raise ValueError("Unexpected source fields")
if not isinstance(source["url"], str) or not source["url"].startswith("https://"):
raise ValueError("HTTPS source URL required")
if not isinstance(source["supports"], str) or not source["supports"].strip():
raise ValueError("Source-to-claim mapping required")
return obj
Include malformed outputs in the test
Test a valid object, a fenced JSON string, a trailing comma, an unexpected field, sources as a string and a source without supports. Reject invalid shapes and record the failure. Do not silently strip or invent fields to make a response look compliant; if you repair output, keep the original and rerun the checks.
For a human report, test headings, links and the source labels after rendering. A readable report can contain an unsupported assertion just as easily as a valid JSON object. Have a separate support-review state instead of treating successful rendering or parsing as factual verification.
Keep input conversion outside this page’s job
If your source is a webpage, Reader can provide extracted Markdown, which you then validate before generating either output. Use the URL conversion guide and the ingestion checks for that input stage. the HTML versus Markdown token experiment measures representation cost. This page owns the output contract and consumer choice.