Skip to main content
Lesson 2 of 16 · Chat Completions: Mastering the API

Roles and Messages

Lesson 2 of 16 Updated November 2, 2026 Developer
On this page
  1. The Core of the Conversational Protocol
  2. The Four Roles
  3. Complete Structure of a Multi-Turn Exchange
  4. Structured Content
  5. The Prefix Flag
  6. Key Takeaways

The Core of the Conversational Protocol

The Chat Completions API doesn’t accept raw text. It expects an array of structured messages, each assigned a specific role. This role system lets the model distinguish who is speaking, what the context is, and how it should respond. The same text—“Always respond in JSON”—carries different weight depending on whether it arrives as a developer instruction or a user input. Mastering this structure is what separates a coherent chatbot from a system that drifts off-topic after three exchanges.

The Four Roles

Mistral’s API recognizes four distinct roles, each serving a precise function in the conversation.

The system Role

The system message defines the model’s overall behavior. Placed first, it is read before any exchange occurs. This is where you set the tone, constraints, and context for your application. Imagine an assistant designed for a law firm; the system message is equivalent to the briefing note you would hand a new associate before their first case.

messages = [
    {
        "role": "system",
        "content": "You are a legal assistant specializing in French law. "
                   "Respond precisely and cite relevant legal articles. "
                   "If you are unsure, state it explicitly."
    }
]

The developer manages this message, never the end user, and it never appears in your application’s interface.

The user Role

Next comes the human user’s message: the question, instruction, or text to process. This is the only part of the conversation your users control directly.

messages.append({
    "role": "user",
    "content": "What are the statutes of limitations in civil law?"
})

The assistant Role

This role holds the model’s previous responses. It becomes essential once the conversation exceeds a single turn. Without it, a user asking “What about commercial debt?” would get an off-topic response because the model wouldn’t know what was being discussed.

messages.append({
    "role": "assistant",
    "content": "In French civil law, the standard statute of limitations is 5 years..."
})

The tool Role

The final role comes into play during function calling. When the model requests execution of an external function—querying a weather API, for example—you run that function on your end and return the result under this role, referencing the ID of the specific call:

messages.append({
    "role": "tool",
    "content": '{"temperature": 22, "city": "Paris"}',
    "tool_call_id": "call_abc123"
})

Complete Structure of a Multi-Turn Exchange

Let’s see these roles in action during a real conversation where the user refines their request after an initial response:

from mistralai.client import Mistral
import os

client = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))

conversation = [
    {
        "role": "system",
        "content": "You are a French chef. Propose simple and tasty recipes."
    },
    {
        "role": "user",
        "content": "I want to make a dessert with apples."
    }
]

# First turn
response = client.chat.complete(
    model="mistral-large-latest",
    messages=conversation
)

assistant_reply = response.choices[0].message.content
print(assistant_reply)

# Add response to history
conversation.append({"role": "assistant", "content": assistant_reply})

# Second turn
conversation.append({
    "role": "user",
    "content": "Can you adapt this recipe for 8 people?"
})

response = client.chat.complete(
    model="mistral-large-latest",
    messages=conversation
)

print(response.choices[0].message.content)

Notice what this code actually does: at every turn, it sends back the entire history. The model has no persistent memory between requests. “This recipe” only makes sense because the previous response is still in the array. You are entirely responsible for managing the conversation state.

Structured Content

The content field accepts two forms. The most common is a simple string. But as soon as you transmit something other than text—an image to describe, for example—you switch to a list of typed blocks:

# Simple content (text)
{"role": "user", "content": "Describe this image."}

# Structured content (multimodal)
{"role": "user", "content": [
    {"type": "text", "text": "Describe this image."},
    {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
]}

The Prefix Flag

Mistral offers a unique feature: the prefix flag. You write the beginning of the assistant’s response yourself, and the model is forced to continue from there. This is incredibly effective when you need to parse the output with zero margin for error.

response = client.chat.complete(
    model="mistral-large-latest",
    messages=[
        {"role": "system", "content": "You always respond in JSON."},
        {"role": "user", "content": "List 3 European capitals."},
        {"role": "assistant", "content": '{"capitals": [', "prefix": True}
    ]
)

The model resumes generation after {"capitals": [, immediately eliminating the risk of seeing “Sure, here is your JSON:” appear before the opening brace. In production, this format guarantee is often worth more than an instruction repeated three times in the system message.

Adopt two habits right away. Keep the system message concise: one well-written paragraph yields better results than two pages of rules, part of which the model will eventually ignore. And never invent fake assistant responses to steer the model, with one exception—few-shot learning, which we will cover in Lesson 10. For long conversations, send the full history, but monitor the context window and prune the oldest turns before you exceed it.

Key Takeaways

  • Four roles: system (behavior), user (request), assistant (response), tool (function result)
  • The system message goes first and defines the application framework
  • The model has no memory: send the full history with every request
  • The prefix flag forces the start of the response to control output format