How FreeLLMAPI Maps Anthropic's Messages Format: Implementation Guide
FreeLLMAPI maps Anthropic's messages format through a three-stage pipeline that resolves Claude model families via resolveAnthropicModel(), validates requests against a Zod messagesSchema, and normalizes system messages and tool calls into a unified internal ChatMessage structure before provider dispatch.
FreeLLMAPI is an open-source LLM proxy that standardizes requests across multiple providers. When handling Anthropic Claude API requests, the service converts the native /v1/messages payload into its internal representation using specialized mapping utilities in server/src/services/anthropic-map.ts and route handlers in server/src/routes/anthropic.ts.
Claude Family Resolution and Model Mapping
Defining Claude Families and Aliases
In server/src/services/anthropic-map.ts, the service maintains the CLAUDE_FAMILIES constant defining four supported families: default, opus, sonnet, and haiku. The CLAUDE_FAMILY_ALIASES mapping associates each family with concrete model identifiers such as anthropic/claude-3-opus-20240229. The claudeFamilyDiscoveryEntries() function generates discovery metadata used to enumerate available models for client selection.
Resolving Model Identifiers with resolveAnthropicModel
The resolveAnthropicModel() function processes incoming model names to return a ResolvedAnthropicModel object. When a client sends a family-style identifier like claude-3-sonnet, the function selects the highest-specificity matching model from the alias map. For explicit model IDs, it validates against the map and sets the pinned flag to true, indicating a user-specified selection rather than a family default. Unknown inputs fall back to the default family entry, ensuring robust request handling.
import { resolveAnthropicModel } from './services/anthropic-map.js';
const { modelId, family, pinned } = resolveAnthropicModel('claude-3-opus-20240229');
// modelId: 'anthropic/claude-3-opus-20240229'
// family: 'opus'
// pinned: true
Converting Anthropic Messages to Internal Chat Format
Request Validation with Zod Schemas
The route handler in server/src/routes/anthropic.ts validates incoming payloads against messagesSchema, a strict Zod schema enforcing the Anthropic API contract. This validation layer ensures type safety for fields like system, messages, and tool definitions before conversion begins.
Normalizing System Message Placement
Anthropic's API accepts system prompts either as a top-level system string or as role-based entries within the messages array. FreeLLMAPI collapses both representations into a single consolidated system message prepended to the internal chat history. The parser first checks for the standalone system field, then iterates through the message array to collect any additional system turns, combining them into the standardized ChatMessage format with role system.
import { messagesSchema } from './routes/anthropic.js';
const parsed = messagesSchema.safeParse(req.body);
if (!parsed.success) throw new BadRequestError(parsed.error);
const { system, messages: rawMsgs } = parsed.data;
const chat = [];
if (system) {
chat.push({ role: 'system', content: system });
}
for (const msg of rawMsgs) {
if (msg.role === 'system') {
chat.push({ role: 'system', content: msg.content });
continue;
}
chat.push({ role: msg.role, content: msg.content });
}
Translating Tool Calls and Results
Tool invocations undergo conversion to OpenAI-compatible message structures. When processing a message containing a tool object, the handler executes the tool and appends the results as assistant-role messages containing the output content. This transformation allows FreeLLMAPI to handle Anthropic's native tool use format while maintaining compatibility with providers expecting OpenAI-style function call conventions.
// Inside the message processing loop
if (msg.tool) {
const toolResult = await runTool(msg.tool);
chat.push(...toolResult); // assistant-role messages with tool outputs
}
Processing Document Blocks
For requests containing base64-encoded PDFs or other media types, server/src/lib/anthropic-documents.ts provides convertDocumentBlock() to process Anthropic-style document blocks before they enter the chat payload. This ensures multimedia content is properly formatted for downstream providers that may expect different encoding standards.
Provider Dispatch Integration
After mapping and normalization, the pipeline forwards the standardized payload to the appropriate provider handler. The resolved modelId (e.g., anthropic/claude-3-sonnet-20240229) and normalized chat array are passed to the streaming completion service, which executes the actual API call using the converted format.
import { streamChatCompletion } from './providers/claude.js';
await streamChatCompletion({
apiKey: req.headers['x-api-key'],
modelId,
messages: chat,
options: { temperature: 0.7 }
});
Summary
- Model Resolution:
server/src/services/anthropic-map.tsdefinesCLAUDE_FAMILIESandCLAUDE_FAMILY_ALIASESto map family identifiers to specific model IDs viaresolveAnthropicModel(). - System Message Handling: The converter collapses both top-level and in-array system messages into a single prepended system message in the internal format.
- Tool Translation: Anthropic tool calls are transformed into OpenAI-compatible assistant-role messages with tool results appended to the chat history.
- Validation: Zod schemas in
server/src/routes/anthropic.tsenforce type safety on incoming Anthropic payloads before processing. - Document Support:
server/src/lib/anthropic-documents.tshandles specialized document block conversion for multimodal inputs.
Frequently Asked Questions
How does resolveAnthropicModel handle unknown model names?
If the input string does not match any entry in CLAUDE_FAMILY_ALIASES, the function falls back to the default family mapping entry as defined in server/src/services/anthropic-map.ts. This ensures the request proceeds with a valid model ID rather than failing outright, providing graceful degradation for ambiguous client inputs.
What happens to system messages in the Anthropic format conversion?
The conversion logic in server/src/routes/anthropic.ts extracts system content from both the top-level system field and any in-array system role entries. It collapses these into a single system message that is prepended to the internal chat history array, maintaining conversation context while standardizing the structure for downstream providers.
How are tool calls processed in the mapping pipeline?
When the parser encounters a tool invocation in the Anthropic payload, it calls runTool() to execute the function and captures the output. The results are appended as assistant-role messages to the chat array, effectively translating Anthropic's native tool format into the OpenAI-compatible tool message structure expected by FreeLLMAPI's provider layer.
Where is the model-to-family classification defined?
The classification logic and family definitions reside in server/src/services/anthropic-map.ts, which exports CLAUDE_FAMILIES for the enumeration of supported families and implements resolution helpers that determine which family a specific model ID belongs to based on the predefined alias mappings.
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 →