chuk-tool-processor 0.6.29__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.
Files changed (72) hide show
  1. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/PKG-INFO +198 -7
  2. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/README.md +196 -5
  3. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/pyproject.toml +2 -2
  4. chuk_tool_processor-0.8/src/chuk_tool_processor/core/__init__.py +32 -0
  5. chuk_tool_processor-0.8/src/chuk_tool_processor/core/exceptions.py +257 -0
  6. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/core/processor.py +30 -1
  7. chuk_tool_processor-0.8/src/chuk_tool_processor/execution/wrappers/__init__.py +42 -0
  8. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/caching.py +7 -3
  9. chuk_tool_processor-0.8/src/chuk_tool_processor/execution/wrappers/circuit_breaker.py +343 -0
  10. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/retry.py +12 -0
  11. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +31 -2
  12. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +31 -2
  13. chuk_tool_processor-0.8/src/chuk_tool_processor/models/__init__.py +21 -0
  14. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_call.py +34 -1
  15. chuk_tool_processor-0.8/src/chuk_tool_processor/models/tool_spec.py +350 -0
  16. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/validated_tool.py +22 -2
  17. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/PKG-INFO +198 -7
  18. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/SOURCES.txt +2 -0
  19. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/requires.txt +1 -1
  20. chuk_tool_processor-0.6.29/src/chuk_tool_processor/core/__init__.py +0 -1
  21. chuk_tool_processor-0.6.29/src/chuk_tool_processor/core/exceptions.py +0 -51
  22. chuk_tool_processor-0.6.29/src/chuk_tool_processor/models/__init__.py +0 -1
  23. chuk_tool_processor-0.6.29/src/chuk_tool_processor/utils/__init__.py +0 -0
  24. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/setup.cfg +0 -0
  25. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/__init__.py +0 -0
  26. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/__init__.py +0 -0
  27. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
  28. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
  29. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
  30. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
  31. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
  32. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/__init__.py +0 -0
  33. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/context.py +0 -0
  34. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/formatter.py +0 -0
  35. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/helpers.py +0 -0
  36. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/logging/metrics.py +0 -0
  37. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/__init__.py +0 -0
  38. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
  39. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
  40. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
  41. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/stream_manager.py +0 -0
  42. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/__init__.py +0 -0
  43. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -0
  44. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +0 -0
  45. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/models.py +0 -0
  46. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/sse_transport.py +0 -0
  47. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -0
  48. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
  49. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
  50. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
  51. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/models/tool_result.py +0 -0
  52. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/__init__.py +0 -0
  53. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/discovery.py +0 -0
  54. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
  55. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
  56. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
  57. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
  58. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
  59. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
  60. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/__init__.py +0 -0
  61. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/auto_register.py +0 -0
  62. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/decorators.py +0 -0
  63. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/interface.py +0 -0
  64. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/metadata.py +0 -0
  65. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/provider.py +0 -0
  66. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
  67. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
  68. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/registry/tool_export.py +0 -0
  69. {chuk_tool_processor-0.6.29/src/chuk_tool_processor/execution/wrappers → chuk_tool_processor-0.8/src/chuk_tool_processor/utils}/__init__.py +0 -0
  70. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor/utils/validation.py +0 -0
  71. {chuk_tool_processor-0.6.29 → chuk_tool_processor-0.8}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
  72. {chuk_tool_processor-0.6.29 → 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.6.29
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>
@@ -20,7 +20,7 @@ Classifier: Framework :: AsyncIO
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: chuk-mcp>=0.6
23
+ Requires-Dist: chuk-mcp>=0.7.1
24
24
  Requires-Dist: dotenv>=0.9.9
25
25
  Requires-Dist: psutil>=7.0.0
26
26
  Requires-Dist: pydantic>=2.11.3
@@ -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
- - **Error Handling**: Graceful degradation, never crashes your app
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
- - **Error Handling**: Graceful degradation, never crashes your app
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.6.29"
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"
@@ -41,7 +41,7 @@ classifiers = [
41
41
  "Typing :: Typed",
42
42
  ]
43
43
  dependencies = [
44
- "chuk-mcp>=0.6",
44
+ "chuk-mcp>=0.7.1",
45
45
  "dotenv>=0.9.9",
46
46
  "psutil>=7.0.0",
47
47
  "pydantic>=2.11.3",
@@ -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
+ ]