chuk-tool-processor 0.14__tar.gz → 0.15__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.14 → chuk_tool_processor-0.15}/PKG-INFO +144 -13
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/README.md +143 -12
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/pyproject.toml +1 -1
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/__init__.py +19 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/core/processor.py +12 -1
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/bulkhead.py +70 -3
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +51 -15
- chuk_tool_processor-0.15/src/chuk_tool_processor/mcp/__init__.py +93 -0
- chuk_tool_processor-0.15/src/chuk_tool_processor/mcp/middleware.py +490 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/stream_manager.py +104 -3
- chuk_tool_processor-0.15/src/chuk_tool_processor/models/return_order.py +19 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/tool_result.py +2 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/decorators.py +39 -12
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/providers/memory.py +21 -3
- chuk_tool_processor-0.15/src/chuk_tool_processor/scheduling/__init__.py +48 -0
- chuk_tool_processor-0.15/src/chuk_tool_processor/scheduling/greedy_dag.py +473 -0
- chuk_tool_processor-0.15/src/chuk_tool_processor/scheduling/policy.py +66 -0
- chuk_tool_processor-0.15/src/chuk_tool_processor/scheduling/types.py +220 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor.egg-info/PKG-INFO +144 -13
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor.egg-info/SOURCES.txt +6 -0
- chuk_tool_processor-0.14/src/chuk_tool_processor/mcp/__init__.py +0 -34
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/setup.cfg +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/core/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/core/context.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/core/exceptions.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/code_sandbox.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/wrappers/caching.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/wrappers/circuit_breaker.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/execution/wrappers/retry.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/logging/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/logging/context.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/logging/formatter.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/logging/helpers.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/logging/metrics.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/models.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/models.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/sse_transport.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/tool_call.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/tool_spec.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/models/validated_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/observability/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/observability/metrics.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/observability/setup.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/observability/tracing.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/discovery.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/py.typed +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/auto_register.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/interface.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/metadata.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/provider.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/registry/tool_export.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/utils/__init__.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/utils/fast_json.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/utils/validation.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor.egg-info/requires.txt +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/tests/test_bulkhead.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/tests/test_execution_context.py +0 -0
- {chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/tests/test_scoped_registry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15
|
|
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>
|
|
@@ -30,7 +30,7 @@ Requires-Dist: orjson<4,>=3.10.0; extra == "fast-json"
|
|
|
30
30
|
Provides-Extra: full
|
|
31
31
|
Requires-Dist: orjson<4,>=3.10.0; extra == "full"
|
|
32
32
|
|
|
33
|
-
# CHUK Tool Processor —
|
|
33
|
+
# CHUK Tool Processor — A Tool Execution Runtime for AI Systems
|
|
34
34
|
|
|
35
35
|
[](https://pypi.org/project/chuk-tool-processor/)
|
|
36
36
|
[](https://pypi.org/project/chuk-tool-processor/)
|
|
@@ -43,16 +43,18 @@ Requires-Dist: orjson<4,>=3.10.0; extra == "full"
|
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## The Missing Layer
|
|
46
|
+
## The Missing Runtime Layer
|
|
47
47
|
|
|
48
|
-
LLMs are good at *
|
|
48
|
+
LLMs are good at *deciding which tools to call*. The hard part is **executing** those tools reliably.
|
|
49
49
|
|
|
50
|
-
**CHUK Tool Processor
|
|
50
|
+
**CHUK Tool Processor** is a **tool execution runtime** — it doesn't plan workflows or decide which tools to call. It executes tool calls reliably, under constraints, as directed by higher-level planners (your agent, LangChain, LlamaIndex, or a custom orchestrator).
|
|
51
|
+
|
|
52
|
+
**What it does:**
|
|
51
53
|
- Parses tool calls from any model (Anthropic XML, OpenAI `tool_calls`, JSON)
|
|
52
54
|
- Executes them with **timeouts, retries, caching, rate limits, circuit breaker, observability**
|
|
53
55
|
- Runs tools locally, in **isolated subprocesses**, or **remote via MCP**
|
|
54
56
|
|
|
55
|
-
Works with OpenAI, Anthropic, local models (Ollama/MLX/vLLM), and any framework
|
|
57
|
+
Works with OpenAI, Anthropic, local models (Ollama/MLX/vLLM), and any framework.
|
|
56
58
|
|
|
57
59
|
---
|
|
58
60
|
|
|
@@ -110,18 +112,20 @@ uv pip install chuk-tool-processor
|
|
|
110
112
|
|
|
111
113
|
```python
|
|
112
114
|
import asyncio
|
|
113
|
-
from chuk_tool_processor import ToolProcessor,
|
|
115
|
+
from chuk_tool_processor import ToolProcessor, create_registry
|
|
114
116
|
|
|
115
|
-
@tool(name="calculator")
|
|
116
117
|
class Calculator:
|
|
117
118
|
async def execute(self, operation: str, a: float, b: float) -> dict:
|
|
118
119
|
ops = {"add": a + b, "multiply": a * b, "subtract": a - b}
|
|
119
120
|
return {"result": ops.get(operation, 0)}
|
|
120
121
|
|
|
121
122
|
async def main():
|
|
122
|
-
|
|
123
|
+
registry = create_registry()
|
|
124
|
+
await registry.register_tool(Calculator, name="math.calculator") # Dotted name → namespace="math"
|
|
125
|
+
|
|
126
|
+
async with ToolProcessor(registry=registry, enable_caching=True, enable_retries=True) as p:
|
|
123
127
|
# Works with OpenAI, Anthropic, or JSON formats
|
|
124
|
-
result = await p.process('<tool name="calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>')
|
|
128
|
+
result = await p.process('<tool name="math.calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>')
|
|
125
129
|
print(result[0].result) # {'result': 345}
|
|
126
130
|
|
|
127
131
|
asyncio.run(main())
|
|
@@ -129,6 +133,19 @@ asyncio.run(main())
|
|
|
129
133
|
|
|
130
134
|
**That's it.** You now have production-ready tool execution with timeouts, retries, and caching.
|
|
131
135
|
|
|
136
|
+
### Dotted Names for Namespacing
|
|
137
|
+
|
|
138
|
+
Dotted names are auto-parsed into namespace and tool name:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
# These are equivalent:
|
|
142
|
+
await registry.register_tool(FetchUser, name="web.fetch_user") # Auto-parsed
|
|
143
|
+
await registry.register_tool(FetchUser, name="fetch_user", namespace="web") # Explicit
|
|
144
|
+
|
|
145
|
+
# Call using the full dotted name
|
|
146
|
+
result = await processor.process([{"tool": "web.fetch_user", "arguments": {"user_id": "123"}}])
|
|
147
|
+
```
|
|
148
|
+
|
|
132
149
|
### Works with Any LLM Format
|
|
133
150
|
|
|
134
151
|
```python
|
|
@@ -171,10 +188,19 @@ results = await processor.process(json_output)
|
|
|
171
188
|
| Feature | Description |
|
|
172
189
|
|---------|-------------|
|
|
173
190
|
| **Bulkheads** | Per-tool/namespace concurrency limits to prevent resource starvation |
|
|
191
|
+
| **Pattern Bulkheads** | Glob patterns like `"db.*": 3` for grouped concurrency limits |
|
|
174
192
|
| **Scoped Registries** | Isolated registries for multi-tenant apps and testing |
|
|
175
193
|
| **ExecutionContext** | Request-scoped metadata propagation (user, tenant, tracing, deadlines) |
|
|
176
194
|
| **Isolated Strategy** | Subprocess execution for untrusted code (zero crash blast radius) |
|
|
177
195
|
|
|
196
|
+
### Advanced Scheduling
|
|
197
|
+
|
|
198
|
+
| Feature | Description |
|
|
199
|
+
|---------|-------------|
|
|
200
|
+
| **Return Order** | Choose completion order (fast first) or submission order (deterministic) |
|
|
201
|
+
| **SchedulerPolicy** | DAG-based scheduling with dependencies, deadlines, pool limits |
|
|
202
|
+
| **GreedyDagScheduler** | Built-in scheduler with topological sort and deadline-aware skipping |
|
|
203
|
+
|
|
178
204
|
### Integration & Observability
|
|
179
205
|
|
|
180
206
|
| Feature | Description |
|
|
@@ -211,6 +237,7 @@ async with ToolProcessor(
|
|
|
211
237
|
bulkhead_config=BulkheadConfig(
|
|
212
238
|
default_limit=10,
|
|
213
239
|
tool_limits={"slow_api": 2},
|
|
240
|
+
patterns={"db.*": 3, "mcp.notion.*": 2}, # Pattern-based limits
|
|
214
241
|
),
|
|
215
242
|
) as processor:
|
|
216
243
|
# Execute with request context
|
|
@@ -224,6 +251,56 @@ async with ToolProcessor(
|
|
|
224
251
|
|
|
225
252
|
---
|
|
226
253
|
|
|
254
|
+
## Return Order & Scheduling
|
|
255
|
+
|
|
256
|
+
Control how results are returned and plan complex execution graphs:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from chuk_tool_processor import ToolProcessor, ReturnOrder
|
|
260
|
+
|
|
261
|
+
async with ToolProcessor() as processor:
|
|
262
|
+
# Results return as tools complete (fast tools first) - default
|
|
263
|
+
results = await processor.process(calls, return_order="completion")
|
|
264
|
+
|
|
265
|
+
# Results return in submission order (deterministic)
|
|
266
|
+
results = await processor.process(calls, return_order="submission")
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### DAG Scheduling with Dependencies
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from chuk_tool_processor import (
|
|
273
|
+
GreedyDagScheduler,
|
|
274
|
+
SchedulingConstraints,
|
|
275
|
+
ToolCallSpec,
|
|
276
|
+
ToolMetadata,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
scheduler = GreedyDagScheduler()
|
|
280
|
+
|
|
281
|
+
# Define calls with dependencies
|
|
282
|
+
calls = [
|
|
283
|
+
ToolCallSpec(call_id="fetch", tool_name="api.fetch",
|
|
284
|
+
metadata=ToolMetadata(pool="web", est_ms=300)),
|
|
285
|
+
ToolCallSpec(call_id="transform", tool_name="compute.transform",
|
|
286
|
+
depends_on=("fetch",)),
|
|
287
|
+
ToolCallSpec(call_id="store", tool_name="db.write",
|
|
288
|
+
depends_on=("transform",)),
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
# Plan execution with constraints
|
|
292
|
+
constraints = SchedulingConstraints(
|
|
293
|
+
deadline_ms=5000,
|
|
294
|
+
pool_limits={"web": 2, "db": 1},
|
|
295
|
+
)
|
|
296
|
+
plan = scheduler.plan(calls, constraints)
|
|
297
|
+
|
|
298
|
+
# plan.stages: (('fetch',), ('transform',), ('store',))
|
|
299
|
+
# plan.skip: () or low-priority calls that would miss deadline
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
227
304
|
## MCP Integration
|
|
228
305
|
|
|
229
306
|
Connect to remote tool servers using the [Model Context Protocol](https://modelcontextprotocol.io):
|
|
@@ -259,6 +336,33 @@ results = await processor.process(
|
|
|
259
336
|
|
|
260
337
|
See [MCP_INTEGRATION.md](docs/MCP_INTEGRATION.md) for complete examples with OAuth token refresh.
|
|
261
338
|
|
|
339
|
+
### MCP Middleware Stack
|
|
340
|
+
|
|
341
|
+
For production deployments, wrap MCP connections with resilience middleware:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from chuk_tool_processor.mcp.middleware import (
|
|
345
|
+
MiddlewareConfig,
|
|
346
|
+
MiddlewareStack,
|
|
347
|
+
RetrySettings,
|
|
348
|
+
CircuitBreakerSettings,
|
|
349
|
+
RateLimitSettings,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Configure middleware layers
|
|
353
|
+
config = MiddlewareConfig(
|
|
354
|
+
retry=RetrySettings(max_retries=3, base_delay=1.0),
|
|
355
|
+
circuit_breaker=CircuitBreakerSettings(failure_threshold=5),
|
|
356
|
+
rate_limiting=RateLimitSettings(enabled=True, global_limit=100),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Wrap StreamManager with middleware
|
|
360
|
+
middleware = MiddlewareStack(stream_manager, config=config)
|
|
361
|
+
|
|
362
|
+
# Execute with automatic retry, circuit breaking, and rate limiting
|
|
363
|
+
result = await middleware.call_tool("notion.search", {"query": "docs"})
|
|
364
|
+
```
|
|
365
|
+
|
|
262
366
|
---
|
|
263
367
|
|
|
264
368
|
## Observability
|
|
@@ -294,7 +398,7 @@ See [OBSERVABILITY.md](docs/OBSERVABILITY.md) for complete setup guide.
|
|
|
294
398
|
| [**GETTING_STARTED.md**](docs/GETTING_STARTED.md) | Creating tools, using the processor, ValidatedTool, StreamingTool |
|
|
295
399
|
| [**CORE_CONCEPTS.md**](docs/CORE_CONCEPTS.md) | Registry, strategies, wrappers, parsers, MCP overview |
|
|
296
400
|
| [**PRODUCTION_PATTERNS.md**](docs/PRODUCTION_PATTERNS.md) | Bulkheads, scoped registries, ExecutionContext, parallel execution |
|
|
297
|
-
| [**MCP_INTEGRATION.md**](docs/MCP_INTEGRATION.md) | HTTP Streamable, STDIO, SSE, OAuth
|
|
401
|
+
| [**MCP_INTEGRATION.md**](docs/MCP_INTEGRATION.md) | HTTP Streamable, STDIO, SSE, OAuth, Middleware Stack |
|
|
298
402
|
| [**ADVANCED_TOPICS.md**](docs/ADVANCED_TOPICS.md) | Deferred loading, code sandbox, isolated strategy, testing |
|
|
299
403
|
| [**CONFIGURATION.md**](docs/CONFIGURATION.md) | All config options and environment variables |
|
|
300
404
|
| [**OBSERVABILITY.md**](docs/OBSERVABILITY.md) | OpenTelemetry, Prometheus, metrics reference |
|
|
@@ -308,15 +412,22 @@ See [OBSERVABILITY.md](docs/OBSERVABILITY.md) for complete setup guide.
|
|
|
308
412
|
# Getting started
|
|
309
413
|
python examples/01_getting_started/hello_tool.py
|
|
310
414
|
|
|
415
|
+
# Hero demo: 8 tools, 5-second deadline, 3 pools (DAG + bulkheads + context)
|
|
416
|
+
python examples/02_production_features/hero_runtime_demo.py
|
|
417
|
+
|
|
311
418
|
# Production patterns (bulkheads, context, scoped registries)
|
|
312
419
|
python examples/02_production_features/production_patterns_demo.py
|
|
313
420
|
|
|
421
|
+
# Runtime features (return order, pattern bulkheads, scheduling)
|
|
422
|
+
python examples/02_production_features/runtime_features_demo.py
|
|
423
|
+
|
|
314
424
|
# Observability demo
|
|
315
425
|
python examples/02_production_features/observability_demo.py
|
|
316
426
|
|
|
317
427
|
# MCP integration
|
|
318
428
|
python examples/04_mcp_integration/stdio_echo.py
|
|
319
429
|
python examples/04_mcp_integration/notion_oauth.py
|
|
430
|
+
python examples/04_mcp_integration/middleware_demo.py
|
|
320
431
|
```
|
|
321
432
|
|
|
322
433
|
See [examples/](examples/) for 20+ working examples.
|
|
@@ -367,10 +478,30 @@ pip install chuk-tool-processor[all]
|
|
|
367
478
|
- You want production-grade observability
|
|
368
479
|
|
|
369
480
|
**Don't use this if:**
|
|
370
|
-
- You want an agent framework (this is the execution
|
|
481
|
+
- You want an agent framework (this is the execution runtime, not the agent)
|
|
371
482
|
- You want conversation flow/memory orchestration
|
|
483
|
+
- You need a planner to decide *which* tools to call
|
|
484
|
+
|
|
485
|
+
### The Seam: Runtime vs Planner
|
|
486
|
+
|
|
487
|
+
CHUK Tool Processor deliberately does not plan workflows or decide which tools to call. It executes tool calls reliably, under constraints, as directed by higher-level planners.
|
|
488
|
+
|
|
489
|
+
```
|
|
490
|
+
┌─────────────────────────────────────────────────────┐
|
|
491
|
+
│ Your Agent / LangChain / LlamaIndex / Custom │ ← Decides WHICH tools
|
|
492
|
+
└─────────────────────────────────────────────────────┘
|
|
493
|
+
↓
|
|
494
|
+
┌─────────────────────────────────────────────────────┐
|
|
495
|
+
│ CHUK Tool Processor │ ← Executes tools RELIABLY
|
|
496
|
+
│ (timeouts, retries, caching, rate limits, etc.) │
|
|
497
|
+
└─────────────────────────────────────────────────────┘
|
|
498
|
+
↓
|
|
499
|
+
┌─────────────────────────────────────────────────────┐
|
|
500
|
+
│ Local Tools / MCP Servers │ ← Does the actual work
|
|
501
|
+
└─────────────────────────────────────────────────────┘
|
|
502
|
+
```
|
|
372
503
|
|
|
373
|
-
|
|
504
|
+
This separation means you can swap planners without changing execution infrastructure, and vice versa.
|
|
374
505
|
|
|
375
506
|
---
|
|
376
507
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CHUK Tool Processor —
|
|
1
|
+
# CHUK Tool Processor — A Tool Execution Runtime for AI Systems
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/chuk-tool-processor/)
|
|
4
4
|
[](https://pypi.org/project/chuk-tool-processor/)
|
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
## The Missing Layer
|
|
14
|
+
## The Missing Runtime Layer
|
|
15
15
|
|
|
16
|
-
LLMs are good at *
|
|
16
|
+
LLMs are good at *deciding which tools to call*. The hard part is **executing** those tools reliably.
|
|
17
17
|
|
|
18
|
-
**CHUK Tool Processor
|
|
18
|
+
**CHUK Tool Processor** is a **tool execution runtime** — it doesn't plan workflows or decide which tools to call. It executes tool calls reliably, under constraints, as directed by higher-level planners (your agent, LangChain, LlamaIndex, or a custom orchestrator).
|
|
19
|
+
|
|
20
|
+
**What it does:**
|
|
19
21
|
- Parses tool calls from any model (Anthropic XML, OpenAI `tool_calls`, JSON)
|
|
20
22
|
- Executes them with **timeouts, retries, caching, rate limits, circuit breaker, observability**
|
|
21
23
|
- Runs tools locally, in **isolated subprocesses**, or **remote via MCP**
|
|
22
24
|
|
|
23
|
-
Works with OpenAI, Anthropic, local models (Ollama/MLX/vLLM), and any framework
|
|
25
|
+
Works with OpenAI, Anthropic, local models (Ollama/MLX/vLLM), and any framework.
|
|
24
26
|
|
|
25
27
|
---
|
|
26
28
|
|
|
@@ -78,18 +80,20 @@ uv pip install chuk-tool-processor
|
|
|
78
80
|
|
|
79
81
|
```python
|
|
80
82
|
import asyncio
|
|
81
|
-
from chuk_tool_processor import ToolProcessor,
|
|
83
|
+
from chuk_tool_processor import ToolProcessor, create_registry
|
|
82
84
|
|
|
83
|
-
@tool(name="calculator")
|
|
84
85
|
class Calculator:
|
|
85
86
|
async def execute(self, operation: str, a: float, b: float) -> dict:
|
|
86
87
|
ops = {"add": a + b, "multiply": a * b, "subtract": a - b}
|
|
87
88
|
return {"result": ops.get(operation, 0)}
|
|
88
89
|
|
|
89
90
|
async def main():
|
|
90
|
-
|
|
91
|
+
registry = create_registry()
|
|
92
|
+
await registry.register_tool(Calculator, name="math.calculator") # Dotted name → namespace="math"
|
|
93
|
+
|
|
94
|
+
async with ToolProcessor(registry=registry, enable_caching=True, enable_retries=True) as p:
|
|
91
95
|
# Works with OpenAI, Anthropic, or JSON formats
|
|
92
|
-
result = await p.process('<tool name="calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>')
|
|
96
|
+
result = await p.process('<tool name="math.calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>')
|
|
93
97
|
print(result[0].result) # {'result': 345}
|
|
94
98
|
|
|
95
99
|
asyncio.run(main())
|
|
@@ -97,6 +101,19 @@ asyncio.run(main())
|
|
|
97
101
|
|
|
98
102
|
**That's it.** You now have production-ready tool execution with timeouts, retries, and caching.
|
|
99
103
|
|
|
104
|
+
### Dotted Names for Namespacing
|
|
105
|
+
|
|
106
|
+
Dotted names are auto-parsed into namespace and tool name:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# These are equivalent:
|
|
110
|
+
await registry.register_tool(FetchUser, name="web.fetch_user") # Auto-parsed
|
|
111
|
+
await registry.register_tool(FetchUser, name="fetch_user", namespace="web") # Explicit
|
|
112
|
+
|
|
113
|
+
# Call using the full dotted name
|
|
114
|
+
result = await processor.process([{"tool": "web.fetch_user", "arguments": {"user_id": "123"}}])
|
|
115
|
+
```
|
|
116
|
+
|
|
100
117
|
### Works with Any LLM Format
|
|
101
118
|
|
|
102
119
|
```python
|
|
@@ -139,10 +156,19 @@ results = await processor.process(json_output)
|
|
|
139
156
|
| Feature | Description |
|
|
140
157
|
|---------|-------------|
|
|
141
158
|
| **Bulkheads** | Per-tool/namespace concurrency limits to prevent resource starvation |
|
|
159
|
+
| **Pattern Bulkheads** | Glob patterns like `"db.*": 3` for grouped concurrency limits |
|
|
142
160
|
| **Scoped Registries** | Isolated registries for multi-tenant apps and testing |
|
|
143
161
|
| **ExecutionContext** | Request-scoped metadata propagation (user, tenant, tracing, deadlines) |
|
|
144
162
|
| **Isolated Strategy** | Subprocess execution for untrusted code (zero crash blast radius) |
|
|
145
163
|
|
|
164
|
+
### Advanced Scheduling
|
|
165
|
+
|
|
166
|
+
| Feature | Description |
|
|
167
|
+
|---------|-------------|
|
|
168
|
+
| **Return Order** | Choose completion order (fast first) or submission order (deterministic) |
|
|
169
|
+
| **SchedulerPolicy** | DAG-based scheduling with dependencies, deadlines, pool limits |
|
|
170
|
+
| **GreedyDagScheduler** | Built-in scheduler with topological sort and deadline-aware skipping |
|
|
171
|
+
|
|
146
172
|
### Integration & Observability
|
|
147
173
|
|
|
148
174
|
| Feature | Description |
|
|
@@ -179,6 +205,7 @@ async with ToolProcessor(
|
|
|
179
205
|
bulkhead_config=BulkheadConfig(
|
|
180
206
|
default_limit=10,
|
|
181
207
|
tool_limits={"slow_api": 2},
|
|
208
|
+
patterns={"db.*": 3, "mcp.notion.*": 2}, # Pattern-based limits
|
|
182
209
|
),
|
|
183
210
|
) as processor:
|
|
184
211
|
# Execute with request context
|
|
@@ -192,6 +219,56 @@ async with ToolProcessor(
|
|
|
192
219
|
|
|
193
220
|
---
|
|
194
221
|
|
|
222
|
+
## Return Order & Scheduling
|
|
223
|
+
|
|
224
|
+
Control how results are returned and plan complex execution graphs:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from chuk_tool_processor import ToolProcessor, ReturnOrder
|
|
228
|
+
|
|
229
|
+
async with ToolProcessor() as processor:
|
|
230
|
+
# Results return as tools complete (fast tools first) - default
|
|
231
|
+
results = await processor.process(calls, return_order="completion")
|
|
232
|
+
|
|
233
|
+
# Results return in submission order (deterministic)
|
|
234
|
+
results = await processor.process(calls, return_order="submission")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### DAG Scheduling with Dependencies
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from chuk_tool_processor import (
|
|
241
|
+
GreedyDagScheduler,
|
|
242
|
+
SchedulingConstraints,
|
|
243
|
+
ToolCallSpec,
|
|
244
|
+
ToolMetadata,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
scheduler = GreedyDagScheduler()
|
|
248
|
+
|
|
249
|
+
# Define calls with dependencies
|
|
250
|
+
calls = [
|
|
251
|
+
ToolCallSpec(call_id="fetch", tool_name="api.fetch",
|
|
252
|
+
metadata=ToolMetadata(pool="web", est_ms=300)),
|
|
253
|
+
ToolCallSpec(call_id="transform", tool_name="compute.transform",
|
|
254
|
+
depends_on=("fetch",)),
|
|
255
|
+
ToolCallSpec(call_id="store", tool_name="db.write",
|
|
256
|
+
depends_on=("transform",)),
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
# Plan execution with constraints
|
|
260
|
+
constraints = SchedulingConstraints(
|
|
261
|
+
deadline_ms=5000,
|
|
262
|
+
pool_limits={"web": 2, "db": 1},
|
|
263
|
+
)
|
|
264
|
+
plan = scheduler.plan(calls, constraints)
|
|
265
|
+
|
|
266
|
+
# plan.stages: (('fetch',), ('transform',), ('store',))
|
|
267
|
+
# plan.skip: () or low-priority calls that would miss deadline
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
195
272
|
## MCP Integration
|
|
196
273
|
|
|
197
274
|
Connect to remote tool servers using the [Model Context Protocol](https://modelcontextprotocol.io):
|
|
@@ -227,6 +304,33 @@ results = await processor.process(
|
|
|
227
304
|
|
|
228
305
|
See [MCP_INTEGRATION.md](docs/MCP_INTEGRATION.md) for complete examples with OAuth token refresh.
|
|
229
306
|
|
|
307
|
+
### MCP Middleware Stack
|
|
308
|
+
|
|
309
|
+
For production deployments, wrap MCP connections with resilience middleware:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
from chuk_tool_processor.mcp.middleware import (
|
|
313
|
+
MiddlewareConfig,
|
|
314
|
+
MiddlewareStack,
|
|
315
|
+
RetrySettings,
|
|
316
|
+
CircuitBreakerSettings,
|
|
317
|
+
RateLimitSettings,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Configure middleware layers
|
|
321
|
+
config = MiddlewareConfig(
|
|
322
|
+
retry=RetrySettings(max_retries=3, base_delay=1.0),
|
|
323
|
+
circuit_breaker=CircuitBreakerSettings(failure_threshold=5),
|
|
324
|
+
rate_limiting=RateLimitSettings(enabled=True, global_limit=100),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Wrap StreamManager with middleware
|
|
328
|
+
middleware = MiddlewareStack(stream_manager, config=config)
|
|
329
|
+
|
|
330
|
+
# Execute with automatic retry, circuit breaking, and rate limiting
|
|
331
|
+
result = await middleware.call_tool("notion.search", {"query": "docs"})
|
|
332
|
+
```
|
|
333
|
+
|
|
230
334
|
---
|
|
231
335
|
|
|
232
336
|
## Observability
|
|
@@ -262,7 +366,7 @@ See [OBSERVABILITY.md](docs/OBSERVABILITY.md) for complete setup guide.
|
|
|
262
366
|
| [**GETTING_STARTED.md**](docs/GETTING_STARTED.md) | Creating tools, using the processor, ValidatedTool, StreamingTool |
|
|
263
367
|
| [**CORE_CONCEPTS.md**](docs/CORE_CONCEPTS.md) | Registry, strategies, wrappers, parsers, MCP overview |
|
|
264
368
|
| [**PRODUCTION_PATTERNS.md**](docs/PRODUCTION_PATTERNS.md) | Bulkheads, scoped registries, ExecutionContext, parallel execution |
|
|
265
|
-
| [**MCP_INTEGRATION.md**](docs/MCP_INTEGRATION.md) | HTTP Streamable, STDIO, SSE, OAuth
|
|
369
|
+
| [**MCP_INTEGRATION.md**](docs/MCP_INTEGRATION.md) | HTTP Streamable, STDIO, SSE, OAuth, Middleware Stack |
|
|
266
370
|
| [**ADVANCED_TOPICS.md**](docs/ADVANCED_TOPICS.md) | Deferred loading, code sandbox, isolated strategy, testing |
|
|
267
371
|
| [**CONFIGURATION.md**](docs/CONFIGURATION.md) | All config options and environment variables |
|
|
268
372
|
| [**OBSERVABILITY.md**](docs/OBSERVABILITY.md) | OpenTelemetry, Prometheus, metrics reference |
|
|
@@ -276,15 +380,22 @@ See [OBSERVABILITY.md](docs/OBSERVABILITY.md) for complete setup guide.
|
|
|
276
380
|
# Getting started
|
|
277
381
|
python examples/01_getting_started/hello_tool.py
|
|
278
382
|
|
|
383
|
+
# Hero demo: 8 tools, 5-second deadline, 3 pools (DAG + bulkheads + context)
|
|
384
|
+
python examples/02_production_features/hero_runtime_demo.py
|
|
385
|
+
|
|
279
386
|
# Production patterns (bulkheads, context, scoped registries)
|
|
280
387
|
python examples/02_production_features/production_patterns_demo.py
|
|
281
388
|
|
|
389
|
+
# Runtime features (return order, pattern bulkheads, scheduling)
|
|
390
|
+
python examples/02_production_features/runtime_features_demo.py
|
|
391
|
+
|
|
282
392
|
# Observability demo
|
|
283
393
|
python examples/02_production_features/observability_demo.py
|
|
284
394
|
|
|
285
395
|
# MCP integration
|
|
286
396
|
python examples/04_mcp_integration/stdio_echo.py
|
|
287
397
|
python examples/04_mcp_integration/notion_oauth.py
|
|
398
|
+
python examples/04_mcp_integration/middleware_demo.py
|
|
288
399
|
```
|
|
289
400
|
|
|
290
401
|
See [examples/](examples/) for 20+ working examples.
|
|
@@ -335,10 +446,30 @@ pip install chuk-tool-processor[all]
|
|
|
335
446
|
- You want production-grade observability
|
|
336
447
|
|
|
337
448
|
**Don't use this if:**
|
|
338
|
-
- You want an agent framework (this is the execution
|
|
449
|
+
- You want an agent framework (this is the execution runtime, not the agent)
|
|
339
450
|
- You want conversation flow/memory orchestration
|
|
451
|
+
- You need a planner to decide *which* tools to call
|
|
452
|
+
|
|
453
|
+
### The Seam: Runtime vs Planner
|
|
454
|
+
|
|
455
|
+
CHUK Tool Processor deliberately does not plan workflows or decide which tools to call. It executes tool calls reliably, under constraints, as directed by higher-level planners.
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
┌─────────────────────────────────────────────────────┐
|
|
459
|
+
│ Your Agent / LangChain / LlamaIndex / Custom │ ← Decides WHICH tools
|
|
460
|
+
└─────────────────────────────────────────────────────┘
|
|
461
|
+
↓
|
|
462
|
+
┌─────────────────────────────────────────────────────┐
|
|
463
|
+
│ CHUK Tool Processor │ ← Executes tools RELIABLY
|
|
464
|
+
│ (timeouts, retries, caching, rate limits, etc.) │
|
|
465
|
+
└─────────────────────────────────────────────────────┘
|
|
466
|
+
↓
|
|
467
|
+
┌─────────────────────────────────────────────────────┐
|
|
468
|
+
│ Local Tools / MCP Servers │ ← Does the actual work
|
|
469
|
+
└─────────────────────────────────────────────────────┘
|
|
470
|
+
```
|
|
340
471
|
|
|
341
|
-
|
|
472
|
+
This separation means you can swap planners without changing execution infrastructure, and vice versa.
|
|
342
473
|
|
|
343
474
|
---
|
|
344
475
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chuk-tool-processor"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15"
|
|
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"
|
|
@@ -59,6 +59,7 @@ from chuk_tool_processor.mcp import (
|
|
|
59
59
|
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
60
60
|
|
|
61
61
|
# Models (commonly used)
|
|
62
|
+
from chuk_tool_processor.models.return_order import ReturnOrder
|
|
62
63
|
from chuk_tool_processor.models.tool_call import ToolCall
|
|
63
64
|
from chuk_tool_processor.models.tool_result import ToolResult
|
|
64
65
|
|
|
@@ -75,6 +76,16 @@ from chuk_tool_processor.registry.auto_register import register_fn_tool
|
|
|
75
76
|
# Decorators for registering tools
|
|
76
77
|
from chuk_tool_processor.registry.decorators import register_tool, tool
|
|
77
78
|
|
|
79
|
+
# Scheduling
|
|
80
|
+
from chuk_tool_processor.scheduling import (
|
|
81
|
+
ExecutionPlan,
|
|
82
|
+
GreedyDagScheduler,
|
|
83
|
+
SchedulerPolicy,
|
|
84
|
+
SchedulingConstraints,
|
|
85
|
+
ToolCallSpec,
|
|
86
|
+
ToolMetadata,
|
|
87
|
+
)
|
|
88
|
+
|
|
78
89
|
# Type checking imports (not available at runtime)
|
|
79
90
|
if TYPE_CHECKING:
|
|
80
91
|
# Advanced models for type hints
|
|
@@ -114,6 +125,14 @@ __all__ = [
|
|
|
114
125
|
# Models
|
|
115
126
|
"ToolCall",
|
|
116
127
|
"ToolResult",
|
|
128
|
+
"ReturnOrder",
|
|
129
|
+
# Scheduling
|
|
130
|
+
"ToolMetadata",
|
|
131
|
+
"ToolCallSpec",
|
|
132
|
+
"SchedulingConstraints",
|
|
133
|
+
"ExecutionPlan",
|
|
134
|
+
"SchedulerPolicy",
|
|
135
|
+
"GreedyDagScheduler",
|
|
117
136
|
# Registry
|
|
118
137
|
"ToolInfo",
|
|
119
138
|
"initialize",
|
{chuk_tool_processor-0.14 → chuk_tool_processor-0.15}/src/chuk_tool_processor/core/processor.py
RENAMED
|
@@ -339,6 +339,7 @@ class ToolProcessor:
|
|
|
339
339
|
use_cache: bool = True, # noqa: ARG002
|
|
340
340
|
request_id: str | None = None,
|
|
341
341
|
context: ExecutionContext | None = None,
|
|
342
|
+
return_order: str | None = None,
|
|
342
343
|
) -> list[ToolResult]:
|
|
343
344
|
"""
|
|
344
345
|
Process tool calls from various LLM output formats.
|
|
@@ -393,6 +394,9 @@ class ToolProcessor:
|
|
|
393
394
|
context: Optional ExecutionContext for request-scoped data.
|
|
394
395
|
Carries user_id, tenant_id, traceparent, deadline, etc.
|
|
395
396
|
If provided, context.request_id takes precedence over request_id.
|
|
397
|
+
return_order: Order to return results in. Can be:
|
|
398
|
+
- "completion" (default): Results return as each tool completes
|
|
399
|
+
- "submission": Results return in the same order as submitted
|
|
396
400
|
|
|
397
401
|
Returns:
|
|
398
402
|
List of ToolResult objects. Each result contains:
|
|
@@ -514,7 +518,14 @@ class ToolProcessor:
|
|
|
514
518
|
# Execute tools (with context scope if provided)
|
|
515
519
|
async def _execute_with_context() -> list[ToolResult]:
|
|
516
520
|
assert self.executor is not None
|
|
517
|
-
|
|
521
|
+
# If return_order is specified and strategy supports it, call run() directly
|
|
522
|
+
# This bypasses the wrapper chain but preserves return_order semantics
|
|
523
|
+
if return_order is not None and self.strategy is not None and hasattr(self.strategy, "run"):
|
|
524
|
+
result: list[ToolResult] = await self.strategy.run(
|
|
525
|
+
calls, timeout=effective_timeout, return_order=return_order
|
|
526
|
+
)
|
|
527
|
+
else:
|
|
528
|
+
result = await self.executor.execute(calls, timeout=effective_timeout)
|
|
518
529
|
return result
|
|
519
530
|
|
|
520
531
|
if context_manager:
|