chuk-tool-processor 0.7.0__tar.gz → 0.8__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.7.0 → chuk_tool_processor-0.8}/PKG-INFO +197 -6
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/README.md +196 -5
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/pyproject.toml +1 -1
- chuk_tool_processor-0.8/src/chuk_tool_processor/core/__init__.py +32 -0
- chuk_tool_processor-0.8/src/chuk_tool_processor/core/exceptions.py +257 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/core/processor.py +30 -1
- chuk_tool_processor-0.8/src/chuk_tool_processor/execution/wrappers/__init__.py +42 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/caching.py +7 -3
- chuk_tool_processor-0.8/src/chuk_tool_processor/execution/wrappers/circuit_breaker.py +343 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/retry.py +12 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +31 -2
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +31 -2
- chuk_tool_processor-0.8/src/chuk_tool_processor/models/__init__.py +21 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_call.py +34 -1
- chuk_tool_processor-0.8/src/chuk_tool_processor/models/tool_spec.py +350 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/validated_tool.py +22 -2
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/PKG-INFO +197 -6
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/SOURCES.txt +2 -0
- chuk_tool_processor-0.7.0/src/chuk_tool_processor/core/__init__.py +0 -1
- chuk_tool_processor-0.7.0/src/chuk_tool_processor/core/exceptions.py +0 -51
- chuk_tool_processor-0.7.0/src/chuk_tool_processor/models/__init__.py +0 -1
- chuk_tool_processor-0.7.0/src/chuk_tool_processor/utils/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/setup.cfg +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/context.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/formatter.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/helpers.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/metrics.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/stream_manager.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/models.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/sse_transport.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_result.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/discovery.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/auto_register.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/decorators.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/interface.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/metadata.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/provider.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/tool_export.py +0 -0
- {chuk_tool_processor-0.7.0/src/chuk_tool_processor/execution/wrappers → chuk_tool_processor-0.8/src/chuk_tool_processor/utils}/__init__.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor/utils/validation.py +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/requires.txt +0 -0
- {chuk_tool_processor-0.7.0 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8
|
|
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>
|
|
@@ -72,12 +72,15 @@ Unlike full-fledged LLM frameworks (LangChain, LlamaIndex, etc.), CHUK Tool Proc
|
|
|
72
72
|
Research code vs production code is about handling the edges:
|
|
73
73
|
|
|
74
74
|
- **Timeouts**: Every tool execution has proper timeout handling
|
|
75
|
-
- **Retries**: Automatic retry with exponential backoff
|
|
75
|
+
- **Retries**: Automatic retry with exponential backoff and deadline awareness
|
|
76
76
|
- **Rate Limiting**: Global and per-tool rate limits with sliding windows
|
|
77
|
-
- **Caching**: Intelligent result caching with TTL
|
|
78
|
-
- **
|
|
77
|
+
- **Caching**: Intelligent result caching with TTL and idempotency key support
|
|
78
|
+
- **Circuit Breakers**: Prevent cascading failures with automatic fault detection
|
|
79
|
+
- **Error Handling**: Machine-readable error codes with structured details
|
|
79
80
|
- **Observability**: Structured logging, metrics, request tracing
|
|
80
81
|
- **Safety**: Subprocess isolation for untrusted code
|
|
82
|
+
- **Type Safety**: Pydantic validation with LLM-friendly argument coercion
|
|
83
|
+
- **Tool Discovery**: Formal schema export (OpenAI, Anthropic, MCP formats)
|
|
81
84
|
|
|
82
85
|
### It's About Stacks
|
|
83
86
|
|
|
@@ -91,11 +94,13 @@ CHUK Tool Processor uses a **composable stack architecture**:
|
|
|
91
94
|
│ tool calls
|
|
92
95
|
▼
|
|
93
96
|
┌─────────────────────────────────┐
|
|
94
|
-
│ Caching Wrapper │ ← Cache expensive results
|
|
97
|
+
│ Caching Wrapper │ ← Cache expensive results (idempotency keys)
|
|
95
98
|
├─────────────────────────────────┤
|
|
96
99
|
│ Rate Limiting Wrapper │ ← Prevent API abuse
|
|
97
100
|
├─────────────────────────────────┤
|
|
98
|
-
│ Retry Wrapper │ ← Handle transient failures
|
|
101
|
+
│ Retry Wrapper │ ← Handle transient failures (exponential backoff)
|
|
102
|
+
├─────────────────────────────────┤
|
|
103
|
+
│ Circuit Breaker Wrapper │ ← Prevent cascading failures (CLOSED/OPEN/HALF_OPEN)
|
|
99
104
|
├─────────────────────────────────┤
|
|
100
105
|
│ Execution Strategy │ ← How to run tools
|
|
101
106
|
│ • InProcess (fast) │
|
|
@@ -639,6 +644,192 @@ processor = ToolProcessor(
|
|
|
639
644
|
)
|
|
640
645
|
```
|
|
641
646
|
|
|
647
|
+
### Advanced Production Features
|
|
648
|
+
|
|
649
|
+
Beyond basic configuration, CHUK Tool Processor includes several advanced features for production environments:
|
|
650
|
+
|
|
651
|
+
#### Circuit Breaker Pattern
|
|
652
|
+
|
|
653
|
+
Prevent cascading failures by automatically opening circuits for failing tools:
|
|
654
|
+
|
|
655
|
+
```python
|
|
656
|
+
from chuk_tool_processor.core.processor import ToolProcessor
|
|
657
|
+
|
|
658
|
+
processor = ToolProcessor(
|
|
659
|
+
enable_circuit_breaker=True,
|
|
660
|
+
circuit_breaker_threshold=5, # Open after 5 failures
|
|
661
|
+
circuit_breaker_timeout=60.0, # Try recovery after 60s
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
# Circuit states: CLOSED → OPEN → HALF_OPEN → CLOSED
|
|
665
|
+
# - CLOSED: Normal operation
|
|
666
|
+
# - OPEN: Blocking requests (too many failures)
|
|
667
|
+
# - HALF_OPEN: Testing recovery with limited requests
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**How it works:**
|
|
671
|
+
1. Tool fails repeatedly (hits threshold)
|
|
672
|
+
2. Circuit opens → requests blocked immediately
|
|
673
|
+
3. After timeout, circuit enters HALF_OPEN
|
|
674
|
+
4. If test requests succeed → circuit closes
|
|
675
|
+
5. If test requests fail → back to OPEN
|
|
676
|
+
|
|
677
|
+
**Benefits:**
|
|
678
|
+
- Prevents wasting resources on failing services
|
|
679
|
+
- Fast-fail for better UX
|
|
680
|
+
- Automatic recovery detection
|
|
681
|
+
|
|
682
|
+
#### Idempotency Keys
|
|
683
|
+
|
|
684
|
+
Automatically deduplicate LLM tool calls using SHA256-based keys:
|
|
685
|
+
|
|
686
|
+
```python
|
|
687
|
+
from chuk_tool_processor.models.tool_call import ToolCall
|
|
688
|
+
|
|
689
|
+
# Idempotency keys are auto-generated
|
|
690
|
+
call1 = ToolCall(tool="search", arguments={"query": "Python"})
|
|
691
|
+
call2 = ToolCall(tool="search", arguments={"query": "Python"})
|
|
692
|
+
|
|
693
|
+
# Same arguments = same idempotency key
|
|
694
|
+
assert call1.idempotency_key == call2.idempotency_key
|
|
695
|
+
|
|
696
|
+
# Used automatically by caching layer
|
|
697
|
+
processor = ToolProcessor(enable_caching=True)
|
|
698
|
+
results1 = await processor.execute([call1]) # Executes
|
|
699
|
+
results2 = await processor.execute([call2]) # Cache hit!
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Benefits:**
|
|
703
|
+
- Prevents duplicate executions from LLM retries
|
|
704
|
+
- Deterministic cache keys
|
|
705
|
+
- No manual key management needed
|
|
706
|
+
|
|
707
|
+
#### Tool Schema Export
|
|
708
|
+
|
|
709
|
+
Export tool definitions to multiple formats for LLM prompting:
|
|
710
|
+
|
|
711
|
+
```python
|
|
712
|
+
from chuk_tool_processor.models.tool_spec import ToolSpec, ToolCapability
|
|
713
|
+
from chuk_tool_processor.models.validated_tool import ValidatedTool
|
|
714
|
+
|
|
715
|
+
@register_tool(name="weather")
|
|
716
|
+
class WeatherTool(ValidatedTool):
|
|
717
|
+
"""Get current weather for a location."""
|
|
718
|
+
|
|
719
|
+
class Arguments(BaseModel):
|
|
720
|
+
location: str = Field(..., description="City name")
|
|
721
|
+
|
|
722
|
+
class Result(BaseModel):
|
|
723
|
+
temperature: float
|
|
724
|
+
conditions: str
|
|
725
|
+
|
|
726
|
+
# Generate tool spec
|
|
727
|
+
spec = ToolSpec.from_validated_tool(WeatherTool)
|
|
728
|
+
|
|
729
|
+
# Export to different formats
|
|
730
|
+
openai_format = spec.to_openai() # For OpenAI function calling
|
|
731
|
+
anthropic_format = spec.to_anthropic() # For Claude tools
|
|
732
|
+
mcp_format = spec.to_mcp() # For MCP servers
|
|
733
|
+
|
|
734
|
+
# Example OpenAI format:
|
|
735
|
+
# {
|
|
736
|
+
# "type": "function",
|
|
737
|
+
# "function": {
|
|
738
|
+
# "name": "weather",
|
|
739
|
+
# "description": "Get current weather for a location.",
|
|
740
|
+
# "parameters": {...} # JSON Schema
|
|
741
|
+
# }
|
|
742
|
+
# }
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**Use cases:**
|
|
746
|
+
- Generate tool definitions for LLM system prompts
|
|
747
|
+
- Documentation generation
|
|
748
|
+
- API contract validation
|
|
749
|
+
- Cross-platform tool sharing
|
|
750
|
+
|
|
751
|
+
#### Machine-Readable Error Codes
|
|
752
|
+
|
|
753
|
+
Structured error handling with error codes for programmatic responses:
|
|
754
|
+
|
|
755
|
+
```python
|
|
756
|
+
from chuk_tool_processor.core.exceptions import (
|
|
757
|
+
ErrorCode,
|
|
758
|
+
ToolNotFoundError,
|
|
759
|
+
ToolTimeoutError,
|
|
760
|
+
ToolCircuitOpenError,
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
try:
|
|
764
|
+
results = await processor.process(llm_output)
|
|
765
|
+
except ToolNotFoundError as e:
|
|
766
|
+
if e.code == ErrorCode.TOOL_NOT_FOUND:
|
|
767
|
+
# Suggest available tools to LLM
|
|
768
|
+
available = e.details.get("available_tools", [])
|
|
769
|
+
print(f"Try one of: {available}")
|
|
770
|
+
except ToolTimeoutError as e:
|
|
771
|
+
if e.code == ErrorCode.TOOL_TIMEOUT:
|
|
772
|
+
# Inform LLM to use faster alternative
|
|
773
|
+
timeout = e.details["timeout"]
|
|
774
|
+
print(f"Tool timed out after {timeout}s")
|
|
775
|
+
except ToolCircuitOpenError as e:
|
|
776
|
+
if e.code == ErrorCode.TOOL_CIRCUIT_OPEN:
|
|
777
|
+
# Tell LLM this service is temporarily down
|
|
778
|
+
reset_time = e.details.get("reset_timeout")
|
|
779
|
+
print(f"Service unavailable, retry in {reset_time}s")
|
|
780
|
+
|
|
781
|
+
# All errors include .to_dict() for logging
|
|
782
|
+
error_dict = e.to_dict()
|
|
783
|
+
# {
|
|
784
|
+
# "error": "ToolCircuitOpenError",
|
|
785
|
+
# "code": "TOOL_CIRCUIT_OPEN",
|
|
786
|
+
# "message": "Tool 'api_tool' circuit breaker is open...",
|
|
787
|
+
# "details": {"tool_name": "api_tool", "failure_count": 5, ...}
|
|
788
|
+
# }
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**Available error codes:**
|
|
792
|
+
- `TOOL_NOT_FOUND` - Tool doesn't exist in registry
|
|
793
|
+
- `TOOL_EXECUTION_FAILED` - Tool execution error
|
|
794
|
+
- `TOOL_TIMEOUT` - Tool exceeded timeout
|
|
795
|
+
- `TOOL_CIRCUIT_OPEN` - Circuit breaker is open
|
|
796
|
+
- `TOOL_RATE_LIMITED` - Rate limit exceeded
|
|
797
|
+
- `TOOL_VALIDATION_ERROR` - Argument validation failed
|
|
798
|
+
- `MCP_CONNECTION_FAILED` - MCP server unreachable
|
|
799
|
+
- Plus 11 more for comprehensive error handling
|
|
800
|
+
|
|
801
|
+
#### LLM-Friendly Argument Coercion
|
|
802
|
+
|
|
803
|
+
Automatically coerce LLM outputs to correct types:
|
|
804
|
+
|
|
805
|
+
```python
|
|
806
|
+
from chuk_tool_processor.models.validated_tool import ValidatedTool
|
|
807
|
+
|
|
808
|
+
class SearchTool(ValidatedTool):
|
|
809
|
+
class Arguments(BaseModel):
|
|
810
|
+
query: str
|
|
811
|
+
limit: int = 10
|
|
812
|
+
category: str = "all"
|
|
813
|
+
|
|
814
|
+
# Pydantic config for LLM outputs:
|
|
815
|
+
# - str_strip_whitespace=True → Remove accidental whitespace
|
|
816
|
+
# - extra="ignore" → Ignore unknown fields
|
|
817
|
+
# - use_enum_values=True → Convert enums to values
|
|
818
|
+
# - coerce_numbers_to_str=False → Keep type strictness
|
|
819
|
+
|
|
820
|
+
# LLM outputs often have quirks:
|
|
821
|
+
llm_output = {
|
|
822
|
+
"query": " Python tutorials ", # Extra whitespace
|
|
823
|
+
"limit": "5", # String instead of int
|
|
824
|
+
"unknown_field": "ignored" # Extra field
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
# ValidatedTool automatically coerces and validates
|
|
828
|
+
tool = SearchTool()
|
|
829
|
+
result = await tool.execute(**llm_output)
|
|
830
|
+
# ✅ Works! Whitespace stripped, "5" → 5, extra field ignored
|
|
831
|
+
```
|
|
832
|
+
|
|
642
833
|
## Advanced Topics
|
|
643
834
|
|
|
644
835
|
### Using Subprocess Strategy
|
|
@@ -44,12 +44,15 @@ Unlike full-fledged LLM frameworks (LangChain, LlamaIndex, etc.), CHUK Tool Proc
|
|
|
44
44
|
Research code vs production code is about handling the edges:
|
|
45
45
|
|
|
46
46
|
- **Timeouts**: Every tool execution has proper timeout handling
|
|
47
|
-
- **Retries**: Automatic retry with exponential backoff
|
|
47
|
+
- **Retries**: Automatic retry with exponential backoff and deadline awareness
|
|
48
48
|
- **Rate Limiting**: Global and per-tool rate limits with sliding windows
|
|
49
|
-
- **Caching**: Intelligent result caching with TTL
|
|
50
|
-
- **
|
|
49
|
+
- **Caching**: Intelligent result caching with TTL and idempotency key support
|
|
50
|
+
- **Circuit Breakers**: Prevent cascading failures with automatic fault detection
|
|
51
|
+
- **Error Handling**: Machine-readable error codes with structured details
|
|
51
52
|
- **Observability**: Structured logging, metrics, request tracing
|
|
52
53
|
- **Safety**: Subprocess isolation for untrusted code
|
|
54
|
+
- **Type Safety**: Pydantic validation with LLM-friendly argument coercion
|
|
55
|
+
- **Tool Discovery**: Formal schema export (OpenAI, Anthropic, MCP formats)
|
|
53
56
|
|
|
54
57
|
### It's About Stacks
|
|
55
58
|
|
|
@@ -63,11 +66,13 @@ CHUK Tool Processor uses a **composable stack architecture**:
|
|
|
63
66
|
│ tool calls
|
|
64
67
|
▼
|
|
65
68
|
┌─────────────────────────────────┐
|
|
66
|
-
│ Caching Wrapper │ ← Cache expensive results
|
|
69
|
+
│ Caching Wrapper │ ← Cache expensive results (idempotency keys)
|
|
67
70
|
├─────────────────────────────────┤
|
|
68
71
|
│ Rate Limiting Wrapper │ ← Prevent API abuse
|
|
69
72
|
├─────────────────────────────────┤
|
|
70
|
-
│ Retry Wrapper │ ← Handle transient failures
|
|
73
|
+
│ Retry Wrapper │ ← Handle transient failures (exponential backoff)
|
|
74
|
+
├─────────────────────────────────┤
|
|
75
|
+
│ Circuit Breaker Wrapper │ ← Prevent cascading failures (CLOSED/OPEN/HALF_OPEN)
|
|
71
76
|
├─────────────────────────────────┤
|
|
72
77
|
│ Execution Strategy │ ← How to run tools
|
|
73
78
|
│ • InProcess (fast) │
|
|
@@ -611,6 +616,192 @@ processor = ToolProcessor(
|
|
|
611
616
|
)
|
|
612
617
|
```
|
|
613
618
|
|
|
619
|
+
### Advanced Production Features
|
|
620
|
+
|
|
621
|
+
Beyond basic configuration, CHUK Tool Processor includes several advanced features for production environments:
|
|
622
|
+
|
|
623
|
+
#### Circuit Breaker Pattern
|
|
624
|
+
|
|
625
|
+
Prevent cascading failures by automatically opening circuits for failing tools:
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
from chuk_tool_processor.core.processor import ToolProcessor
|
|
629
|
+
|
|
630
|
+
processor = ToolProcessor(
|
|
631
|
+
enable_circuit_breaker=True,
|
|
632
|
+
circuit_breaker_threshold=5, # Open after 5 failures
|
|
633
|
+
circuit_breaker_timeout=60.0, # Try recovery after 60s
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
# Circuit states: CLOSED → OPEN → HALF_OPEN → CLOSED
|
|
637
|
+
# - CLOSED: Normal operation
|
|
638
|
+
# - OPEN: Blocking requests (too many failures)
|
|
639
|
+
# - HALF_OPEN: Testing recovery with limited requests
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**How it works:**
|
|
643
|
+
1. Tool fails repeatedly (hits threshold)
|
|
644
|
+
2. Circuit opens → requests blocked immediately
|
|
645
|
+
3. After timeout, circuit enters HALF_OPEN
|
|
646
|
+
4. If test requests succeed → circuit closes
|
|
647
|
+
5. If test requests fail → back to OPEN
|
|
648
|
+
|
|
649
|
+
**Benefits:**
|
|
650
|
+
- Prevents wasting resources on failing services
|
|
651
|
+
- Fast-fail for better UX
|
|
652
|
+
- Automatic recovery detection
|
|
653
|
+
|
|
654
|
+
#### Idempotency Keys
|
|
655
|
+
|
|
656
|
+
Automatically deduplicate LLM tool calls using SHA256-based keys:
|
|
657
|
+
|
|
658
|
+
```python
|
|
659
|
+
from chuk_tool_processor.models.tool_call import ToolCall
|
|
660
|
+
|
|
661
|
+
# Idempotency keys are auto-generated
|
|
662
|
+
call1 = ToolCall(tool="search", arguments={"query": "Python"})
|
|
663
|
+
call2 = ToolCall(tool="search", arguments={"query": "Python"})
|
|
664
|
+
|
|
665
|
+
# Same arguments = same idempotency key
|
|
666
|
+
assert call1.idempotency_key == call2.idempotency_key
|
|
667
|
+
|
|
668
|
+
# Used automatically by caching layer
|
|
669
|
+
processor = ToolProcessor(enable_caching=True)
|
|
670
|
+
results1 = await processor.execute([call1]) # Executes
|
|
671
|
+
results2 = await processor.execute([call2]) # Cache hit!
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Benefits:**
|
|
675
|
+
- Prevents duplicate executions from LLM retries
|
|
676
|
+
- Deterministic cache keys
|
|
677
|
+
- No manual key management needed
|
|
678
|
+
|
|
679
|
+
#### Tool Schema Export
|
|
680
|
+
|
|
681
|
+
Export tool definitions to multiple formats for LLM prompting:
|
|
682
|
+
|
|
683
|
+
```python
|
|
684
|
+
from chuk_tool_processor.models.tool_spec import ToolSpec, ToolCapability
|
|
685
|
+
from chuk_tool_processor.models.validated_tool import ValidatedTool
|
|
686
|
+
|
|
687
|
+
@register_tool(name="weather")
|
|
688
|
+
class WeatherTool(ValidatedTool):
|
|
689
|
+
"""Get current weather for a location."""
|
|
690
|
+
|
|
691
|
+
class Arguments(BaseModel):
|
|
692
|
+
location: str = Field(..., description="City name")
|
|
693
|
+
|
|
694
|
+
class Result(BaseModel):
|
|
695
|
+
temperature: float
|
|
696
|
+
conditions: str
|
|
697
|
+
|
|
698
|
+
# Generate tool spec
|
|
699
|
+
spec = ToolSpec.from_validated_tool(WeatherTool)
|
|
700
|
+
|
|
701
|
+
# Export to different formats
|
|
702
|
+
openai_format = spec.to_openai() # For OpenAI function calling
|
|
703
|
+
anthropic_format = spec.to_anthropic() # For Claude tools
|
|
704
|
+
mcp_format = spec.to_mcp() # For MCP servers
|
|
705
|
+
|
|
706
|
+
# Example OpenAI format:
|
|
707
|
+
# {
|
|
708
|
+
# "type": "function",
|
|
709
|
+
# "function": {
|
|
710
|
+
# "name": "weather",
|
|
711
|
+
# "description": "Get current weather for a location.",
|
|
712
|
+
# "parameters": {...} # JSON Schema
|
|
713
|
+
# }
|
|
714
|
+
# }
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**Use cases:**
|
|
718
|
+
- Generate tool definitions for LLM system prompts
|
|
719
|
+
- Documentation generation
|
|
720
|
+
- API contract validation
|
|
721
|
+
- Cross-platform tool sharing
|
|
722
|
+
|
|
723
|
+
#### Machine-Readable Error Codes
|
|
724
|
+
|
|
725
|
+
Structured error handling with error codes for programmatic responses:
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
from chuk_tool_processor.core.exceptions import (
|
|
729
|
+
ErrorCode,
|
|
730
|
+
ToolNotFoundError,
|
|
731
|
+
ToolTimeoutError,
|
|
732
|
+
ToolCircuitOpenError,
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
try:
|
|
736
|
+
results = await processor.process(llm_output)
|
|
737
|
+
except ToolNotFoundError as e:
|
|
738
|
+
if e.code == ErrorCode.TOOL_NOT_FOUND:
|
|
739
|
+
# Suggest available tools to LLM
|
|
740
|
+
available = e.details.get("available_tools", [])
|
|
741
|
+
print(f"Try one of: {available}")
|
|
742
|
+
except ToolTimeoutError as e:
|
|
743
|
+
if e.code == ErrorCode.TOOL_TIMEOUT:
|
|
744
|
+
# Inform LLM to use faster alternative
|
|
745
|
+
timeout = e.details["timeout"]
|
|
746
|
+
print(f"Tool timed out after {timeout}s")
|
|
747
|
+
except ToolCircuitOpenError as e:
|
|
748
|
+
if e.code == ErrorCode.TOOL_CIRCUIT_OPEN:
|
|
749
|
+
# Tell LLM this service is temporarily down
|
|
750
|
+
reset_time = e.details.get("reset_timeout")
|
|
751
|
+
print(f"Service unavailable, retry in {reset_time}s")
|
|
752
|
+
|
|
753
|
+
# All errors include .to_dict() for logging
|
|
754
|
+
error_dict = e.to_dict()
|
|
755
|
+
# {
|
|
756
|
+
# "error": "ToolCircuitOpenError",
|
|
757
|
+
# "code": "TOOL_CIRCUIT_OPEN",
|
|
758
|
+
# "message": "Tool 'api_tool' circuit breaker is open...",
|
|
759
|
+
# "details": {"tool_name": "api_tool", "failure_count": 5, ...}
|
|
760
|
+
# }
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**Available error codes:**
|
|
764
|
+
- `TOOL_NOT_FOUND` - Tool doesn't exist in registry
|
|
765
|
+
- `TOOL_EXECUTION_FAILED` - Tool execution error
|
|
766
|
+
- `TOOL_TIMEOUT` - Tool exceeded timeout
|
|
767
|
+
- `TOOL_CIRCUIT_OPEN` - Circuit breaker is open
|
|
768
|
+
- `TOOL_RATE_LIMITED` - Rate limit exceeded
|
|
769
|
+
- `TOOL_VALIDATION_ERROR` - Argument validation failed
|
|
770
|
+
- `MCP_CONNECTION_FAILED` - MCP server unreachable
|
|
771
|
+
- Plus 11 more for comprehensive error handling
|
|
772
|
+
|
|
773
|
+
#### LLM-Friendly Argument Coercion
|
|
774
|
+
|
|
775
|
+
Automatically coerce LLM outputs to correct types:
|
|
776
|
+
|
|
777
|
+
```python
|
|
778
|
+
from chuk_tool_processor.models.validated_tool import ValidatedTool
|
|
779
|
+
|
|
780
|
+
class SearchTool(ValidatedTool):
|
|
781
|
+
class Arguments(BaseModel):
|
|
782
|
+
query: str
|
|
783
|
+
limit: int = 10
|
|
784
|
+
category: str = "all"
|
|
785
|
+
|
|
786
|
+
# Pydantic config for LLM outputs:
|
|
787
|
+
# - str_strip_whitespace=True → Remove accidental whitespace
|
|
788
|
+
# - extra="ignore" → Ignore unknown fields
|
|
789
|
+
# - use_enum_values=True → Convert enums to values
|
|
790
|
+
# - coerce_numbers_to_str=False → Keep type strictness
|
|
791
|
+
|
|
792
|
+
# LLM outputs often have quirks:
|
|
793
|
+
llm_output = {
|
|
794
|
+
"query": " Python tutorials ", # Extra whitespace
|
|
795
|
+
"limit": "5", # String instead of int
|
|
796
|
+
"unknown_field": "ignored" # Extra field
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
# ValidatedTool automatically coerces and validates
|
|
800
|
+
tool = SearchTool()
|
|
801
|
+
result = await tool.execute(**llm_output)
|
|
802
|
+
# ✅ Works! Whitespace stripped, "5" → 5, extra field ignored
|
|
803
|
+
```
|
|
804
|
+
|
|
614
805
|
## Advanced Topics
|
|
615
806
|
|
|
616
807
|
### Using Subprocess Strategy
|
|
@@ -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.8"
|
|
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"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# chuk_tool_processor/core/__init__.py
|
|
2
|
+
"""Core functionality for the tool processor."""
|
|
3
|
+
|
|
4
|
+
from chuk_tool_processor.core.exceptions import (
|
|
5
|
+
ErrorCode,
|
|
6
|
+
MCPConnectionError,
|
|
7
|
+
MCPError,
|
|
8
|
+
MCPTimeoutError,
|
|
9
|
+
ParserError,
|
|
10
|
+
ToolCircuitOpenError,
|
|
11
|
+
ToolExecutionError,
|
|
12
|
+
ToolNotFoundError,
|
|
13
|
+
ToolProcessorError,
|
|
14
|
+
ToolRateLimitedError,
|
|
15
|
+
ToolTimeoutError,
|
|
16
|
+
ToolValidationError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ErrorCode",
|
|
21
|
+
"ToolProcessorError",
|
|
22
|
+
"ToolNotFoundError",
|
|
23
|
+
"ToolExecutionError",
|
|
24
|
+
"ToolTimeoutError",
|
|
25
|
+
"ToolValidationError",
|
|
26
|
+
"ParserError",
|
|
27
|
+
"ToolRateLimitedError",
|
|
28
|
+
"ToolCircuitOpenError",
|
|
29
|
+
"MCPError",
|
|
30
|
+
"MCPConnectionError",
|
|
31
|
+
"MCPTimeoutError",
|
|
32
|
+
]
|