This document describes the XML-based tool calling support implemented for GLM-4.5 and Qwen3-coder models in TabbyAPI.
Some models (GLM-4.5, Qwen3-coder) generate tool calls in XML format, which differs from the OpenAI JSON format that TabbyAPI expects. This implementation provides a generic XML tool call processor that converts various XML tool call formats to OpenAI-compatible JSON format.
-
BaseXMLToolCallProcessor (
endpoints/OAI/utils/xml_tool_processors.py)- Abstract base class for XML tool call processors
- Provides common functionality for parsing and converting tool calls
- Extensible design allows support for other XML-based models
-
GLM45ToolCallProcessor (
endpoints/OAI/utils/xml_tool_processors.py)- Concrete implementation for GLM-4.5 specific XML format
- Handles the
<tool_call>and<arg_key>/<arg_value>structure - Converts XML to OpenAI JSON format
-
Qwen3CoderToolCallProcessor (
endpoints/OAI/utils/xml_tool_processors.py)- Concrete implementation for Qwen3-coder specific XML format
- Handles nested
<tool_call><function=name><parameter=name>value</parameter></function></tool_call>structure - Supports multi-line parameter values
- Converts XML to OpenAI JSON format
-
XMLToolCallProcessorFactory (
endpoints/OAI/utils/xml_tool_processors.py)- Factory class for creating appropriate XML processors
- Supports GLM-4.5 ("glm45", "glm-4.5", "glm4") and Qwen3-coder ("qwen3-coder", "qwen3") processors
- Supports extensibility by allowing registration of new processor types
-
Enhanced TemplateMetadata (
common/templating.py)- Extended to support XML tool call configuration
- New fields:
tool_call_format,xml_processor_type,tool_end
-
Enhanced ToolCallProcessor (
endpoints/OAI/utils/tools.py)- Added
from_text()method that routes to appropriate processor - Added
from_xml()method for XML-specific processing - Maintains backward compatibility with JSON processing
- Added
The GLM-4.5 model generates tool calls in this format:
<tool_call>function_name
<arg_key>parameter1</arg_key>
<arg_value>value1</arg_value>
<arg_key>parameter2</arg_key>
<arg_value>value2</arg_value>
</tool_call>The Qwen3-coder model generates tool calls in this nested format:
<tool_call>
<function=function_name>
<parameter=parameter1>
value1
</parameter>
<parameter=parameter2>
This is a multi-line
parameter value that spans
multiple lines
</parameter>
</function>
</tool_call>Both formats get converted to OpenAI JSON format:
{
"id": "call_12345",
"type": "function",
"function": {
"name": "function_name",
"arguments": "{\"parameter1\": \"value1\", \"parameter2\": \"value2\"}"
}
}The GLM-4.5 template (templates/tool_calls/glm-4p5-chat-template-tabbyapi.jinja) includes:
{# Metadata #}
{%- set stop_strings = ["<|user|>", "<|assistant|>", "<|observation|>", "<|system|>"] -%}
{%- set tool_start = "<tool_call>" -%}
{%- set tool_end = "</tool_call>" -%}
{%- set tool_call_format = "xml" -%}
{%- set xml_processor_type = "glm45" -%}The Qwen3-coder template (templates/tool_calls/qwen3-coder-tabbyapi.jinja) includes:
{# XML Tool Call Processing Configuration #}
{%- set tool_call_format = "xml" -%}
{%- set xml_processor_type = "qwen3-coder" -%}When loading a GLM-4.5 model, specify the tool-calling template:
# config.yml
model:
model_name: "path/to/glm-4.5-model"
prompt_template: "tool_calls/glm-4p5-chat-template-tabbyapi"When loading a Qwen3-coder model, specify the tool-calling template:
# config.yml
model:
model_name: "path/to/qwen3-coder-model"
prompt_template: "tool_calls/qwen3-coder-tabbyapi"Or via API:
# GLM-4.5
curl -X POST "http://localhost:5000/v1/model/load" \
-H "Content-Type: application/json" \
-d '{
"name": "path/to/glm-4.5-model",
"prompt_template": "tool_calls/glm-4p5-chat-template-tabbyapi"
}'
# Qwen3-coder
curl -X POST "http://localhost:5000/v1/model/load" \
-H "Content-Type: application/json" \
-d '{
"name": "path/to/qwen3-coder-model",
"prompt_template": "tool_calls/qwen3-coder-tabbyapi"
}'Standard OpenAI-compatible tool calling request:
{
"model": "glm-4.5",
"messages": [
{
"role": "user",
"content": "What's the weather in Beijing?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather information",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
},
"date": {
"type": "string",
"description": "Date in YYYY-MM-DD format"
}
},
"required": ["city"]
}
}
}
]
}- Template Processing: The template metadata indicates XML format tool calls
- Model Generation: GLM-4.5 generates XML tool calls when
<tool_call>trigger is detected - XML Parsing:
GLM45ToolCallProcessorparses the XML structure - JSON Conversion: XML is converted to OpenAI-compatible JSON format
- Standard Pipeline: Converted tool calls flow through normal TabbyAPI processing
To support other XML-based models:
- Create a new processor class extending
BaseXMLToolCallProcessor - Implement the required methods for the specific XML format
- Register the processor with the factory:
# Custom processor
class CustomXMLProcessor(BaseXMLToolCallProcessor):
def has_tool_call(self, text: str) -> bool:
return "<custom_tool>" in text
def parse_xml_to_json(self, text: str, tools: List[Tool]) -> List[ToolCall]:
# Custom parsing logic
pass
# Register processor
XMLToolCallProcessorFactory.register_processor("custom", CustomXMLProcessor)Create a template with appropriate metadata:
{%- set tool_call_format = "xml" -%}
{%- set xml_processor_type = "custom" -%}
{%- set tool_start = "<custom_tool>" -%}
{%- set tool_end = "</custom_tool>" -%}Unit tests are provided in tests/test_xml_tool_calls.py covering:
- XML parsing functionality
- Multiple tool call handling
- JSON conversion accuracy
- Error handling for malformed XML
- Factory pattern functionality
- Argument type processing
Run tests with:
python -m pytest tests/test_xml_tool_calls.py -vThe implementation includes robust error handling:
- Malformed XML: Returns empty tool call list, logs error
- Unknown Functions: Still processes but without type validation
- Parsing Failures: Falls back gracefully, maintains system stability
- Missing Dependencies: Graceful degradation to JSON processing
- Regex-based Parsing: Efficient for typical tool call volumes
- Lazy Evaluation: Processors created only when needed
- Memory Efficient: Processes tool calls incrementally
- Caching: Template metadata cached after first extraction
- Backward Compatible: Existing JSON tool calling continues to work
- OpenAI Standard: Output format matches OpenAI API specification
- Streaming Support: Works with both streaming and non-streaming responses
- Multi-tool: Supports multiple tool calls in single response
-
Tool calls not detected
- Verify template has
tool_call_format = "xml" - Check
tool_startmatches model output - Ensure
xml_processor_typeis correct
- Verify template has
-
Parsing errors
- Validate XML format matches expected structure
- Check for missing closing tags
- Verify argument key/value pairing
-
JSON conversion failures
- Check argument types in tool definitions
- Validate JSON-formatted argument values
- Review error logs for specific parsing issues
Enable detailed logging for troubleshooting:
import logging
logging.getLogger("endpoints.OAI.utils.xml_tool_processors").setLevel(logging.DEBUG)This implementation provides a robust, extensible foundation for XML-based tool calling in TabbyAPI while maintaining full compatibility with existing JSON-based tool calling functionality.