How to Build a System-User-Assistant Message List in Python

Use dataclasses to model a chat conversation and build the system/user/assistant message list expected by LLM APIs.

Easy Python 3.9+ Aug 9, 2026 AI & LLM integration patterns 13 views 0 copies

Python code

33 lines
Python 3.9+
from dataclasses import dataclass, field
from typing import List


@dataclass
class Message:
    role: str
    content: str


@dataclass
class Conversation:
    messages: List[Message] = field(default_factory=list)

    def add_system(self, content: str) -> None:
        self.messages.append(Message(role="system", content=content))

    def add_user(self, content: str) -> None:
        self.messages.append(Message(role="user", content=content))

    def add_assistant(self, content: str) -> None:
        self.messages.append(Message(role="assistant", content=content))

    def to_dict_list(self) -> List[dict]:
        return [{"role": m.role, "content": m.content} for m in self.messages]


if __name__ == "__main__":
    convo = Conversation()
    convo.add_system("You are a helpful coding assistant.")
    convo.add_user("How do I sort a list in Python?")
    convo.add_assistant("Use the sorted() function or the list.sort() method.")
    print(convo.to_dict_list())

Output

stdout
[{'role': 'system', 'content': 'You are a helpful coding assistant.'}, {'role': 'user', 'content': 'How do I sort a list in Python?'}, {'role': 'assistant', 'content': 'Use the sorted() function or the list.sort() method.'}]

How it works

The Conversation dataclass wraps a list of Message objects, each storing a role and content string. The add_system, add_user, and add_assistant methods append correctly typed messages to the list, keeping the call site simple and readable. The to_dict_list method converts each Message into a plain dict with role and content keys, which is the exact structure most LLM APIs (OpenAI, Anthropic, etc.) expect. Using a field(default_factory=list) avoids the classic mutable-default-argument bug that would share one list across all Conversation instances. This pattern keeps your chat history explicit and easy to extend with extra fields like name or tool_calls later.

Common mistakes

  • Using `field(default=[])` instead of `field(default_factory=list)` — this shares one mutable list across all instances.
  • Forgetting to include the system message, which many LLM APIs require for behavior instructions.
  • Hard-coding role strings as raw literals in every call instead of using dedicated add_ methods, making typos like 'Asssistant' likely.

Variations

  1. Use a list of dicts directly and append with `{'role': 'system', 'content': ...}` for quick scripts.
  2. Add a `from_dict_list` classmethod to load existing chat logs back into a `Conversation`.

Real-world use cases

  • Assembling the multi-turn message payload for an OpenAI or Anthropic chat completion call.
  • Logging and replaying conversation history in a support chatbot for debugging or fine-tuning datasets.
  • Building prompt templates that interleave system instructions, user questions, and prior assistant answers for few-shot prompting.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from AI & LLM integration patterns

Related tutorials and quizzes for this topic.