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

Files changed (66) hide show
  1. chuk_tool_processor/core/__init__.py +32 -1
  2. chuk_tool_processor/core/exceptions.py +225 -13
  3. chuk_tool_processor/core/processor.py +135 -104
  4. chuk_tool_processor/execution/strategies/__init__.py +6 -0
  5. chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
  6. chuk_tool_processor/execution/strategies/subprocess_strategy.py +202 -206
  7. chuk_tool_processor/execution/tool_executor.py +82 -84
  8. chuk_tool_processor/execution/wrappers/__init__.py +42 -0
  9. chuk_tool_processor/execution/wrappers/caching.py +150 -116
  10. chuk_tool_processor/execution/wrappers/circuit_breaker.py +370 -0
  11. chuk_tool_processor/execution/wrappers/rate_limiting.py +76 -43
  12. chuk_tool_processor/execution/wrappers/retry.py +116 -78
  13. chuk_tool_processor/logging/__init__.py +23 -17
  14. chuk_tool_processor/logging/context.py +40 -45
  15. chuk_tool_processor/logging/formatter.py +22 -21
  16. chuk_tool_processor/logging/helpers.py +28 -42
  17. chuk_tool_processor/logging/metrics.py +13 -15
  18. chuk_tool_processor/mcp/__init__.py +8 -12
  19. chuk_tool_processor/mcp/mcp_tool.py +158 -114
  20. chuk_tool_processor/mcp/register_mcp_tools.py +22 -22
  21. chuk_tool_processor/mcp/setup_mcp_http_streamable.py +57 -17
  22. chuk_tool_processor/mcp/setup_mcp_sse.py +57 -17
  23. chuk_tool_processor/mcp/setup_mcp_stdio.py +11 -11
  24. chuk_tool_processor/mcp/stream_manager.py +333 -276
  25. chuk_tool_processor/mcp/transport/__init__.py +22 -29
  26. chuk_tool_processor/mcp/transport/base_transport.py +180 -44
  27. chuk_tool_processor/mcp/transport/http_streamable_transport.py +505 -325
  28. chuk_tool_processor/mcp/transport/models.py +100 -0
  29. chuk_tool_processor/mcp/transport/sse_transport.py +607 -276
  30. chuk_tool_processor/mcp/transport/stdio_transport.py +597 -116
  31. chuk_tool_processor/models/__init__.py +21 -1
  32. chuk_tool_processor/models/execution_strategy.py +16 -21
  33. chuk_tool_processor/models/streaming_tool.py +28 -25
  34. chuk_tool_processor/models/tool_call.py +49 -31
  35. chuk_tool_processor/models/tool_export_mixin.py +22 -8
  36. chuk_tool_processor/models/tool_result.py +40 -77
  37. chuk_tool_processor/models/tool_spec.py +350 -0
  38. chuk_tool_processor/models/validated_tool.py +36 -18
  39. chuk_tool_processor/observability/__init__.py +30 -0
  40. chuk_tool_processor/observability/metrics.py +312 -0
  41. chuk_tool_processor/observability/setup.py +105 -0
  42. chuk_tool_processor/observability/tracing.py +345 -0
  43. chuk_tool_processor/plugins/__init__.py +1 -1
  44. chuk_tool_processor/plugins/discovery.py +11 -11
  45. chuk_tool_processor/plugins/parsers/__init__.py +1 -1
  46. chuk_tool_processor/plugins/parsers/base.py +1 -2
  47. chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
  48. chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
  49. chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
  50. chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
  51. chuk_tool_processor/registry/__init__.py +12 -12
  52. chuk_tool_processor/registry/auto_register.py +22 -30
  53. chuk_tool_processor/registry/decorators.py +127 -129
  54. chuk_tool_processor/registry/interface.py +26 -23
  55. chuk_tool_processor/registry/metadata.py +27 -22
  56. chuk_tool_processor/registry/provider.py +17 -18
  57. chuk_tool_processor/registry/providers/__init__.py +16 -19
  58. chuk_tool_processor/registry/providers/memory.py +18 -25
  59. chuk_tool_processor/registry/tool_export.py +42 -51
  60. chuk_tool_processor/utils/validation.py +15 -16
  61. chuk_tool_processor-0.9.7.dist-info/METADATA +1813 -0
  62. chuk_tool_processor-0.9.7.dist-info/RECORD +67 -0
  63. chuk_tool_processor-0.6.4.dist-info/METADATA +0 -697
  64. chuk_tool_processor-0.6.4.dist-info/RECORD +0 -60
  65. {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/WHEEL +0 -0
  66. {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/top_level.txt +0 -0
@@ -1 +1,32 @@
1
- # chuk_tool_processor/core/__init__.py
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
+ ]
@@ -1,45 +1,257 @@
1
1
  # chuk_tool_processor/exceptions.py
2
- from typing import Optional, Any, Dict
2
+ from enum import Enum
3
+ from typing import Any
4
+
5
+
6
+ class ErrorCode(str, Enum):
7
+ """Machine-readable error codes for tool processor errors."""
8
+
9
+ # Tool registry errors
10
+ TOOL_NOT_FOUND = "TOOL_NOT_FOUND"
11
+ TOOL_REGISTRATION_FAILED = "TOOL_REGISTRATION_FAILED"
12
+
13
+ # Execution errors
14
+ TOOL_EXECUTION_FAILED = "TOOL_EXECUTION_FAILED"
15
+ TOOL_TIMEOUT = "TOOL_TIMEOUT"
16
+ TOOL_CANCELLED = "TOOL_CANCELLED"
17
+
18
+ # Validation errors
19
+ TOOL_VALIDATION_ERROR = "TOOL_VALIDATION_ERROR"
20
+ TOOL_ARGUMENT_ERROR = "TOOL_ARGUMENT_ERROR"
21
+ TOOL_RESULT_ERROR = "TOOL_RESULT_ERROR"
22
+
23
+ # Rate limiting and circuit breaker
24
+ TOOL_RATE_LIMITED = "TOOL_RATE_LIMITED"
25
+ TOOL_CIRCUIT_OPEN = "TOOL_CIRCUIT_OPEN"
26
+
27
+ # Parser errors
28
+ PARSER_ERROR = "PARSER_ERROR"
29
+ PARSER_INVALID_FORMAT = "PARSER_INVALID_FORMAT"
30
+
31
+ # MCP errors
32
+ MCP_CONNECTION_FAILED = "MCP_CONNECTION_FAILED"
33
+ MCP_TRANSPORT_ERROR = "MCP_TRANSPORT_ERROR"
34
+ MCP_SERVER_ERROR = "MCP_SERVER_ERROR"
35
+ MCP_TIMEOUT = "MCP_TIMEOUT"
36
+
37
+ # System errors
38
+ RESOURCE_EXHAUSTED = "RESOURCE_EXHAUSTED"
39
+ CONFIGURATION_ERROR = "CONFIGURATION_ERROR"
3
40
 
4
41
 
5
42
  class ToolProcessorError(Exception):
6
- """Base exception for all tool processor errors."""
7
- pass
43
+ """Base exception for all tool processor errors with machine-readable codes."""
44
+
45
+ def __init__(
46
+ self,
47
+ message: str,
48
+ code: ErrorCode | None = None,
49
+ details: dict[str, Any] | None = None,
50
+ original_error: Exception | None = None,
51
+ ):
52
+ super().__init__(message)
53
+ self.code = code or ErrorCode.TOOL_EXECUTION_FAILED
54
+ self.details = details or {}
55
+ self.original_error = original_error
56
+
57
+ def to_dict(self) -> dict[str, Any]:
58
+ """Convert exception to a structured dictionary for logging/monitoring."""
59
+ result = {
60
+ "error": self.__class__.__name__,
61
+ "code": self.code.value,
62
+ "message": str(self),
63
+ "details": self.details,
64
+ }
65
+ if self.original_error:
66
+ result["original_error"] = {
67
+ "type": type(self.original_error).__name__,
68
+ "message": str(self.original_error),
69
+ }
70
+ return result
8
71
 
9
72
 
10
73
  class ToolNotFoundError(ToolProcessorError):
11
74
  """Raised when a requested tool is not found in the registry."""
12
- def __init__(self, tool_name: str):
75
+
76
+ def __init__(self, tool_name: str, available_tools: list[str] | None = None):
13
77
  self.tool_name = tool_name
14
- super().__init__(f"Tool '{tool_name}' not found in registry")
78
+ details: dict[str, Any] = {"tool_name": tool_name}
79
+ if available_tools:
80
+ details["available_tools"] = available_tools
81
+ super().__init__(
82
+ f"Tool '{tool_name}' not found in registry",
83
+ code=ErrorCode.TOOL_NOT_FOUND,
84
+ details=details,
85
+ )
15
86
 
16
87
 
17
88
  class ToolExecutionError(ToolProcessorError):
18
89
  """Raised when a tool execution fails."""
19
- def __init__(self, tool_name: str, original_error: Optional[Exception] = None):
90
+
91
+ def __init__(
92
+ self,
93
+ tool_name: str,
94
+ original_error: Exception | None = None,
95
+ details: dict[str, Any] | None = None,
96
+ ):
20
97
  self.tool_name = tool_name
21
- self.original_error = original_error
22
98
  message = f"Tool '{tool_name}' execution failed"
23
99
  if original_error:
24
100
  message += f": {str(original_error)}"
25
- super().__init__(message)
101
+
102
+ error_details = {"tool_name": tool_name}
103
+ if details:
104
+ error_details.update(details)
105
+
106
+ super().__init__(
107
+ message,
108
+ code=ErrorCode.TOOL_EXECUTION_FAILED,
109
+ details=error_details,
110
+ original_error=original_error,
111
+ )
26
112
 
27
113
 
28
114
  class ToolTimeoutError(ToolExecutionError):
29
115
  """Raised when a tool execution times out."""
30
- def __init__(self, tool_name: str, timeout: float):
116
+
117
+ def __init__(self, tool_name: str, timeout: float, attempts: int = 1):
31
118
  self.timeout = timeout
32
- super().__init__(tool_name, Exception(f"Execution timed out after {timeout}s"))
119
+ self.attempts = attempts
120
+ # Call ToolProcessorError.__init__ directly to set the right code
121
+ ToolProcessorError.__init__(
122
+ self,
123
+ f"Tool '{tool_name}' timed out after {timeout}s (attempts: {attempts})",
124
+ code=ErrorCode.TOOL_TIMEOUT,
125
+ details={"tool_name": tool_name, "timeout": timeout, "attempts": attempts},
126
+ )
127
+ self.tool_name = tool_name
33
128
 
34
129
 
35
130
  class ToolValidationError(ToolProcessorError):
36
131
  """Raised when tool arguments or results fail validation."""
37
- def __init__(self, tool_name: str, errors: Dict[str, Any]):
132
+
133
+ def __init__(
134
+ self,
135
+ tool_name: str,
136
+ errors: dict[str, Any],
137
+ validation_type: str = "arguments",
138
+ ):
38
139
  self.tool_name = tool_name
39
140
  self.errors = errors
40
- super().__init__(f"Validation failed for tool '{tool_name}': {errors}")
141
+ self.validation_type = validation_type
142
+ super().__init__(
143
+ f"Validation failed for tool '{tool_name}' {validation_type}: {errors}",
144
+ code=ErrorCode.TOOL_VALIDATION_ERROR,
145
+ details={"tool_name": tool_name, "validation_type": validation_type, "errors": errors},
146
+ )
41
147
 
42
148
 
43
149
  class ParserError(ToolProcessorError):
44
150
  """Raised when parsing tool calls from raw input fails."""
45
- pass
151
+
152
+ def __init__(
153
+ self,
154
+ message: str,
155
+ parser_name: str | None = None,
156
+ input_sample: str | None = None,
157
+ ):
158
+ self.parser_name = parser_name
159
+ self.input_sample = input_sample
160
+ details = {}
161
+ if parser_name:
162
+ details["parser_name"] = parser_name
163
+ if input_sample:
164
+ # Truncate sample for logging
165
+ details["input_sample"] = input_sample[:200] + "..." if len(input_sample) > 200 else input_sample
166
+ super().__init__(
167
+ message,
168
+ code=ErrorCode.PARSER_ERROR,
169
+ details=details,
170
+ )
171
+
172
+
173
+ class ToolRateLimitedError(ToolProcessorError):
174
+ """Raised when a tool call is rate limited."""
175
+
176
+ def __init__(
177
+ self,
178
+ tool_name: str,
179
+ retry_after: float | None = None,
180
+ limit: int | None = None,
181
+ ):
182
+ self.tool_name = tool_name
183
+ self.retry_after = retry_after
184
+ self.limit = limit
185
+ message = f"Tool '{tool_name}' rate limited"
186
+ if retry_after:
187
+ message += f" (retry after {retry_after}s)"
188
+ super().__init__(
189
+ message,
190
+ code=ErrorCode.TOOL_RATE_LIMITED,
191
+ details={"tool_name": tool_name, "retry_after": retry_after, "limit": limit},
192
+ )
193
+
194
+
195
+ class ToolCircuitOpenError(ToolProcessorError):
196
+ """Raised when a tool circuit breaker is open."""
197
+
198
+ def __init__(
199
+ self,
200
+ tool_name: str,
201
+ failure_count: int,
202
+ reset_timeout: float | None = None,
203
+ ):
204
+ self.tool_name = tool_name
205
+ self.failure_count = failure_count
206
+ self.reset_timeout = reset_timeout
207
+ message = f"Tool '{tool_name}' circuit breaker is open (failures: {failure_count})"
208
+ if reset_timeout:
209
+ message += f" (reset in {reset_timeout}s)"
210
+ super().__init__(
211
+ message,
212
+ code=ErrorCode.TOOL_CIRCUIT_OPEN,
213
+ details={"tool_name": tool_name, "failure_count": failure_count, "reset_timeout": reset_timeout},
214
+ )
215
+
216
+
217
+ class MCPError(ToolProcessorError):
218
+ """Base class for MCP-related errors."""
219
+
220
+ def __init__(
221
+ self,
222
+ message: str,
223
+ code: ErrorCode,
224
+ server_name: str | None = None,
225
+ details: dict[str, Any] | None = None,
226
+ ):
227
+ error_details = details or {}
228
+ if server_name:
229
+ error_details["server_name"] = server_name
230
+ super().__init__(message, code=code, details=error_details)
231
+
232
+
233
+ class MCPConnectionError(MCPError):
234
+ """Raised when MCP connection fails."""
235
+
236
+ def __init__(self, server_name: str, reason: str | None = None):
237
+ message = f"Failed to connect to MCP server '{server_name}'"
238
+ if reason:
239
+ message += f": {reason}"
240
+ super().__init__(
241
+ message,
242
+ code=ErrorCode.MCP_CONNECTION_FAILED,
243
+ server_name=server_name,
244
+ details={"reason": reason} if reason else None,
245
+ )
246
+
247
+
248
+ class MCPTimeoutError(MCPError):
249
+ """Raised when MCP operation times out."""
250
+
251
+ def __init__(self, server_name: str, operation: str, timeout: float):
252
+ super().__init__(
253
+ f"MCP operation '{operation}' on server '{server_name}' timed out after {timeout}s",
254
+ code=ErrorCode.MCP_TIMEOUT,
255
+ server_name=server_name,
256
+ details={"operation": operation, "timeout": timeout},
257
+ )