How aisuite Converts Messages Between LLM Provider Formats
aisuite normalizes chat messages through a provider-agnostic Message class and dedicated converter objects that translate between the framework's internal representation and each LLM provider's specific API schema.
The aisuite library by Andrew Ng provides a unified interface for multiple large language model (LLM) providers, eliminating the need to juggle different client libraries. To achieve this interoperability, the framework implements a sophisticated message conversion system that transparently handles format translation between its internal objects and provider-specific request/response schemas.
The Provider-Agnostic Message Architecture
At the core of aisuite's conversion system lies a clean separation between the framework's internal representation and external provider formats.
The Internal Message Model
All chat interactions within aisuite use the Message class defined in aisuite/framework/message.py. This provider-agnostic model standardizes fields like role, content, tool_calls, and tool_call_id, ensuring that application code remains decoupled from any specific LLM vendor's API structure.
The Converter Pattern
Each LLM provider ships with a dedicated message converter class that inherits from conversion utilities in aisuite/providers/message_converter.py. These converters implement two critical transformation methods:
convert_request()– Transforms frameworkMessageobjects into provider-specific payload shapesconvert_response()– Normalizes provider-specific responses into the framework'sChatCompletionResponseformat
Converting Requests from Framework to Provider Format
When you call chat_completions_create(), aisuite delegates message transformation to the appropriate converter before sending HTTP requests.
OpenAI-Compatible Conversion
Providers using OpenAI-style schemas rely on OpenAICompliantMessageConverter in aisuite/providers/message_converter.py. This base class handles the majority of modern providers (OpenAI, Groq, Mistral, etc.) with minimal customization.
# aisuite/providers/message_converter.py
class OpenAICompliantMessageConverter:
@staticmethod
def convert_request(messages):
"""Convert messages to OpenAI-compatible format."""
transformed_messages = []
for message in messages:
# Accept both framework Message objects and raw dicts
if isinstance(message, Message):
message_dict = message.model_dump(mode="json")
message_dict.pop("refusal", None) # OpenAI does not support refusal
tmsg = message_dict
else:
tmsg = message
# Handle tool results as strings when required
if tmsg.get("role") == "tool" and OpenAICompliantMessageConverter.tool_results_as_strings:
tmsg["content"] = str(tmsg["content"])
transformed_messages.append(tmsg)
return transformed_messages
The provider's chat_completions_create method invokes this transformer before calling the underlying client, as implemented in aisuite/providers/openai_provider.py.
Anthropic-Specific Conversion
Anthropic's API requires distinct handling for system messages and tool results. The AnthropicMessageConverter in aisuite/providers/anthropic_provider.py overrides the base behavior to handle these differences:
# aisuite/providers/anthropic_provider.py
class AnthropicMessageConverter:
def convert_request(self, messages):
"""Convert framework messages to Anthropic format."""
system_message = self._extract_system_message(messages)
converted_messages = [self._convert_single_message(msg) for msg in messages]
return system_message, converted_messages
def _extract_system_message(self, messages):
# Removes system role entries and returns them for Anthropic's system= parameter
pass
Key transformations include:
- System message extraction – The first message with
role="system"is removed from the message list and passed separately to Anthropic'ssystem=argument, as seen in lines 66-74 of the anthropic provider. - Content conversion – OpenAI-style image URLs (
{"type": "image_url", ...}) are transformed into Anthropic's expected image blocks, handling both data URLs and remote URLs. - Tool result formatting – Framework messages with
role="tool"are converted to Anthropic'stool_resultblocks.
Normalizing Provider Responses
After receiving API responses, converters normalize the data back into framework-standard objects.
Response Conversion Flow
Each converter implements convert_response() to build a ChatCompletionResponse object from aisuite/framework/chat_completion_response.py. This normalization ensures that tool calls, usage statistics, and finish reasons appear in a consistent structure regardless of the underlying provider.
def convert_response(self, response_data) -> ChatCompletionResponse:
"""Normalize the response to match OpenAI's response format."""
completion_response = ChatCompletionResponse()
message = response_data["choices"][0]["message"]
completion_response.choices[0].message.content = message["content"]
completion_response.choices[0].message.role = message.get("role", "assistant")
# Normalize tool calls into ChatCompletionMessageToolCall objects
if "tool_calls" in message and message["tool_calls"] is not None:
tool_calls = [
ChatCompletionMessageToolCall(
id=tc.get("id"),
type="function",
function=tc.get("function"),
)
for tc in message["tool_calls"]
]
completion_response.choices[0].message.tool_calls = tool_calls
# Extract usage metadata
if usage_data := response_data.get("usage"):
completion_response.usage = self.get_completion_usage(usage_data)
return completion_response
The Anthropic converter performs the inverse operation, extracting content from Anthropic's proprietary response objects and reconstructing standard ChatCompletionMessageToolCall instances for function calling workflows.
Working with Message Converters in Practice
Provider classes instantiate their converters during initialization and delegate transformation tasks to them.
Example: OpenAI Tool Call Conversion
from aisuite.providers.openai_provider import OpenaiProvider
from aisuite.framework.message import Message
provider = OpenaiProvider(api_key="sk-...")
framework_messages = [
Message(role="user", content="What is the weather in Paris?"),
Message(role="assistant", content=None, tool_calls=[
{"id": "tc_1", "type": "function",
"function": {"name": "get_weather", "arguments": '{"city":"Paris"}'}}
]),
]
# The provider internally calls the converter
payload = provider.transformer.convert_request(framework_messages)
# payload is now formatted for OpenAI's /chat/completions endpoint
Example: Anthropic Response Normalization
from aisuite.providers.anthropic_provider import AnthropicProvider
provider = AnthropicProvider()
anthropic_response = provider.client.messages.create(
model="claude-3-opus-20240229",
system="You are a helpful assistant.",
messages=[{"role": "user", "content": "Hello"}]
)
# Convert proprietary response to framework standard
framework_response = provider.converter.convert_response(anthropic_response)
# Access normalized fields: framework_response.choices[0].message.content
Extending to New Providers
To add support for a new LLM provider (e.g., "FooAI"):
- Create
aisuite/providers/fooai_provider.py - Implement
FooAIMessageConverterwithconvert_request()andconvert_response()methods mapping to FooAI's JSON schema - Instantiate the converter in the provider's
__init__method and invoke it duringchat_completions_create()calls
Summary
- aisuite maintains a provider-agnostic internal representation using the
Messageclass inaisuite/framework/message.py, decoupling application logic from vendor-specific APIs. - The
OpenAICompliantMessageConverterinaisuite/providers/message_converter.pyprovides a base class for OpenAI-compatible providers, handling request transformation and response normalization. - Provider-specific converters like
AnthropicMessageConverteroverride base behavior to handle unique schema requirements such as system message separation and distinct tool-call formats. - All converters implement bidirectional translation:
convert_request()prepares provider payloads from framework objects, whileconvert_response()normalizes API responses into standardChatCompletionResponseobjects. - This architecture allows developers to switch between LLM providers (OpenAI, Anthropic, Gemini, etc.) without modifying application message handling code.
Frequently Asked Questions
What is the Message class in aisuite?
The Message class in aisuite/framework/message.py is a standardized data model representing chat messages within the framework. It encapsulates fields like role, content, tool_calls, and refusal in a provider-agnostic structure, ensuring that application logic remains independent of any specific LLM vendor's API format before conversion occurs.
How does aisuite handle Anthropic's unique system message format?
Anthropic requires system instructions as a separate parameter rather than as a message with role="system". The AnthropicMessageConverter in aisuite/providers/anthropic_provider.py extracts system messages from the message list using _extract_system_message(), removing them from the conversation array and passing them to the system= argument in the API call, while converting remaining messages to Anthropic's expected format.
Can I extend aisuite to support a new LLM provider?
Yes. Create a new provider file in aisuite/providers/ and implement a message converter class with convert_request() and convert_response() methods. Instantiate this converter in your provider's __init__ method and use it to transform messages before API calls and normalize responses afterward, following the pattern established by OpenaiProvider and AnthropicProvider.
How are tool calls converted between different provider formats?
Tool calls are normalized into ChatCompletionMessageToolCall objects within the framework's response handling. During requests, converters transform these objects into provider-specific schemas—for example, Anthropic's tool_use blocks or OpenAI's function definitions. During response processing, converters extract tool call data from provider-specific response structures (like Anthropic's content blocks) and reconstruct standard ChatCompletionMessageToolCall instances with id, type, and function attributes.
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 →