wizelit-sdk 0.1.26__py3-none-any.whl → 0.1.28__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.
- wizelit_sdk/agent_wrapper/__init__.py +4 -1
- wizelit_sdk/agent_wrapper/agent_wrapper.py +40 -27
- wizelit_sdk/agent_wrapper/signature_validation.py +118 -0
- wizelit_sdk/database.py +5 -5
- {wizelit_sdk-0.1.26.dist-info → wizelit_sdk-0.1.28.dist-info}/METADATA +2 -1
- wizelit_sdk-0.1.28.dist-info/RECORD +13 -0
- wizelit_sdk-0.1.26.dist-info/RECORD +0 -12
- {wizelit_sdk-0.1.26.dist-info → wizelit_sdk-0.1.28.dist-info}/WHEEL +0 -0
|
@@ -5,4 +5,7 @@ from .agent_wrapper import WizelitAgent
|
|
|
5
5
|
from .job import Job
|
|
6
6
|
from .streaming import LogStreamer
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# Backward-compatible alias used by sample MCP servers
|
|
9
|
+
WizelitAgentWrapper = WizelitAgent
|
|
10
|
+
|
|
11
|
+
__all__ = ["WizelitAgent", "WizelitAgentWrapper", "Job", "LogStreamer"]
|
|
@@ -8,6 +8,11 @@ from contextvars import ContextVar
|
|
|
8
8
|
from fastmcp import FastMCP, Context
|
|
9
9
|
from fastmcp.dependencies import CurrentContext
|
|
10
10
|
from wizelit_sdk.agent_wrapper.job import Job
|
|
11
|
+
from wizelit_sdk.agent_wrapper.signature_validation import (
|
|
12
|
+
SignatureValidationError,
|
|
13
|
+
bind_and_validate_arguments,
|
|
14
|
+
ensure_type_hints,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
17
|
if TYPE_CHECKING:
|
|
13
18
|
from wizelit_sdk.database import DatabaseManager
|
|
@@ -191,6 +196,35 @@ class WizelitAgent:
|
|
|
191
196
|
|
|
192
197
|
new_sig = sig.replace(parameters=params_list)
|
|
193
198
|
|
|
199
|
+
# Exclude dependency-injected params from validation/schema
|
|
200
|
+
exclude_args = ["ctx"]
|
|
201
|
+
if has_job_param:
|
|
202
|
+
exclude_args.append("job")
|
|
203
|
+
|
|
204
|
+
# Validate that the user function has explicit type hints
|
|
205
|
+
ensure_type_hints(func, exclude_params=exclude_args)
|
|
206
|
+
|
|
207
|
+
# Validate response_handling schema early to catch drift
|
|
208
|
+
if response_handling is not None:
|
|
209
|
+
allowed_keys = {"mode", "extract_path", "template", "content_type"}
|
|
210
|
+
unknown_keys = set(response_handling.keys()) - allowed_keys
|
|
211
|
+
if unknown_keys:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"response_handling for {tool_name} has unsupported keys: {sorted(unknown_keys)}"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
mode = response_handling.get("mode", "default")
|
|
217
|
+
if mode not in {"direct", "formatted", "default"}:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"response_handling.mode for {tool_name} must be one of 'direct', 'formatted', 'default'"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
content_type = response_handling.get("content_type", "text")
|
|
223
|
+
if content_type not in {"text", "json", "auto"}:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"response_handling.content_type for {tool_name} must be one of 'text', 'json', 'auto'"
|
|
226
|
+
)
|
|
227
|
+
|
|
194
228
|
# Create the wrapper function
|
|
195
229
|
async def tool_wrapper(*args, **kwargs):
|
|
196
230
|
"""MCP-compliant wrapper with streaming."""
|
|
@@ -209,31 +243,14 @@ class WizelitAgent:
|
|
|
209
243
|
job = job()
|
|
210
244
|
# If job is still None, _execute_tool will create it
|
|
211
245
|
|
|
212
|
-
# Bind all arguments (including positional) to the original function signature
|
|
213
|
-
# This ensures parameters are correctly passed even if fast-mcp uses positional args
|
|
214
|
-
# Create a signature without 'job' since we've already extracted it
|
|
215
|
-
func_sig = inspect.signature(func)
|
|
216
|
-
if has_job_param and "job" in func_sig.parameters:
|
|
217
|
-
# Remove 'job' from signature for binding since we handle it separately
|
|
218
|
-
params_without_job = {
|
|
219
|
-
name: param
|
|
220
|
-
for name, param in func_sig.parameters.items()
|
|
221
|
-
if name != "job"
|
|
222
|
-
}
|
|
223
|
-
func_sig = func_sig.replace(
|
|
224
|
-
parameters=list(params_without_job.values())
|
|
225
|
-
)
|
|
226
|
-
|
|
227
246
|
try:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
func_kwargs = bound_args.arguments
|
|
231
|
-
except TypeError as e:
|
|
232
|
-
# Fallback: if binding fails, use kwargs as-is (shouldn't happen normally)
|
|
233
|
-
logging.warning(
|
|
234
|
-
f"Failed to bind arguments for {tool_name}: {e}. Args: {args}, Kwargs: {kwargs}"
|
|
247
|
+
func_kwargs = bind_and_validate_arguments(
|
|
248
|
+
func, args, kwargs, exclude_params=exclude_args
|
|
235
249
|
)
|
|
236
|
-
|
|
250
|
+
except SignatureValidationError as exc:
|
|
251
|
+
raise ValueError(
|
|
252
|
+
f"Argument validation failed for {tool_name}: {exc}"
|
|
253
|
+
) from exc
|
|
237
254
|
|
|
238
255
|
return await self._execute_tool(
|
|
239
256
|
func, ctx, is_async, is_long_running, tool_name, job, **func_kwargs
|
|
@@ -258,10 +275,6 @@ class WizelitAgent:
|
|
|
258
275
|
|
|
259
276
|
# Register with fast-mcp
|
|
260
277
|
# Exclude ctx and job from schema generation since they're dependency-injected
|
|
261
|
-
exclude_args = ["ctx"]
|
|
262
|
-
if has_job_param:
|
|
263
|
-
exclude_args.append("job")
|
|
264
|
-
|
|
265
278
|
# Prepare tool kwargs
|
|
266
279
|
tool_kwargs = {
|
|
267
280
|
"description": tool_description,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Common helpers for function signature validation.
|
|
2
|
+
|
|
3
|
+
These helpers are used by the Wizelit MCP wrappers to enforce that tool
|
|
4
|
+
functions declare explicit type hints and that incoming arguments match the
|
|
5
|
+
expected signature at runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import inspect
|
|
11
|
+
from typing import Any, Dict, Iterable, Mapping, Sequence, get_type_hints
|
|
12
|
+
|
|
13
|
+
from typeguard import check_type
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SignatureValidationError(TypeError):
|
|
17
|
+
"""Raised when a function signature or call arguments are invalid."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _clean_excluded(exclude: Iterable[str] | None) -> set[str]:
|
|
21
|
+
return {name for name in (exclude or [])}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ensure_type_hints(
|
|
25
|
+
func: Any, *, exclude_params: Iterable[str] | None = None
|
|
26
|
+
) -> Dict[str, Any]:
|
|
27
|
+
"""Ensure a function has type hints for all non-excluded parameters.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
func: The target function.
|
|
31
|
+
exclude_params: Parameter names to ignore (e.g., dependency-injected params).
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
The resolved type hints for the function.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
SignatureValidationError: If any non-excluded parameter lacks a type hint.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
exclude = _clean_excluded(exclude_params)
|
|
41
|
+
hints = get_type_hints(func, include_extras=True)
|
|
42
|
+
missing = [
|
|
43
|
+
name
|
|
44
|
+
for name, param in inspect.signature(func).parameters.items()
|
|
45
|
+
if name not in exclude and param.kind != inspect.Parameter.VAR_KEYWORD
|
|
46
|
+
and param.kind != inspect.Parameter.VAR_POSITIONAL
|
|
47
|
+
and name not in hints
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if missing:
|
|
51
|
+
raise SignatureValidationError(
|
|
52
|
+
f"Function {func.__name__} is missing type hints for: {', '.join(missing)}"
|
|
53
|
+
)
|
|
54
|
+
return hints
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def bind_and_validate_arguments(
|
|
58
|
+
func: Any,
|
|
59
|
+
args: Sequence[Any],
|
|
60
|
+
kwargs: Mapping[str, Any],
|
|
61
|
+
*,
|
|
62
|
+
exclude_params: Iterable[str] | None = None,
|
|
63
|
+
) -> Dict[str, Any]:
|
|
64
|
+
"""Bind args/kwargs to a function signature and validate types.
|
|
65
|
+
|
|
66
|
+
This ensures required parameters are present and values match the annotated
|
|
67
|
+
types. Excluded parameters are ignored for both binding and validation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
func: The target function.
|
|
71
|
+
args: Positional arguments.
|
|
72
|
+
kwargs: Keyword arguments.
|
|
73
|
+
exclude_params: Parameter names to ignore during validation.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A dictionary of bound arguments suitable for calling ``func``.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
SignatureValidationError: If required parameters are missing or types mismatch.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
exclude = _clean_excluded(exclude_params)
|
|
83
|
+
sig = inspect.signature(func)
|
|
84
|
+
|
|
85
|
+
# Drop excluded parameters from the signature for binding
|
|
86
|
+
filtered_params = [
|
|
87
|
+
param
|
|
88
|
+
for name, param in sig.parameters.items()
|
|
89
|
+
if name not in exclude
|
|
90
|
+
]
|
|
91
|
+
filtered_sig = sig.replace(parameters=filtered_params)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
bound = filtered_sig.bind(*args, **kwargs)
|
|
95
|
+
bound.apply_defaults()
|
|
96
|
+
except TypeError as exc:
|
|
97
|
+
raise SignatureValidationError(
|
|
98
|
+
f"Invalid arguments for {func.__name__}: {exc}"
|
|
99
|
+
) from exc
|
|
100
|
+
|
|
101
|
+
hints = get_type_hints(func, include_extras=True)
|
|
102
|
+
for name, value in bound.arguments.items():
|
|
103
|
+
if name in exclude:
|
|
104
|
+
continue
|
|
105
|
+
expected = hints.get(name)
|
|
106
|
+
if expected is None:
|
|
107
|
+
continue
|
|
108
|
+
try:
|
|
109
|
+
# typeguard 4.x uses check_type(value, expected_type)
|
|
110
|
+
# typeguard 2.x uses check_type(argname, value, expected_type)
|
|
111
|
+
check_type(value, expected)
|
|
112
|
+
except TypeError as exc:
|
|
113
|
+
raise SignatureValidationError(
|
|
114
|
+
f"Argument '{name}' to {func.__name__} must be {expected!r}, got {type(value)!r}. "
|
|
115
|
+
f"Value: {value!r}. Original error: {exc}"
|
|
116
|
+
) from exc
|
|
117
|
+
|
|
118
|
+
return bound.arguments
|
wizelit_sdk/database.py
CHANGED
|
@@ -18,11 +18,11 @@ class DatabaseManager:
|
|
|
18
18
|
"""Singleton database manager with connection pooling."""
|
|
19
19
|
|
|
20
20
|
DATABASE_URL: str = ""
|
|
21
|
-
POOL_SIZE: int = 5
|
|
22
|
-
MAX_OVERFLOW: int = 10
|
|
23
|
-
POOL_TIMEOUT: int = 30
|
|
24
|
-
POOL_RECYCLE: int = 3600
|
|
25
|
-
ECHO_SQL: bool =
|
|
21
|
+
POOL_SIZE: int = int(os.getenv("DB_POOL_SIZE", "5"))
|
|
22
|
+
MAX_OVERFLOW: int = int(os.getenv("DB_MAX_OVERFLOW", "10"))
|
|
23
|
+
POOL_TIMEOUT: int = int(os.getenv("DB_POOL_TIMEOUT", "30"))
|
|
24
|
+
POOL_RECYCLE: int = int(os.getenv("DB_POOL_RECYCLE", "3600"))
|
|
25
|
+
ECHO_SQL: bool = os.getenv("DB_ECHO_SQL", "false").lower() == "true"
|
|
26
26
|
|
|
27
27
|
_instance = None
|
|
28
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wizelit-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
4
4
|
Summary: Wizelit Agent Wrapper - Internal utility package
|
|
5
5
|
Author-email: Your Name <your.email@company.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -8,6 +8,7 @@ Requires-Dist: asyncpg>=0.26.0
|
|
|
8
8
|
Requires-Dist: fastmcp>=0.1.0
|
|
9
9
|
Requires-Dist: redis>=4.5.0
|
|
10
10
|
Requires-Dist: sqlalchemy>=1.4
|
|
11
|
+
Requires-Dist: typeguard>=4.3.0
|
|
11
12
|
Requires-Dist: typing-extensions>=4.0.0
|
|
12
13
|
Provides-Extra: dev
|
|
13
14
|
Requires-Dist: black>=22.0.0; extra == 'dev'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
wizelit_sdk/__init__.py,sha256=76Dt-WxNbEJwOCPqu2P9AooVSQoGmc8-CH1fxIWJ5Lo,471
|
|
2
|
+
wizelit_sdk/database.py,sha256=6F2Nyt-sr5U5GkAB9OeSCU-iv2byZBDsg7aXqCl-ntk,4822
|
|
3
|
+
wizelit_sdk/agent_wrapper/__init__.py,sha256=srVcHZfvpf3q8m2nzIST6Ofl2V8Vv5GRtcNXudOLiD8,333
|
|
4
|
+
wizelit_sdk/agent_wrapper/agent_wrapper.py,sha256=LiSYpnKF-RTx_9UnVgW9KqyfTWtilK_Gft1yCuxujus,23204
|
|
5
|
+
wizelit_sdk/agent_wrapper/job.py,sha256=Gk2CyLBQFzIosYuGrqUj6_rZ9kDIO6YLYQ7Pxk6ASuM,13776
|
|
6
|
+
wizelit_sdk/agent_wrapper/signature_validation.py,sha256=Njplwofw36jXoWgBooaZCbwhkf72pR-UoRQ3Q6-biro,3729
|
|
7
|
+
wizelit_sdk/agent_wrapper/streaming.py,sha256=f0VV3IzGAlfQY_cw2OHgxWjvM16Hs42_b700EUX2QpY,6633
|
|
8
|
+
wizelit_sdk/models/__init__.py,sha256=UB7nfHoE6hGcjfzy7w8AmuKVmYrTg9wIgGjG8GWziJ0,143
|
|
9
|
+
wizelit_sdk/models/base.py,sha256=aEPGMZVczKnlWz4Ps99e_xI5TcuSV3DxCigRMU5BnhE,704
|
|
10
|
+
wizelit_sdk/models/job.py,sha256=P68Vb1k77Z_Tha4dE0GzrLPa0LJrnMCxyYMENZyD7Kc,2480
|
|
11
|
+
wizelit_sdk-0.1.28.dist-info/METADATA,sha256=wBU5HBbuQBXynPvt_L2blH-JzArvPpDgU2jkRZ2kOWQ,3297
|
|
12
|
+
wizelit_sdk-0.1.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
+
wizelit_sdk-0.1.28.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
wizelit_sdk/__init__.py,sha256=76Dt-WxNbEJwOCPqu2P9AooVSQoGmc8-CH1fxIWJ5Lo,471
|
|
2
|
-
wizelit_sdk/database.py,sha256=39Vu5A8CV7u7ItIwRgrkAugD9UqNbG5vQrCsMOgTamc,4631
|
|
3
|
-
wizelit_sdk/agent_wrapper/__init__.py,sha256=OAF36jAmI0A3ByppsYMzJDHeDG9X0Ac4LbFzNp0b7jw,219
|
|
4
|
-
wizelit_sdk/agent_wrapper/agent_wrapper.py,sha256=e9KCQSpC7hox3q6sqzhOLDIq37btw7IT4ejeuljc6Gk,22742
|
|
5
|
-
wizelit_sdk/agent_wrapper/job.py,sha256=Gk2CyLBQFzIosYuGrqUj6_rZ9kDIO6YLYQ7Pxk6ASuM,13776
|
|
6
|
-
wizelit_sdk/agent_wrapper/streaming.py,sha256=f0VV3IzGAlfQY_cw2OHgxWjvM16Hs42_b700EUX2QpY,6633
|
|
7
|
-
wizelit_sdk/models/__init__.py,sha256=UB7nfHoE6hGcjfzy7w8AmuKVmYrTg9wIgGjG8GWziJ0,143
|
|
8
|
-
wizelit_sdk/models/base.py,sha256=aEPGMZVczKnlWz4Ps99e_xI5TcuSV3DxCigRMU5BnhE,704
|
|
9
|
-
wizelit_sdk/models/job.py,sha256=P68Vb1k77Z_Tha4dE0GzrLPa0LJrnMCxyYMENZyD7Kc,2480
|
|
10
|
-
wizelit_sdk-0.1.26.dist-info/METADATA,sha256=0QpbkJ8Z5Vl-2s7xrYHoTrutSAMUgQol9JNr9fgXkgo,3265
|
|
11
|
-
wizelit_sdk-0.1.26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
-
wizelit_sdk-0.1.26.dist-info/RECORD,,
|
|
File without changes
|