chuk-tool-processor 0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chuk-tool-processor might be problematic. Click here for more details.

Files changed (63) hide show
  1. chuk_tool_processor-0.4/PKG-INFO +831 -0
  2. chuk_tool_processor-0.4/README.md +819 -0
  3. chuk_tool_processor-0.4/pyproject.toml +40 -0
  4. chuk_tool_processor-0.4/setup.cfg +4 -0
  5. chuk_tool_processor-0.4/src/chuk_tool_processor/__init__.py +0 -0
  6. chuk_tool_processor-0.4/src/chuk_tool_processor/core/__init__.py +1 -0
  7. chuk_tool_processor-0.4/src/chuk_tool_processor/core/exceptions.py +45 -0
  8. chuk_tool_processor-0.4/src/chuk_tool_processor/core/processor.py +488 -0
  9. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/__init__.py +0 -0
  10. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
  11. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +619 -0
  12. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +598 -0
  13. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/tool_executor.py +342 -0
  14. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
  15. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/wrappers/caching.py +576 -0
  16. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +262 -0
  17. chuk_tool_processor-0.4/src/chuk_tool_processor/execution/wrappers/retry.py +286 -0
  18. chuk_tool_processor-0.4/src/chuk_tool_processor/logging/__init__.py +106 -0
  19. chuk_tool_processor-0.4/src/chuk_tool_processor/logging/context.py +243 -0
  20. chuk_tool_processor-0.4/src/chuk_tool_processor/logging/formatter.py +98 -0
  21. chuk_tool_processor-0.4/src/chuk_tool_processor/logging/helpers.py +187 -0
  22. chuk_tool_processor-0.4/src/chuk_tool_processor/logging/metrics.py +128 -0
  23. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/__init__.py +21 -0
  24. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/mcp_tool.py +135 -0
  25. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/register_mcp_tools.py +100 -0
  26. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/setup_mcp_sse.py +99 -0
  27. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +80 -0
  28. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/stream_manager.py +409 -0
  29. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/transport/__init__.py +14 -0
  30. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/transport/base_transport.py +103 -0
  31. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/transport/sse_transport.py +508 -0
  32. chuk_tool_processor-0.4/src/chuk_tool_processor/mcp/transport/stdio_transport.py +197 -0
  33. chuk_tool_processor-0.4/src/chuk_tool_processor/models/__init__.py +1 -0
  34. chuk_tool_processor-0.4/src/chuk_tool_processor/models/execution_strategy.py +68 -0
  35. chuk_tool_processor-0.4/src/chuk_tool_processor/models/streaming_tool.py +110 -0
  36. chuk_tool_processor-0.4/src/chuk_tool_processor/models/tool_call.py +59 -0
  37. chuk_tool_processor-0.4/src/chuk_tool_processor/models/tool_export_mixin.py +29 -0
  38. chuk_tool_processor-0.4/src/chuk_tool_processor/models/tool_result.py +155 -0
  39. chuk_tool_processor-0.4/src/chuk_tool_processor/models/validated_tool.py +157 -0
  40. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/__init__.py +1 -0
  41. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/discovery.py +183 -0
  42. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/__init__.py +1 -0
  43. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/base.py +26 -0
  44. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +100 -0
  45. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/json_tool.py +50 -0
  46. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/openai_tool.py +88 -0
  47. chuk_tool_processor-0.4/src/chuk_tool_processor/plugins/parsers/xml_tool.py +99 -0
  48. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/__init__.py +60 -0
  49. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/auto_register.py +189 -0
  50. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/decorators.py +165 -0
  51. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/interface.py +113 -0
  52. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/metadata.py +82 -0
  53. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/provider.py +138 -0
  54. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/providers/__init__.py +80 -0
  55. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/providers/memory.py +141 -0
  56. chuk_tool_processor-0.4/src/chuk_tool_processor/registry/tool_export.py +245 -0
  57. chuk_tool_processor-0.4/src/chuk_tool_processor/utils/__init__.py +0 -0
  58. chuk_tool_processor-0.4/src/chuk_tool_processor/utils/validation.py +126 -0
  59. chuk_tool_processor-0.4/src/chuk_tool_processor.egg-info/PKG-INFO +831 -0
  60. chuk_tool_processor-0.4/src/chuk_tool_processor.egg-info/SOURCES.txt +61 -0
  61. chuk_tool_processor-0.4/src/chuk_tool_processor.egg-info/dependency_links.txt +1 -0
  62. chuk_tool_processor-0.4/src/chuk_tool_processor.egg-info/requires.txt +5 -0
  63. chuk_tool_processor-0.4/src/chuk_tool_processor.egg-info/top_level.txt +1 -0
