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.

Files changed (32) hide show
  1. chuk_tool_processor/core/processor.py +1 -1
  2. chuk_tool_processor/execution/strategies/inprocess_strategy.py +110 -148
  3. chuk_tool_processor/execution/strategies/subprocess_strategy.py +1 -1
  4. chuk_tool_processor/logging/__init__.py +35 -0
  5. chuk_tool_processor/logging/context.py +47 -0
  6. chuk_tool_processor/logging/formatter.py +55 -0
  7. chuk_tool_processor/logging/helpers.py +112 -0
  8. chuk_tool_processor/logging/metrics.py +59 -0
  9. chuk_tool_processor/models/execution_strategy.py +1 -1
  10. chuk_tool_processor/models/tool_export_mixin.py +29 -0
  11. chuk_tool_processor/models/validated_tool.py +155 -0
  12. chuk_tool_processor/plugins/discovery.py +105 -172
  13. chuk_tool_processor/plugins/parsers/__init__.py +1 -1
  14. chuk_tool_processor/plugins/parsers/base.py +18 -0
  15. chuk_tool_processor/plugins/parsers/function_call_tool_plugin.py +81 -0
  16. chuk_tool_processor/plugins/parsers/json_tool_plugin.py +38 -0
  17. chuk_tool_processor/plugins/parsers/openai_tool_plugin.py +76 -0
  18. chuk_tool_processor/plugins/parsers/xml_tool.py +28 -24
  19. chuk_tool_processor/registry/__init__.py +11 -10
  20. chuk_tool_processor/registry/auto_register.py +125 -0
  21. chuk_tool_processor/registry/provider.py +84 -29
  22. chuk_tool_processor/registry/providers/memory.py +77 -112
  23. chuk_tool_processor/registry/tool_export.py +76 -0
  24. chuk_tool_processor/utils/validation.py +106 -177
  25. {chuk_tool_processor-0.1.0.dist-info → chuk_tool_processor-0.1.1.dist-info}/METADATA +5 -2
  26. chuk_tool_processor-0.1.1.dist-info/RECORD +47 -0
  27. chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -105
  28. chuk_tool_processor/plugins/parsers/json_tool.py +0 -17
  29. chuk_tool_processor/utils/logging.py +0 -260
  30. chuk_tool_processor-0.1.0.dist-info/RECORD +0 -37
  31. {chuk_tool_processor-0.1.0.dist-info → chuk_tool_processor-0.1.1.dist-info}/WHEEL +0 -0
  32. {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
- from typing import Any, Dict, Optional, Type, get_type_hints, Union, List, Callable
3
- from pydantic import BaseModel, ValidationError, create_model
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
- def validate_arguments(tool_name: str, tool_func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
11
- """
12
- Validate tool arguments against function signature.
13
-
14
- Args:
15
- tool_name: Name of the tool for error reporting.
16
- tool_func: Tool function to validate against.
17
- args: Arguments to validate.
18
-
19
- Returns:
20
- Validated arguments dict.
21
-
22
- Raises:
23
- ToolValidationError: If validation fails.
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
- # Get type hints from function
27
- type_hints = get_type_hints(tool_func)
28
-
29
- # Remove return type hint if present
30
- if 'return' in type_hints:
31
- type_hints.pop('return')
32
-
33
- # Create dynamic Pydantic model for validation
34
- field_definitions = {
35
- name: (type_hint, ...) for name, type_hint in type_hints.items()
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
- # Get return type hint
76
- type_hints = get_type_hints(tool_func)
77
- return_type = type_hints.get('return')
78
-
79
- if return_type is None:
80
- # No return type to validate against
81
- return result
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
- Class decorator to add type validation to tool classes.
102
-
103
- Example:
104
- @with_validation
105
- class MyTool:
106
- def execute(self, x: int, y: str) -> float:
107
- return float(x) + float(y)
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
- class ValidatedTool(BaseModel):
130
- """
131
- Base class for tools with built-in validation.
132
-
133
- Example:
134
- class AddTool(ValidatedTool):
135
- class Arguments(BaseModel):
136
- x: int
137
- y: int
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.0
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.utils.logging import get_logger, log_context_span
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 []