chuk-tool-processor 0.6__py3-none-any.whl → 0.6.1__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.

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