# How to Format Messages for aisuite Chat Completions API

> Learn how to format messages for the aisuite Chat Completions API. Understand the role, content, and tool call fields for effective API integration and efficient communication.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-16

---

**The aisuite Chat Completions API accepts a list of Message objects (or equivalent dictionaries) containing `role`, `content`, and optional tool call fields, which the library converts to provider-specific formats via `OpenAICompliantMessageConverter`.**

Formatting messages correctly for the `andrewyng/aisuite` library ensures seamless interoperability across multiple LLM providers. The Chat Completions endpoint expects structured conversation data that mirrors the OpenAI Chat API specification, using specific data models defined in the framework to represent chat turns, tool calls, and function parameters.

## Core Data Models

### The Message Class

At the heart of aisuite's message formatting is the **`Message`** class located in [`aisuite/framework/message.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/framework/message.py). This model represents a single turn in the conversation and contains the following fields:

- **`role`**: Required. Must be one of `"system"`, `"user"`, `"assistant"`, or `"tool"`
- **`content`**: Required. The text content of the message
- **`tool_calls`**: Optional. A list of `ChatCompletionMessageToolCall` objects when the assistant invokes functions
- **`reasoning_content`**: Optional. Stores internal reasoning or "thinking" content that can be displayed separately from the main response
- **`refusal`**: Optional. Indicates if the model refused to generate content

### Tool Call and Function Models

For function calling workflows, aisuite uses two additional models defined in the same file:

- **`ChatCompletionMessageToolCall`**: Represents a specific tool invocation with fields for `id`, `type` (always `"function"`), and `function`
- **`Function`**: Contains the `name` of the function being called and its `arguments` as a JSON-serializable string

## Message Format Rules

When assembling your conversation history, adhere to these structural requirements:

1. **Chronological ordering** – Messages must appear in sequence, starting with the system prompt (if used), followed by alternating user and assistant turns
2. **Role restrictions** – Only four roles are supported: `"system"`, `"user"`, `"assistant"`, and `"tool"`
3. **Tool call syntax** – Assistant messages that trigger functions must include a `tool_calls` list containing `ChatCompletionMessageToolCall` objects
4. **Tool result formatting** – Responses from executed functions must be sent back as messages with `role="tool"` and `content` containing the stringified result
5. **Reasoning handling** – Place internal reasoning chains in `reasoning_content`; the converter automatically strips the `refusal` field when processing requests

## How Message Conversion Works

Before sending to the underlying provider, `Chat.completions.create()` forwards your message list to **`OpenAICompliantMessageConverter.convert_request`** in [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py). This converter transforms `Message` objects (or plain dictionaries) into the exact JSON structure required by the specific LLM provider, handling role mappings and parameter normalization automatically.

## Code Examples

### Basic User-Assistant Exchange

```python
from aisuite.framework.message import Message

messages = [
    Message(role="system", content="You are a helpful assistant."),
    Message(role="user", content="What is the capital of France?"),
]

response = client.chat.completions.create(
    model="openai:gpt-4o-mini",
    messages=messages,
)
print(response.choices[0].message.content)   # → Paris

```

### Function Calling Workflow

```python
from aisuite.framework.message import Message, ChatCompletionMessageToolCall, Function

# 1️⃣ Assistant asks the model to run a tool

assistant_msg = Message(
    role="assistant",
    content="",
    tool_calls=[
        ChatCompletionMessageToolCall(
            id="tool-1",
            function=Function(name="search_web", arguments='{"query":"latest AI news"}')
        )
    ],
)

messages = [
    Message(role="system", content="You can search the web for up‑to‑date info."),
    Message(role="user", content="Give me the latest AI headlines."),
    assistant_msg,
]

# 2️⃣ Send to the model – provider will see the tool spec in the payload

response = client.chat.completions.create(
    model="openai:gpt-4o-mini",
    messages=messages,
    tools=[  # declare the available tool for the provider

        {
            "type": "function",
            "function": {
                "name": "search_web",
                "description": "Search the web and return results.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string", "description": "Search query"}
                    },
                    "required": ["query"]
                },
            },
        }
    ],
)

# 3️⃣ The provider returns a tool call – extract it:

tool_call = response.choices[0].message.tool_calls[0]

# Execute the real tool (here we just mock a result)

tool_result = {"results": ["AI‑generated art beats humans", "New GPT‑5 rumors"]}

# 4️⃣ Feed the tool result back as a new message

tool_msg = Message(
    role="tool",
    content=str(tool_result),   # content must be JSON‑serialisable string

    tool_calls=[tool_call]      # keep the original call id for correlation

)

messages.append(tool_msg)

# 5️⃣ Continue the conversation

final = client.chat.completions.create(
    model="openai:gpt-4o-mini",
    messages=messages,
)
print(final.choices[0].message.content)   # assistant now replies using the web data

```

### Handling Reasoning Content

```python
msg = Message(
    role="assistant",
    content="The current year is 2026.",
    reasoning_content="I need to check my knowledge cutoff and calculate from there."
)

```

## Summary

- **Use the `Message` class** from [`aisuite/framework/message.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/framework/message.py) to ensure type safety and proper structure
- **Supported roles** are strictly limited to `"system"`, `"user"`, `"assistant"`, and `"tool"`
- **Tool calls** require `ChatCompletionMessageToolCall` and `Function` objects with proper ID correlation
- **Plain dictionaries** work interchangeably with `Message` objects due to the `OpenAICompliantMessageConverter`
- **Tool results** must be sent back with `role="tool"` and stringified content to continue the function calling loop

## Frequently Asked Questions

### What roles are supported in aisuite Message objects?

The `role` field accepts four values: `"system"` for initial instructions, `"user"` for human inputs, `"assistant"` for model responses, and `"tool"` for function execution results. These align with the OpenAI Chat API specification and are validated during conversion.

### Can I use plain Python dictionaries instead of Message objects?

Yes. The `OpenAICompliantMessageConverter.convert_request` method in [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py) handles both `Message` instances and raw dictionaries, converting them to the provider's required JSON format before transmission.

### How do I handle tool results in the conversation flow?

After executing a function, append a new `Message` with `role="tool"` to your messages list. Set `content` to the stringified JSON result and include the original `tool_calls` data to maintain correlation with the assistant's request, as shown in the function calling example.

### Where does aisuite handle message format conversion?

Conversion logic resides in [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py), specifically within the `OpenAICompliantMessageConverter` class. This utility strips unsupported fields like `refusal` and optionally converts tool results to strings when `tool_results_as_strings` is enabled, ensuring compatibility across different LLM providers.