XML and Markdown in the API
On this page
Structuring your instructions for the model
As long as a prompt fits in three sentences, plain text works fine. Problems arise when it grows: multiple context blocks, conditional rules, user data to process, an output schema. Everything blends together, the model confuses what to read with what to apply, and your team loses track of which line to modify. XML tags and Markdown syntax address this dual need: readable for the model, maintainable for humans. Mistral explicitly recommends their use in production prompts.
Their effectiveness comes down to a simple reason. Language models were trained on billions of documents containing HTML, XML, and Markdown; these formats are familiar because they appear massively in training data. The model clearly identifies sections and their roles, while you get a prompt a colleague can audit at a glance.
XML tags to delimit sections
The most cost-effective use of XML tags is isolating data from instructions. In the following example, the system prompt carries the rules and expected schema; the user message wraps the document in a <document> tag, preventing the model from mistaking the report content for an instruction.
from mistralai.client import Mistral
import os
client = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))
system_prompt = """You are a document analysis assistant.
<instructions>
Analyze the provided document and extract the requested information.
Respond only in JSON.
If information is missing, use null.
</instructions>
<output_schema>
{
"title": "string",
"author": "string",
"date": "string (YYYY-MM-DD)",
"summary": "string (max 100 words)",
"keywords": ["string"]
}
</output_schema>"""
user_prompt = """<document>
Q1 2026 Quarterly Report — Mistral AI
Written by Arthur Mensch, CEO
Publication date: April 1, 2026
This report presents the results for the first quarter of 2026. The company experienced significant growth in its API business with a 300% increase in daily requests.
</document>
Extract the metadata from this document."""
response = client.chat.complete(
model="mistral-large-latest",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.0
)
print(response.choices[0].message.content)
In production, a small vocabulary of tags recurs constantly and benefits from standardization across your prompt base:
<instructions>...</instructions> <!-- Rules for the model -->
<context>...</context> <!-- Background information -->
<document>...</document> <!-- Text to analyze -->
<examples>...</examples> <!-- Few-shot examples -->
<constraints>...</constraints> <!-- Limits and prohibitions -->
<output_format>...</output_format> <!-- Expected output format -->
<user_data>...</user_data> <!-- User data -->
Markdown for readable instructions
Markdown addresses another need: hierarchizing rules. Where XML cuts the prompt into zones, Markdown headings, lists, and bold text organize the content within a zone. This technical writing prompt thus places a role, writing rules, a mandatory numbered structure, and a list of prohibitions on the same visual plane—a human writer could work with this, which is the right test.
system_prompt = """# Technical Writing Assistant
## Your role
You write technical documentation for REST APIs.
## Writing rules
- **Use "you"** throughout the text
- Short sentences (max 25 words)
- One concept per paragraph
- Inline code in backticks: `GET /api/v1/users`
## Mandatory structure
1. **Introduction** — Context and objective of the endpoint
2. **Request** — Method, URL, headers, parameters
3. **Response** — HTTP codes, JSON body, examples
4. **Errors** — Possible error codes and resolution
## Prohibitions
- No undefined jargon
- No "simply" or "easily"
- No examples with unrealistic fictional data"""
Combining both formats
For complex prompts, the most effective practice is nesting the two: XML delimits the major zones, while Markdown structures the interior of each. The multilingual support agent below uses <persona> for identity and <rules> for behavior—with two Markdown subsections inside, including a conditional escalation rule—and <knowledge_base> for perishable facts, which become the only part to update when the software version changes.
system_prompt = """# Multilingual Support Agent
<persona>
You are the assistant for DataFlow, an ETL software publisher.
You speak French, English, and Spanish.
</persona>
<rules>
## Behavior rules
- Respond in the user's language
- Identify the problem category: **bug**, **question**, **feature request**
- For bugs: collect software version, OS, exact error message
## Escalation
- If the problem persists after 3 exchanges → propose a support ticket
- If the client mentions data loss → immediate escalation (high priority)
</rules>
<knowledge_base>
- Current version: DataFlow 4.2.1 (April 2026)
- Supported OS: Windows 11, Ubuntu 22.04+, macOS 14+
- Documentation: docs.dataflow.io
</knowledge_base>"""
A reusable prompt template
The next step is stopping manual prompt writing and starting to build them. The function below takes a document and a list of questions, then produces the complete messages. Two details deserve your attention: the instructions require a justifying citation for every answer and an explicit fallback phrase when information is absent, which sharply reduces hallucinations on contractual documents; and the temperature at 0.1 leaves just enough flexibility for phrasing without allowing interpretation.
def build_analysis_prompt(document: str, questions: list[str]) -> list:
"""Builds a structured analysis prompt using XML."""
system = """# Document Analyst
<instructions>
Read the provided document and answer the questions asked.
Every answer must cite the part of the document that justifies it.
If the answer is not in the document, reply "Not mentioned in the document."
</instructions>
<output_format>
For each question, respond in this format:
**Q{n}**: [your answer]
> Citation: "[excerpt from document]"
</output_format>"""
questions_formatted = "\n".join(
f"{i+1}. {q}" for i, q in enumerate(questions)
)
user = f"""<document>
{document}
</document>
<questions>
{questions_formatted}
</questions>"""
return [
{"role": "system", "content": system},
{"role": "user", "content": user}
]
# Usage
messages = build_analysis_prompt(
document="Service agreement contract between...",
questions=[
"What is the contract duration?",
"What are the late payment penalties?",
"Is there a non-compete clause?"
]
)
response = client.chat.complete(
model="mistral-large-latest",
messages=messages,
temperature=0.1
)
A few habits make these prompts durable. Name your tags descriptively—<document> says what it contains, <data> says nothing and you will waste five minutes on every review. Close them systematically: an open tag without a closing tag blurs the boundary between two sections, and the model may then read your instructions as content to analyze. Finally, indent for human readability; the model does fine without it, but the person picking up the prompt in six months will not.
Key takeaways
- XML tags isolate data from instructions in the prompt
- Markdown structures rules and lists for readability
- Combine both for complex production prompts
- Name your tags descriptively and close them systematically
- Structured prompts are easier to maintain, test, and version