The chat completions pattern structures a request as a list of role-labeled messages (system, user, assistant) rather than a single flat block of text — the dominant interface style for modern conversational LLM APIs.
Message Roles
| Role | Purpose |
|---|---|
| system | Standing instructions/persona for the whole conversation — see System Prompt |
| user | What the person (or application, on their behalf) is asking — see User Prompt |
| assistant | The model's own prior responses — included so multi-turn conversations have continuity |
A Multi-Turn Example (Conceptual)
messages = [
{"role": "system", "content": "You are a coding assistant."},
{"role": "user", "content": "How do I read a file in Python?"},
{"role": "assistant", "content": "Use open('file.txt', 'r') with
a context manager: with
open('file.txt') as f: ..."},
{"role": "user", "content": "What if the file doesn't exist?"}
]
# The model sees the full conversation so far, including its own
# prior response, and generates the next assistant turn.
Every message from earlier turns is typically resent with each new request — the model itself has no memory between separate API calls (see How LLMs Work); continuity comes entirely from resending history.
Why This Structure Replaced Plain Text Completion
Older "text completion" style APIs took a single raw string and continued it — workable, but required manually formatting conversational structure into that string yourself, and made role separation (which part is a standing instruction vs part of the ongoing exchange) less explicit. The message-based structure makes this explicit and consistent across requests.
Practical Use Case
Any multi-turn chatbot or conversational application is built on this pattern — managing the growing message list (and trimming it as it approaches the context window, see Context Window) is a core piece of chat application architecture.
Common Mistakes
- Forgetting to include prior assistant messages in the history, breaking conversational continuity
- Letting the message list grow unbounded across a long conversation without trimming or summarizing, eventually hitting context or cost limits
- Putting content that should be a system message into a user message (or vice versa), muddying the intended role separation
Interview Relevance
"How does a multi-turn conversation actually work with a stateless LLM API?" — the expected answer: the full (or trimmed/summarized) message history is resent with every request; the model has no memory of its own between calls.
Practice Question
Write the message list you'd send for the 4th turn of a conversation, given 3 prior user/assistant exchanges and a new user question.