How MTPLX Handles Tool Calling Differently for OpenAI vs Anthropic APIs
MTPLX implements a unified parsing architecture in mtplx/server/openai.py that bridges OpenAI's JSON-based function calling and Anthropic's XML-based tool markup through dialect detection, specialized streaming guards, and bidirectional translation utilities.
The MTPLX server (youssofal/MTPLX) provides a compatibility layer that normalizes tool calling across different LLM providers. While OpenAI APIs expect JSON-encoded function objects with specific schema constraints, Anthropic models return XML-wrapped tool calls using <tool_call> tags, requiring MTPLX to maintain dual parsing strategies within a single codebase.
Core Architectural Differences
Payload Format and Dialect
The primary distinction lies in how each API serializes tool invocations.
-
OpenAI-style: Uses JSON objects containing a
functionfield withnameandargumentskeys. The arguments are escaped JSON strings that the server parses into dictionaries. -
Anthropic-style: Uses XML markup wrapped in
<tool_call>tags with<function=...>headings. The function body contains raw argument payloads, often JSON or simple values, embedded directly within the XML structure.
In mtplx/server/openai.py, the _parse_generated_tool_calls function handles both dialects by examining the first non-whitespace character of the incoming string: { triggers JSON parsing, while < triggers XML parsing.
Parsing Entry Points and Translation Flow
Despite the format differences, MTPLX funnels both dialects through a single entry point.
The _parse_generated_tool_calls function in mtplx/server/openai.py serves as the unified parser. For Anthropic requests, the system first processes the raw input through _anthropic_to_chat_request and _anthropic_payload_from_openai helpers, which normalize the XML markup into a form that the generic parser can consume. This ensures that downstream components receive a canonical internal representation regardless of the source API.
Streaming Semantics and Guard Logic
Hidden-Tool Guard Implementation
Both dialects implement hidden-tool guards to prevent malformed tool calls from corrupting the response stream, but the detection mechanisms differ.
-
OpenAI: Uses constants like
STREAM_HIDDEN_TOOL_GUARD_STARTandSTREAM_HIDDEN_TOOL_GUARD_ENDto track JSON parsing state. The guard monitors for incomplete JSON bodies (e.g., missing closing braces) and retries parsing when streams terminate. -
Anthropic: Re-arms the guard when opening
<tool_call>tags are detected and disarms on matching</tool_call>closures. This protects against unclosed XML tags and suppresses "leaked" tool markup when models produce stray text before closing tags.
The parser sets tool_argument_in_progress flags during streaming to track whether it is currently inside a tool argument context.
Error Handling Strategies
When a tool call is malformed, MTPLX returns a fallback reason (e.g., "malformed tool_call: unterminated stream") rather than failing the entire request. For OpenAI-style calls, the _repair_tool_argument_keys_for_schema function attempts to repair missing argument keys based on the tool schema. For Anthropic, the guard ensures stray XML markup never reaches the user-facing response, allowing the request to succeed with the tool payload omitted.
Parallel Tool Call Handling
MTPLX normalizes parallel tool execution flags between the two APIs through explicit mapping.
In _anthropic_to_chat_request, the Anthropic flag disable_parallel_tool_use is converted to the OpenAI-equivalent parallel_tool_calls. When disabled, MTPLX collapses multiple calls into a single payload (one JSON object for OpenAI, one XML block for Anthropic). When enabled, each call appears as a separate entry in the tool_calls list or as separate <tool_call> tags.
Implementation Code Examples
The following examples demonstrate how MTPLX processes each dialect in practice.
OpenAI-Style JSON Tool Call
# From tests/test_tool_aware_stream_translator.py
PYTHONIC_CALL = "<|tool_call_start|>[get_time(city='Tokyo')]<|tool_call_end|>"
translator = _ToolAwareContentStreamTranslator(tools=EDIT_FILE_TOOL_SPECS)
translator.feed("content", PYTHONIC_CALL)
assert translator.tool_calls[0]["function"]["name"] == "get_time"
This example shows the "pythonic" envelope format that MTPLX supports alongside standard OpenAI function objects. The translator extracts the function name and arguments from the JSON structure.
Anthropic-Style XML Tool Call
# From tests/test_server_openai.py
anthropic_request = {
"type": "message",
"content": [
{"type": "tool_use", "name": "lookup", "input": {"key": "value"}}
],
}
chat = openai._anthropic_to_chat_request(anthropic_request)
assert chat.messages[0]["role"] == "assistant"
assert chat.messages[0]["tool_calls"][0]["name"] == "lookup"
Here, _anthropic_to_chat_request converts Anthropic's tool_use content blocks into OpenAI-compatible message structures before the unified parser processes them.
Hidden-Tool Guard Handling
# From tests/test_tool_call_hidden_guard_and_key_repair.py
t = _QwenXMLToolCallStreamParser(tools=EDIT_FILE_TOOL_SPECS)
out = t.feed("content", "<tool_call>\n<function=write>\n")
assert t.tool_argument_in_progress is True
out.extend(t.feed("content", "\n</function>\n</tool_call>"))
assert t.has_tool_calls is True
This demonstrates the XML guard logic tracking whether the parser is currently inside a function argument block, preventing premature content emission.
Summary
- MTPLX uses a unified
_parse_generated_tool_callsparser inmtplx/server/openai.pythat detects dialects by examining leading characters ({for JSON,<for XML). - OpenAI tool calls use JSON
functionobjects with string-encoded arguments, while Anthropic uses<tool_call>XML tags with embedded function payloads. - The system implements distinct hidden-tool guards for each dialect to handle incomplete JSON braces or unclosed XML tags during streaming.
- Translation utilities like
_anthropic_to_chat_requestand_anthropic_payload_from_openainormalize Anthropic content into OpenAI-compatible structures before parsing. - Parallel tool call flags are mapped between APIs, with
disable_parallel_tool_useconverting toparallel_tool_callsfor downstream compatibility. - Static tool specifications (
EDIT_FILE_TOOL_SPECS,LOOKUP_TOOL_SPECS) provide schema validation that works uniformly across both dialects.
Frequently Asked Questions
How does MTPLX detect which tool calling dialect to use?
MTPLX examines the first non-whitespace character of the incoming tool call string within _parse_generated_tool_calls. If the content starts with {, the parser treats it as OpenAI-style JSON. If it starts with <, the parser processes it as Anthropic-style XML. The parser also looks for specific delimiters like <|tool_call_start|> used by the pythonic envelope format.
What happens when a tool call is malformed during streaming?
MTPLX implements hidden-tool guards that track parsing state through flags like tool_argument_in_progress. If a stream terminates with incomplete JSON (missing closing braces) or unclosed XML tags, the system returns a fallback reason such as "malformed tool_call: unterminated stream" rather than failing the request. For OpenAI dialects, the system may attempt to repair missing argument keys using _repair_tool_argument_keys_for_schema.
How does MTPLX handle parallel tool calls differently between OpenAI and Anthropic?
In _anthropic_to_chat_request, MTPLX maps Anthropic's disable_parallel_tool_use flag to OpenAI's parallel_tool_calls parameter. When parallel calls are disabled, multiple tool invocations collapse into a single payload object. When enabled, each call generates separate entries in the tool_calls array for OpenAI or distinct <tool_call> XML blocks for Anthropic, ensuring the scheduler receives a consistent representation regardless of source API.
Can MTPLX convert Anthropic tool calls to OpenAI format for downstream processing?
Yes. The _anthropic_content_to_chat_content and _anthropic_content_to_text functions in mtplx/server/openai.py convert Anthropic content blocks (including images and tool_use blocks) into OpenAI-compatible structures before the unified parser processes them. This bidirectional translation allows MTPLX to serve as a compatibility bridge between the two API styles.
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 →