chuk-tool-processor 0.11__tar.gz → 0.11.2__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.
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/PKG-INFO +35 -2
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/README.md +29 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/pyproject.toml +15 -2
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/processor.py +63 -7
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +1 -1
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/wrappers/caching.py +9 -5
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/stream_manager.py +15 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/base_transport.py +10 -10
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +15 -2
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/sse_transport.py +13 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_call.py +12 -11
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +1 -1
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/json_tool.py +1 -1
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +1 -1
- chuk_tool_processor-0.11.2/src/chuk_tool_processor/utils/fast_json.py +157 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/PKG-INFO +35 -2
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/SOURCES.txt +1 -0
- chuk_tool_processor-0.11.2/src/chuk_tool_processor.egg-info/requires.txt +11 -0
- chuk_tool_processor-0.11/src/chuk_tool_processor.egg-info/requires.txt +0 -5
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/setup.cfg +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/exceptions.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/wrappers/circuit_breaker.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/execution/wrappers/retry.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/context.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/formatter.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/helpers.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/metrics.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/models.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/models.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_result.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_spec.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/validated_tool.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/observability/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/observability/metrics.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/observability/setup.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/observability/tracing.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/discovery.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/py.typed +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/auto_register.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/decorators.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/interface.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/metadata.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/provider.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/tool_export.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/utils/__init__.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/utils/validation.py +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
- {chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.11
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: Async-native framework for registering, discovering, and executing tools referenced in LLM responses
|
|
5
5
|
Author-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
6
6
|
Maintainer-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
@@ -20,11 +20,15 @@ Classifier: Framework :: AsyncIO
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
|
-
Requires-Dist: chuk-mcp>=0.
|
|
23
|
+
Requires-Dist: chuk-mcp>=0.9
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9
|
|
25
25
|
Requires-Dist: psutil>=7.0.0
|
|
26
26
|
Requires-Dist: pydantic>=2.11.3
|
|
27
27
|
Requires-Dist: uuid>=1.30
|
|
28
|
+
Provides-Extra: fast-json
|
|
29
|
+
Requires-Dist: orjson<4,>=3.10.0; extra == "fast-json"
|
|
30
|
+
Provides-Extra: full
|
|
31
|
+
Requires-Dist: orjson<4,>=3.10.0; extra == "full"
|
|
28
32
|
|
|
29
33
|
# CHUK Tool Processor — Production-grade execution for LLM tool calls
|
|
30
34
|
|
|
@@ -287,12 +291,41 @@ pip install chuk-tool-processor[observability]
|
|
|
287
291
|
# With MCP extras
|
|
288
292
|
pip install chuk-tool-processor[mcp]
|
|
289
293
|
|
|
294
|
+
# With fast JSON serialization (2-3x faster, recommended for production)
|
|
295
|
+
pip install chuk-tool-processor[fast-json]
|
|
296
|
+
|
|
290
297
|
# All extras
|
|
291
298
|
pip install chuk-tool-processor[all]
|
|
292
299
|
```
|
|
293
300
|
|
|
294
301
|
</details>
|
|
295
302
|
|
|
303
|
+
<details>
|
|
304
|
+
<summary><strong>Performance Optimization (Optional)</strong></summary>
|
|
305
|
+
|
|
306
|
+
For **2-3x faster JSON operations**, install with the `fast-json` extra:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
pip install chuk-tool-processor[fast-json]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This installs [orjson](https://github.com/ijl/orjson), a fast C-based JSON library. When available, it's automatically used for JSON serialization/deserialization throughout the processor while maintaining full compatibility with stdlib json.
|
|
313
|
+
|
|
314
|
+
**Benchmarks** (see `benchmarks/` for full results):
|
|
315
|
+
|
|
316
|
+
| Operation | stdlib json | orjson | Speedup |
|
|
317
|
+
|-----------|-------------|--------|---------|
|
|
318
|
+
| Simple JSON (100 bytes) | 1.23 µs | 0.45 µs | **2.7x faster** |
|
|
319
|
+
| Complex JSON (5 KB) | 12.5 µs | 4.2 µs | **3.0x faster** |
|
|
320
|
+
| OpenAI tool calls | 8.9 µs | 3.1 µs | **2.9x faster** |
|
|
321
|
+
|
|
322
|
+
**Notes:**
|
|
323
|
+
- Falls back to stdlib json automatically if orjson is not installed
|
|
324
|
+
- Hash computation uses stdlib json for consistency across environments
|
|
325
|
+
- No code changes required—just install the extra
|
|
326
|
+
|
|
327
|
+
</details>
|
|
328
|
+
|
|
296
329
|
<details>
|
|
297
330
|
<summary><strong>Type Checking Support (PEP 561 compliant)</strong></summary>
|
|
298
331
|
|
|
@@ -259,12 +259,41 @@ pip install chuk-tool-processor[observability]
|
|
|
259
259
|
# With MCP extras
|
|
260
260
|
pip install chuk-tool-processor[mcp]
|
|
261
261
|
|
|
262
|
+
# With fast JSON serialization (2-3x faster, recommended for production)
|
|
263
|
+
pip install chuk-tool-processor[fast-json]
|
|
264
|
+
|
|
262
265
|
# All extras
|
|
263
266
|
pip install chuk-tool-processor[all]
|
|
264
267
|
```
|
|
265
268
|
|
|
266
269
|
</details>
|
|
267
270
|
|
|
271
|
+
<details>
|
|
272
|
+
<summary><strong>Performance Optimization (Optional)</strong></summary>
|
|
273
|
+
|
|
274
|
+
For **2-3x faster JSON operations**, install with the `fast-json` extra:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pip install chuk-tool-processor[fast-json]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This installs [orjson](https://github.com/ijl/orjson), a fast C-based JSON library. When available, it's automatically used for JSON serialization/deserialization throughout the processor while maintaining full compatibility with stdlib json.
|
|
281
|
+
|
|
282
|
+
**Benchmarks** (see `benchmarks/` for full results):
|
|
283
|
+
|
|
284
|
+
| Operation | stdlib json | orjson | Speedup |
|
|
285
|
+
|-----------|-------------|--------|---------|
|
|
286
|
+
| Simple JSON (100 bytes) | 1.23 µs | 0.45 µs | **2.7x faster** |
|
|
287
|
+
| Complex JSON (5 KB) | 12.5 µs | 4.2 µs | **3.0x faster** |
|
|
288
|
+
| OpenAI tool calls | 8.9 µs | 3.1 µs | **2.9x faster** |
|
|
289
|
+
|
|
290
|
+
**Notes:**
|
|
291
|
+
- Falls back to stdlib json automatically if orjson is not installed
|
|
292
|
+
- Hash computation uses stdlib json for consistency across environments
|
|
293
|
+
- No code changes required—just install the extra
|
|
294
|
+
|
|
295
|
+
</details>
|
|
296
|
+
|
|
268
297
|
<details>
|
|
269
298
|
<summary><strong>Type Checking Support (PEP 561 compliant)</strong></summary>
|
|
270
299
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chuk-tool-processor"
|
|
7
|
-
version = "0.11"
|
|
7
|
+
version = "0.11.2"
|
|
8
8
|
description = "Async-native framework for registering, discovering, and executing tools referenced in LLM responses"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -41,13 +41,25 @@ classifiers = [
|
|
|
41
41
|
"Typing :: Typed",
|
|
42
42
|
]
|
|
43
43
|
dependencies = [
|
|
44
|
-
"chuk-mcp>=0.
|
|
44
|
+
"chuk-mcp>=0.9",
|
|
45
45
|
"dotenv>=0.9.9",
|
|
46
46
|
"psutil>=7.0.0",
|
|
47
47
|
"pydantic>=2.11.3",
|
|
48
48
|
"uuid>=1.30",
|
|
49
49
|
]
|
|
50
50
|
|
|
51
|
+
# Optional dependency groups
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
# Fast JSON parsing (2-3x faster than stdlib json)
|
|
54
|
+
fast-json = [
|
|
55
|
+
"orjson>=3.10.0,<4",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Full feature set with performance optimizations
|
|
59
|
+
full = [
|
|
60
|
+
"orjson>=3.10.0,<4",
|
|
61
|
+
]
|
|
62
|
+
|
|
51
63
|
# Tell setuptools to look in src/ for your a2a package
|
|
52
64
|
[tool.setuptools.packages.find]
|
|
53
65
|
where = ["src"]
|
|
@@ -80,6 +92,7 @@ dev = [
|
|
|
80
92
|
"bandit>=1.7.0",
|
|
81
93
|
"pre-commit>=3.8.0",
|
|
82
94
|
"coverage[toml]>=7.6.0",
|
|
95
|
+
"orjson>=3.10.0",
|
|
83
96
|
]
|
|
84
97
|
|
|
85
98
|
observability = [
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/processor.py
RENAMED
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import asyncio
|
|
14
14
|
import hashlib
|
|
15
|
-
import json
|
|
15
|
+
import json as stdlib_json # Use stdlib json for consistent hashing
|
|
16
16
|
import time
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
@@ -29,6 +29,7 @@ from chuk_tool_processor.models.tool_call import ToolCall
|
|
|
29
29
|
from chuk_tool_processor.models.tool_result import ToolResult
|
|
30
30
|
from chuk_tool_processor.plugins.discovery import discover_default_plugins, plugin_registry
|
|
31
31
|
from chuk_tool_processor.registry import ToolRegistryInterface, ToolRegistryProvider
|
|
32
|
+
from chuk_tool_processor.utils import fast_json as json
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class ToolProcessor:
|
|
@@ -571,6 +572,9 @@ class ToolProcessor:
|
|
|
571
572
|
"""
|
|
572
573
|
Extract tool calls from text using all available parsers.
|
|
573
574
|
|
|
575
|
+
PERFORMANCE: Uses content sniffing to try most likely parser first,
|
|
576
|
+
with early exit on success. Falls back to concurrent parsing if needed.
|
|
577
|
+
|
|
574
578
|
Args:
|
|
575
579
|
text: Text to parse.
|
|
576
580
|
|
|
@@ -581,6 +585,39 @@ class ToolProcessor:
|
|
|
581
585
|
|
|
582
586
|
# Try each parser
|
|
583
587
|
async with log_context_span("parsing", {"text_length": len(text)}):
|
|
588
|
+
# PERFORMANCE: Smart parser selection based on content hints
|
|
589
|
+
# Most inputs match exactly ONE format, so try the obvious one first
|
|
590
|
+
likely_parser = None
|
|
591
|
+
|
|
592
|
+
# Quick content sniffing (cheap string checks)
|
|
593
|
+
if '"tool_calls"' in text or '"function"' in text:
|
|
594
|
+
# Likely OpenAI format
|
|
595
|
+
likely_parser = next((p for p in self.parsers if "OpenAI" in p.__class__.__name__), None)
|
|
596
|
+
elif text.strip().startswith("{") and ('"name"' in text and '"arguments"' in text):
|
|
597
|
+
# Likely direct JSON tool format
|
|
598
|
+
likely_parser = next((p for p in self.parsers if "Json" in p.__class__.__name__), None)
|
|
599
|
+
elif "<tool" in text or "</tool>" in text:
|
|
600
|
+
# Likely XML format
|
|
601
|
+
likely_parser = next((p for p in self.parsers if "Xml" in p.__class__.__name__), None)
|
|
602
|
+
|
|
603
|
+
# PERFORMANCE: Early exit path - try likely parser first
|
|
604
|
+
if likely_parser:
|
|
605
|
+
try:
|
|
606
|
+
result = await self._try_parser(likely_parser, text)
|
|
607
|
+
if result and isinstance(result, list) and len(result) > 0:
|
|
608
|
+
# Success! Return immediately without trying other parsers
|
|
609
|
+
all_calls.extend(result)
|
|
610
|
+
# Skip to deduplication
|
|
611
|
+
if len(all_calls) <= 1:
|
|
612
|
+
# Fast path: single call, no dedup needed
|
|
613
|
+
return all_calls
|
|
614
|
+
# Jump to dedup section
|
|
615
|
+
return self._deduplicate_calls(all_calls)
|
|
616
|
+
except Exception:
|
|
617
|
+
# Failed, fall through to try all parsers
|
|
618
|
+
pass
|
|
619
|
+
|
|
620
|
+
# PERFORMANCE: Fallback - try all parsers concurrently
|
|
584
621
|
parse_tasks = []
|
|
585
622
|
|
|
586
623
|
# Create parsing tasks
|
|
@@ -591,20 +628,39 @@ class ToolProcessor:
|
|
|
591
628
|
parser_results = await asyncio.gather(*parse_tasks, return_exceptions=True)
|
|
592
629
|
|
|
593
630
|
# Collect successful results
|
|
594
|
-
for result in parser_results:
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if result and isinstance(result, list):
|
|
631
|
+
for result in parser_results: # type: ignore[assignment]
|
|
632
|
+
# Skip exceptions (return_exceptions=True gives us Exception | result)
|
|
633
|
+
if isinstance(result, list):
|
|
634
|
+
# Type narrowing: result is list[ToolCall] here, not BaseException
|
|
599
635
|
all_calls.extend(result)
|
|
600
636
|
|
|
637
|
+
# PERFORMANCE: Skip deduplication for single calls (common case)
|
|
638
|
+
return self._deduplicate_calls(all_calls)
|
|
639
|
+
|
|
640
|
+
def _deduplicate_calls(self, all_calls: list[ToolCall]) -> list[ToolCall]:
|
|
641
|
+
"""
|
|
642
|
+
Remove duplicate tool calls from the list.
|
|
643
|
+
|
|
644
|
+
PERFORMANCE: Fast path for single calls (no dedup needed).
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
all_calls: List of tool calls (may contain duplicates).
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
List of unique tool calls.
|
|
651
|
+
"""
|
|
652
|
+
# PERFORMANCE: Fast path - no deduplication needed for 0 or 1 calls
|
|
653
|
+
if len(all_calls) <= 1:
|
|
654
|
+
return all_calls
|
|
655
|
+
|
|
601
656
|
# ------------------------------------------------------------------ #
|
|
602
657
|
# Remove duplicates - use a stable digest instead of hashing a
|
|
603
658
|
# frozenset of argument items (which breaks on unhashable types).
|
|
604
659
|
# ------------------------------------------------------------------ #
|
|
605
660
|
def _args_digest(args: dict[str, Any]) -> str:
|
|
606
661
|
"""Return a stable hash for any JSON-serialisable payload."""
|
|
607
|
-
|
|
662
|
+
# Use stdlib json for consistent hashing across orjson/stdlib
|
|
663
|
+
blob = stdlib_json.dumps(args, sort_keys=True, default=str)
|
|
608
664
|
return hashlib.md5(blob.encode(), usedforsecurity=False).hexdigest() # nosec B324
|
|
609
665
|
|
|
610
666
|
unique_calls: dict[str, ToolCall] = {}
|
|
@@ -232,7 +232,7 @@ class SubprocessStrategy(ExecutionStrategy):
|
|
|
232
232
|
await asyncio.wait_for(
|
|
233
233
|
loop.run_in_executor(self._process_pool, _pool_test_func), timeout=self.worker_init_timeout
|
|
234
234
|
)
|
|
235
|
-
logger.
|
|
235
|
+
logger.debug("Process pool initialized with %d workers", self.max_workers)
|
|
236
236
|
except Exception as e:
|
|
237
237
|
# Clean up on initialization error
|
|
238
238
|
self._process_pool.shutdown(wait=False)
|
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
18
|
import hashlib
|
|
19
|
-
import json
|
|
19
|
+
import json as stdlib_json # Use stdlib json for consistent hashing
|
|
20
20
|
from abc import ABC, abstractmethod
|
|
21
21
|
from datetime import UTC, datetime, timedelta
|
|
22
22
|
from typing import Any
|
|
@@ -379,7 +379,8 @@ class CachingToolExecutor:
|
|
|
379
379
|
MD5 hash of the sorted JSON representation
|
|
380
380
|
"""
|
|
381
381
|
try:
|
|
382
|
-
|
|
382
|
+
# Use stdlib json for consistent hashing across orjson/stdlib
|
|
383
|
+
blob = stdlib_json.dumps(arguments, sort_keys=True, default=str)
|
|
383
384
|
return hashlib.md5(blob.encode(), usedforsecurity=False).hexdigest() # nosec B324
|
|
384
385
|
except Exception as e:
|
|
385
386
|
logger.warning(f"Error hashing arguments: {e}")
|
|
@@ -447,7 +448,8 @@ class CachingToolExecutor:
|
|
|
447
448
|
continue
|
|
448
449
|
|
|
449
450
|
# Use idempotency_key if available, otherwise hash arguments
|
|
450
|
-
|
|
451
|
+
# PERFORMANCE: Only compute idempotency key when caching is actually used
|
|
452
|
+
cache_key = call.get_idempotency_key()
|
|
451
453
|
|
|
452
454
|
# Trace cache lookup operation
|
|
453
455
|
with trace_cache_operation("lookup", call.tool):
|
|
@@ -515,7 +517,8 @@ class CachingToolExecutor:
|
|
|
515
517
|
logger.debug(f"Caching result for {call.tool} with TTL={ttl}s")
|
|
516
518
|
|
|
517
519
|
# Use idempotency_key if available, otherwise hash arguments
|
|
518
|
-
|
|
520
|
+
# PERFORMANCE: Only compute idempotency key when caching is actually used
|
|
521
|
+
cache_key = call.get_idempotency_key()
|
|
519
522
|
|
|
520
523
|
# Trace and record cache set operation
|
|
521
524
|
# Bind loop variables to avoid B023 error
|
|
@@ -598,8 +601,9 @@ def invalidate_cache(tool: str, arguments: dict[str, Any] | None = None):
|
|
|
598
601
|
|
|
599
602
|
async def _invalidate(cache: CacheInterface):
|
|
600
603
|
if arguments is not None:
|
|
604
|
+
# Use stdlib json for consistent hashing across orjson/stdlib
|
|
601
605
|
h = hashlib.md5(
|
|
602
|
-
|
|
606
|
+
stdlib_json.dumps(arguments, sort_keys=True, default=str).encode(), usedforsecurity=False
|
|
603
607
|
).hexdigest() # nosec B324
|
|
604
608
|
await cache.invalidate(tool, h)
|
|
605
609
|
logger.debug(f"Invalidated cache entry for {tool} with specific arguments")
|
|
@@ -565,6 +565,21 @@ class StreamManager:
|
|
|
565
565
|
def get_server_info(self) -> list[dict[str, Any]]:
|
|
566
566
|
return self.server_info
|
|
567
567
|
|
|
568
|
+
def set_session_id(self, session_id: str | None) -> None:
|
|
569
|
+
"""
|
|
570
|
+
Set the session ID on all HTTP/SSE transports.
|
|
571
|
+
|
|
572
|
+
This allows dynamically updating the session ID at runtime,
|
|
573
|
+
which is useful when the session ID is only known after agent initialization.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
session_id: Session ID to set, or None to clear it
|
|
577
|
+
"""
|
|
578
|
+
for name, transport in self.transports.items():
|
|
579
|
+
if hasattr(transport, "set_session_id"):
|
|
580
|
+
transport.set_session_id(session_id)
|
|
581
|
+
logger.debug("Set session ID for transport %s", name)
|
|
582
|
+
|
|
568
583
|
async def list_tools(self, server_name: str) -> list[dict[str, Any]]:
|
|
569
584
|
"""List all tools available from a specific server."""
|
|
570
585
|
if self._closed:
|
|
@@ -28,12 +28,12 @@ class MCPBaseTransport(ABC):
|
|
|
28
28
|
Returns:
|
|
29
29
|
True if initialization was successful, False otherwise.
|
|
30
30
|
"""
|
|
31
|
-
|
|
31
|
+
raise NotImplementedError
|
|
32
32
|
|
|
33
33
|
@abstractmethod
|
|
34
34
|
async def close(self) -> None:
|
|
35
35
|
"""Close the transport connection and clean up all resources."""
|
|
36
|
-
|
|
36
|
+
raise NotImplementedError
|
|
37
37
|
|
|
38
38
|
# ------------------------------------------------------------------ #
|
|
39
39
|
# Health and diagnostics #
|
|
@@ -46,7 +46,7 @@ class MCPBaseTransport(ABC):
|
|
|
46
46
|
Returns:
|
|
47
47
|
True if ping was successful, False otherwise.
|
|
48
48
|
"""
|
|
49
|
-
|
|
49
|
+
raise NotImplementedError
|
|
50
50
|
|
|
51
51
|
@abstractmethod
|
|
52
52
|
def is_connected(self) -> bool:
|
|
@@ -56,7 +56,7 @@ class MCPBaseTransport(ABC):
|
|
|
56
56
|
Returns:
|
|
57
57
|
True if connected, False otherwise.
|
|
58
58
|
"""
|
|
59
|
-
|
|
59
|
+
raise NotImplementedError
|
|
60
60
|
|
|
61
61
|
# ------------------------------------------------------------------ #
|
|
62
62
|
# Core MCP operations #
|
|
@@ -69,7 +69,7 @@ class MCPBaseTransport(ABC):
|
|
|
69
69
|
Returns:
|
|
70
70
|
List of tool definitions.
|
|
71
71
|
"""
|
|
72
|
-
|
|
72
|
+
raise NotImplementedError
|
|
73
73
|
|
|
74
74
|
@abstractmethod
|
|
75
75
|
async def call_tool(
|
|
@@ -86,7 +86,7 @@ class MCPBaseTransport(ABC):
|
|
|
86
86
|
Returns:
|
|
87
87
|
Dictionary with 'isError' boolean and either 'content' or 'error'
|
|
88
88
|
"""
|
|
89
|
-
|
|
89
|
+
raise NotImplementedError
|
|
90
90
|
|
|
91
91
|
@abstractmethod
|
|
92
92
|
async def list_resources(self) -> dict[str, Any]:
|
|
@@ -96,7 +96,7 @@ class MCPBaseTransport(ABC):
|
|
|
96
96
|
Returns:
|
|
97
97
|
Dictionary containing resources list or empty dict if not supported.
|
|
98
98
|
"""
|
|
99
|
-
|
|
99
|
+
raise NotImplementedError
|
|
100
100
|
|
|
101
101
|
@abstractmethod
|
|
102
102
|
async def list_prompts(self) -> dict[str, Any]:
|
|
@@ -106,7 +106,7 @@ class MCPBaseTransport(ABC):
|
|
|
106
106
|
Returns:
|
|
107
107
|
Dictionary containing prompts list or empty dict if not supported.
|
|
108
108
|
"""
|
|
109
|
-
|
|
109
|
+
raise NotImplementedError
|
|
110
110
|
|
|
111
111
|
# ------------------------------------------------------------------ #
|
|
112
112
|
# Metrics and monitoring (all transports should support these) #
|
|
@@ -119,12 +119,12 @@ class MCPBaseTransport(ABC):
|
|
|
119
119
|
Returns:
|
|
120
120
|
Dictionary containing metrics data.
|
|
121
121
|
"""
|
|
122
|
-
|
|
122
|
+
raise NotImplementedError
|
|
123
123
|
|
|
124
124
|
@abstractmethod
|
|
125
125
|
def reset_metrics(self) -> None:
|
|
126
126
|
"""Reset performance metrics to initial state."""
|
|
127
|
-
|
|
127
|
+
raise NotImplementedError
|
|
128
128
|
|
|
129
129
|
# ------------------------------------------------------------------ #
|
|
130
130
|
# Backward compatibility and utility methods #
|
|
@@ -122,9 +122,9 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
122
122
|
if self.api_key and "Authorization" not in headers:
|
|
123
123
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
124
124
|
|
|
125
|
-
# Add session ID if provided
|
|
125
|
+
# Add session ID if provided (use mcp-session-id header expected by MCP server)
|
|
126
126
|
if self.session_id:
|
|
127
|
-
headers["
|
|
127
|
+
headers["mcp-session-id"] = self.session_id
|
|
128
128
|
|
|
129
129
|
return headers
|
|
130
130
|
|
|
@@ -642,6 +642,19 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
642
642
|
)
|
|
643
643
|
return metrics
|
|
644
644
|
|
|
645
|
+
def set_session_id(self, session_id: str | None) -> None:
|
|
646
|
+
"""
|
|
647
|
+
Dynamically update the session ID for this transport.
|
|
648
|
+
|
|
649
|
+
This allows setting or changing the session ID after initialization,
|
|
650
|
+
which is useful when the session ID is only known at runtime.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
session_id: New session ID to use, or None to clear it
|
|
654
|
+
"""
|
|
655
|
+
self.session_id = session_id
|
|
656
|
+
logger.debug("Session ID updated: %s", session_id if session_id else "(cleared)")
|
|
657
|
+
|
|
645
658
|
def reset_metrics(self) -> None:
|
|
646
659
|
"""Enhanced metrics reset preserving health state."""
|
|
647
660
|
if not self._metrics:
|
|
@@ -714,6 +714,19 @@ class SSETransport(MCPBaseTransport):
|
|
|
714
714
|
self._last_successful_ping = None
|
|
715
715
|
self._initialization_time = None
|
|
716
716
|
|
|
717
|
+
def set_session_id(self, session_id: str | None) -> None:
|
|
718
|
+
"""
|
|
719
|
+
Dynamically update the session ID for this transport.
|
|
720
|
+
|
|
721
|
+
This allows setting or changing the session ID after initialization,
|
|
722
|
+
which is useful when the session ID is only known at runtime.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
session_id: New session ID to use, or None to clear it
|
|
726
|
+
"""
|
|
727
|
+
self.session_id = session_id
|
|
728
|
+
logger.debug("Session ID updated: %s", session_id if session_id else "(cleared)")
|
|
729
|
+
|
|
717
730
|
def get_metrics(self) -> dict[str, Any]:
|
|
718
731
|
"""Get performance and connection metrics with health info."""
|
|
719
732
|
if not self._metrics:
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_call.py
RENAMED
|
@@ -10,7 +10,7 @@ import json
|
|
|
10
10
|
import uuid
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class ToolCall(BaseModel):
|
|
@@ -31,17 +31,18 @@ class ToolCall(BaseModel):
|
|
|
31
31
|
tool: str = Field(..., min_length=1, description="Name of the tool to call; must be non-empty")
|
|
32
32
|
namespace: str = Field(default="default", description="Namespace the tool belongs to")
|
|
33
33
|
arguments: dict[str, Any] = Field(default_factory=dict, description="Arguments to pass to the tool")
|
|
34
|
-
|
|
35
|
-
None,
|
|
36
|
-
description="Idempotency key for deduplication. Auto-generated if not provided.",
|
|
37
|
-
)
|
|
34
|
+
_idempotency_key: str | None = None # Cached value, computed lazily
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
def get_idempotency_key(self) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Get or compute idempotency key lazily.
|
|
39
|
+
|
|
40
|
+
PERFORMANCE: Only computed when explicitly needed, avoiding overhead
|
|
41
|
+
in hot paths where deduplication isn't required.
|
|
42
|
+
"""
|
|
43
|
+
if self._idempotency_key is None:
|
|
44
|
+
self._idempotency_key = self._compute_idempotency_key()
|
|
45
|
+
return self._idempotency_key
|
|
45
46
|
|
|
46
47
|
def _compute_idempotency_key(self) -> str:
|
|
47
48
|
"""
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import json
|
|
7
6
|
import re
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
@@ -12,6 +11,7 @@ from pydantic import ValidationError
|
|
|
12
11
|
from chuk_tool_processor.logging import get_logger
|
|
13
12
|
from chuk_tool_processor.models.tool_call import ToolCall
|
|
14
13
|
from chuk_tool_processor.plugins.parsers.base import ParserPlugin
|
|
14
|
+
from chuk_tool_processor.utils import fast_json as json
|
|
15
15
|
|
|
16
16
|
__all__ = ["FunctionCallPlugin"]
|
|
17
17
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import json
|
|
7
6
|
from typing import Any
|
|
8
7
|
|
|
9
8
|
from pydantic import ValidationError
|
|
@@ -11,6 +10,7 @@ from pydantic import ValidationError
|
|
|
11
10
|
from chuk_tool_processor.logging import get_logger
|
|
12
11
|
from chuk_tool_processor.models.tool_call import ToolCall
|
|
13
12
|
from chuk_tool_processor.plugins.parsers.base import ParserPlugin
|
|
13
|
+
from chuk_tool_processor.utils import fast_json as json
|
|
14
14
|
|
|
15
15
|
__all__ = ["JsonToolPlugin"]
|
|
16
16
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import json
|
|
7
6
|
from typing import Any
|
|
8
7
|
|
|
9
8
|
from pydantic import ValidationError
|
|
@@ -11,6 +10,7 @@ from pydantic import ValidationError
|
|
|
11
10
|
from chuk_tool_processor.logging import get_logger
|
|
12
11
|
from chuk_tool_processor.models.tool_call import ToolCall
|
|
13
12
|
from chuk_tool_processor.plugins.parsers.base import ParserPlugin
|
|
13
|
+
from chuk_tool_processor.utils import fast_json as json
|
|
14
14
|
|
|
15
15
|
__all__ = ["OpenAIToolPlugin"]
|
|
16
16
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# chuk_tool_processor/utils/fast_json.py
|
|
2
|
+
"""
|
|
3
|
+
Fast JSON encoding/decoding with automatic fallback.
|
|
4
|
+
|
|
5
|
+
PERFORMANCE OPTIMIZED:
|
|
6
|
+
- Uses orjson if available (2-3x faster than stdlib json)
|
|
7
|
+
- Automatic fallback to stdlib json if orjson not installed
|
|
8
|
+
- Compatible API for seamless integration
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json as _stdlib_json
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Try to import orjson for 2-3x faster JSON operations
|
|
18
|
+
try:
|
|
19
|
+
import orjson as _orjson
|
|
20
|
+
|
|
21
|
+
HAS_ORJSON = True
|
|
22
|
+
logger.debug("orjson available - using fast JSON implementation")
|
|
23
|
+
except ImportError:
|
|
24
|
+
HAS_ORJSON = False
|
|
25
|
+
logger.debug("orjson not available - using stdlib json")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def dumps(obj: Any, **kwargs: Any) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Serialize obj to a JSON formatted string.
|
|
31
|
+
|
|
32
|
+
PERFORMANCE: Uses orjson if available (2-3x faster), falls back to stdlib json.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
obj: Python object to serialize
|
|
36
|
+
**kwargs: Additional arguments (for stdlib json compatibility)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
JSON string
|
|
40
|
+
|
|
41
|
+
Note:
|
|
42
|
+
orjson returns bytes, we decode to str for compatibility with existing code.
|
|
43
|
+
"""
|
|
44
|
+
if HAS_ORJSON:
|
|
45
|
+
# orjson.dumps returns bytes, decode to str for compatibility
|
|
46
|
+
# orjson is ~2-3x faster than stdlib json
|
|
47
|
+
try:
|
|
48
|
+
# orjson options for compatibility with stdlib json
|
|
49
|
+
# OPT_INDENT_2 for pretty printing if indent kwarg present
|
|
50
|
+
options = 0
|
|
51
|
+
if kwargs.get("indent"):
|
|
52
|
+
options |= _orjson.OPT_INDENT_2
|
|
53
|
+
|
|
54
|
+
return _orjson.dumps(obj, option=options).decode("utf-8")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
# Fallback to stdlib json if orjson fails (e.g., unsupported types)
|
|
57
|
+
logger.debug(f"orjson failed, falling back to stdlib json: {e}")
|
|
58
|
+
return _stdlib_json.dumps(obj, **kwargs)
|
|
59
|
+
else:
|
|
60
|
+
# Use stdlib json
|
|
61
|
+
return _stdlib_json.dumps(obj, **kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def loads(s: str | bytes) -> Any:
|
|
65
|
+
"""
|
|
66
|
+
Deserialize s (a str, bytes or bytearray containing a JSON document) to a Python object.
|
|
67
|
+
|
|
68
|
+
PERFORMANCE: Uses orjson if available (2-3x faster), falls back to stdlib json.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
s: JSON string or bytes to deserialize
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Python object
|
|
75
|
+
"""
|
|
76
|
+
if HAS_ORJSON:
|
|
77
|
+
# orjson.loads accepts both str and bytes
|
|
78
|
+
try:
|
|
79
|
+
return _orjson.loads(s)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
# Fallback to stdlib json if orjson fails
|
|
82
|
+
logger.debug(f"orjson failed, falling back to stdlib json: {e}")
|
|
83
|
+
if isinstance(s, bytes):
|
|
84
|
+
s = s.decode("utf-8")
|
|
85
|
+
return _stdlib_json.loads(s)
|
|
86
|
+
else:
|
|
87
|
+
# Use stdlib json
|
|
88
|
+
if isinstance(s, bytes):
|
|
89
|
+
s = s.decode("utf-8")
|
|
90
|
+
return _stdlib_json.loads(s)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def dump(obj: Any, fp: Any, **kwargs: Any) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object).
|
|
96
|
+
|
|
97
|
+
PERFORMANCE: Uses orjson if available, falls back to stdlib json.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
obj: Python object to serialize
|
|
101
|
+
fp: File-like object with .write() method
|
|
102
|
+
**kwargs: Additional arguments (for stdlib json compatibility)
|
|
103
|
+
"""
|
|
104
|
+
if HAS_ORJSON:
|
|
105
|
+
# orjson doesn't have dump(), so we use dumps() and write
|
|
106
|
+
try:
|
|
107
|
+
options = 0
|
|
108
|
+
if kwargs.get("indent"):
|
|
109
|
+
options |= _orjson.OPT_INDENT_2
|
|
110
|
+
|
|
111
|
+
json_bytes = _orjson.dumps(obj, option=options)
|
|
112
|
+
fp.write(json_bytes)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.debug(f"orjson failed, falling back to stdlib json: {e}")
|
|
115
|
+
_stdlib_json.dump(obj, fp, **kwargs)
|
|
116
|
+
else:
|
|
117
|
+
_stdlib_json.dump(obj, fp, **kwargs)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def load(fp: Any) -> Any:
|
|
121
|
+
"""
|
|
122
|
+
Deserialize fp (a .read()-supporting file-like object containing a JSON document) to a Python object.
|
|
123
|
+
|
|
124
|
+
PERFORMANCE: Uses orjson if available, falls back to stdlib json.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
fp: File-like object with .read() method
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Python object
|
|
131
|
+
"""
|
|
132
|
+
if HAS_ORJSON:
|
|
133
|
+
try:
|
|
134
|
+
content = fp.read()
|
|
135
|
+
return _orjson.loads(content)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.debug(f"orjson failed, falling back to stdlib json: {e}")
|
|
138
|
+
# Re-read if needed
|
|
139
|
+
if hasattr(fp, "seek"):
|
|
140
|
+
fp.seek(0)
|
|
141
|
+
content = fp.read()
|
|
142
|
+
if isinstance(content, bytes):
|
|
143
|
+
content = content.decode("utf-8")
|
|
144
|
+
return _stdlib_json.loads(content)
|
|
145
|
+
else:
|
|
146
|
+
return _stdlib_json.load(fp)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Export JSONDecodeError for compatibility
|
|
150
|
+
if HAS_ORJSON:
|
|
151
|
+
# orjson uses the same JSONDecodeError from json module
|
|
152
|
+
from json import JSONDecodeError
|
|
153
|
+
else:
|
|
154
|
+
from json import JSONDecodeError
|
|
155
|
+
|
|
156
|
+
# Export flag for conditional behavior
|
|
157
|
+
__all__ = ["dumps", "loads", "dump", "load", "HAS_ORJSON", "JSONDecodeError"]
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.11
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: Async-native framework for registering, discovering, and executing tools referenced in LLM responses
|
|
5
5
|
Author-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
6
6
|
Maintainer-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
@@ -20,11 +20,15 @@ Classifier: Framework :: AsyncIO
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
|
-
Requires-Dist: chuk-mcp>=0.
|
|
23
|
+
Requires-Dist: chuk-mcp>=0.9
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9
|
|
25
25
|
Requires-Dist: psutil>=7.0.0
|
|
26
26
|
Requires-Dist: pydantic>=2.11.3
|
|
27
27
|
Requires-Dist: uuid>=1.30
|
|
28
|
+
Provides-Extra: fast-json
|
|
29
|
+
Requires-Dist: orjson<4,>=3.10.0; extra == "fast-json"
|
|
30
|
+
Provides-Extra: full
|
|
31
|
+
Requires-Dist: orjson<4,>=3.10.0; extra == "full"
|
|
28
32
|
|
|
29
33
|
# CHUK Tool Processor — Production-grade execution for LLM tool calls
|
|
30
34
|
|
|
@@ -287,12 +291,41 @@ pip install chuk-tool-processor[observability]
|
|
|
287
291
|
# With MCP extras
|
|
288
292
|
pip install chuk-tool-processor[mcp]
|
|
289
293
|
|
|
294
|
+
# With fast JSON serialization (2-3x faster, recommended for production)
|
|
295
|
+
pip install chuk-tool-processor[fast-json]
|
|
296
|
+
|
|
290
297
|
# All extras
|
|
291
298
|
pip install chuk-tool-processor[all]
|
|
292
299
|
```
|
|
293
300
|
|
|
294
301
|
</details>
|
|
295
302
|
|
|
303
|
+
<details>
|
|
304
|
+
<summary><strong>Performance Optimization (Optional)</strong></summary>
|
|
305
|
+
|
|
306
|
+
For **2-3x faster JSON operations**, install with the `fast-json` extra:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
pip install chuk-tool-processor[fast-json]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This installs [orjson](https://github.com/ijl/orjson), a fast C-based JSON library. When available, it's automatically used for JSON serialization/deserialization throughout the processor while maintaining full compatibility with stdlib json.
|
|
313
|
+
|
|
314
|
+
**Benchmarks** (see `benchmarks/` for full results):
|
|
315
|
+
|
|
316
|
+
| Operation | stdlib json | orjson | Speedup |
|
|
317
|
+
|-----------|-------------|--------|---------|
|
|
318
|
+
| Simple JSON (100 bytes) | 1.23 µs | 0.45 µs | **2.7x faster** |
|
|
319
|
+
| Complex JSON (5 KB) | 12.5 µs | 4.2 µs | **3.0x faster** |
|
|
320
|
+
| OpenAI tool calls | 8.9 µs | 3.1 µs | **2.9x faster** |
|
|
321
|
+
|
|
322
|
+
**Notes:**
|
|
323
|
+
- Falls back to stdlib json automatically if orjson is not installed
|
|
324
|
+
- Hash computation uses stdlib json for consistency across environments
|
|
325
|
+
- No code changes required—just install the extra
|
|
326
|
+
|
|
327
|
+
</details>
|
|
328
|
+
|
|
296
329
|
<details>
|
|
297
330
|
<summary><strong>Type Checking Support (PEP 561 compliant)</strong></summary>
|
|
298
331
|
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor.egg-info/SOURCES.txt
RENAMED
|
@@ -69,4 +69,5 @@ src/chuk_tool_processor/registry/tool_export.py
|
|
|
69
69
|
src/chuk_tool_processor/registry/providers/__init__.py
|
|
70
70
|
src/chuk_tool_processor/registry/providers/memory.py
|
|
71
71
|
src/chuk_tool_processor/utils/__init__.py
|
|
72
|
+
src/chuk_tool_processor/utils/fast_json.py
|
|
72
73
|
src/chuk_tool_processor/utils/validation.py
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/core/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/context.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/formatter.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/helpers.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/logging/metrics.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/mcp_tool.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/mcp/setup_mcp_sse.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/models/tool_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/plugins/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/metadata.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/registry/provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/utils/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.11 → chuk_tool_processor-0.11.2}/src/chuk_tool_processor/utils/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|