How to Write LLM Prompts That Actually Work
What "writing an LLM" actually means
Most people typing "how to write LLM" aren't trying to build a language model from scratch — that requires a research team, a GPU cluster, and months of pretraining. What they actually need is one of two things: how to write prompts that get good output from an existing model, or how to write code that calls an LLM from an application. This article covers both, because in practice you rarely do one without the other.
If you're building a product feature — a summarizer, a support bot, a code reviewer — your job is to write the instructions (the prompt) and the plumbing (the API call) that connect your app to a model like Claude. Get the prompt wrong and the output is vague or inconsistent. Get the integration wrong and you'll fight timeouts, malformed JSON, and untraceable costs. Below is a practical approach to both.
Writing prompts that produce consistent output
A prompt is not a question, it's a specification. Treat it like one.
Structure your prompt in layers
- Role and constraints — who the model is acting as, and what it must never do.
- Task — the specific action, stated as an instruction, not a question.
- Input — the actual data to work on, clearly delimited.
- Output format — exact shape you expect back (JSON schema, bullet list, word limit).
You are a support ticket classifier. Only output valid JSON.
Never include explanations outside the JSON object.
Task: classify the ticket below into one of: billing, bug, feature_request, other.
Ticket:
"""
{ticket_text}
"""
Output format:
{"category": "...", "confidence": 0-1}
This structure works because it removes ambiguity. The model isn't guessing what you want — you told it exactly what the response should look like, which cuts down on retries and post-processing.
Use examples instead of more adjectives
Adding words like "concise" or "professional" to a prompt has limited effect. Showing one or two examples of the input/output pair you want (few-shot prompting) is far more reliable, especially for formatting-sensitive tasks like extraction or classification.
Separate system instructions from user content
Don't concatenate instructions and user data into one blob of text — it makes prompt injection trivial and makes your prompt hard to maintain. Use the system prompt for persistent rules and the user/message content for the actual input. Every modern LLM API, including Claude's Messages API, supports this separation natively.
Iterate against real inputs, not one example
Write the prompt, run it against 10–20 real (or realistic) inputs, and look for where it breaks — edge cases, ambiguous inputs, unexpected formatting. Adjust the prompt, not the output parser, whenever possible. Parsers that "fix" bad model output tend to mask prompt problems that resurface later.
Writing the code that calls the LLM
Once the prompt is solid, you need reliable code around it. At minimum, that means:
- Setting a low temperature for tasks that need consistency (classification, extraction) and a higher one for creative tasks.
- Handling streaming if you want responses to appear incrementally in a UI.
- Validating structured output before you trust it downstream.
- Logging token usage so cost doesn't surprise you in production.
A basic call against Claude via SubToAPI looks like this:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 300,
"system": "You are a support ticket classifier. Only output valid JSON.",
"messages": [
{"role": "user", "content": "Ticket: My invoice charged me twice this month."}
]
}'
The same call in JavaScript, with streaming enabled for a chat-style UI:
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 500,
stream: true,
messages: [{ role: "user", content: userMessage }]
})
});
If your workflow needs the model to take actions — call a function, query a database, hit an internal API — you write a tool definition alongside the prompt rather than trying to parse free text for intent. That's a more reliable pattern than asking the model to "output the command to run" in plain English.
Writing prompts for multi-step or agentic tasks
For tasks with several steps (research, then summarize, then draft), it's usually more reliable to write separate prompts for each step and pass the output of one as input to the next, rather than writing one giant prompt asking the model to do everything at once. Each step is easier to test, easier to debug, and easier to swap out later.
Common mistakes when writing LLM prompts
- Vague success criteria. If you can't describe what a "good" output looks like, the model can't produce one reliably.
- No output format. Free text is fine for chat; it's a liability for anything you parse programmatically.
- Overloading one prompt with too many tasks. Split it.
- Ignoring token limits. Long system prompts eat into your context budget and cost more per call — trim to what actually changes model behavior.
- Not testing on adversarial input. Users will paste garbage, empty strings, and unrelated text. Your prompt should degrade gracefully, not crash your parser.
Getting from prototype to production
Writing a good prompt in a chat playground is the easy part. Turning it into a dependable feature means wrapping it in an API you can call from your backend, track usage on, and scale across a team without juggling personal accounts. SubToAPI turns your existing Claude access into an HTTPS API with application keys, streaming, and usage metadata, so the prompt you wrote is the same prompt running in production — see the quickstart or the messages docs to get from a working prompt to a deployed endpoint.
questions
Do I need to train my own model to "write an LLM"? No, unless you're doing research or have a very specific need pretrained models can't meet. Nearly all practical LLM work is writing prompts and application code that call an existing model like Claude.
What's the difference between a prompt and a system prompt? A system prompt sets persistent rules and role instructions for the whole conversation; the user prompt or message contains the specific task or data for that turn. Keeping them separate makes prompts easier to maintain and harder to manipulate.
How do I make LLM output consistent enough to parse? Specify an exact output format (like JSON with a schema), give one or two examples, use a low temperature, and validate the response before using it — see /docs/tools for structured tool-based output patterns.