Observed since 20 Oct, 23:34 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
High-performance dual-engine (Rust + Python) tool call parser for DeepSeek R1/V3, Claude 3.7, Qwen 2.5, Llama 3, OpenAI & ReAct formats with auto-repair and zero dependencies.
High-performance, dual-engine (Rust + Python) parser and cleaner for heterogeneous LLM tool calls and structured outputs.
Interactive Documentation & Live Playground ↗
agent-tool-parser is a high-performance library with a dual-engine architecture (compiled Rust core with PyO3 acceleration + pure-Python universal fallback) designed to extract, normalize, and repair tool calls from large language model outputs. It processes standard OpenAI schemas, Anthropic Claude XML, DeepSeek DSML and native tokens, Qwen tokens, Llama AST syntax, and truncated JSON payloads with sub-0.05 millisecond latency and zero required runtime dependencies. Cross-language bindings are available for Python, Rust, Go, C/C++, and WebAssembly.
When integrating exclusively with closed-source APIs (such as OpenAI or Anthropic) via their native client SDKs, server-side infrastructure typically frames tool calls into structured protocol objects prior to delivery. In such architectures, an external parser is generally unnecessary.
However, in systems integrating open-weights models, local inference engines (e.g., vLLM, SGLang, Ollama), or third-party inference aggregators (e.g., OpenRouter, Together AI, Groq), tool invocations are frequently delivered within the unsegmented text stream (message.content). Common integration challenges include:
<tool_use>), DSML blocks (<|DSML|invoke>), native tokens (<|tool call begin|>), or AST function calls (call:tool(arg=val)).<think>...</think>) in the same stream as the final tool invocation.cmd instead of command, file instead of path) can cause downstream schema validation failures.agent-tool-parser provides a deterministic, zero-dependency processing layer to normalize these outputs before execution.
| Approach | Design Focus | Execution Characteristics | Scope & Constraints |
|---|---|---|---|
agent-tool-parser | Tool invocation extraction & normalization | Sub-0.05 ms latency; zero dependencies; dual-engine (Rust + Python). | Specialized for tool calling; supports XML, DSML, AST, and JSON repair. |
json-repair | General-purpose JSON syntax repair | Lightweight; standard JSON normalization. | Restricted to JSON grammar; does not process markup tags, thinking blocks, or argument schemas. |
LangChain / LlamaIndex | Full-stack application frameworks | Comprehensive multi-package ecosystem. | Higher memory footprint and dependency tree for pipelines requiring only output parsing. |
Instructor | Pydantic validation via function calling | Enforces structured types against server schemas. | Requires provider-level schema support; does not extract delimiter-based markup from message text. |
Guided Decoding (vLLM / SGLang) | Constrained token generation | Enforces grammar during sampling. | Can restrict intermediate reasoning steps in CoT models; inapplicable when calling third-party API endpoints. |
| Ad-Hoc Regular Expressions | Project-specific pattern matching | Zero external dependencies. | Brittle against unquoted attributes, nested tags, and varying model formatting conventions. |
In production agent architectures, extracting structured tool invocations is typically approached at one of three system boundaries:
| Architectural Dimension | Client-Side Post-Parsing (agent-tool-parser) | Logit-Level Constrained Decoding (e.g., XGrammar, Outlines) | Secondary Model Pass (Neural Re-Parser) |
|---|---|---|---|
| Execution Boundary | Client runtime (post-generation) | Inference engine sampling loop | Secondary inference endpoint or worker |
| Inference Engine Access | Engine-agnostic (processes standard HTTP / REST text streams) | Requires direct access to logits and the sampling pipeline | Requires secondary model invocation or separate service deployment |
| Runtime Dependencies | Python standard library (zero external dependencies) | Inference runtime stack (e.g., PyTorch, CUDA, Triton) | Model inference framework or API client |
| Provider API Support | Compatible with any endpoint returning text (OpenRouter, Groq, Together, etc.) | Incompatible with third-party APIs lacking custom sampler controls | Compatible via sequential API round-trip |
| Grammar Enforcement Mechanism | Deterministic lexical parsing and structural repair | Logit masking via finite-state machine (FSM) during token sampling | Probabilistic generation guided by system prompt |
| Format Scope | Multi-format: XML, DSML, Markdown JSON, Python AST, CDATA blocks | Strictly defined JSON Schemas or context-free grammars (EBNF) | Unconstrained (dependent on secondary model training distribution) |
<think>...</think>), logit-level grammars must support dynamic transitions between free-form text and structured output schemas. Client-side extraction isolates the deliberation trace from the structured invocation payload after generation completes.Standard ReAct loops require resilient extraction when alternating between reasoning and tool execution:
from agent_tool_parser import try_parse_tool_calls
def run_agent_turn(model_output: str) -> list[dict]:
# Extracts all valid tool calls, removing any <think> blocks
calls = try_parse_tool_calls(model_output)
results = []
for call in calls:
execution_result = dispatch_tool(call.name, call.args)
results.append({"tool": call.name, "result": execution_result})
return results
When proxying multi-provider models into standard OpenAI-compatible endpoints:
from agent_tool_parser import try_parse_tool_calls
def normalize_completion_response(response_dict: dict) -> dict:
choice = response_dict["choices"][0]
message = choice["message"]
# If the provider omitted tool_calls but returned markup in content:
if not message.get("tool_calls") and message.get("content"):
extracted = try_parse_tool_calls(message["content"])
if extracted:
message["tool_calls"] = [
{
"id": f"call_{i}",
"type": "function",
"function": {"name": c.name, "arguments": json.dumps(c.args)},
}
for i, c in enumerate(extracted)
]
return response_dict
In multi-agent environments managing autonomous background workers, normalizing arguments prior to approval gates prevents stalled task queues:
from agent_tool_parser import ToolParser, ToolError
parser = ToolParser(
allowed_tools={"read_file", "write_file", "execute_command"},
param_aliases={"cmd": "command", "filepath": "path"},
)
def process_worker_step(raw_step_output: str) -> dict:
try:
call = parser.parse(raw_step_output)
return {"status": "ready_for_approval", "call": call}
except ToolError as error:
return {"status": "rejected", "error": str(error)}
Processing multiple tool calls from a single model generation step:
import asyncio
from agent_tool_parser import parse_tool_calls
async def handle_parallel_generation(model_output: str):
calls = parse_tool_calls(model_output)
tasks = [async_execute(call.name, call.args) for call in calls]
return await asyncio.gather(*tasks, return_exceptions=True)
# Standard Python installation (zero required dependencies, pure Python fallback by default)
pip install agent-tool-parser
# With optional performance and advanced JSON repair extras:
pip install "agent-tool-parser[all]" # installs orjson and json-repair
pip install "agent-tool-parser[fast]" # installs orjson for accelerated pure-Python JSON
pip install "agent-tool-parser[repair]" # installs json-repair for advanced corrupt JSON
# Go module
go get github.com/inboost-dev/agent-tool-parser/bindings/go/agenttoolparser
from agent_tool_parser import parse_tool_call, ACCELERATED
print(f"Accelerated native core active: {ACCELERATED}")
# Example response containing reasoning tokens, DSML markup, and unquoted parameters:
raw_output = """
<think>
Evaluating dependency versions before test execution.
</think>
<|DSML|tool_calls>
<|DSML|invoke name="run_tests">
<|DSML|parameter name="suite">unit</|DSML|parameter>
<|DSML|parameter name="verbose">true</|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls>
"""
call = parse_tool_call(raw_output)
print(call.name) # 'run_tests'
print(call.args) # {'suite': 'unit', 'verbose': True}
print(call.raw_source) # Raw matched substring
cgo)package main
import (
"fmt"
"log"
agenttoolparser "github.com/inboost-dev/agent-tool-parser/bindings/go/agenttoolparser"
)
func main() {
raw := `<|tool calls begin|><|tool call begin|>function=search<|tool sep|>{"pattern": "parse", "path": "."}<|tool call end|><|tool calls end|>`
call, err := agenttoolparser.ParseToolCall(raw)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tool: %s, Args: %v\n", call.Name, call.Args)
}
use agent_tool_parser_core::parse_tool_call;
fn main() {
let raw = "<invoke name=\"bash\"><parameter name=\"cmd\">git status</parameter></invoke>";
let call = parse_tool_call(raw).expect("Valid tool invocation");
println!("Tool: {}, Args: {:?}", call.name, call.args);
}
agent_tool_parser.h)#include "agent_tool_parser.h"
#include <stdio.h>
int main() {
const char* raw = "<invoke name=\"bash\"><parameter name=\"cmd\">pwd</parameter></invoke>";
ATPToolCall* call = atp_parse_tool_call(raw);
if (call) {
printf("Tool: %s\nArguments: %s\n", call->name, call->args_json);
atp_free_tool_call(call);
}
return 0;
}
agent-tool-parser is structured with a modular, dual-engine design:
agent-tool-parser/
├── src/agent_tool_parser/ # Pure-Python engine (zero-dependency, no compiler required)
│ ├── _accelerated.so # Optional PyO3 compiled native extension
│ └── __init__.py # Transparent runtime dispatch (ACCELERATED flag)
├── crates/
│ ├── agent-tool-parser-core/ # High-performance Rust core extraction engine
│ ├── agent-tool-parser-c/ # C-ABI export & standard C header (agent_tool_parser.h)
│ ├── agent-tool-parser-py/ # PyO3 bindings with pythonize zero-copy deserialization
│ └── agent-tool-parser-wasm/ # WebAssembly bindings (wasm-bindgen) for Node.js & browser
└── bindings/
└── go/agenttoolparser/ # Native Go module wrapping C-ABI via cgo
AGENT_TOOL_PARSER_NO_EXT=1 is set in the environment), the package operates in pure Python with zero external dependencies.| Format Specification | Representative Input | Extraction Status |
|---|---|---|
| Anthropic Claude XML | <tool_use><name>read_file</name><arguments>{"path": "src/core.py"}</arguments></tool_use> | Supported |
| OpenAI Function Format | {"tool_calls": [{"type": "function", "function": {"name": "search", "arguments": "{...}"}}]} | Supported |
| DeepSeek Native Tokens | <|tool call begin|>function=bash<|tool sep|>{"command": "pytest"}<|tool call end|> | Supported |
| DeepSeek DSML | <|DSML|tool name=search><|DSML|parameter name=query>vector</|DSML|parameter> | Supported |
| Qwen-Agent Tokens | <|action_start|><|action_name|>run<|action_args|>{"script": "main.py"}<|action_end|> | Supported |
| Llama 3.1+ Python Tag | <|python_tag|>bash.call(command="pytest") | Supported |
| Mistral Tool Syntax | [TOOL_CALLS] [{"name": "search", "arguments": {"query": "vector"}}] | Supported |
| Python Expression Syntax | read_file(path="src/main.py", offset=10) | Supported |
| ReAct Action Notation | Action: bash\nAction Input: pytest | Supported |
| Hermes 2/3 XML Markup | <tool_call>{"name": "search", "arguments": {"query": "vector"}}</tool_call> | Supported |
| Direct Parameter XML | <tool_name>search</tool_name><query>vector</query> | Supported |
| Markdown Fenced JSON | json\n{"tool": "bash", "command": "git status"}\n | Supported |
| Embedded String CDATA | <parameter name="patch"><![CDATA[def test():\n assert True]]></parameter> | Supported |
| Malformed JSON Repair | {'tool': 'bash', 'command': 'pytest', 'verbose': True,} | Supported |
| Thinking Blocks | <think>Analyzing context...</think> | Delimited & Stripped |
parse_tool_call(text: str, ...) -> ToolCallExtracts a single tool call from text. Raises ToolError if no valid structure is recognized.
parse_tool_calls(text: str, ...) -> list[ToolCall]Extracts all tool calls present in the text. Raises ToolError if the output contains no extractable invocations.
try_parse_tool_call(text: str, ...) -> Optional[ToolCall]Non-raising variant of parse_tool_call. Returns None if no invocation is detected.
try_parse_tool_calls(text: str, ...) -> list[ToolCall]Non-raising variant of parse_tool_calls. Returns an empty list [] if no invocations are detected.
safe_json_loads(s: str, default: Any = None) -> AnyStandalone utility for resilient JSON decoding. Resolves trailing commas, single-quoted keys, unquoted boolean/null literals, and unclosed brackets.
ToolParserThe ToolParser class allows setting persistent validation rules, allowed tool sets, and parameter mappings across calls:
from agent_tool_parser import ToolParser, ToolError
parser = ToolParser(
allowed_tools={"read_file", "write_file", "execute_command"},
tool_aliases={"run": "execute_command", "view": "read_file"},
param_aliases={"cmd": "command", "file": "path"},
allow_shell_fallback=True,
)
call = parser.parse("```bash\npytest tests/\n```")
# Returns ToolCall(name='execute_command', args={'command': 'pytest tests/'})
Measurements conducted on Python 3.12 (AMD EPYC, single execution thread):
| Evaluation Scenario | Average Latency | Throughput |
|---|---|---|
| DSML Markup Extraction | 0.038 ms | ~26,000 calls / sec |
| DeepSeek Delimiter Parsing | 0.024 ms | ~41,000 calls / sec |
| Markdown & Malformed JSON Repair | 0.015 ms | ~65,000 calls / sec |
All pull requests and commits to main are continuously validated across runtimes and target architectures in GitHub Actions (.github/workflows/ci.yml):
ruff, mypy, and the complete 104-test suite under pure-Python fallback (AGENT_TOOL_PARSER_NO_EXT=1).cargo fmt, cargo clippy -D warnings, and 58 unit, integration, and benchmark tests.libagent_tool_parser_c.so) and C unit tests.go test -v ./bindings/go/...).wasm32-unknown-unknown release build).tests/test_parity.py with 240+ assertions).We welcome contributions to agent-tool-parser. Because the project maintains dual implementations (pure-Python reference and compiled Rust core), all pull requests modifying parsing heuristics, format extractors, or cleaner rules must provide synchronized changes across both engines. If full parity is technically infeasible, a comprehensive technical rationale must be provided.
See our Contributing Guide (CONTRIBUTING.md) for full instructions, test commands, and submission checklists.
agent-tool-parser is strictly a passive lexical and structural parser. It does not validate, authenticate, sandbox, or execute tool calls, shell commands, or arbitrary parameters emitted by language models.
See our full Legal & Execution Disclaimer (DISCLAIMER.md).
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2026 InBoost Team.
Maintained by the InBoost Team.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.3 | 16 Sept 2026 | 5 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 23:34 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:1b648bfffb2940ebf10854689d3ccc754e8e9237705cc721629fa4fa48684025