@@ -0,0 +1,831 @@
1
+ Metadata-Version: 2.4
2
+ Name: chuk-tool-processor
3
+ Version: 0.4
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: chuk-mcp>=0.1.12
8
+ Requires-Dist: dotenv>=0.9.9
9
+ Requires-Dist: openai>=1.76.0
10
+ Requires-Dist: pydantic>=2.11.3
11
+ Requires-Dist: uuid>=1.30
12
+
13
+ # CHUK Tool Processor
14
+
15
+ An async-native framework for registering, discovering, and executing tools referenced in LLM responses. Built from the ground up for production use with comprehensive error handling, monitoring, and scalability features.
16
+
17
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
18
+ [![Async Native](https://img.shields.io/badge/async-native-green.svg)](https://docs.python.org/3/library/asyncio.html)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
20
+
21
+ ## 🚀 Quick Start
22
+
23
+ ### Installation
24
+
25
+ ```bash
26
+ # From source (recommended for development)
27
+ git clone https://github.com/chrishayuk/chuk-tool-processor.git
28
+ cd chuk-tool-processor
29
+ pip install -e .
30
+
31
+ # Or install from PyPI (when available)
32
+ pip install chuk-tool-processor
33
+ ```
34
+
35
+ ### Your First Tool in 60 Seconds
36
+
37
+ ```python
38
+ import asyncio
39
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
40
+
41
+ # 1. Create a tool
42
+ @register_tool(name="calculator", description="Perform basic math operations")
43
+ class Calculator:
44
+ async def execute(self, operation: str, a: float, b: float) -> dict:
45
+ operations = {
46
+ "add": a + b,
47
+ "subtract": a - b,
48
+ "multiply": a * b,
49
+ "divide": a / b if b != 0 else None
50
+ }
51
+
52
+ if operation not in operations:
53
+ raise ValueError(f"Unknown operation: {operation}")
54
+
55
+ result = operations[operation]
56
+ if result is None:
57
+ raise ValueError("Cannot divide by zero")
58
+
59
+ return {
60
+ "operation": operation,
61
+ "operands": [a, b],
62
+ "result": result
63
+ }
64
+
65
+ async def main():
66
+ # 2. Initialize the system
67
+ await initialize()
68
+
69
+ # 3. Create processor
70
+ processor = ToolProcessor()
71
+
72
+ # 4. Process LLM output containing tool calls
73
+ llm_response = '''
74
+ I'll calculate 15 * 23 for you.
75
+
76
+ <tool name="calculator" args='{"operation": "multiply", "a": 15, "b": 23}'/>
77
+
78
+ The result is 345.
79
+ '''
80
+
81
+ # 5. Execute the tools
82
+ results = await processor.process(llm_response)
83
+
84
+ # 6. Handle results
85
+ for result in results:
86
+ if result.error:
87
+ print(f"❌ Tool '{result.tool}' failed: {result.error}")
88
+ else:
89
+ print(f"✅ Tool '{result.tool}' result: {result.result}")
90
+ print(f"⏱️ Executed in {result.duration:.3f}s")
91
+
92
+ if __name__ == "__main__":
93
+ asyncio.run(main())
94
+ ```
95
+
96
+ **Output:**
97
+ ```
98
+ ✅ Tool 'calculator' result: {'operation': 'multiply', 'operands': [15, 23], 'result': 345}
99
+ ⏱️ Executed in 0.001s
100
+ ```
101
+
102
+ ## 🎯 Key Features
103
+
104
+ - **🔄 Async-Native**: Built for `async/await` from the ground up
105
+ - **🛡️ Production Ready**: Comprehensive error handling, timeouts, retries
106
+ - **📦 Multiple Execution**: In-process and subprocess strategies
107
+ - **🚀 High Performance**: Caching, rate limiting, and concurrency control
108
+ - **📊 Monitoring**: Structured logging and metrics collection
109
+ - **🔗 MCP Integration**: Full Model Context Protocol support
110
+ - **📡 Streaming**: Real-time incremental results
111
+ - **🔧 Extensible**: Plugin system for custom parsers and strategies
112
+
113
+ ## 📖 Getting Started Guide
114
+
115
+ ### Environment Setup
116
+
117
+ Create a `.env` file or set environment variables:
118
+
119
+ ```bash
120
+ # Optional: Registry provider (default: memory)
121
+ export CHUK_TOOL_REGISTRY_PROVIDER=memory
122
+
123
+ # Optional: Default timeout for tool execution (default: 30.0)
124
+ export CHUK_DEFAULT_TIMEOUT=30.0
125
+
126
+ # Optional: Logging level (default: INFO)
127
+ export CHUK_LOG_LEVEL=INFO
128
+
129
+ # Optional: Enable structured JSON logging (default: true)
130
+ export CHUK_STRUCTURED_LOGGING=true
131
+
132
+ # MCP Integration (if using MCP servers)
133
+ export MCP_BEARER_TOKEN=your_bearer_token_here
134
+ export MCP_CONFIG_FILE=/path/to/mcp_config.json
135
+ ```
136
+
137
+ ### Basic Tool Development
138
+
139
+ #### 1. Simple Function-Based Tool
140
+
141
+ ```python
142
+ from chuk_tool_processor.registry.auto_register import register_fn_tool
143
+
144
+ async def get_current_time(timezone: str = "UTC") -> str:
145
+ """Get the current time in the specified timezone."""
146
+ from datetime import datetime
147
+ import pytz
148
+
149
+ tz = pytz.timezone(timezone)
150
+ current_time = datetime.now(tz)
151
+ return current_time.strftime("%Y-%m-%d %H:%M:%S %Z")
152
+
153
+ # Register the function as a tool
154
+ await register_fn_tool(get_current_time, namespace="utilities")
155
+ ```
156
+
157
+ #### 2. Class-Based Tool with Validation
158
+
159
+ ```python
160
+ from chuk_tool_processor.models.validated_tool import ValidatedTool
161
+ from pydantic import BaseModel, Field
162
+
163
+ @register_tool(name="weather", namespace="api")
164
+ class WeatherTool(ValidatedTool):
165
+ """Get weather information for a location."""
166
+
167
+ class Arguments(BaseModel):
168
+ location: str = Field(..., description="City name or coordinates")
169
+ units: str = Field("metric", description="Temperature units: metric, imperial, kelvin")
170
+ include_forecast: bool = Field(False, description="Include 5-day forecast")
171
+
172
+ class Result(BaseModel):
173
+ location: str
174
+ temperature: float
175
+ conditions: str
176
+ humidity: int
177
+ forecast: list[dict] = Field(default_factory=list)
178
+
179
+ async def _execute(self, location: str, units: str, include_forecast: bool) -> Result:
180
+ # Simulate API call
181
+ await asyncio.sleep(0.1)
182
+
183
+ return self.Result(
184
+ location=location,
185
+ temperature=22.5,
186
+ conditions="Partly cloudy",
187
+ humidity=65,
188
+ forecast=[{"day": "Tomorrow", "temp": 24, "conditions": "Sunny"}] if include_forecast else []
189
+ )
190
+ ```
191
+
192
+ #### 3. Streaming Tool
193
+
194
+ ```python
195
+ from chuk_tool_processor.models.streaming_tool import StreamingTool
196
+ import asyncio
197
+
198
+ @register_tool(name="file_processor")
199
+ class FileProcessorTool(StreamingTool):
200
+ """Process a large file line by line."""
201
+
202
+ class Arguments(BaseModel):
203
+ file_path: str
204
+ operation: str = "count_lines"
205
+
206
+ class Result(BaseModel):
207
+ line_number: int
208
+ content: str
209
+ processed_at: str
210
+
211
+ async def _stream_execute(self, file_path: str, operation: str):
212
+ """Stream results as each line is processed."""
213
+ from datetime import datetime
214
+
215
+ # Simulate processing a large file
216
+ total_lines = 100
217
+
218
+ for i in range(1, total_lines + 1):
219
+ await asyncio.sleep(0.01) # Simulate processing time
220
+
221
+ yield self.Result(
222
+ line_number=i,
223
+ content=f"Processed line {i}",
224
+ processed_at=datetime.now().isoformat()
225
+ )
226
+ ```
227
+
228
+ ### Advanced Configuration
229
+
230
+ #### Production-Ready Processor Setup
231
+
232
+ ```python
233
+ from chuk_tool_processor import ToolProcessor
234
+ from chuk_tool_processor.execution.strategies.subprocess_strategy import SubprocessStrategy
235
+
236
+ async def create_production_processor():
237
+ """Create a production-ready processor with all features enabled."""
238
+
239
+ processor = ToolProcessor(
240
+ # Execution settings
241
+ default_timeout=30.0,
242
+ max_concurrency=10,
243
+
244
+ # Use subprocess strategy for isolation
245
+ strategy=SubprocessStrategy(
246
+ registry=await get_default_registry(),
247
+ max_workers=4,
248
+ default_timeout=30.0
249
+ ),
250
+
251
+ # Enable caching for performance
252
+ enable_caching=True,
253
+ cache_ttl=300, # 5 minutes
254
+
255
+ # Rate limiting to prevent abuse
256
+ enable_rate_limiting=True,
257
+ global_rate_limit=100, # 100 requests per minute globally
258
+ tool_rate_limits={
259
+ "expensive_api": (10, 60), # 10 per minute
260
+ "file_processor": (5, 60), # 5 per minute
261
+ "weather": (50, 60) # 50 per minute
262
+ },
263
+
264
+ # Automatic retries for reliability
265
+ enable_retries=True,
266
+ max_retries=3,
267
+
268
+ # Specify which parsers to use
269
+ parser_plugins=["xml_tool", "openai_tool", "json_tool", "function_call"]
270
+ )
271
+
272
+ await processor.initialize()
273
+ return processor
274
+ ```
275
+
276
+ #### Custom Tool with All Features
277
+
278
+ ```python
279
+ from chuk_tool_processor.execution.wrappers.caching import cacheable
280
+ from chuk_tool_processor.execution.wrappers.rate_limiting import rate_limited
281
+ from chuk_tool_processor.execution.wrappers.retry import retryable
282
+
283
+ @register_tool(name="advanced_api", namespace="external")
284
+ @cacheable(ttl=600) # Cache for 10 minutes
285
+ @rate_limited(limit=20, period=60.0) # 20 calls per minute
286
+ @retryable(max_retries=3, base_delay=1.0) # Retry on failures
287
+ class AdvancedApiTool(ValidatedTool):
288
+ """Example tool with all production features."""
289
+
290
+ class Arguments(BaseModel):
291
+ query: str = Field(..., min_length=1, max_length=1000)
292
+ format: str = Field("json", regex="^(json|xml|csv)$")
293
+ timeout: float = Field(10.0, gt=0, le=30)
294
+
295
+ class Result(BaseModel):
296
+ data: dict
297
+ format: str
298
+ processing_time: float
299
+ cached: bool = False
300
+
301
+ async def _execute(self, query: str, format: str, timeout: float) -> Result:
302
+ start_time = time.time()
303
+
304
+ # Simulate expensive API call
305
+ await asyncio.sleep(0.5)
306
+
307
+ # Simulate potential failure (for retry testing)
308
+ if random.random() < 0.1: # 10% failure rate
309
+ raise Exception("Temporary API failure")
310
+
311
+ processing_time = time.time() - start_time
312
+
313
+ return self.Result(
314
+ data={"query": query, "results": ["result1", "result2", "result3"]},
315
+ format=format,
316
+ processing_time=processing_time
317
+ )
318
+ ```
319
+
320
+ ### Working with LLM Responses
321
+
322
+ #### Supported Input Formats
323
+
324
+ The processor automatically detects and parses multiple formats:
325
+
326
+ ```python
327
+ # 1. XML Tool Tags (most common)
328
+ xml_response = """
329
+ Let me search for information about Python.
330
+
331
+ <tool name="search" args='{"query": "Python programming", "limit": 5}'/>
332
+
333
+ I'll also get the current time.
334
+
335
+ <tool name="get_current_time" args='{"timezone": "UTC"}'/>
336
+ """
337
+
338
+ # 2. OpenAI Chat Completions Format
339
+ openai_response = {
340
+ "tool_calls": [
341
+ {
342
+ "id": "call_123",
343
+ "type": "function",
344
+ "function": {
345
+ "name": "search",
346
+ "arguments": '{"query": "Python programming", "limit": 5}'
347
+ }
348
+ }
349
+ ]
350
+ }
351
+
352
+ # 3. Direct ToolCall objects
353
+ tool_calls = [
354
+ {
355
+ "tool": "search",
356
+ "arguments": {"query": "Python programming", "limit": 5}
357
+ },
358
+ {
359
+ "tool": "get_current_time",
360
+ "arguments": {"timezone": "UTC"}
361
+ }
362
+ ]
363
+
364
+ # Process any format
365
+ processor = ToolProcessor()
366
+ results1 = await processor.process(xml_response)
367
+ results2 = await processor.process(openai_response)
368
+ results3 = await processor.process(tool_calls)
369
+ ```
370
+
371
+ #### Error Handling Best Practices
372
+
373
+ ```python
374
+ async def robust_tool_processing(llm_response: str):
375
+ """Example of robust error handling."""
376
+ processor = ToolProcessor(
377
+ default_timeout=30.0,
378
+ enable_retries=True,
379
+ max_retries=3
380
+ )
381
+
382
+ try:
383
+ results = await processor.process(llm_response, timeout=60.0)
384
+
385
+ successful_results = []
386
+ failed_results = []
387
+
388
+ for result in results:
389
+ if result.error:
390
+ failed_results.append(result)
391
+ logger.error(
392
+ f"Tool {result.tool} failed: {result.error}",
393
+ extra={
394
+ "tool": result.tool,
395
+ "duration": result.duration,
396
+ "attempts": getattr(result, "attempts", 1),
397
+ "machine": result.machine
398
+ }
399
+ )
400
+ else:
401
+ successful_results.append(result)
402
+ logger.info(
403
+ f"Tool {result.tool} succeeded",
404
+ extra={
405
+ "tool": result.tool,
406
+ "duration": result.duration,
407
+ "cached": getattr(result, "cached", False)
408
+ }
409
+ )
410
+
411
+ return {
412
+ "successful": successful_results,
413
+ "failed": failed_results,
414
+ "total": len(results)
415
+ }
416
+
417
+ except Exception as e:
418
+ logger.exception("Failed to process LLM response")
419
+ raise
420
+ ```
421
+
422
+ ### MCP (Model Context Protocol) Integration
423
+
424
+ #### Quick MCP Setup with SSE
425
+
426
+ ```python
427
+ from chuk_tool_processor.mcp import setup_mcp_sse
428
+ import os
429
+
430
+ async def setup_mcp_tools():
431
+ """Set up MCP tools from external servers."""
432
+
433
+ # Configure MCP servers
434
+ servers = [
435
+ {
436
+ "name": "weather-service",
437
+ "url": "https://weather-mcp.example.com",
438
+ "api_key": os.getenv("WEATHER_API_KEY")
439
+ },
440
+ {
441
+ "name": "database-service",
442
+ "url": "https://db-mcp.example.com",
443
+ "api_key": os.getenv("DB_API_KEY")
444
+ }
445
+ ]
446
+
447
+ # Initialize MCP with full configuration
448
+ processor, stream_manager = await setup_mcp_sse(
449
+ servers=servers,
450
+ namespace="mcp", # Tools available as mcp.tool_name
451
+ default_timeout=30.0,
452
+ max_concurrency=5,
453
+ enable_caching=True,
454
+ cache_ttl=300,
455
+ enable_rate_limiting=True,
456
+ global_rate_limit=100,
457
+ enable_retries=True,
458
+ max_retries=3
459
+ )
460
+
461
+ return processor, stream_manager
462
+
463
+ # Use MCP tools
464
+ processor, manager = await setup_mcp_tools()
465
+
466
+ # Tools are now available in the processor
467
+ results = await processor.process('''
468
+ <tool name="mcp.weather" args='{"location": "London"}'/>
469
+ <tool name="mcp.database_query" args='{"sql": "SELECT COUNT(*) FROM users"}'/>
470
+ ''')
471
+ ```
472
+
473
+ #### MCP with Stdio Transport
474
+
475
+ ```python
476
+ from chuk_tool_processor.mcp import setup_mcp_stdio
477
+
478
+ # Create MCP config file (mcp_config.json)
479
+ mcp_config = {
480
+ "weather": {
481
+ "command": "python",
482
+ "args": ["-m", "weather_mcp_server"],
483
+ "env": {"API_KEY": "your_weather_key"}
484
+ },
485
+ "calculator": {
486
+ "command": "node",
487
+ "args": ["calculator-server.js"]
488
+ }
489
+ }
490
+
491
+ # Setup MCP with stdio
492
+ processor, stream_manager = await setup_mcp_stdio(
493
+ config_file="mcp_config.json",
494
+ servers=["weather", "calculator"],
495
+ namespace="tools"
496
+ )
497
+
498
+ # Use the tools
499
+ results = await processor.process('<tool name="tools.weather" args=\'{"city": "Paris"}\'/>')
500
+ ```
501
+
502
+ ### Monitoring and Observability
503
+
504
+ #### Structured Logging Setup
505
+
506
+ ```python
507
+ from chuk_tool_processor.logging import setup_logging, get_logger, log_context_span
508
+ import logging
509
+
510
+ # Setup logging
511
+ await setup_logging(
512
+ level=logging.INFO,
513
+ structured=True, # JSON output
514
+ log_file="tool_processor.log" # Optional file output
515
+ )
516
+
517
+ # Use structured logging in your application
518
+ logger = get_logger("my_app")
519
+
520
+ async def process_user_request(user_id: str, request: str):
521
+ """Example of using structured logging with context."""
522
+
523
+ async with log_context_span("user_request", {"user_id": user_id}):
524
+ logger.info("Processing user request", extra={
525
+ "request_length": len(request),
526
+ "user_id": user_id
527
+ })
528
+
529
+ try:
530
+ results = await processor.process(request)
531
+
532
+ logger.info("Request processed successfully", extra={
533
+ "num_tools": len(results),
534
+ "user_id": user_id
535
+ })
536
+
537
+ return results
538
+
539
+ except Exception as e:
540
+ logger.error("Request processing failed", extra={
541
+ "error": str(e),
542
+ "user_id": user_id
543
+ })
544
+ raise
545
+ ```
546
+
547
+ #### Metrics Collection
548
+
549
+ ```python
550
+ from chuk_tool_processor.logging import metrics
551
+
552
+ # Metrics are automatically collected for:
553
+ # - Tool execution success/failure rates
554
+ # - Execution durations
555
+ # - Cache hit/miss rates
556
+ # - Parser performance
557
+ # - Registry operations
558
+
559
+ # Access metrics programmatically
560
+ async def get_tool_stats():
561
+ """Get statistics about tool usage."""
562
+
563
+ # Example: Get cache statistics
564
+ if hasattr(processor.executor, 'cache'):
565
+ cache_stats = await processor.executor.cache.get_stats()
566
+ print(f"Cache hit rate: {cache_stats['hit_rate']:.2%}")
567
+ print(f"Total entries: {cache_stats['entry_count']}")
568
+
569
+ # Custom metrics can be logged
570
+ await metrics.log_tool_execution(
571
+ tool="custom_metric",
572
+ success=True,
573
+ duration=1.5,
574
+ cached=False
575
+ )
576
+ ```
577
+
578
+ ### Testing Your Tools
579
+
580
+ #### Unit Testing
581
+
582
+ ```python
583
+ import pytest
584
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
585
+
586
+ @pytest.mark.asyncio
587
+ async def test_calculator_tool():
588
+ """Test the calculator tool."""
589
+
590
+ # Setup
591
+ await initialize()
592
+ processor = ToolProcessor()
593
+
594
+ # Test successful operation
595
+ results = await processor.process(
596
+ '<tool name="calculator" args=\'{"operation": "add", "a": 5, "b": 3}\'/>'
597
+ )
598
+
599
+ assert len(results) == 1
600
+ result = results[0]
601
+ assert result.error is None
602
+ assert result.result["result"] == 8
603
+ assert result.result["operation"] == "add"
604
+
605
+ @pytest.mark.asyncio
606
+ async def test_calculator_error_handling():
607
+ """Test calculator error handling."""
608
+
609
+ await initialize()
610
+ processor = ToolProcessor()
611
+
612
+ # Test division by zero
613
+ results = await processor.process(
614
+ '<tool name="calculator" args=\'{"operation": "divide", "a": 5, "b": 0}\'/>'
615
+ )
616
+
617
+ assert len(results) == 1
618
+ result = results[0]
619
+ assert result.error is not None
620
+ assert "Cannot divide by zero" in result.error
621
+ ```
622
+
623
+ #### Integration Testing
624
+
625
+ ```python
626
+ @pytest.mark.asyncio
627
+ async def test_full_workflow():
628
+ """Test a complete workflow with multiple tools."""
629
+
630
+ # Register additional test tools
631
+ @register_tool(name="formatter")
632
+ class FormatterTool:
633
+ async def execute(self, text: str, format: str) -> str:
634
+ if format == "upper":
635
+ return text.upper()
636
+ elif format == "lower":
637
+ return text.lower()
638
+ return text
639
+
640
+ await initialize()
641
+ processor = ToolProcessor(enable_caching=True)
642
+
643
+ # Test multiple tool calls
644
+ llm_response = """
645
+ <tool name="calculator" args='{"operation": "multiply", "a": 6, "b": 7}'/>
646
+ <tool name="formatter" args='{"text": "Hello World", "format": "upper"}'/>
647
+ """
648
+
649
+ results = await processor.process(llm_response)
650
+
651
+ assert len(results) == 2
652
+
653
+ # Check calculator result
654
+ calc_result = next(r for r in results if r.tool == "calculator")
655
+ assert calc_result.result["result"] == 42
656
+
657
+ # Check formatter result
658
+ format_result = next(r for r in results if r.tool == "formatter")
659
+ assert format_result.result == "HELLO WORLD"
660
+ ```
661
+
662
+ ### Performance Optimization
663
+
664
+ #### Concurrent Execution
665
+
666
+ ```python
667
+ # Configure for high-throughput scenarios
668
+ processor = ToolProcessor(
669
+ max_concurrency=20, # Allow 20 concurrent tool executions
670
+ default_timeout=60.0, # Longer timeout for complex operations
671
+ enable_caching=True, # Cache frequently used results
672
+ cache_ttl=900, # 15-minute cache
673
+ enable_rate_limiting=True,
674
+ global_rate_limit=500 # 500 requests per minute
675
+ )
676
+
677
+ # Process multiple requests concurrently
678
+ async def process_batch(requests: list[str]):
679
+ """Process multiple LLM responses concurrently."""
680
+
681
+ tasks = [processor.process(request) for request in requests]
682
+ all_results = await asyncio.gather(*tasks, return_exceptions=True)
683
+
684
+ successful = []
685
+ failed = []
686
+
687
+ for i, result in enumerate(all_results):
688
+ if isinstance(result, Exception):
689
+ failed.append({"request_index": i, "error": str(result)})
690
+ else:
691
+ successful.append({"request_index": i, "results": result})
692
+
693
+ return {"successful": successful, "failed": failed}
694
+ ```
695
+
696
+ #### Memory Management
697
+
698
+ ```python
699
+ # For long-running applications, periodically clear caches
700
+ async def maintenance_task():
701
+ """Periodic maintenance for long-running applications."""
702
+
703
+ while True:
704
+ await asyncio.sleep(3600) # Every hour
705
+
706
+ # Clear old cache entries
707
+ if hasattr(processor.executor, 'cache'):
708
+ # Clear entire cache or implement LRU eviction
709
+ stats_before = await processor.executor.cache.get_stats()
710
+ await processor.executor.cache.clear()
711
+
712
+ logger.info("Cache cleared", extra={
713
+ "entries_cleared": stats_before.get("entry_count", 0)
714
+ })
715
+
716
+ # Run maintenance in background
717
+ asyncio.create_task(maintenance_task())
718
+ ```
719
+
720
+ ## 🔧 Configuration Reference
721
+
722
+ ### Environment Variables
723
+
724
+ | Variable | Default | Description |
725
+ |----------|---------|-------------|
726
+ | `CHUK_TOOL_REGISTRY_PROVIDER` | `memory` | Registry backend (memory, redis, etc.) |
727
+ | `CHUK_DEFAULT_TIMEOUT` | `30.0` | Default tool execution timeout (seconds) |
728
+ | `CHUK_LOG_LEVEL` | `INFO` | Logging level (DEBUG, INFO, WARNING, ERROR) |
729
+ | `CHUK_STRUCTURED_LOGGING` | `true` | Enable JSON structured logging |
730
+ | `CHUK_MAX_CONCURRENCY` | `10` | Default max concurrent executions |
731
+ | `MCP_BEARER_TOKEN` | - | Bearer token for MCP SSE authentication |
732
+ | `MCP_CONFIG_FILE` | - | Path to MCP configuration file |
733
+
734
+ ### ToolProcessor Configuration
735
+
736
+ ```python
737
+ processor = ToolProcessor(
738
+ # Execution
739
+ default_timeout=30.0, # Default timeout per tool
740
+ max_concurrency=10, # Max concurrent executions
741
+
742
+ # Strategy (choose one)
743
+ strategy=InProcessStrategy(...), # Fast, shared memory
744
+ # strategy=SubprocessStrategy(...), # Isolated, safer
745
+
746
+ # Caching
747
+ enable_caching=True, # Enable result caching
748
+ cache_ttl=300, # Cache TTL in seconds
749
+
750
+ # Rate limiting
751
+ enable_rate_limiting=False, # Enable rate limiting
752
+ global_rate_limit=100, # Global requests per minute
753
+ tool_rate_limits={ # Per-tool limits
754
+ "expensive_tool": (10, 60), # 10 per minute
755
+ },
756
+
757
+ # Retries
758
+ enable_retries=True, # Enable automatic retries
759
+ max_retries=3, # Max retry attempts
760
+
761
+ # Parsing
762
+ parser_plugins=[ # Enabled parsers
763
+ "xml_tool",
764
+ "openai_tool",
765
+ "json_tool",
766
+ "function_call"
767
+ ]
768
+ )
769
+ ```
770
+
771
+ ## 🤝 Contributing
772
+
773
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
774
+
775
+ ### Development Setup
776
+
777
+ ```bash
778
+ # Clone and setup development environment
779
+ git clone https://github.com/chrishayuk/chuk-tool-processor.git
780
+ cd chuk-tool-processor
781
+
782
+ # Create virtual environment
783
+ python -m venv venv
784
+ source venv/bin/activate # On Windows: venv\Scripts\activate
785
+
786
+ # Install in development mode with all extras
787
+ pip install -e ".[dev,test,mcp,all]"
788
+
789
+ # Run tests
790
+ pytest
791
+
792
+ # Run with coverage
793
+ pytest --cov=chuk_tool_processor
794
+
795
+ # Format code
796
+ black chuk_tool_processor
797
+ isort chuk_tool_processor
798
+
799
+ # Type checking
800
+ mypy chuk_tool_processor
801
+ ```
802
+
803
+ ### Adding New Features
804
+
805
+ 1. **New Tool Types**: Extend `ValidatedTool` or `StreamingTool`
806
+ 2. **New Parsers**: Implement `ParserPlugin` interface
807
+ 3. **New Strategies**: Implement `ExecutionStrategy` interface
808
+ 4. **New Wrappers**: Create execution wrappers for cross-cutting concerns
809
+
810
+ ## 📚 Documentation
811
+
812
+ - [API Reference](docs/api.md)
813
+ - [Architecture Guide](docs/architecture.md)
814
+ - [Plugin Development](docs/plugins.md)
815
+ - [MCP Integration Guide](docs/mcp.md)
816
+ - [Performance Tuning](docs/performance.md)
817
+ - [Deployment Guide](docs/deployment.md)
818
+
819
+ ## 🆘 Support
820
+
821
+ - [GitHub Issues](https://github.com/chrishayuk/chuk-tool-processor/issues)
822
+ - [Discussions](https://github.com/chrishayuk/chuk-tool-processor/discussions)
823
+ - [Discord Community](https://discord.gg/chuk-tools)
824
+
825
+ ## 📄 License
826
+
827
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
828
+
829
+ ---
830
+
831
+ **Made with ❤️ by the CHUK team**