agent-tool-parser 0.1.0__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/Cargo.lock +5 -4
  2. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/Cargo.toml +4 -7
  3. agent_tool_parser-0.1.4/PKG-INFO +592 -0
  4. agent_tool_parser-0.1.0/PKG-INFO → agent_tool_parser-0.1.4/README.md +164 -45
  5. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/Cargo.toml +3 -1
  6. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4/crates/agent-tool-parser-core}/README.md +164 -5
  7. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/canonical.rs +138 -0
  8. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/src/cleaners.rs +279 -1
  9. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/src/lib.rs +23 -1
  10. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/src/models.rs +71 -0
  11. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/src/parser.rs +92 -240
  12. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/python_calls.rs +621 -0
  13. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/raw.rs +373 -0
  14. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/semantics.rs +363 -0
  15. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/streaming.rs +661 -0
  16. agent_tool_parser-0.1.4/crates/agent-tool-parser-core/src/vector.rs +137 -0
  17. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/tests/degenerate_tests.rs +119 -1
  18. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-py/Cargo.toml +1 -1
  19. agent_tool_parser-0.1.4/crates/agent-tool-parser-py/src/lib.rs +759 -0
  20. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/pyproject.toml +11 -5
  21. agent_tool_parser-0.1.4/src/agent_tool_parser/__init__.py +195 -0
  22. agent_tool_parser-0.1.4/src/agent_tool_parser/canonical.py +82 -0
  23. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/src/agent_tool_parser/cleaners.py +167 -0
  24. agent_tool_parser-0.1.4/src/agent_tool_parser/models.py +117 -0
  25. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/src/agent_tool_parser/parser.py +101 -11
  26. agent_tool_parser-0.1.4/src/agent_tool_parser/python_calls.py +467 -0
  27. agent_tool_parser-0.1.4/src/agent_tool_parser/raw.py +314 -0
  28. agent_tool_parser-0.1.4/src/agent_tool_parser/semantics.py +223 -0
  29. agent_tool_parser-0.1.4/src/agent_tool_parser/streaming.py +565 -0
  30. agent_tool_parser-0.1.4/src/agent_tool_parser/vector.py +66 -0
  31. agent_tool_parser-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +0 -41
  32. agent_tool_parser-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  33. agent_tool_parser-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +0 -28
  34. agent_tool_parser-0.1.0/.github/workflows/ci.yml +0 -77
  35. agent_tool_parser-0.1.0/.github/workflows/pages.yml +0 -41
  36. agent_tool_parser-0.1.0/.github/workflows/release.yml +0 -177
  37. agent_tool_parser-0.1.0/.gitignore +0 -25
  38. agent_tool_parser-0.1.0/CHANGELOG.md +0 -41
  39. agent_tool_parser-0.1.0/CODE_OF_CONDUCT.md +0 -66
  40. agent_tool_parser-0.1.0/CONTRIBUTING.md +0 -183
  41. agent_tool_parser-0.1.0/DCO.md +0 -32
  42. agent_tool_parser-0.1.0/DISCLAIMER.md +0 -37
  43. agent_tool_parser-0.1.0/SECURITY.md +0 -59
  44. agent_tool_parser-0.1.0/bindings/go/agenttoolparser/go.mod +0 -3
  45. agent_tool_parser-0.1.0/bindings/go/agenttoolparser/parser.go +0 -101
  46. agent_tool_parser-0.1.0/bindings/go/agenttoolparser/parser_test.go +0 -42
  47. agent_tool_parser-0.1.0/crates/agent-tool-parser-c/Cargo.toml +0 -15
  48. agent_tool_parser-0.1.0/crates/agent-tool-parser-c/include/agent_tool_parser.h +0 -62
  49. agent_tool_parser-0.1.0/crates/agent-tool-parser-c/src/lib.rs +0 -194
  50. agent_tool_parser-0.1.0/crates/agent-tool-parser-py/src/lib.rs +0 -251
  51. agent_tool_parser-0.1.0/crates/agent-tool-parser-wasm/Cargo.toml +0 -17
  52. agent_tool_parser-0.1.0/crates/agent-tool-parser-wasm/src/lib.rs +0 -57
  53. agent_tool_parser-0.1.0/docs/index.html +0 -922
  54. agent_tool_parser-0.1.0/examples/go/go.mod +0 -7
  55. agent_tool_parser-0.1.0/examples/go/main.go +0 -21
  56. agent_tool_parser-0.1.0/go.work +0 -3
  57. agent_tool_parser-0.1.0/src/agent_tool_parser/__init__.py +0 -70
  58. agent_tool_parser-0.1.0/src/agent_tool_parser/models.py +0 -36
  59. agent_tool_parser-0.1.0/tests/test_benchmark_formats.py +0 -289
  60. agent_tool_parser-0.1.0/tests/test_degenerate_inputs.py +0 -306
  61. agent_tool_parser-0.1.0/tests/test_parity.py +0 -334
  62. agent_tool_parser-0.1.0/tests/test_parser.py +0 -549
  63. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/LICENSE +0 -0
  64. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/crates/agent-tool-parser-core/tests/benchmark_tests.rs +0 -0
  65. {agent_tool_parser-0.1.0 → agent_tool_parser-0.1.4}/src/agent_tool_parser/py.typed +0 -0
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "agent-tool-parser-c"
7
- version = "0.1.0"
7
+ version = "0.1.4"
8
8
  dependencies = [
9
9
  "agent-tool-parser-core",
10
10
  "serde_json",
@@ -12,9 +12,10 @@ dependencies = [
12
12
 
13
13
  [[package]]
14
14
  name = "agent-tool-parser-core"
15
- version = "0.1.0"
15
+ version = "0.1.4"
16
16
  dependencies = [
17
17
  "html-escape",
18
+ "memchr",
18
19
  "once_cell",
19
20
  "regex",
20
21
  "serde",
@@ -23,7 +24,7 @@ dependencies = [
23
24
 
24
25
  [[package]]
25
26
  name = "agent-tool-parser-py"
26
- version = "0.1.0"
27
+ version = "0.1.4"
27
28
  dependencies = [
28
29
  "agent-tool-parser-core",
29
30
  "pyo3",
@@ -33,7 +34,7 @@ dependencies = [
33
34
 
34
35
  [[package]]
35
36
  name = "agent-tool-parser-wasm"
36
- version = "0.1.0"
37
+ version = "0.1.4"
37
38
  dependencies = [
38
39
  "agent-tool-parser-core",
39
40
  "serde-wasm-bindgen",
@@ -1,14 +1,9 @@
1
1
  [workspace]
2
2
  resolver = "2"
3
- members = [
4
- "crates/agent-tool-parser-core",
5
- "crates/agent-tool-parser-c",
6
- "crates/agent-tool-parser-py",
7
- "crates/agent-tool-parser-wasm",
8
- ]
3
+ members = ["crates/agent-tool-parser-core", "crates/agent-tool-parser-py"]
9
4
 
10
5
  [workspace.package]
11
- version = "0.1.0"
6
+ version = "0.1.4"
12
7
  edition = "2021"
13
8
  authors = ["InBoost Team"]
14
9
  license = "MIT"
@@ -21,3 +16,5 @@ serde_json = "1.0"
21
16
  regex = "1.10"
22
17
  once_cell = "1.19"
23
18
  html-escape = "0.2"
19
+ memchr = "2.7"
20
+
@@ -0,0 +1,592 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-tool-parser
3
+ Version: 0.1.4
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Dist: orjson>=3.9.0 ; extra == 'all'
16
+ Requires-Dist: json-repair>=0.30.0 ; extra == 'all'
17
+ Requires-Dist: mypy>=1.10.0 ; extra == 'dev'
18
+ Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
19
+ Requires-Dist: ruff>=0.5.0 ; extra == 'dev'
20
+ Requires-Dist: orjson>=3.9.0 ; extra == 'dev'
21
+ Requires-Dist: json-repair>=0.30.0 ; extra == 'dev'
22
+ Requires-Dist: orjson>=3.9.0 ; extra == 'fast'
23
+ Requires-Dist: json-repair>=0.30.0 ; extra == 'repair'
24
+ Provides-Extra: all
25
+ Provides-Extra: dev
26
+ Provides-Extra: fast
27
+ Provides-Extra: repair
28
+ License-File: LICENSE
29
+ Summary: 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.
30
+ Keywords: llm,agent,tool-calling,function-calling,deepseek,dsml,claude,qwen,llama,json-repair,zero-dependencies,inboost
31
+ Author: InBoost Team
32
+ License-Expression: MIT
33
+ Requires-Python: >=3.9
34
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
35
+ Project-URL: Changelog, https://github.com/inboost-dev/agent-tool-parser/blob/main/CHANGELOG.md
36
+ Project-URL: Documentation, https://inboost-dev.github.io/agent-tool-parser/
37
+ Project-URL: Homepage, https://github.com/inboost-dev/agent-tool-parser
38
+ Project-URL: Issues, https://github.com/inboost-dev/agent-tool-parser/issues
39
+ Project-URL: Repository, https://github.com/inboost-dev/agent-tool-parser.git
40
+
41
+ # agent-tool-parser ⚡
42
+
43
+ [![Documentation](https://img.shields.io/badge/docs-interactive%20playground-blueviolet.svg)](https://inboost-dev.github.io/agent-tool-parser/)
44
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.1.4-blue.svg)](https://pypi.org/project/agent-tool-parser/)
45
+ [![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://pypi.org/)
46
+ [![Crates.io](https://img.shields.io/badge/crates.io-v0.1.4-orange.svg)](https://crates.io/crates/agent-tool-parser-core)
47
+ [![Rust](https://img.shields.io/badge/rust-1.75%2B-red.svg)](https://www.rust-lang.org/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
49
+ [![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)]()
50
+ [![Speed](https://img.shields.io/badge/latency-%3C0.05ms-orange.svg)]()
51
+
52
+ **High-performance, dual-engine (Rust + Python) parser and cleaner for heterogeneous LLM tool calls and structured outputs.**
53
+ [**Interactive Documentation & Live Playground ↗**](https://inboost-dev.github.io/agent-tool-parser/)
54
+
55
+ `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.
56
+
57
+ ---
58
+
59
+ ## Design Rationale & Architectural Context
60
+
61
+ ### Scope and Applicability
62
+ 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.
63
+
64
+ 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:
65
+
66
+ 1. **Delimiter Heterogeneity**: Models output diverse serialization schemes, including XML tags (`<tool_use>`), DSML blocks (`<|DSML|invoke>`), native tokens (`<|tool call begin|>`), or AST function calls (`call:tool(arg=val)`).
67
+ 2. **Co-occurring Chain-of-Thought Blocks**: Reasoning models output internal deliberation traces (such as `<think>...</think>`) in the same stream as the final tool invocation.
68
+ 3. **Code Payload Escaping**: Serializing multi-line scripts, shell commands, or unified diffs inside standard JSON strings frequently leads to escape sequence errors with quotes and whitespace.
69
+ 4. **Parameter Name Variations**: Minor model deviations in argument naming (e.g., `cmd` instead of `command`, `file` instead of `path`) can cause downstream schema validation failures.
70
+ 5. **Stream Truncation**: When generation terminates prematurely due to token budget limits, partial JSON structures fail standard deserialization.
71
+
72
+ `agent-tool-parser` provides a deterministic, zero-dependency processing layer to normalize these outputs before execution.
73
+
74
+ ---
75
+
76
+ ## Comparative Analysis
77
+
78
+ | Approach | Design Focus | Execution Characteristics | Scope & Constraints |
79
+ | :--- | :--- | :--- | :--- |
80
+ | **`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. |
81
+ | **`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. |
82
+ | **`LangChain` / `LlamaIndex`** | Full-stack application frameworks | Comprehensive multi-package ecosystem. | Higher memory footprint and dependency tree for pipelines requiring only output parsing. |
83
+ | **`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. |
84
+ | **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. |
85
+ | **Ad-Hoc Regular Expressions** | Project-specific pattern matching | Zero external dependencies. | Brittle against unquoted attributes, nested tags, and varying model formatting conventions. |
86
+
87
+ ### Architectural Trade-offs: Post-Parsing vs. Server-Side Alternatives
88
+
89
+ In production agent architectures, extracting structured tool invocations is typically approached at one of three system boundaries:
90
+
91
+ | Architectural Dimension | Client-Side Post-Parsing (`agent-tool-parser`) | Logit-Level Constrained Decoding (e.g., XGrammar, Outlines) | Secondary Model Pass (Neural Re-Parser) |
92
+ | :--- | :--- | :--- | :--- |
93
+ | **Execution Boundary** | Client runtime (post-generation) | Inference engine sampling loop | Secondary inference endpoint or worker |
94
+ | **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 |
95
+ | **Runtime Dependencies** | Python standard library (zero external dependencies) | Inference runtime stack (e.g., PyTorch, CUDA, Triton) | Model inference framework or API client |
96
+ | **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 |
97
+ | **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 |
98
+ | **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) |
99
+
100
+ #### Architectural Trade-offs
101
+
102
+ - **API Boundary & Sampler Control:** Logit masking operates inside the autoregressive generation loop on the inference host. When utilizing managed or third-party inference providers (such as OpenRouter, DeepSeek API, or Groq), clients receive serialized text rather than sampler-level logit access, making server-side grammar constraints unavailable.
103
+ - **Intermediate Reasoning Tokens:** When reasoning architectures emit unconstrained chain-of-thought blocks (such as `<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.
104
+ - **Payload Heterogeneity:** Tool calls frequently involve non-JSON payloads, such as multi-line shell scripts, unified diffs, or XML CDATA blocks. Client-side parsers process these alternative delimiters directly without requiring JSON string escaping.
105
+
106
+ ---
107
+
108
+ ## Architectural Reference Patterns
109
+
110
+ ### Pattern 1: Autonomous Agent Loop (ReAct / Task Execution)
111
+ Standard ReAct loops require resilient extraction when alternating between reasoning and tool execution:
112
+
113
+ ```python
114
+ from agent_tool_parser import try_parse_tool_calls
115
+
116
+
117
+ def run_agent_turn(model_output: str) -> list[dict]:
118
+ # Extracts all valid tool calls, removing any <think> blocks
119
+ calls = try_parse_tool_calls(model_output)
120
+
121
+ results = []
122
+ for call in calls:
123
+ execution_result = dispatch_tool(call.name, call.args)
124
+ results.append({"tool": call.name, "result": execution_result})
125
+
126
+ return results
127
+ ```
128
+
129
+ ### Pattern 2: API Gateway & Proxy Normalization (LiteLLM / FastAPI Middleware)
130
+ When proxying multi-provider models into standard OpenAI-compatible endpoints:
131
+
132
+ ```python
133
+ from agent_tool_parser import try_parse_tool_calls
134
+
135
+
136
+ def normalize_completion_response(response_dict: dict) -> dict:
137
+ choice = response_dict["choices"][0]
138
+ message = choice["message"]
139
+
140
+ # If the provider omitted tool_calls but returned markup in content:
141
+ if not message.get("tool_calls") and message.get("content"):
142
+ extracted = try_parse_tool_calls(message["content"])
143
+ if extracted:
144
+ message["tool_calls"] = [
145
+ {
146
+ "id": f"call_{i}",
147
+ "type": "function",
148
+ "function": {"name": c.name, "arguments": json.dumps(c.args)},
149
+ }
150
+ for i, c in enumerate(extracted)
151
+ ]
152
+ return response_dict
153
+ ```
154
+
155
+ ### Pattern 3: Control Plane & Fleet Worker Ingress (Mission Control / Paperclip / Dify)
156
+ In multi-agent environments managing autonomous background workers, normalizing arguments prior to approval gates prevents stalled task queues:
157
+
158
+ ```python
159
+ from agent_tool_parser import ToolParser, ToolError
160
+
161
+ parser = ToolParser(
162
+ allowed_tools={"read_file", "write_file", "execute_command"},
163
+ param_aliases={"cmd": "command", "filepath": "path"},
164
+ )
165
+
166
+
167
+ def process_worker_step(raw_step_output: str) -> dict:
168
+ try:
169
+ call = parser.parse(raw_step_output)
170
+ return {"status": "ready_for_approval", "call": call}
171
+ except ToolError as error:
172
+ return {"status": "rejected", "error": str(error)}
173
+ ```
174
+
175
+ ### Pattern 4: Asynchronous Parallel Tool Dispatch
176
+ Processing multiple tool calls from a single model generation step:
177
+
178
+ ```python
179
+ import asyncio
180
+ from agent_tool_parser import parse_tool_calls
181
+
182
+
183
+ async def handle_parallel_generation(model_output: str):
184
+ calls = parse_tool_calls(model_output)
185
+ tasks = [async_execute(call.name, call.args) for call in calls]
186
+ return await asyncio.gather(*tasks, return_exceptions=True)
187
+ ```
188
+
189
+ ### Pattern 5: KV-Cache Invariance & Turn Buffer Canonicalization
190
+ In multi-turn agent loops running on vLLM or SGLang, permuted JSON argument keys (e.g. `{"b": 2, "a": 1}` vs `{"a": 1, "b": 2}`) invalidate prefix KV-caches and trigger expensive prompt recomputation. Canonicalizing tool calls ensures bit-level prefix invariance:
191
+
192
+ ```python
193
+ from agent_tool_parser import canonicalize_tool_calls, parse_tool_call
194
+
195
+ # 1. Convert parsed ToolCall directly into canonical OpenAI format
196
+ call = parse_tool_call(model_raw_output)
197
+ openai_tool_call = call.to_openai_tool_call() # Deterministic sorted keys
198
+
199
+ # 2. Canonicalize an entire conversation turn buffer before dispatching to inference
200
+ messages[-1]["tool_calls"] = canonicalize_tool_calls(messages[-1]["tool_calls"])
201
+ ```
202
+
203
+ ### Pattern 6: Zero-Copy Structural Scan (SilentJSON Multi-GB/s Routing)
204
+ When filtering, routing, or authorizing tool calls at gateway speeds without incurring the latency and memory overhead of full JSON DOM deserialization:
205
+
206
+ ```python
207
+ from agent_tool_parser import extract_raw_tool_calls
208
+
209
+ # Fast structural scan: extracts tool name and raw unparsed arguments directly from slices
210
+ raw_calls = extract_raw_tool_calls(stream_or_message_text)
211
+
212
+ for raw in raw_calls:
213
+ # 1. Early security policy and permission routing (multi-GB/s throughput)
214
+ if raw.name not in ALLOWED_GATEWAY_TOOLS:
215
+ raise PermissionError(f"Unauthorized tool invocation: {raw.name}")
216
+
217
+ # 2. Deserialization on demand only when dispatching
218
+ args = raw.parse_args()
219
+ execute_tool(raw.name, args)
220
+ ```
221
+
222
+ ### Pattern 7: Token-by-Token Event-Driven Streaming
223
+ When consuming streaming responses token-by-token (via SSE or async iterators), `StreamingToolParser` emits real-time events for text, thinking blocks, early tool starts, argument deltas, and completed calls:
224
+
225
+ ```python
226
+ from agent_tool_parser import StreamingToolParser
227
+
228
+ parser = StreamingToolParser()
229
+
230
+ async for chunk in model_stream:
231
+ for event in parser.feed(chunk.text):
232
+ if event.is_text:
233
+ await stream_to_client(event.text)
234
+ elif event.is_thinking:
235
+ await stream_thought_to_ui(event.thinking)
236
+ elif event.is_tool_call_started:
237
+ # Minimal Time-To-First-Event: warm up sandbox or request user confirmation
238
+ await notify_ui_tool_starting(event.tool_name)
239
+ elif event.is_tool_call_arguments_chunk:
240
+ await stream_arguments_delta(event.delta)
241
+ elif event.is_tool_call_completed:
242
+ await dispatch_tool_execution(event.tool_call)
243
+
244
+ # Flush any trailing tokens and recover truncated calls on stream cutoff
245
+ for event in parser.finish():
246
+ if event.is_tool_call_completed:
247
+ await dispatch_tool_execution(event.tool_call)
248
+ ```
249
+
250
+ ### Pattern 8: Cross-Framework Semantic Categorization & Permission Gating
251
+ When building multi-framework autonomous agents (OpenHands, Claude Code, Cursor, Aider), tool names and argument schemes vary (`str_replace` vs `edit_file`, `file_path` vs `path`). `agent-tool-parser` provides canonical semantic categories (`EDIT`, `EXEC`, `READ`, `SEARCH`, `TEST`, `SUBMIT`, `L0_ACCEL`), sub-command inspection for bash tools (distinguishing test runs like `pytest` from file mutations like `patch` / `sed`), and parameter normalizers:
252
+
253
+ ```python
254
+ from agent_tool_parser import ToolCategory, parse_tool_call
255
+
256
+ call = parse_tool_call(model_output)
257
+
258
+ # 1. Semantic inspection properties
259
+ print(call.category) # ToolCategory.EDIT, ToolCategory.TEST, etc.
260
+ print(call.is_mutation) # True if the tool modifies files or code
261
+ print(call.is_verification) # True if running test suites or verification
262
+ print(call.is_execution) # True for shell / bash execution
263
+
264
+ # 2. Canonical argument & tool name normalization
265
+ print(call.normalized_name) # Canonical lowercase name
266
+ print(call.normalized_args) # Canonicalized keys ('file_path' -> 'path', 'cmd' -> 'command')
267
+ ```
268
+
269
+ ### Pattern 9: Safe SIMD Hardware Architecture Detection
270
+ To prevent illegal instruction (`SIGILL`) crashes when deploying compiled native binaries across heterogeneous virtualization environments (AWS Graviton, Google Cloud, Docker), `detect_vector_engine` inspects CPU vector capabilities safely at runtime:
271
+
272
+ ```python
273
+ from agent_tool_parser import detect_vector_engine, CpuVectorEngine
274
+
275
+ engine = detect_vector_engine()
276
+ # Returns CpuVectorEngine.Avx2_256, Sse42_128, Neon_128, or Scalar_Fallback
277
+ print(f"Detected vector engine: {engine}")
278
+ ```
279
+
280
+
281
+ ---
282
+
283
+ ## Installation
284
+
285
+ ```bash
286
+ # Standard Python installation (zero required dependencies, pure Python fallback by default)
287
+ pip install agent-tool-parser
288
+
289
+ # With optional performance and advanced JSON repair extras:
290
+ pip install "agent-tool-parser[all]" # installs orjson and json-repair
291
+ pip install "agent-tool-parser[fast]" # installs orjson for accelerated pure-Python JSON
292
+ pip install "agent-tool-parser[repair]" # installs json-repair for advanced corrupt JSON
293
+
294
+ # Go module
295
+ go get github.com/inboost-dev/agent-tool-parser/bindings/go/agenttoolparser
296
+ ```
297
+
298
+ ### Dev & Nightly Builds (Pre-compiled for downstream dependencies)
299
+ Downstream projects tracking active development on the `dev` branch can download pre-compiled native wheels and C libraries directly without requiring local Rust toolchains or recompiling from source:
300
+
301
+ ```bash
302
+ # Python: download pre-built wheels bundle from rolling nightly release
303
+ curl -LO https://github.com/inboost-dev/agent-tool-parser/releases/download/nightly/agent-tool-parser-wheels.zip
304
+ unzip agent-tool-parser-wheels.zip && pip install *.whl
305
+
306
+ # C/C++: download pre-compiled shared library and headers
307
+ curl -LO https://github.com/inboost-dev/agent-tool-parser/releases/download/nightly/agent-tool-parser-c-linux-x86_64.tar.gz
308
+ tar -xzf agent-tool-parser-c-linux-x86_64.tar.gz -C ./libs/
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Quickstart
314
+
315
+ ### Python
316
+
317
+ ```python
318
+ from agent_tool_parser import parse_tool_call, ACCELERATED
319
+
320
+ print(f"Accelerated native core active: {ACCELERATED}")
321
+
322
+ # Example response containing reasoning tokens, DSML markup, and unquoted parameters:
323
+ raw_output = """
324
+ <think>
325
+ Evaluating dependency versions before test execution.
326
+ </think>
327
+ <|DSML|tool_calls>
328
+ <|DSML|invoke name="run_tests">
329
+ <|DSML|parameter name="suite">unit</|DSML|parameter>
330
+ <|DSML|parameter name="verbose">true</|DSML|parameter>
331
+ </|DSML|invoke>
332
+ </|DSML|tool_calls>
333
+ """
334
+
335
+ call = parse_tool_call(raw_output)
336
+
337
+ print(call.name) # 'run_tests'
338
+ print(call.args) # {'suite': 'unit', 'verbose': True}
339
+ print(call.raw_source) # Raw matched substring
340
+ ```
341
+
342
+ ### Go (`cgo`)
343
+
344
+ ```go
345
+ package main
346
+
347
+ import (
348
+ "fmt"
349
+ "log"
350
+ agenttoolparser "github.com/inboost-dev/agent-tool-parser/bindings/go/agenttoolparser"
351
+ )
352
+
353
+ func main() {
354
+ raw := `<|tool calls begin|><|tool call begin|>function=search<|tool sep|>{"pattern": "parse", "path": "."}<|tool call end|><|tool calls end|>`
355
+ call, err := agenttoolparser.ParseToolCall(raw)
356
+ if err != nil {
357
+ log.Fatal(err)
358
+ }
359
+ fmt.Printf("Tool: %s, Args: %v\n", call.Name, call.Args)
360
+ }
361
+ ```
362
+
363
+ ### Rust (Native Crate)
364
+
365
+ ```rust
366
+ use agent_tool_parser_core::parse_tool_call;
367
+
368
+ fn main() {
369
+ let raw = "<invoke name=\"bash\"><parameter name=\"cmd\">git status</parameter></invoke>";
370
+ let call = parse_tool_call(raw).expect("Valid tool invocation");
371
+ println!("Tool: {}, Args: {:?}", call.name, call.args);
372
+ }
373
+ ```
374
+
375
+ ### C / C++ (`agent_tool_parser.h`)
376
+
377
+ ```c
378
+ #include "agent_tool_parser.h"
379
+ #include <stdio.h>
380
+
381
+ int main() {
382
+ const char* raw = "<invoke name=\"bash\"><parameter name=\"cmd\">pwd</parameter></invoke>";
383
+ ATPToolCall* call = atp_parse_tool_call(raw);
384
+ if (call) {
385
+ printf("Tool: %s\nArguments: %s\n", call->name, call->args_json);
386
+ atp_free_tool_call(call);
387
+ }
388
+ return 0;
389
+ }
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Dual-Engine Architecture & Multi-Language Core
395
+
396
+ `agent-tool-parser` is structured with a modular, dual-engine design:
397
+
398
+ ```
399
+ agent-tool-parser/
400
+ ├── src/agent_tool_parser/ # Pure-Python engine (zero-dependency, no compiler required)
401
+ │ ├── _accelerated.so # Optional PyO3 compiled native extension
402
+ │ └── __init__.py # Transparent runtime dispatch (ACCELERATED flag)
403
+ ├── crates/
404
+ │ ├── agent-tool-parser-core/ # High-performance Rust core extraction engine
405
+ │ ├── agent-tool-parser-c/ # C-ABI export & standard C header (agent_tool_parser.h)
406
+ │ ├── agent-tool-parser-py/ # PyO3 bindings with pythonize zero-copy deserialization
407
+ │ └── agent-tool-parser-wasm/ # WebAssembly bindings (wasm-bindgen) for Node.js & browser
408
+ └── bindings/
409
+ └── go/agenttoolparser/ # Native Go module wrapping C-ABI via cgo
410
+ ```
411
+
412
+ ### Engine Dispatch & Synchronization
413
+
414
+ - **Parity Guarantee**: Both the pure-Python and Rust core engines pass the exact same validation suite (145 Python unit & parity tests, 92 Rust unit/integration/benchmark tests, and 260+ differential parity assertions) covering all supported formats, error handling, parameter aliases, and degenerate/adversarial input texts.
415
+ - **Zero-Compile Fallback**: If the compiled native extension is not present (or when `AGENT_TOOL_PARSER_NO_EXT=1` is set in the environment), the package operates in pure Python with zero external dependencies.
416
+ - **Compiled Acceleration**: When the compiled extension is available, parsing execution occurs entirely in compiled native code with sub-millisecond execution times.
417
+
418
+ ## Supported Formats
419
+
420
+ | Format Specification | Representative Input | Extraction Status |
421
+ | :--- | :--- | :---: |
422
+ | **Anthropic Claude XML** | `<tool_use><name>read_file</name><arguments>{"path": "src/core.py"}</arguments></tool_use>` | Supported |
423
+ | **OpenAI Function Format** | `{"tool_calls": [{"type": "function", "function": {"name": "search", "arguments": "{...}"}}]}` | Supported |
424
+ | **DeepSeek Native Tokens** | `<|tool call begin|>function=bash<|tool sep|>{"command": "pytest"}<|tool call end|>` | Supported |
425
+ | **DeepSeek DSML** | `<|DSML|tool name=search><|DSML|parameter name=query>vector</|DSML|parameter>` | Supported |
426
+ | **Qwen-Agent Tokens** | `<\|action_start\|><\|action_name\|>run<\|action_args\|>{"script": "main.py"}<\|action_end\|>` | Supported |
427
+ | **Llama 3.1+ Python Tag** | `<\|python_tag\|>bash.call(command="pytest")` | Supported |
428
+ | **Mistral Tool Syntax** | `[TOOL_CALLS] [{"name": "search", "arguments": {"query": "vector"}}]` | Supported |
429
+ | **Python Expression Syntax** | `read_file(path="src/main.py", offset=10)` | Supported |
430
+ | **ReAct Action Notation** | `Action: bash\nAction Input: pytest` | Supported |
431
+ | **Hermes 2/3 XML Markup** | `<tool_call>{"name": "search", "arguments": {"query": "vector"}}</tool_call>` | Supported |
432
+ | **Direct Parameter XML** | `<tool_name>search</tool_name><query>vector</query>` | Supported |
433
+ | **Markdown Fenced JSON** | ````json\n{"tool": "bash", "command": "git status"}\n```` | Supported |
434
+ | **Embedded String CDATA** | `<parameter name="patch"><![CDATA[def test():\n assert True]]></parameter>` | Supported |
435
+ | **Malformed JSON Repair** | `{'tool': 'bash', 'command': 'pytest', 'verbose': True,}` | Supported |
436
+ | **Thinking Blocks** | `<think>Analyzing context...</think>` | Delimited & Stripped |
437
+
438
+ ---
439
+
440
+ ## API Reference
441
+
442
+ ### Functional Interface
443
+
444
+ #### `parse_tool_call(text: str, ...) -> ToolCall`
445
+ Extracts a single tool call from text. Raises `ToolError` if no valid structure is recognized.
446
+
447
+ #### `parse_tool_calls(text: str, ...) -> list[ToolCall]`
448
+ Extracts all tool calls present in the text. Raises `ToolError` if the output contains no extractable invocations.
449
+
450
+ #### `try_parse_tool_call(text: str, ...) -> Optional[ToolCall]`
451
+ Non-raising variant of `parse_tool_call`. Returns `None` if no invocation is detected.
452
+
453
+ #### `try_parse_tool_calls(text: str, ...) -> list[ToolCall]`
454
+ Non-raising variant of `parse_tool_calls`. Returns an empty list `[]` if no invocations are detected.
455
+
456
+ #### `safe_json_loads(s: str, default: Any = None) -> Any`
457
+ Standalone utility for resilient JSON decoding. Resolves trailing commas, single-quoted keys, unquoted boolean/null literals, and unclosed brackets.
458
+
459
+ ---
460
+
461
+ ### KV-Cache Canonicalization Utilities
462
+
463
+ #### `canonicalize_tool_calls(tool_calls: list[dict[str, Any]]) -> list[dict[str, Any]]`
464
+ Recursively sorts argument keys and performs stable sorting of tool calls by function name and call ID for multi-turn KV-cache preservation.
465
+
466
+ #### `canonical_json_dumps(obj: Any) -> str`
467
+ Serializes any dictionary, list, or primitive into a deterministic JSON string with alphabetically sorted keys and compact whitespace (`separators=(',', ':')`).
468
+
469
+ #### `canonical_sort_keys(obj: Any) -> Any`
470
+ Recursively reorders dictionary keys alphabetically while preserving original list order.
471
+
472
+ #### `canonicalize_arguments_string(args_str: str) -> str`
473
+ Parses and re-serializes a raw JSON argument string into compact, key-sorted canonical form.
474
+
475
+ #### `ToolCall.to_canonical_json() -> str`
476
+ Serializes `call.args` directly into a canonical JSON string with sorted keys.
477
+
478
+ #### `ToolCall.to_openai_tool_call(call_id: Optional[str] = None, canonical: bool = True) -> dict[str, Any]`
479
+ Converts the `ToolCall` instance into an OpenAI-compatible `{"id": ..., "type": "function", "function": {"name": ..., "arguments": ...}}` dictionary.
480
+
481
+ ---
482
+
483
+ ### Semantic Categorization & Action Inspection
484
+
485
+ #### `categorize_tool(name: str, args: Optional[dict[str, Any]] = None) -> ToolCategory`
486
+ Determines the semantic category (`EDIT`, `EXEC`, `READ`, `SEARCH`, `TEST`, `SUBMIT`, `L0_ACCEL`, `UNKNOWN`). For execution tools (`bash`), automatically inspects command arguments to disambiguate test suites (`pytest`, `unittest`) and file mutations (`git apply`, redirection).
487
+
488
+ #### `normalize_tool_name(raw_name: str) -> str`
489
+ Resolves canonical lowercase tool name across agent framework variations (`execute_bash` -> `bash`, `notebookedit` -> `notebookedit`).
490
+
491
+ #### `normalize_tool_args(args: dict[str, Any]) -> dict[str, Any]`
492
+ Normalizes parameter keys across common aliases (`file_path` / `filepath` / `filename` -> `path`, `old_string` -> `old_str`, `cmd` -> `command`).
493
+
494
+ #### `ToolCall` Semantic Inspection Properties
495
+ - `call.category`: Canonical `ToolCategory` enum.
496
+ - `call.is_mutation`: `True` if tool mutates files or code on disk.
497
+ - `call.is_verification`: `True` if tool runs tests or assertion checks.
498
+ - `call.is_execution`: `True` for shell / bash command execution.
499
+ - `call.is_read`: `True` for inspection / file read tools.
500
+ - `call.is_search`: `True` for search / AST grep tools.
501
+ - `call.is_submit`: `True` for task completion / patch submission tools.
502
+
503
+ ---
504
+
505
+ ### Resilient Cleaners & Hardware SIMD Detection
506
+
507
+ #### `repair_unescaped_quotes(s: str) -> str`
508
+ Heuristically repairs unescaped inner double quotes inside JSON string values commonly emitted by code-editing and shell execution agents.
509
+
510
+ #### `detect_vector_engine() -> CpuVectorEngine`
511
+ Detects active runtime CPU SIMD vector engine (`Avx2_256`, `Sse42_128`, `Neon_128`, or `Scalar_Fallback`) safely without risking illegal instruction crashes on virtualized cloud hardware.
512
+
513
+ ---
514
+
515
+ ### Object-Oriented Interface: `ToolParser`
516
+
517
+ The `ToolParser` class allows setting persistent validation rules, allowed tool sets, and parameter mappings across calls:
518
+
519
+ ```python
520
+ from agent_tool_parser import ToolParser, ToolError
521
+
522
+ parser = ToolParser(
523
+ allowed_tools={"read_file", "write_file", "execute_command"},
524
+ tool_aliases={"run": "execute_command", "view": "read_file"},
525
+ param_aliases={"cmd": "command", "file": "path"},
526
+ allow_shell_fallback=True,
527
+ )
528
+
529
+ call = parser.parse("```bash\npytest tests/\n```")
530
+ # Returns ToolCall(name='execute_command', args={'command': 'pytest tests/'})
531
+ ```
532
+
533
+ ---
534
+
535
+ ## Performance Characteristics
536
+
537
+ Measurements conducted on Python 3.12 (AMD EPYC, single execution thread):
538
+
539
+ | Evaluation Scenario | Average Latency | Throughput |
540
+ | :--- | :---: | :---: |
541
+ | DSML Markup Extraction | **0.038 ms** | ~26,000 calls / sec |
542
+ | DeepSeek Delimiter Parsing | **0.024 ms** | ~41,000 calls / sec |
543
+ | Markdown & Malformed JSON Repair | **0.015 ms** | ~65,000 calls / sec |
544
+
545
+ ---
546
+
547
+ ## Automated Testing & CI
548
+
549
+ All pull requests and commits to `main` are continuously validated across runtimes and target architectures in GitHub Actions ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)):
550
+
551
+ - **Python Multi-Version Matrix**: Python 3.9, 3.10, 3.11, 3.12, and 3.13 running `ruff`, `mypy`, and the complete 114-test suite under pure-Python fallback (`AGENT_TOOL_PARSER_NO_EXT=1`).
552
+ - **Rust Core & Multi-Language Bindings**:
553
+ - Rust stable toolchain: `cargo fmt`, `cargo clippy -D warnings`, and 65 unit, integration, and benchmark tests.
554
+ - C-ABI shared library compilation (`libagent_tool_parser_c.so`) and C unit tests.
555
+ - Native Go binding integration tests (`go test -v ./bindings/go/...`).
556
+ - WebAssembly compilation target verification (`wasm32-unknown-unknown` release build).
557
+ - PyO3 native module build and dual-engine differential parity verification (`tests/test_parity.py` with 240+ assertions).
558
+ - **Storage-Conscious CI Architecture**: The entire test matrix runs in ephemeral runner memory with zero artifact storage overhead, preventing billing impact on account storage quotas.
559
+
560
+ ---
561
+
562
+ ## Contributing
563
+
564
+ 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.
565
+
566
+ See our [**Contributing Guide (CONTRIBUTING.md)**](CONTRIBUTING.md) for full instructions, test commands, and submission checklists.
567
+
568
+ ---
569
+
570
+ ## Security & Execution Disclaimer
571
+
572
+ `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.
573
+
574
+ - **No Execution Safety Guarantees**: Language models can hallucinate, produce malformed payloads, or be manipulated via adversarial prompt injections. Parsed tool calls and arguments must **never** be executed directly without strict downstream input validation, schema enforcement, authorization checks, and process isolation.
575
+ - **Sandboxing & Privilege Boundaries**: The integrating application, agent framework, and operators are solely responsible for enforcing execution security, sandboxing (e.g., containers, VMs, seccomp/gVisor), rate limits, and human-in-the-loop approvals.
576
+ - **Limitation of Liability**: In accordance with the MIT License, this software is provided **"AS IS"**, without warranty of any kind, express or implied. In no event shall the authors, maintainers, or copyright holders (including the InBoost Team) be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, data loss, unauthorized system access, financial losses, service downtime, or hardware damage) arising in any way out of the use, interpretation, execution, or parsing anomalies of this software.
577
+
578
+ See our full [**Legal & Execution Disclaimer (DISCLAIMER.md)**](DISCLAIMER.md).
579
+
580
+ ---
581
+
582
+ ## License
583
+
584
+ This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details.
585
+
586
+ Copyright (c) 2026 InBoost Team.
587
+
588
+ ---
589
+
590
+ *Maintained by the **InBoost Team**.*
591
+
592
+