PromptingFormat the output

Format the output

Who reads the reply decides its format. A person in a chat window wants short paragraphs; a web page may need Markdown or plain text; your code needs something it can parse every time. This page covers how to get each, and how to make parsing robust.

Describe the format precisely

“Return a list” leaves the model to choose bullets, numbers, commas or JSON. Say exactly what you want, ideally with a template:

Reply in exactly this layout, with no text before or after it:

Verdict: <approve | reject | escalate>
Reason: <one sentence>
Next step: <one sentence, or "none">

Templates work because the model can match them line by line.

Plain text, Markdown or HTML

DestinationAsk for
SMS, push notifications, speech”Plain text only. No Markdown, no bullet characters, no emoji.”
Chat UI that renders Markdown”Use Markdown: short paragraphs, bullet lists where helpful, no headings.”
Email body”Plain text paragraphs separated by blank lines, a greeting and a sign-off.”
HTML fragment”Return an HTML fragment using only <p>, <ul>, <li>, <strong>. No <html> or <body>.”

Models default to Markdown in many contexts. If yours does not render it, say so explicitly, and strip stray ** or # characters in code as a safety net.

JSON for code

Ask for JSON, show its shape, and parse defensively:

Reply with one JSON object and nothing else, in this shape:
{"title": string, "tags": string[] (max 5, lowercase), "reading_time_min": integer}
import json
import os
 
from openai import OpenAI
 
client = OpenAI(base_url="https://app.syntic.ai/v1", api_key=os.environ["SYNTIC_API_KEY"])
 
SYSTEM = """
You prepare metadata for blog articles.
Reply with one JSON object and nothing else, in this shape:
{"title": string, "tags": string[] (max 5, lowercase), "reading_time_min": integer}
""".strip()
 
 
def metadata(article: str) -> dict:
    r = client.chat.completions.create(
        model="Amara Quick", stream=False, max_tokens=200,
        messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": article}],
    )
    text = r.choices[0].message.content or ""
    start, end = text.find("{"), text.rfind("}")
    if start == -1 or end == -1:
        raise ValueError("no JSON object in reply")
    data = json.loads(text[start:end + 1])
    if not isinstance(data.get("title"), str) or not isinstance(data.get("tags"), list):
        raise ValueError("JSON has the wrong shape")
    data["tags"] = [str(t).lower() for t in data["tags"]][:5]
    return data

Defensive habits that prevent most parsing failures:

  • Slice from the first { to the last } in case of a stray sentence or a code fence around the JSON.
  • Validate the shape, not just the syntax. A schema library (Pydantic, Zod, Ajv) makes this one line.
  • Check finish_reason. If it is length, the JSON was cut off; raise max_tokens rather than trying to repair it.
  • Retry once with the error. Send the validator’s message back and ask for a corrected object. See Extract data to JSON.

For structured output that must match a schema, a function definition with a forced tool_choice gives stronger guidance than prose; the guide above shows both approaches.

⚠️
Preview — this feature is being built; the design below is the spec. response_format (json_object and json_schema) will let the server guarantee valid JSON matching your schema. Today the field is ignored. See JSON output.

Tags for mixed output

When you need both free text and fields, ask for XML-style tags. They are easy to extract with a regular expression and tolerate any content inside:

Put your analysis in <analysis> tags and the final recommendation, one of buy / hold / sell,
in <recommendation> tags.

Starting the reply for the model

Some APIs let you end the conversation with a partial assistant message that the model then continues. Chat completions on Syntic does not document this behaviour, so do not depend on it: end the conversation with a user message (or tool results), and steer the opening of the reply with format instructions and examples instead.

Length

  • Ask for length in units the model can count: sentences, bullets, words. “Under 80 words” works better than “brief”.
  • max_tokens is a hard stop, not a target. Set it above the expected length so replies are not cut mid-sentence.
  • If replies are consistently too long, show a shorter example rather than repeating “be concise”.
Was this page helpful?