Standardizing Message Formats Across Different LLM Providers in AISuite
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. 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 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:
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 (lines 45-50), the OpenAI provider uses this converter directly, while 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.
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:
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
Messageclass inaisuite/framework/message.pyprovides a single Pydantic model for all provider interactions, capturing roles, content, and tool calls consistently. - OpenAI-Compliant Conversion: The
OpenAICompliantMessageConverterinaisuite/providers/message_converter.pynormalizes both requests and responses to match OpenAI's schema throughconvert_request()andconvert_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_stringsclass 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, 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 and 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →