chuk-tool-processor 0.6.12__py3-none-any.whl → 0.6.13__py3-none-any.whl
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.
Potentially problematic release.
This version of chuk-tool-processor might be problematic. Click here for more details.
- chuk_tool_processor/core/__init__.py +1 -1
- chuk_tool_processor/core/exceptions.py +10 -4
- chuk_tool_processor/core/processor.py +97 -97
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +200 -205
- chuk_tool_processor/execution/tool_executor.py +82 -84
- chuk_tool_processor/execution/wrappers/caching.py +102 -103
- chuk_tool_processor/execution/wrappers/rate_limiting.py +45 -42
- chuk_tool_processor/execution/wrappers/retry.py +23 -25
- chuk_tool_processor/logging/__init__.py +23 -17
- chuk_tool_processor/logging/context.py +40 -45
- chuk_tool_processor/logging/formatter.py +22 -21
- chuk_tool_processor/logging/helpers.py +24 -38
- chuk_tool_processor/logging/metrics.py +11 -13
- chuk_tool_processor/mcp/__init__.py +8 -12
- chuk_tool_processor/mcp/mcp_tool.py +124 -112
- chuk_tool_processor/mcp/register_mcp_tools.py +17 -17
- chuk_tool_processor/mcp/setup_mcp_http_streamable.py +11 -13
- chuk_tool_processor/mcp/setup_mcp_sse.py +11 -13
- chuk_tool_processor/mcp/setup_mcp_stdio.py +7 -9
- chuk_tool_processor/mcp/stream_manager.py +168 -204
- chuk_tool_processor/mcp/transport/__init__.py +4 -4
- chuk_tool_processor/mcp/transport/base_transport.py +43 -58
- chuk_tool_processor/mcp/transport/http_streamable_transport.py +145 -163
- chuk_tool_processor/mcp/transport/sse_transport.py +217 -255
- chuk_tool_processor/mcp/transport/stdio_transport.py +171 -189
- chuk_tool_processor/models/__init__.py +1 -1
- chuk_tool_processor/models/execution_strategy.py +16 -21
- chuk_tool_processor/models/streaming_tool.py +28 -25
- chuk_tool_processor/models/tool_call.py +19 -34
- chuk_tool_processor/models/tool_export_mixin.py +22 -8
- chuk_tool_processor/models/tool_result.py +40 -77
- chuk_tool_processor/models/validated_tool.py +14 -16
- chuk_tool_processor/plugins/__init__.py +1 -1
- chuk_tool_processor/plugins/discovery.py +10 -10
- chuk_tool_processor/plugins/parsers/__init__.py +1 -1
- chuk_tool_processor/plugins/parsers/base.py +1 -2
- chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
- chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
- chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
- chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
- chuk_tool_processor/registry/__init__.py +12 -12
- chuk_tool_processor/registry/auto_register.py +22 -30
- chuk_tool_processor/registry/decorators.py +127 -129
- chuk_tool_processor/registry/interface.py +26 -23
- chuk_tool_processor/registry/metadata.py +27 -22
- chuk_tool_processor/registry/provider.py +17 -18
- chuk_tool_processor/registry/providers/__init__.py +16 -19
- chuk_tool_processor/registry/providers/memory.py +18 -25
- chuk_tool_processor/registry/tool_export.py +42 -51
- chuk_tool_processor/utils/validation.py +15 -16
- {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/METADATA +1 -1
- chuk_tool_processor-0.6.13.dist-info/RECORD +60 -0
- chuk_tool_processor-0.6.12.dist-info/RECORD +0 -60
- {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
# chuk_tool_processor/core/__init__.py
|
|
1
|
+
# chuk_tool_processor/core/__init__.py
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# chuk_tool_processor/exceptions.py
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ToolProcessorError(Exception):
|
|
6
6
|
"""Base exception for all tool processor errors."""
|
|
7
|
+
|
|
7
8
|
pass
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ToolNotFoundError(ToolProcessorError):
|
|
11
12
|
"""Raised when a requested tool is not found in the registry."""
|
|
13
|
+
|
|
12
14
|
def __init__(self, tool_name: str):
|
|
13
15
|
self.tool_name = tool_name
|
|
14
16
|
super().__init__(f"Tool '{tool_name}' not found in registry")
|
|
@@ -16,7 +18,8 @@ class ToolNotFoundError(ToolProcessorError):
|
|
|
16
18
|
|
|
17
19
|
class ToolExecutionError(ToolProcessorError):
|
|
18
20
|
"""Raised when a tool execution fails."""
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
def __init__(self, tool_name: str, original_error: Exception | None = None):
|
|
20
23
|
self.tool_name = tool_name
|
|
21
24
|
self.original_error = original_error
|
|
22
25
|
message = f"Tool '{tool_name}' execution failed"
|
|
@@ -27,6 +30,7 @@ class ToolExecutionError(ToolProcessorError):
|
|
|
27
30
|
|
|
28
31
|
class ToolTimeoutError(ToolExecutionError):
|
|
29
32
|
"""Raised when a tool execution times out."""
|
|
33
|
+
|
|
30
34
|
def __init__(self, tool_name: str, timeout: float):
|
|
31
35
|
self.timeout = timeout
|
|
32
36
|
super().__init__(tool_name, Exception(f"Execution timed out after {timeout}s"))
|
|
@@ -34,7 +38,8 @@ class ToolTimeoutError(ToolExecutionError):
|
|
|
34
38
|
|
|
35
39
|
class ToolValidationError(ToolProcessorError):
|
|
36
40
|
"""Raised when tool arguments or results fail validation."""
|
|
37
|
-
|
|
41
|
+
|
|
42
|
+
def __init__(self, tool_name: str, errors: dict[str, Any]):
|
|
38
43
|
self.tool_name = tool_name
|
|
39
44
|
self.errors = errors
|
|
40
45
|
super().__init__(f"Validation failed for tool '{tool_name}': {errors}")
|
|
@@ -42,4 +47,5 @@ class ToolValidationError(ToolProcessorError):
|
|
|
42
47
|
|
|
43
48
|
class ParserError(ToolProcessorError):
|
|
44
49
|
"""Raised when parsing tool calls from raw input fails."""
|
|
45
|
-
|
|
50
|
+
|
|
51
|
+
pass
|
|
@@ -7,23 +7,24 @@ This module provides the central ToolProcessor class which handles:
|
|
|
7
7
|
- Tool execution using configurable strategies
|
|
8
8
|
- Application of execution wrappers (caching, retries, etc.)
|
|
9
9
|
"""
|
|
10
|
+
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
|
|
12
13
|
import asyncio
|
|
13
|
-
import time
|
|
14
|
-
import json
|
|
15
14
|
import hashlib
|
|
16
|
-
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from typing import Any
|
|
17
18
|
|
|
19
|
+
from chuk_tool_processor.execution.strategies.inprocess_strategy import InProcessStrategy
|
|
20
|
+
from chuk_tool_processor.execution.wrappers.caching import CachingToolExecutor, InMemoryCache
|
|
21
|
+
from chuk_tool_processor.execution.wrappers.rate_limiting import RateLimitedToolExecutor, RateLimiter
|
|
22
|
+
from chuk_tool_processor.execution.wrappers.retry import RetryableToolExecutor, RetryConfig
|
|
23
|
+
from chuk_tool_processor.logging import get_logger, log_context_span, log_tool_call, metrics, request_logging
|
|
18
24
|
from chuk_tool_processor.models.tool_call import ToolCall
|
|
19
25
|
from chuk_tool_processor.models.tool_result import ToolResult
|
|
26
|
+
from chuk_tool_processor.plugins.discovery import discover_default_plugins, plugin_registry
|
|
20
27
|
from chuk_tool_processor.registry import ToolRegistryInterface, ToolRegistryProvider
|
|
21
|
-
from chuk_tool_processor.execution.strategies.inprocess_strategy import InProcessStrategy
|
|
22
|
-
from chuk_tool_processor.execution.wrappers.caching import CacheInterface, InMemoryCache, CachingToolExecutor
|
|
23
|
-
from chuk_tool_processor.execution.wrappers.rate_limiting import RateLimiter, RateLimitedToolExecutor
|
|
24
|
-
from chuk_tool_processor.execution.wrappers.retry import RetryConfig, RetryableToolExecutor
|
|
25
|
-
from chuk_tool_processor.plugins.discovery import plugin_registry, discover_default_plugins
|
|
26
|
-
from chuk_tool_processor.logging import get_logger, log_context_span, request_logging, log_tool_call, metrics
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class ToolProcessor:
|
|
@@ -34,18 +35,18 @@ class ToolProcessor:
|
|
|
34
35
|
|
|
35
36
|
def __init__(
|
|
36
37
|
self,
|
|
37
|
-
registry:
|
|
38
|
-
strategy
|
|
38
|
+
registry: ToolRegistryInterface | None = None,
|
|
39
|
+
strategy=None,
|
|
39
40
|
default_timeout: float = 10.0,
|
|
40
|
-
max_concurrency:
|
|
41
|
+
max_concurrency: int | None = None,
|
|
41
42
|
enable_caching: bool = True,
|
|
42
43
|
cache_ttl: int = 300,
|
|
43
44
|
enable_rate_limiting: bool = False,
|
|
44
|
-
global_rate_limit:
|
|
45
|
-
tool_rate_limits:
|
|
45
|
+
global_rate_limit: int | None = None,
|
|
46
|
+
tool_rate_limits: dict[str, tuple] | None = None,
|
|
46
47
|
enable_retries: bool = True,
|
|
47
48
|
max_retries: int = 3,
|
|
48
|
-
parser_plugins:
|
|
49
|
+
parser_plugins: list[str] | None = None,
|
|
49
50
|
):
|
|
50
51
|
"""
|
|
51
52
|
Initialize the tool processor.
|
|
@@ -66,7 +67,7 @@ class ToolProcessor:
|
|
|
66
67
|
If None, uses all available parsers.
|
|
67
68
|
"""
|
|
68
69
|
self.logger = get_logger("chuk_tool_processor.processor")
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
# Store initialization parameters for lazy initialization
|
|
71
72
|
self._registry = registry
|
|
72
73
|
self._strategy = strategy
|
|
@@ -80,13 +81,13 @@ class ToolProcessor:
|
|
|
80
81
|
self.enable_retries = enable_retries
|
|
81
82
|
self.max_retries = max_retries
|
|
82
83
|
self.parser_plugin_names = parser_plugins
|
|
83
|
-
|
|
84
|
+
|
|
84
85
|
# Placeholder for initialized components
|
|
85
86
|
self.registry = None
|
|
86
87
|
self.strategy = None
|
|
87
88
|
self.executor = None
|
|
88
89
|
self.parsers = []
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
# Flag for tracking initialization state
|
|
91
92
|
self._initialized = False
|
|
92
93
|
self._init_lock = asyncio.Lock()
|
|
@@ -94,28 +95,28 @@ class ToolProcessor:
|
|
|
94
95
|
async def initialize(self) -> None:
|
|
95
96
|
"""
|
|
96
97
|
Initialize the processor asynchronously.
|
|
97
|
-
|
|
98
|
+
|
|
98
99
|
This method ensures all components are properly initialized before use.
|
|
99
100
|
It is called automatically by other methods if needed.
|
|
100
101
|
"""
|
|
101
102
|
# Fast path if already initialized
|
|
102
103
|
if self._initialized:
|
|
103
104
|
return
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
# Ensure only one initialization happens at a time
|
|
106
107
|
async with self._init_lock:
|
|
107
108
|
# Double-check pattern after acquiring lock
|
|
108
109
|
if self._initialized:
|
|
109
110
|
return
|
|
110
|
-
|
|
111
|
+
|
|
111
112
|
self.logger.debug("Initializing tool processor")
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
# Get the registry
|
|
114
115
|
if self._registry is not None:
|
|
115
116
|
self.registry = self._registry
|
|
116
117
|
else:
|
|
117
118
|
self.registry = await ToolRegistryProvider.get_registry()
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
# Create execution strategy if needed
|
|
120
121
|
if self._strategy is not None:
|
|
121
122
|
self.strategy = self._strategy
|
|
@@ -125,10 +126,10 @@ class ToolProcessor:
|
|
|
125
126
|
default_timeout=self.default_timeout,
|
|
126
127
|
max_concurrency=self.max_concurrency,
|
|
127
128
|
)
|
|
128
|
-
|
|
129
|
+
|
|
129
130
|
# Set up the executor chain with optional wrappers
|
|
130
131
|
executor = self.strategy
|
|
131
|
-
|
|
132
|
+
|
|
132
133
|
# Apply wrappers in reverse order (innermost first)
|
|
133
134
|
if self.enable_retries:
|
|
134
135
|
self.logger.debug("Enabling retry logic")
|
|
@@ -136,7 +137,7 @@ class ToolProcessor:
|
|
|
136
137
|
executor=executor,
|
|
137
138
|
default_config=RetryConfig(max_retries=self.max_retries),
|
|
138
139
|
)
|
|
139
|
-
|
|
140
|
+
|
|
140
141
|
if self.enable_rate_limiting:
|
|
141
142
|
self.logger.debug("Enabling rate limiting")
|
|
142
143
|
rate_limiter = RateLimiter(
|
|
@@ -147,7 +148,7 @@ class ToolProcessor:
|
|
|
147
148
|
executor=executor,
|
|
148
149
|
limiter=rate_limiter,
|
|
149
150
|
)
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
if self.enable_caching:
|
|
152
153
|
self.logger.debug("Enabling result caching")
|
|
153
154
|
cache = InMemoryCache(default_ttl=self.cache_ttl)
|
|
@@ -156,16 +157,16 @@ class ToolProcessor:
|
|
|
156
157
|
cache=cache,
|
|
157
158
|
default_ttl=self.cache_ttl,
|
|
158
159
|
)
|
|
159
|
-
|
|
160
|
+
|
|
160
161
|
self.executor = executor
|
|
161
|
-
|
|
162
|
+
|
|
162
163
|
# Initialize parser plugins
|
|
163
164
|
# Discover plugins if not already done
|
|
164
165
|
plugins = plugin_registry.list_plugins().get("parser", [])
|
|
165
166
|
if not plugins:
|
|
166
167
|
discover_default_plugins()
|
|
167
168
|
plugins = plugin_registry.list_plugins().get("parser", [])
|
|
168
|
-
|
|
169
|
+
|
|
169
170
|
# Get parser plugins
|
|
170
171
|
if self.parser_plugin_names:
|
|
171
172
|
self.parsers = [
|
|
@@ -174,42 +175,40 @@ class ToolProcessor:
|
|
|
174
175
|
if plugin_registry.get_plugin("parser", name)
|
|
175
176
|
]
|
|
176
177
|
else:
|
|
177
|
-
self.parsers = [
|
|
178
|
-
|
|
179
|
-
]
|
|
180
|
-
|
|
178
|
+
self.parsers = [plugin_registry.get_plugin("parser", name) for name in plugins]
|
|
179
|
+
|
|
181
180
|
self.logger.debug(f"Initialized with {len(self.parsers)} parser plugins")
|
|
182
181
|
self._initialized = True
|
|
183
182
|
|
|
184
183
|
async def process(
|
|
185
184
|
self,
|
|
186
|
-
data:
|
|
187
|
-
timeout:
|
|
188
|
-
use_cache: bool = True,
|
|
189
|
-
request_id:
|
|
190
|
-
) ->
|
|
185
|
+
data: str | dict[str, Any] | list[dict[str, Any]],
|
|
186
|
+
timeout: float | None = None,
|
|
187
|
+
use_cache: bool = True, # noqa: ARG002
|
|
188
|
+
request_id: str | None = None,
|
|
189
|
+
) -> list[ToolResult]:
|
|
191
190
|
"""
|
|
192
191
|
Process tool calls from various input formats.
|
|
193
|
-
|
|
192
|
+
|
|
194
193
|
This method handles different input types:
|
|
195
194
|
- String: Parses tool calls from text using registered parsers
|
|
196
195
|
- Dict: Processes an OpenAI-style tool_calls object
|
|
197
196
|
- List[Dict]: Processes a list of individual tool calls
|
|
198
|
-
|
|
197
|
+
|
|
199
198
|
Args:
|
|
200
199
|
data: Input data containing tool calls
|
|
201
200
|
timeout: Optional timeout for execution
|
|
202
201
|
use_cache: Whether to use cached results
|
|
203
202
|
request_id: Optional request ID for logging
|
|
204
|
-
|
|
203
|
+
|
|
205
204
|
Returns:
|
|
206
205
|
List of tool results
|
|
207
206
|
"""
|
|
208
207
|
# Ensure initialization
|
|
209
208
|
await self.initialize()
|
|
210
|
-
|
|
209
|
+
|
|
211
210
|
# Create request context
|
|
212
|
-
async with request_logging(request_id)
|
|
211
|
+
async with request_logging(request_id):
|
|
213
212
|
# Handle different input types
|
|
214
213
|
if isinstance(data, str):
|
|
215
214
|
# Text processing
|
|
@@ -224,13 +223,13 @@ class ToolProcessor:
|
|
|
224
223
|
function = tc["function"]
|
|
225
224
|
name = function.get("name")
|
|
226
225
|
args_str = function.get("arguments", "{}")
|
|
227
|
-
|
|
226
|
+
|
|
228
227
|
# Parse arguments
|
|
229
228
|
try:
|
|
230
229
|
args = json.loads(args_str) if isinstance(args_str, str) else args_str
|
|
231
230
|
except json.JSONDecodeError:
|
|
232
231
|
args = {"raw": args_str}
|
|
233
|
-
|
|
232
|
+
|
|
234
233
|
if name:
|
|
235
234
|
calls.append(ToolCall(tool=name, arguments=args, id=tc.get("id")))
|
|
236
235
|
else:
|
|
@@ -242,13 +241,13 @@ class ToolProcessor:
|
|
|
242
241
|
else:
|
|
243
242
|
self.logger.warning(f"Unsupported input type: {type(data)}")
|
|
244
243
|
return []
|
|
245
|
-
|
|
244
|
+
|
|
246
245
|
if not calls:
|
|
247
246
|
self.logger.debug("No tool calls found")
|
|
248
247
|
return []
|
|
249
|
-
|
|
248
|
+
|
|
250
249
|
self.logger.debug(f"Found {len(calls)} tool calls")
|
|
251
|
-
|
|
250
|
+
|
|
252
251
|
# Execute tool calls
|
|
253
252
|
async with log_context_span("tool_execution", {"num_calls": len(calls)}):
|
|
254
253
|
# Check if any tools are unknown
|
|
@@ -257,17 +256,17 @@ class ToolProcessor:
|
|
|
257
256
|
tool = await self.registry.get_tool(call.tool)
|
|
258
257
|
if not tool:
|
|
259
258
|
unknown_tools.append(call.tool)
|
|
260
|
-
|
|
259
|
+
|
|
261
260
|
if unknown_tools:
|
|
262
261
|
self.logger.warning(f"Unknown tools: {unknown_tools}")
|
|
263
|
-
|
|
262
|
+
|
|
264
263
|
# Execute tools
|
|
265
264
|
results = await self.executor.execute(calls, timeout=timeout)
|
|
266
|
-
|
|
265
|
+
|
|
267
266
|
# Log metrics for each tool call
|
|
268
|
-
for call, result in zip(calls, results):
|
|
267
|
+
for call, result in zip(calls, results, strict=False):
|
|
269
268
|
await log_tool_call(call, result)
|
|
270
|
-
|
|
269
|
+
|
|
271
270
|
# Record metrics
|
|
272
271
|
duration = (result.end_time - result.start_time).total_seconds()
|
|
273
272
|
await metrics.log_tool_execution(
|
|
@@ -278,19 +277,19 @@ class ToolProcessor:
|
|
|
278
277
|
cached=getattr(result, "cached", False),
|
|
279
278
|
attempts=getattr(result, "attempts", 1),
|
|
280
279
|
)
|
|
281
|
-
|
|
280
|
+
|
|
282
281
|
return results
|
|
283
282
|
|
|
284
283
|
async def process_text(
|
|
285
284
|
self,
|
|
286
285
|
text: str,
|
|
287
|
-
timeout:
|
|
286
|
+
timeout: float | None = None,
|
|
288
287
|
use_cache: bool = True,
|
|
289
|
-
request_id:
|
|
290
|
-
) ->
|
|
288
|
+
request_id: str | None = None,
|
|
289
|
+
) -> list[ToolResult]:
|
|
291
290
|
"""
|
|
292
291
|
Process text to extract and execute tool calls.
|
|
293
|
-
|
|
292
|
+
|
|
294
293
|
Legacy alias for process() with string input.
|
|
295
294
|
|
|
296
295
|
Args:
|
|
@@ -308,35 +307,33 @@ class ToolProcessor:
|
|
|
308
307
|
use_cache=use_cache,
|
|
309
308
|
request_id=request_id,
|
|
310
309
|
)
|
|
311
|
-
|
|
310
|
+
|
|
312
311
|
async def execute(
|
|
313
312
|
self,
|
|
314
|
-
calls:
|
|
315
|
-
timeout:
|
|
313
|
+
calls: list[ToolCall],
|
|
314
|
+
timeout: float | None = None,
|
|
316
315
|
use_cache: bool = True,
|
|
317
|
-
) ->
|
|
316
|
+
) -> list[ToolResult]:
|
|
318
317
|
"""
|
|
319
318
|
Execute a list of ToolCall objects directly.
|
|
320
|
-
|
|
319
|
+
|
|
321
320
|
Args:
|
|
322
321
|
calls: List of tool calls to execute
|
|
323
322
|
timeout: Optional execution timeout
|
|
324
323
|
use_cache: Whether to use cached results
|
|
325
|
-
|
|
324
|
+
|
|
326
325
|
Returns:
|
|
327
326
|
List of tool results
|
|
328
327
|
"""
|
|
329
328
|
# Ensure initialization
|
|
330
329
|
await self.initialize()
|
|
331
|
-
|
|
330
|
+
|
|
332
331
|
# Execute with the configured executor
|
|
333
332
|
return await self.executor.execute(
|
|
334
|
-
calls=calls,
|
|
335
|
-
timeout=timeout,
|
|
336
|
-
use_cache=use_cache if hasattr(self.executor, "use_cache") else True
|
|
333
|
+
calls=calls, timeout=timeout, use_cache=use_cache if hasattr(self.executor, "use_cache") else True
|
|
337
334
|
)
|
|
338
335
|
|
|
339
|
-
async def _extract_tool_calls(self, text: str) ->
|
|
336
|
+
async def _extract_tool_calls(self, text: str) -> list[ToolCall]:
|
|
340
337
|
"""
|
|
341
338
|
Extract tool calls from text using all available parsers.
|
|
342
339
|
|
|
@@ -346,19 +343,19 @@ class ToolProcessor:
|
|
|
346
343
|
Returns:
|
|
347
344
|
List of tool calls.
|
|
348
345
|
"""
|
|
349
|
-
all_calls:
|
|
346
|
+
all_calls: list[ToolCall] = []
|
|
350
347
|
|
|
351
348
|
# Try each parser
|
|
352
349
|
async with log_context_span("parsing", {"text_length": len(text)}):
|
|
353
350
|
parse_tasks = []
|
|
354
|
-
|
|
351
|
+
|
|
355
352
|
# Create parsing tasks
|
|
356
353
|
for parser in self.parsers:
|
|
357
354
|
parse_tasks.append(self._try_parser(parser, text))
|
|
358
|
-
|
|
355
|
+
|
|
359
356
|
# Execute all parsers concurrently
|
|
360
357
|
parser_results = await asyncio.gather(*parse_tasks, return_exceptions=True)
|
|
361
|
-
|
|
358
|
+
|
|
362
359
|
# Collect successful results
|
|
363
360
|
for result in parser_results:
|
|
364
361
|
if isinstance(result, Exception):
|
|
@@ -370,29 +367,29 @@ class ToolProcessor:
|
|
|
370
367
|
# Remove duplicates - use a stable digest instead of hashing a
|
|
371
368
|
# frozenset of argument items (which breaks on unhashable types).
|
|
372
369
|
# ------------------------------------------------------------------ #
|
|
373
|
-
def _args_digest(args:
|
|
370
|
+
def _args_digest(args: dict[str, Any]) -> str:
|
|
374
371
|
"""Return a stable hash for any JSON-serialisable payload."""
|
|
375
372
|
blob = json.dumps(args, sort_keys=True, default=str)
|
|
376
373
|
return hashlib.md5(blob.encode()).hexdigest()
|
|
377
374
|
|
|
378
|
-
unique_calls:
|
|
375
|
+
unique_calls: dict[str, ToolCall] = {}
|
|
379
376
|
for call in all_calls:
|
|
380
377
|
key = f"{call.tool}:{_args_digest(call.arguments)}"
|
|
381
378
|
unique_calls[key] = call
|
|
382
379
|
|
|
383
380
|
return list(unique_calls.values())
|
|
384
|
-
|
|
385
|
-
async def _try_parser(self, parser, text: str) ->
|
|
381
|
+
|
|
382
|
+
async def _try_parser(self, parser, text: str) -> list[ToolCall]:
|
|
386
383
|
"""Try a single parser with metrics and logging."""
|
|
387
384
|
parser_name = parser.__class__.__name__
|
|
388
|
-
|
|
385
|
+
|
|
389
386
|
async with log_context_span(f"parser.{parser_name}", log_duration=True):
|
|
390
387
|
start_time = time.time()
|
|
391
|
-
|
|
388
|
+
|
|
392
389
|
try:
|
|
393
390
|
# Try to parse
|
|
394
391
|
calls = await parser.try_parse(text)
|
|
395
|
-
|
|
392
|
+
|
|
396
393
|
# Log success
|
|
397
394
|
duration = time.time() - start_time
|
|
398
395
|
await metrics.log_parser_metric(
|
|
@@ -401,9 +398,9 @@ class ToolProcessor:
|
|
|
401
398
|
duration=duration,
|
|
402
399
|
num_calls=len(calls),
|
|
403
400
|
)
|
|
404
|
-
|
|
401
|
+
|
|
405
402
|
return calls
|
|
406
|
-
|
|
403
|
+
|
|
407
404
|
except Exception as e:
|
|
408
405
|
# Log failure
|
|
409
406
|
duration = time.time() - start_time
|
|
@@ -418,36 +415,38 @@ class ToolProcessor:
|
|
|
418
415
|
|
|
419
416
|
|
|
420
417
|
# Create a global processor instance
|
|
421
|
-
_global_processor:
|
|
418
|
+
_global_processor: ToolProcessor | None = None
|
|
422
419
|
_processor_lock = asyncio.Lock()
|
|
423
420
|
|
|
421
|
+
|
|
424
422
|
async def get_default_processor() -> ToolProcessor:
|
|
425
423
|
"""Get or initialize the default global processor."""
|
|
426
424
|
global _global_processor
|
|
427
|
-
|
|
425
|
+
|
|
428
426
|
if _global_processor is None:
|
|
429
427
|
async with _processor_lock:
|
|
430
428
|
if _global_processor is None:
|
|
431
429
|
_global_processor = ToolProcessor()
|
|
432
430
|
await _global_processor.initialize()
|
|
433
|
-
|
|
431
|
+
|
|
434
432
|
return _global_processor
|
|
435
433
|
|
|
434
|
+
|
|
436
435
|
async def process(
|
|
437
|
-
data:
|
|
438
|
-
timeout:
|
|
436
|
+
data: str | dict[str, Any] | list[dict[str, Any]],
|
|
437
|
+
timeout: float | None = None,
|
|
439
438
|
use_cache: bool = True,
|
|
440
|
-
request_id:
|
|
441
|
-
) ->
|
|
439
|
+
request_id: str | None = None,
|
|
440
|
+
) -> list[ToolResult]:
|
|
442
441
|
"""
|
|
443
442
|
Process tool calls with the default processor.
|
|
444
|
-
|
|
443
|
+
|
|
445
444
|
Args:
|
|
446
445
|
data: Input data (text, dict, or list of dicts)
|
|
447
446
|
timeout: Optional timeout for execution
|
|
448
447
|
use_cache: Whether to use cached results
|
|
449
448
|
request_id: Optional request ID for logging
|
|
450
|
-
|
|
449
|
+
|
|
451
450
|
Returns:
|
|
452
451
|
List of tool results
|
|
453
452
|
"""
|
|
@@ -459,15 +458,16 @@ async def process(
|
|
|
459
458
|
request_id=request_id,
|
|
460
459
|
)
|
|
461
460
|
|
|
461
|
+
|
|
462
462
|
async def process_text(
|
|
463
463
|
text: str,
|
|
464
|
-
timeout:
|
|
464
|
+
timeout: float | None = None,
|
|
465
465
|
use_cache: bool = True,
|
|
466
|
-
request_id:
|
|
467
|
-
) ->
|
|
466
|
+
request_id: str | None = None,
|
|
467
|
+
) -> list[ToolResult]:
|
|
468
468
|
"""
|
|
469
469
|
Process text with the default processor.
|
|
470
|
-
|
|
470
|
+
|
|
471
471
|
Legacy alias for backward compatibility.
|
|
472
472
|
|
|
473
473
|
Args:
|
|
@@ -485,4 +485,4 @@ async def process_text(
|
|
|
485
485
|
timeout=timeout,
|
|
486
486
|
use_cache=use_cache,
|
|
487
487
|
request_id=request_id,
|
|
488
|
-
)
|
|
488
|
+
)
|