# Standardizing Message Formats Across Different LLM Providers in AISuite

> Standardize LLM message formats in AISuite with OpenAICompliantMessageConverter. Seamlessly integrate OpenAI, Groq, and Anthropic for consistent API handling.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: architecture
- Published: 2026-08-04

---

**AISuite unifies disparate LLM provider APIs into a single OpenAI-compatible schema using the `OpenAICompliantMessageConverter` class and the `Message` model, ensuring consistent request and response handling across OpenAI, Groq, Anthropic, and other backends.**

Standardizing message formats across different LLM providers eliminates the need for provider-specific boilerplate when switching between models like GPT-4, Llama, or Claude. The `andrewyng/aisuite` repository achieves this through a lightweight abstraction layer that normalizes all provider interactions to an OpenAI-compatible contract. This architecture allows developers to work with a single internal representation while the framework handles translation to each vendor's native SDK requirements.

## The Unified Message Model in AISuite

At the core of AISuite's standardization strategy is the **unified message model** defined in [`aisuite/framework/message.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/framework/message.py). The `Message` class (lines 26-34) serves as the provider-agnostic data structure that captures all fields required by modern chat completion APIs, including `content`, `role`, `tool_calls`, and optional reasoning fields.

This model acts as the canonical internal representation regardless of which LLM provider you target. Whether communicating with OpenAI, Groq, or Anthropic, your application code constructs `Message` objects while AISuite handles the downstream transformation. Related types like `ChatCompletionMessageToolCall`, `Function`, and `TranscriptionResult` extend this schema to support tool use and multimodal interactions without breaking the unified interface.

## Converting Requests and Responses with OpenAICompliantMessageConverter

The `OpenAICompliantMessageConverter` class in [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py) provides the translation machinery that bridges the unified `Message` model with provider-specific SDKs. This converter exposes two essential methods that every provider integration leverages:

### Normalizing Outbound Messages

The `convert_request(messages)` method (lines 20-34) iterates through lists of message objects, converting any `Message` instances to plain dictionaries matching the OpenAI request schema. This method handles edge cases such as normalizing "tool" role messages when the class variable `tool_results_as_strings` is toggled, allowing providers that expect string-based tool results to receive properly formatted payloads.

### Standardizing Provider Responses

The `convert_response(response_data)` method (lines 44-70) takes raw JSON or model dumps from any provider's SDK and constructs a `ChatCompletionResponse` that mirrors OpenAI's response format. This ensures that `content`, `role`, `tool_calls`, and `usage` fields maintain consistent naming and structure across all backends, enabling downstream components to parse responses without provider-specific logic.

## Provider Integration Pattern

Each provider class—such as `OpenaiProvider`, `GroqProvider`, or `AnthropicProvider`—instantiates its own converter, typically using the base `OpenAICompliantMessageConverter` since Groq, OpenAI, and many others already follow the OpenAI schema. The provider delegates transformation through a consistent two-step pattern:

```python
transformed_messages = self.transformer.convert_request(messages)
response = self.client.chat.completions.create(
    model=model,
    messages=transformed_messages,
    **kwargs
)

# For synchronous providers:

return self.transformer.convert_response(response.model_dump())

```

In [`aisuite/providers/openai_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/openai_provider.py) (lines 45-50), the OpenAI provider uses this converter directly, while [`aisuite/providers/groq_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/groq_provider.py) (lines 51-60) demonstrates the same pattern with a thin subclass that inherits the base conversion logic. This guarantees that **every provider receives the same JSON shape** regardless of its native SDK conventions, eliminating provider-specific quirks at a single point in the architecture.

## Extending the Converter for Custom Providers

When integrating a new provider that deviates from the OpenAI contract, you can subclass `OpenAICompliantMessageConverter` and override `convert_request` or `convert_response` while reusing the shared `Message` model. The `tool_results_as_strings` class variable provides a quick customization hook for providers expecting tool results as plain strings rather than structured objects.

```python
from aisuite.providers.message_converter import OpenAICompliantMessageConverter
from aisuite.provider import Provider, LLMError

class MySpecialProvider(Provider):
    def __init__(self, **config):
        self.client = SomeSdk(**config)
        # Override the base converter to tweak tool-call handling

        class MyConverter(OpenAICompliantMessageConverter):
            tool_results_as_strings = True   # this provider expects strings

        self.transformer = MyConverter()

    def chat_completions_create(self, model, messages, **kwargs):
        try:
            transformed = self.transformer.convert_request(messages)
            raw = self.client.chat.completions.create(
                model=model, messages=transformed, **kwargs
            )
            return self.transformer.convert_response(raw.model_dump())
        except Exception as e:
            raise LLMError(str(e))

```

## Practical Usage Example

When using AISuite with providers like Groq that already follow the OpenAI schema, the conversion happens transparently:

```python
from aisuite.providers.groq_provider import GroqProvider
from aisuite.framework.message import Message

# Initialize the provider

provider = GroqProvider(api_key="YOUR_GROQ_KEY")

# Build unified Message objects

messages = [
    Message(role="user", content="What is the weather in Paris?"),
]

# The provider internally calls the converter

response = provider.chat_completions_create(
    model="llama3-groq-70b-8192-tool-use-preview",
    messages=messages,
)

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

```

## Summary

- **Unified Internal Representation**: The `Message` class in [`aisuite/framework/message.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/framework/message.py) provides a single Pydantic model for all provider interactions, capturing roles, content, and tool calls consistently.
- **OpenAI-Compliant Conversion**: The `OpenAICompliantMessageConverter` in [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py) normalizes both requests and responses to match OpenAI's schema through `convert_request()` and `convert_response()` methods.
- **Provider-Agnostic Architecture**: Provider classes delegate transformation to the converter, allowing agents and UI components to assume OpenAI-style contracts without handling provider-specific quirks.
- **Extensible Design**: The `tool_results_as_strings` class variable and subclassing support enable rapid adaptation of new providers that deviate from standard OpenAI formatting.

## Frequently Asked Questions

### How does AISuite handle tool calling across providers with different formats?

AISuite normalizes tool calling through the `OpenAICompliantMessageConverter` class, which standardizes tool definitions and results to the OpenAI schema. For providers that expect tool results as plain strings rather than structured objects, developers can toggle the `tool_results_as_strings` class variable in a converter subclass, ensuring the provider receives data in its expected format while your application code remains unchanged.

### Can I use AISuite with a custom LLM provider that doesn't follow the OpenAI API structure?

Yes. You can subclass `OpenAICompliantMessageConverter` and override the `convert_request` and `convert_response` methods to handle your provider's unique JSON structure. Your custom provider class should instantiate this converter and call it within `chat_completions_create()`, allowing you to reuse AISuite's `Message` model and framework while adapting to non-standard APIs.

### What fields does the unified Message model support?

According to [`aisuite/framework/message.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/framework/message.py), the `Message` model supports standard fields including `content`, `role`, `tool_calls`, and `function_call`, along with optional reasoning fields. This captures the complete OpenAI Chat Completion API specification, ensuring compatibility with advanced features like function calling and tool use across all integrated providers.

### Where does the actual message transformation happen in the provider code?

The transformation occurs in each provider's `chat_completions_create` method, typically between lines 45-60 as seen in [`openai_provider.py`](https://github.com/andrewyng/aisuite/blob/main/openai_provider.py) and [`groq_provider.py`](https://github.com/andrewyng/aisuite/blob/main/groq_provider.py). The provider first calls `self.transformer.convert_request(messages)` to normalize inputs, then passes the transformed dictionaries to the native SDK, and finally calls `self.transformer.convert_response()` on the raw output before returning to the caller.