chuk-tool-processor 0.1.0__py3-none-any.whl → 0.1.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.
- chuk_tool_processor/core/processor.py +1 -1
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +110 -148
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +1 -1
- chuk_tool_processor/logging/__init__.py +35 -0
- chuk_tool_processor/logging/context.py +47 -0
- chuk_tool_processor/logging/formatter.py +55 -0
- chuk_tool_processor/logging/helpers.py +112 -0
- chuk_tool_processor/logging/metrics.py +59 -0
- chuk_tool_processor/models/execution_strategy.py +1 -1
- chuk_tool_processor/models/tool_export_mixin.py +29 -0
- chuk_tool_processor/models/validated_tool.py +155 -0
- chuk_tool_processor/plugins/discovery.py +105 -172
- chuk_tool_processor/plugins/parsers/__init__.py +1 -1
- chuk_tool_processor/plugins/parsers/base.py +18 -0
- chuk_tool_processor/plugins/parsers/function_call_tool_plugin.py +81 -0
- chuk_tool_processor/plugins/parsers/json_tool_plugin.py +38 -0
- chuk_tool_processor/plugins/parsers/openai_tool_plugin.py +76 -0
- chuk_tool_processor/plugins/parsers/xml_tool.py +28 -24
- chuk_tool_processor/registry/__init__.py +11 -10
- chuk_tool_processor/registry/auto_register.py +125 -0
- chuk_tool_processor/registry/provider.py +84 -29
- chuk_tool_processor/registry/providers/memory.py +77 -112
- chuk_tool_processor/registry/tool_export.py +76 -0
- chuk_tool_processor/utils/validation.py +106 -177
- {chuk_tool_processor-0.1.0.dist-info → chuk_tool_processor-0.1.1.dist-info}/METADATA +5 -2
- chuk_tool_processor-0.1.1.dist-info/RECORD +47 -0
- chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -105
- chuk_tool_processor/plugins/parsers/json_tool.py +0 -17
- chuk_tool_processor/utils/logging.py +0 -260
- chuk_tool_processor-0.1.0.dist-info/RECORD +0 -37
- {chuk_tool_processor-0.1.0.dist-info → chuk_tool_processor-0.1.1.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.1.0.dist-info → chuk_tool_processor-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# chuk_tool_processor/registry/tool_export.py
|
|
2
|
+
"""
|
|
3
|
+
Helpers that expose all registered tools in various formats and
|
|
4
|
+
translate an OpenAI `function.name` back to the matching tool.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
from .provider import ToolRegistryProvider
|
|
11
|
+
|
|
12
|
+
# --------------------------------------------------------------------------- #
|
|
13
|
+
# internal cache so tool-name lookup is O(1)
|
|
14
|
+
# --------------------------------------------------------------------------- #
|
|
15
|
+
_OPENAI_NAME_CACHE: dict[str, object] | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _build_openai_name_cache() -> None:
|
|
19
|
+
"""Populate the global reverse-lookup table once."""
|
|
20
|
+
global _OPENAI_NAME_CACHE
|
|
21
|
+
if _OPENAI_NAME_CACHE is not None: # already built
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
_OPENAI_NAME_CACHE = {}
|
|
25
|
+
reg = ToolRegistryProvider.get_registry()
|
|
26
|
+
|
|
27
|
+
for ns, key in reg.list_tools():
|
|
28
|
+
tool = reg.get_tool(key, ns)
|
|
29
|
+
|
|
30
|
+
# ▸ registry key -> tool
|
|
31
|
+
_OPENAI_NAME_CACHE[key] = tool
|
|
32
|
+
|
|
33
|
+
# ▸ class name -> tool (legacy)
|
|
34
|
+
_OPENAI_NAME_CACHE[tool.__class__.__name__] = tool
|
|
35
|
+
|
|
36
|
+
# ▸ OpenAI name -> tool (may differ from both above)
|
|
37
|
+
_OPENAI_NAME_CACHE[tool.to_openai()["function"]["name"]] = tool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------- #
|
|
41
|
+
# public helpers
|
|
42
|
+
# --------------------------------------------------------------------------- #
|
|
43
|
+
def openai_functions() -> List[Dict]:
|
|
44
|
+
"""
|
|
45
|
+
Return **all** registered tools in the exact schema the Chat-Completions
|
|
46
|
+
API expects in its ``tools=[ … ]`` parameter.
|
|
47
|
+
|
|
48
|
+
The ``function.name`` is always the *registry key* so that the round-trip
|
|
49
|
+
(export → model → parser) stays consistent even when the class name and
|
|
50
|
+
the registered key differ.
|
|
51
|
+
"""
|
|
52
|
+
reg = ToolRegistryProvider.get_registry()
|
|
53
|
+
specs: list[dict] = []
|
|
54
|
+
|
|
55
|
+
for ns, key in reg.list_tools():
|
|
56
|
+
tool = reg.get_tool(key, ns)
|
|
57
|
+
spec = tool.to_openai()
|
|
58
|
+
spec["function"]["name"] = key # ensure round-trip consistency
|
|
59
|
+
specs.append(spec)
|
|
60
|
+
|
|
61
|
+
# Ensure the cache is built the first time we export
|
|
62
|
+
_build_openai_name_cache()
|
|
63
|
+
return specs
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def tool_by_openai_name(name: str):
|
|
67
|
+
"""
|
|
68
|
+
Map an OpenAI ``function.name`` back to the registered tool.
|
|
69
|
+
|
|
70
|
+
Raises ``KeyError`` if the name is unknown.
|
|
71
|
+
"""
|
|
72
|
+
_build_openai_name_cache()
|
|
73
|
+
try:
|
|
74
|
+
return _OPENAI_NAME_CACHE[name] # type: ignore[index]
|
|
75
|
+
except (KeyError, TypeError):
|
|
76
|
+
raise KeyError(f"No tool registered for OpenAI name {name!r}") from None
|
|
@@ -1,192 +1,121 @@
|
|
|
1
1
|
# chuk_tool_processor/utils/validation.py
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
"""
|
|
3
|
+
Runtime helpers for validating tool inputs / outputs with Pydantic.
|
|
4
|
+
|
|
5
|
+
Public API
|
|
6
|
+
----------
|
|
7
|
+
validate_arguments(tool_name, fn, args) -> dict
|
|
8
|
+
validate_result(tool_name, fn, result) -> Any
|
|
9
|
+
@with_validation -> class decorator
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
4
12
|
import inspect
|
|
5
|
-
from functools import wraps
|
|
13
|
+
from functools import lru_cache, wraps
|
|
14
|
+
from typing import Any, Callable, Dict, get_type_hints
|
|
15
|
+
from pydantic import BaseModel, ValidationError, create_model, Extra
|
|
6
16
|
|
|
17
|
+
# excpetion
|
|
7
18
|
from chuk_tool_processor.core.exceptions import ToolValidationError
|
|
8
19
|
|
|
20
|
+
__all__ = [
|
|
21
|
+
"validate_arguments",
|
|
22
|
+
"validate_result",
|
|
23
|
+
"with_validation",
|
|
24
|
+
]
|
|
9
25
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
# --------------------------------------------------------------------------- #
|
|
27
|
+
# helpers – create & cache ad-hoc pydantic models
|
|
28
|
+
# --------------------------------------------------------------------------- #
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@lru_cache(maxsize=256)
|
|
32
|
+
def _arg_model(tool_name: str, fn: Callable) -> type[BaseModel]:
|
|
33
|
+
"""Return (and memoise) a pydantic model derived from *fn*'s signature."""
|
|
34
|
+
hints = get_type_hints(fn)
|
|
35
|
+
hints.pop("return", None)
|
|
36
|
+
|
|
37
|
+
sig = inspect.signature(fn)
|
|
38
|
+
fields: Dict[str, tuple[Any, Any]] = {}
|
|
39
|
+
for name, hint in hints.items():
|
|
40
|
+
param = sig.parameters[name]
|
|
41
|
+
default = param.default if param.default is not inspect.Parameter.empty else ...
|
|
42
|
+
fields[name] = (hint, default)
|
|
43
|
+
|
|
44
|
+
return create_model(
|
|
45
|
+
f"{tool_name}Args",
|
|
46
|
+
__config__=type(
|
|
47
|
+
"Cfg",
|
|
48
|
+
(),
|
|
49
|
+
{"extra": Extra.forbid}, # disallow unknown keys
|
|
50
|
+
),
|
|
51
|
+
**fields,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@lru_cache(maxsize=256)
|
|
56
|
+
def _result_model(tool_name: str, fn: Callable) -> type[BaseModel] | None:
|
|
57
|
+
"""Return a pydantic model for the annotated return type (or None)."""
|
|
58
|
+
return_hint = get_type_hints(fn).get("return")
|
|
59
|
+
if return_hint is None or return_hint is type(None): # noqa: E721
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
return create_model(
|
|
63
|
+
f"{tool_name}Result",
|
|
64
|
+
result=(return_hint, ...),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# --------------------------------------------------------------------------- #
|
|
69
|
+
# public validation helpers
|
|
70
|
+
# --------------------------------------------------------------------------- #
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_arguments(tool_name: str, fn: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
25
74
|
try:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# Add optional fields based on default values
|
|
39
|
-
sig = inspect.signature(tool_func)
|
|
40
|
-
for param_name, param in sig.parameters.items():
|
|
41
|
-
if param.default is not inspect.Parameter.empty:
|
|
42
|
-
if param_name in field_definitions:
|
|
43
|
-
field_type, _ = field_definitions[param_name]
|
|
44
|
-
field_definitions[param_name] = (field_type, param.default)
|
|
45
|
-
|
|
46
|
-
# Create model
|
|
47
|
-
model = create_model(f"{tool_name}Args", **field_definitions)
|
|
48
|
-
|
|
49
|
-
# Validate args
|
|
50
|
-
validated = model(**args)
|
|
51
|
-
return validated.dict()
|
|
52
|
-
|
|
53
|
-
except ValidationError as e:
|
|
54
|
-
raise ToolValidationError(tool_name, e.errors())
|
|
55
|
-
except Exception as e:
|
|
56
|
-
raise ToolValidationError(tool_name, {"general": str(e)})
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def validate_result(tool_name: str, tool_func: Callable, result: Any) -> Any:
|
|
60
|
-
"""
|
|
61
|
-
Validate tool result against function return type.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
tool_name: Name of the tool for error reporting.
|
|
65
|
-
tool_func: Tool function to validate against.
|
|
66
|
-
result: Result to validate.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Validated result.
|
|
70
|
-
|
|
71
|
-
Raises:
|
|
72
|
-
ToolValidationError: If validation fails.
|
|
73
|
-
"""
|
|
75
|
+
model = _arg_model(tool_name, fn)
|
|
76
|
+
return model(**args).dict()
|
|
77
|
+
except ValidationError as exc:
|
|
78
|
+
raise ToolValidationError(tool_name, exc.errors()) from exc
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_result(tool_name: str, fn: Callable, result: Any) -> Any:
|
|
82
|
+
model = _result_model(tool_name, fn)
|
|
83
|
+
if model is None: # no annotation ⇒ no validation
|
|
84
|
+
return result
|
|
74
85
|
try:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Create dynamic Pydantic model for validation
|
|
84
|
-
model = create_model(
|
|
85
|
-
f"{tool_name}Result",
|
|
86
|
-
result=(return_type, ...)
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# Validate result
|
|
90
|
-
validated = model(result=result)
|
|
91
|
-
return validated.result
|
|
92
|
-
|
|
93
|
-
except ValidationError as e:
|
|
94
|
-
raise ToolValidationError(tool_name, e.errors())
|
|
95
|
-
except Exception as e:
|
|
96
|
-
raise ToolValidationError(tool_name, {"general": str(e)})
|
|
86
|
+
return model(result=result).result
|
|
87
|
+
except ValidationError as exc:
|
|
88
|
+
raise ToolValidationError(tool_name, exc.errors()) from exc
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --------------------------------------------------------------------------- #
|
|
92
|
+
# decorator for classic “imperative” tools
|
|
93
|
+
# --------------------------------------------------------------------------- #
|
|
97
94
|
|
|
98
95
|
|
|
99
96
|
def with_validation(cls):
|
|
100
97
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
Wrap *execute* / *_execute* so that their arguments & return values
|
|
99
|
+
are type-checked each call.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
@with_validation
|
|
103
|
+
class MyTool:
|
|
104
|
+
def execute(self, x: int, y: int) -> int:
|
|
105
|
+
return x + y
|
|
106
|
+
```
|
|
108
107
|
"""
|
|
109
|
-
original_execute = cls.execute
|
|
110
|
-
|
|
111
|
-
@wraps(original_execute)
|
|
112
|
-
def execute_with_validation(self, **kwargs):
|
|
113
|
-
# Get tool name
|
|
114
|
-
tool_name = getattr(cls, "__name__", repr(cls))
|
|
115
|
-
|
|
116
|
-
# Validate arguments
|
|
117
|
-
validated_args = validate_arguments(tool_name, original_execute, kwargs)
|
|
118
|
-
|
|
119
|
-
# Execute the tool
|
|
120
|
-
result = original_execute(self, **validated_args)
|
|
121
|
-
|
|
122
|
-
# Validate result
|
|
123
|
-
return validate_result(tool_name, original_execute, result)
|
|
124
|
-
|
|
125
|
-
cls.execute = execute_with_validation
|
|
126
|
-
return cls
|
|
127
108
|
|
|
109
|
+
# Which method did the user provide?
|
|
110
|
+
fn_name = "_execute" if hasattr(cls, "_execute") else "execute"
|
|
111
|
+
original = getattr(cls, fn_name)
|
|
128
112
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class Result(BaseModel):
|
|
140
|
-
sum: int
|
|
141
|
-
|
|
142
|
-
def execute(self, x: int, y: int) -> Result:
|
|
143
|
-
return self.Result(sum=x + y)
|
|
144
|
-
"""
|
|
145
|
-
class Arguments(BaseModel):
|
|
146
|
-
"""Base arguments model to be overridden by subclasses."""
|
|
147
|
-
pass
|
|
148
|
-
|
|
149
|
-
class Result(BaseModel):
|
|
150
|
-
"""Base result model to be overridden by subclasses."""
|
|
151
|
-
pass
|
|
152
|
-
|
|
153
|
-
def execute(self, **kwargs) -> Any:
|
|
154
|
-
"""
|
|
155
|
-
Execute the tool with validated arguments.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
**kwargs: Arguments to validate against Arguments model.
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
Validated result according to Result model.
|
|
162
|
-
|
|
163
|
-
Raises:
|
|
164
|
-
ToolValidationError: If validation fails.
|
|
165
|
-
"""
|
|
166
|
-
try:
|
|
167
|
-
# Validate arguments
|
|
168
|
-
validated_args = self.Arguments(**kwargs)
|
|
169
|
-
|
|
170
|
-
# Execute implementation
|
|
171
|
-
result = self._execute(**validated_args.dict())
|
|
172
|
-
|
|
173
|
-
# Validate result if it's not already a Result instance
|
|
174
|
-
if not isinstance(result, self.Result):
|
|
175
|
-
result = self.Result(**result if isinstance(result, dict) else {"value": result})
|
|
176
|
-
|
|
177
|
-
return result
|
|
178
|
-
|
|
179
|
-
except ValidationError as e:
|
|
180
|
-
raise ToolValidationError(self.__class__.__name__, e.errors())
|
|
181
|
-
|
|
182
|
-
def _execute(self, **kwargs) -> Any:
|
|
183
|
-
"""
|
|
184
|
-
Implementation method to be overridden by subclasses.
|
|
185
|
-
|
|
186
|
-
Args:
|
|
187
|
-
**kwargs: Validated arguments.
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
Result that will be validated against Result model.
|
|
191
|
-
"""
|
|
192
|
-
raise NotImplementedError("Subclasses must implement _execute")
|
|
113
|
+
@wraps(original)
|
|
114
|
+
def _validated(self, **kwargs):
|
|
115
|
+
name = cls.__name__
|
|
116
|
+
kwargs = validate_arguments(name, original, kwargs)
|
|
117
|
+
res = original(self, **kwargs)
|
|
118
|
+
return validate_result(name, original, res)
|
|
119
|
+
|
|
120
|
+
setattr(cls, fn_name, _validated)
|
|
121
|
+
return cls
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: dotenv>=0.9.9
|
|
8
|
+
Requires-Dist: openai>=1.76.0
|
|
7
9
|
Requires-Dist: pydantic>=2.11.3
|
|
10
|
+
Requires-Dist: uuid>=1.30
|
|
8
11
|
|
|
9
12
|
# CHUK Tool Processor
|
|
10
13
|
|
|
@@ -222,7 +225,7 @@ plugin_registry.register_plugin("parser", "BracketToolParser", BracketToolParser
|
|
|
222
225
|
### Structured Logging
|
|
223
226
|
|
|
224
227
|
```python
|
|
225
|
-
from chuk_tool_processor.
|
|
228
|
+
from chuk_tool_processor.logging import get_logger, log_context_span
|
|
226
229
|
|
|
227
230
|
logger = get_logger("my_module")
|
|
228
231
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
chuk_tool_processor/__init__.py,sha256=a4pDi8hta4hjhijLGcv3vlWL8iu3F9EynkmB3si7-hg,33
|
|
2
|
+
chuk_tool_processor/core/__init__.py,sha256=slM7pZna88tyZrF3KtN22ApYyCqGNt5Yscv-knsLOOA,38
|
|
3
|
+
chuk_tool_processor/core/exceptions.py,sha256=h4zL1jpCY1Ud1wT8xDeMxZ8GR8ttmkObcv36peUHJEA,1571
|
|
4
|
+
chuk_tool_processor/core/processor.py,sha256=RbHNFYpzn7hhjBXwqPOdRvTGWky4z8tT7k672nwRE0s,10158
|
|
5
|
+
chuk_tool_processor/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
chuk_tool_processor/execution/tool_executor.py,sha256=e1EHE-744uJuB1XeZZF_6VT25Yg1RCd8XI3v8uOrOSo,1794
|
|
7
|
+
chuk_tool_processor/execution/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
chuk_tool_processor/execution/strategies/inprocess_strategy.py,sha256=zybG8PzIf7VbDfQfMg2TJ67-zxbArt1eEjczRLzZfrQ,5957
|
|
9
|
+
chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=Er8z7x94E7HmhFr9LtOvSBifL1eJ4l-12XampzkjBjU,3786
|
|
10
|
+
chuk_tool_processor/execution/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
chuk_tool_processor/execution/wrappers/caching.py,sha256=dA2OULPQ9xCZj-r3ev5LtsCDFDPgoz8tr70YCX5A4Wg,7714
|
|
12
|
+
chuk_tool_processor/execution/wrappers/rate_limiting.py,sha256=pFqD1vLzOtJzsWzpEI7J786gOAbdFY0gVeiO7ElBXbA,4991
|
|
13
|
+
chuk_tool_processor/execution/wrappers/retry.py,sha256=tRIuT5iNAYUY3r9y3shouWCxJZB5VONkBC9qPBaaVdc,6386
|
|
14
|
+
chuk_tool_processor/logging/__init__.py,sha256=YslBwMTCgxboYwnT4w5qX0_BnA0MR62eI8NH9le9G38,1057
|
|
15
|
+
chuk_tool_processor/logging/context.py,sha256=hQFWGeraHX3DM28JDSiIuhQqep6TBfo1uaLlRRlGMVU,1521
|
|
16
|
+
chuk_tool_processor/logging/formatter.py,sha256=4pO-fLULkD3JPLjZOSiOZPGsjV3c4Ztr5ySda1RAvi4,1754
|
|
17
|
+
chuk_tool_processor/logging/helpers.py,sha256=Fk32BuecWZdyrmv8U0lh4W0AdNx4-gKcCaWBdId_rlI,3569
|
|
18
|
+
chuk_tool_processor/logging/metrics.py,sha256=ti_owuslT-x9cjcbP-_j7jivrlyY-Vb41mVhU-6W-2M,1537
|
|
19
|
+
chuk_tool_processor/models/__init__.py,sha256=TC__rdVa0lQsmJHM_hbLDPRgToa_pQT_UxRcPZk6iVw,40
|
|
20
|
+
chuk_tool_processor/models/execution_strategy.py,sha256=ZPHysmKNHqJmahTtUXAbt1ke09vxy7EhZcsrwTdla8o,508
|
|
21
|
+
chuk_tool_processor/models/tool_call.py,sha256=RZOnx2YczkJN6ym2PLiI4CRzP2qU_5hpMtHxMcFOxY4,298
|
|
22
|
+
chuk_tool_processor/models/tool_export_mixin.py,sha256=U9NJvn9fqH3pW50ozdDAHlA0fQSnjUt-lYhEoW_leZU,998
|
|
23
|
+
chuk_tool_processor/models/tool_result.py,sha256=fqHuRC8bLek1PAwyhLOoKjRCLhpSm-mhDgLpsItjZ60,1532
|
|
24
|
+
chuk_tool_processor/models/validated_tool.py,sha256=REXLfKYNpdvm7hH4tYUZq1-TxiD0784iZUffrucGN_o,5714
|
|
25
|
+
chuk_tool_processor/plugins/__init__.py,sha256=QO_ipvlsWG-rbaqGzj6-YtD7zi7Lx26hw-Cqha4MuWc,48
|
|
26
|
+
chuk_tool_processor/plugins/discovery.py,sha256=pyUQk18c9mNoruoa2eY8T-FhkpomPllnWiyRDVCaJNM,5299
|
|
27
|
+
chuk_tool_processor/plugins/parsers/__init__.py,sha256=07waNfAGuytn-Dntx2IxjhgSkM9F40TBYNUXC8G4VVo,49
|
|
28
|
+
chuk_tool_processor/plugins/parsers/base.py,sha256=VwN1sT03L5Ar37koDgT9El0MRdIJCwwNJMbN76GKB5E,564
|
|
29
|
+
chuk_tool_processor/plugins/parsers/function_call_tool_plugin.py,sha256=rZt-fJ_dhVkRFR1OU86yZNq1R9AFvAiwjk6I3Hktc-A,2706
|
|
30
|
+
chuk_tool_processor/plugins/parsers/json_tool_plugin.py,sha256=wllYEqCzgxmCYdcugelWYFNHTjMYgDsghI-cbh8Qudk,1050
|
|
31
|
+
chuk_tool_processor/plugins/parsers/openai_tool_plugin.py,sha256=IyL6ZE03disNqkRYiyQZqJRV-vk99mLCZoKeLTwNYjQ,2347
|
|
32
|
+
chuk_tool_processor/plugins/parsers/xml_tool.py,sha256=rWdm25TwuT8S7l7GrYOvd-gFiVr6oss5ncjhQZGQxqA,1304
|
|
33
|
+
chuk_tool_processor/registry/__init__.py,sha256=kjNEM5dM7kEucfDjvgnww-QLrBrCzuDAuj9OBTizm5I,782
|
|
34
|
+
chuk_tool_processor/registry/auto_register.py,sha256=moHYkfWsSUOs7uu7MbZj2v-ujUHPbGD4nXsQZ5Dk1hM,5119
|
|
35
|
+
chuk_tool_processor/registry/decorators.py,sha256=WecmzWK2RliMO0xmWEifj7xBqACoGfm2rz1hxcgtkrI,1346
|
|
36
|
+
chuk_tool_processor/registry/interface.py,sha256=40KFbMKzjnwf6iki9aeWBx5c3Dq3Tadr78y-Ko-IYsM,2299
|
|
37
|
+
chuk_tool_processor/registry/metadata.py,sha256=zoSv8QnncqMtvEo7nj_pGKKQohw6maBVDiInXBoNaxY,1744
|
|
38
|
+
chuk_tool_processor/registry/provider.py,sha256=EG1K28nfRddq18UDrymKse_bMt3PAhLcQ3TRaG89Tq8,3869
|
|
39
|
+
chuk_tool_processor/registry/tool_export.py,sha256=msGOszF9NtJEb4u0XhA0tz6EwkL3Uv_R7QAz0p2eJM8,2543
|
|
40
|
+
chuk_tool_processor/registry/providers/__init__.py,sha256=_0dg4YhyfAV0TXuR_i4ewXPU8fY7odFd1RJWCmHIXmk,1326
|
|
41
|
+
chuk_tool_processor/registry/providers/memory.py,sha256=29aI5uvykjDmn9ymIukEdUtmTC9SXOAsDu9hw36XF44,4474
|
|
42
|
+
chuk_tool_processor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
chuk_tool_processor/utils/validation.py,sha256=7ezn_o-3IHDrzOD3j6ttsAn2s3zS-jIjeBTuqicrs6A,3775
|
|
44
|
+
chuk_tool_processor-0.1.1.dist-info/METADATA,sha256=roW8za1Cw1IKO3ZkzzHYVFzAeFZM2-b8CWKCCwa6Ag0,9300
|
|
45
|
+
chuk_tool_processor-0.1.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
46
|
+
chuk_tool_processor-0.1.1.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
|
|
47
|
+
chuk_tool_processor-0.1.1.dist-info/RECORD,,
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# chuk_tool_processor/plugins/function_call_tool.py
|
|
2
|
-
import json
|
|
3
|
-
import re
|
|
4
|
-
from typing import List, Any, Dict
|
|
5
|
-
from pydantic import ValidationError
|
|
6
|
-
|
|
7
|
-
# imports
|
|
8
|
-
from chuk_tool_processor.models.tool_call import ToolCall
|
|
9
|
-
from chuk_tool_processor.utils.logging import get_logger
|
|
10
|
-
|
|
11
|
-
# logger
|
|
12
|
-
logger = get_logger("chuk_tool_processor.plugins.function_call_tool")
|
|
13
|
-
|
|
14
|
-
class FunctionCallPlugin:
|
|
15
|
-
"""
|
|
16
|
-
Parse OpenAI-style `function_call` payloads embedded in the LLM response.
|
|
17
|
-
|
|
18
|
-
Supports two formats:
|
|
19
|
-
1. JSON object with function_call field:
|
|
20
|
-
{
|
|
21
|
-
"function_call": {
|
|
22
|
-
"name": "my_tool",
|
|
23
|
-
"arguments": '{"x":1,"y":"two"}'
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
2. JSON object with function_call field and already parsed arguments:
|
|
28
|
-
{
|
|
29
|
-
"function_call": {
|
|
30
|
-
"name": "my_tool",
|
|
31
|
-
"arguments": {"x":1, "y":"two"}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
"""
|
|
35
|
-
def try_parse(self, raw: str) -> List[ToolCall]:
|
|
36
|
-
calls: List[ToolCall] = []
|
|
37
|
-
|
|
38
|
-
# First, try to parse as a complete JSON object
|
|
39
|
-
try:
|
|
40
|
-
payload = json.loads(raw)
|
|
41
|
-
|
|
42
|
-
# Check if this is a function call payload
|
|
43
|
-
if isinstance(payload, dict) and "function_call" in payload:
|
|
44
|
-
fc = payload.get("function_call")
|
|
45
|
-
if not isinstance(fc, dict):
|
|
46
|
-
return []
|
|
47
|
-
|
|
48
|
-
name = fc.get("name")
|
|
49
|
-
args = fc.get("arguments", {})
|
|
50
|
-
|
|
51
|
-
# Arguments sometimes come back as a JSON-encoded string
|
|
52
|
-
if isinstance(args, str):
|
|
53
|
-
try:
|
|
54
|
-
args = json.loads(args)
|
|
55
|
-
except json.JSONDecodeError:
|
|
56
|
-
# Leave as empty dict if malformed but still create the call
|
|
57
|
-
args = {}
|
|
58
|
-
|
|
59
|
-
# Only proceed if we have a valid name
|
|
60
|
-
if not isinstance(name, str) or not name:
|
|
61
|
-
return []
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
call = ToolCall(tool=name, arguments=args if isinstance(args, Dict) else {})
|
|
65
|
-
calls.append(call)
|
|
66
|
-
logger.debug(f"Found function call to {name}")
|
|
67
|
-
except ValidationError:
|
|
68
|
-
# invalid tool name or args shape
|
|
69
|
-
logger.warning(f"Invalid function call: {name}")
|
|
70
|
-
|
|
71
|
-
# Look for nested function calls
|
|
72
|
-
if not calls:
|
|
73
|
-
# Try to find function calls in nested objects
|
|
74
|
-
json_str = json.dumps(payload)
|
|
75
|
-
json_pattern = r'\{(?:[^{}]|(?:\{[^{}]*\}))*\}'
|
|
76
|
-
matches = re.finditer(json_pattern, json_str)
|
|
77
|
-
|
|
78
|
-
for match in matches:
|
|
79
|
-
# Skip if it's the complete string we already parsed
|
|
80
|
-
json_substr = match.group(0)
|
|
81
|
-
if json_substr == json_str:
|
|
82
|
-
continue
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
nested_payload = json.loads(json_substr)
|
|
86
|
-
if isinstance(nested_payload, dict) and "function_call" in nested_payload:
|
|
87
|
-
nested_calls = self.try_parse(json_substr)
|
|
88
|
-
calls.extend(nested_calls)
|
|
89
|
-
except json.JSONDecodeError:
|
|
90
|
-
continue
|
|
91
|
-
|
|
92
|
-
except json.JSONDecodeError:
|
|
93
|
-
# If it's not valid JSON, try to extract function calls using regex
|
|
94
|
-
json_pattern = r'\{(?:[^{}]|(?:\{[^{}]*\}))*\}'
|
|
95
|
-
matches = re.finditer(json_pattern, raw)
|
|
96
|
-
|
|
97
|
-
for match in matches:
|
|
98
|
-
json_str = match.group(0)
|
|
99
|
-
try:
|
|
100
|
-
nested_calls = self.try_parse(json_str)
|
|
101
|
-
calls.extend(nested_calls)
|
|
102
|
-
except Exception:
|
|
103
|
-
continue
|
|
104
|
-
|
|
105
|
-
return calls
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# chuk_tool_processor/plugins/json_tool.py
|
|
2
|
-
import json
|
|
3
|
-
from typing import List
|
|
4
|
-
from pydantic import ValidationError
|
|
5
|
-
|
|
6
|
-
# tool processor
|
|
7
|
-
from chuk_tool_processor.models.tool_call import ToolCall
|
|
8
|
-
|
|
9
|
-
class JsonToolPlugin:
|
|
10
|
-
"""Parse JSON-encoded `tool_calls` field."""
|
|
11
|
-
def try_parse(self, raw: str) -> List[ToolCall]:
|
|
12
|
-
try:
|
|
13
|
-
data = json.loads(raw)
|
|
14
|
-
calls = data.get('tool_calls', []) if isinstance(data, dict) else []
|
|
15
|
-
return [ToolCall(**c) for c in calls]
|
|
16
|
-
except (json.JSONDecodeError, ValidationError):
|
|
17
|
-
return []
|