veris-ai 1.12.3__py3-none-any.whl → 1.13.0__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 veris-ai might be problematic. Click here for more details.
- veris_ai/tool_mock.py +66 -6
- veris_ai/utils.py +186 -0
- {veris_ai-1.12.3.dist-info → veris_ai-1.13.0.dist-info}/METADATA +1 -1
- {veris_ai-1.12.3.dist-info → veris_ai-1.13.0.dist-info}/RECORD +6 -6
- {veris_ai-1.12.3.dist-info → veris_ai-1.13.0.dist-info}/WHEEL +0 -0
- {veris_ai-1.12.3.dist-info → veris_ai-1.13.0.dist-info}/licenses/LICENSE +0 -0
veris_ai/tool_mock.py
CHANGED
|
@@ -17,7 +17,14 @@ from typing import (
|
|
|
17
17
|
|
|
18
18
|
from veris_ai.models import ResponseExpectation, ToolCallOptions
|
|
19
19
|
from veris_ai.api_client import get_api_client
|
|
20
|
-
from veris_ai.utils import
|
|
20
|
+
from veris_ai.utils import (
|
|
21
|
+
convert_to_type,
|
|
22
|
+
execute_callback,
|
|
23
|
+
extract_json_schema,
|
|
24
|
+
get_function_parameters,
|
|
25
|
+
get_input_parameters,
|
|
26
|
+
launch_callback_task,
|
|
27
|
+
)
|
|
21
28
|
|
|
22
29
|
logger = logging.getLogger(__name__)
|
|
23
30
|
|
|
@@ -243,8 +250,18 @@ class VerisSDK:
|
|
|
243
250
|
mode: Literal["tool", "function"] = "tool",
|
|
244
251
|
expects_response: bool | None = None,
|
|
245
252
|
cache_response: bool | None = None,
|
|
253
|
+
input_callback: Callable[..., Any] | None = None,
|
|
254
|
+
output_callback: Callable[[Any], Any] | None = None,
|
|
246
255
|
) -> Callable:
|
|
247
|
-
"""Decorator for mocking tool calls.
|
|
256
|
+
"""Decorator for mocking tool calls.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
mode: Whether to treat the function as a tool or function
|
|
260
|
+
expects_response: Whether the function expects a response
|
|
261
|
+
cache_response: Whether to cache the response
|
|
262
|
+
input_callback: Callable that receives input parameters as individual arguments
|
|
263
|
+
output_callback: Callable that receives the output value
|
|
264
|
+
"""
|
|
248
265
|
response_expectation = (
|
|
249
266
|
ResponseExpectation.NONE
|
|
250
267
|
if (expects_response is False or (expects_response is None and mode == "function"))
|
|
@@ -273,9 +290,11 @@ class VerisSDK:
|
|
|
273
290
|
f"No session ID found, executing original function: {func.__name__}"
|
|
274
291
|
)
|
|
275
292
|
return await func(*args, **kwargs)
|
|
293
|
+
|
|
294
|
+
# Perform the mock call first
|
|
276
295
|
parameters = get_function_parameters(func, args, kwargs)
|
|
277
296
|
thread_id = _thread_id_context.get()
|
|
278
|
-
|
|
297
|
+
result = await mock_tool_call_async(
|
|
279
298
|
func,
|
|
280
299
|
session_id,
|
|
281
300
|
parameters,
|
|
@@ -283,6 +302,13 @@ class VerisSDK:
|
|
|
283
302
|
thread_id,
|
|
284
303
|
)
|
|
285
304
|
|
|
305
|
+
# Launch callbacks as background tasks (non-blocking)
|
|
306
|
+
input_params = get_input_parameters(func, args, kwargs)
|
|
307
|
+
launch_callback_task(input_callback, input_params, unpack=True)
|
|
308
|
+
launch_callback_task(output_callback, result, unpack=False)
|
|
309
|
+
|
|
310
|
+
return result
|
|
311
|
+
|
|
286
312
|
@wraps(func)
|
|
287
313
|
def sync_wrapper(
|
|
288
314
|
*args: tuple[object, ...],
|
|
@@ -295,9 +321,11 @@ class VerisSDK:
|
|
|
295
321
|
f"No session ID found, executing original function: {func.__name__}"
|
|
296
322
|
)
|
|
297
323
|
return func(*args, **kwargs)
|
|
324
|
+
|
|
325
|
+
# Perform the mock call first
|
|
298
326
|
parameters = get_function_parameters(func, args, kwargs)
|
|
299
327
|
thread_id = _thread_id_context.get()
|
|
300
|
-
|
|
328
|
+
result = mock_tool_call(
|
|
301
329
|
func,
|
|
302
330
|
session_id,
|
|
303
331
|
parameters,
|
|
@@ -305,13 +333,31 @@ class VerisSDK:
|
|
|
305
333
|
thread_id,
|
|
306
334
|
)
|
|
307
335
|
|
|
336
|
+
# Execute callbacks synchronously (can't use async tasks in sync context)
|
|
337
|
+
input_params = get_input_parameters(func, args, kwargs)
|
|
338
|
+
execute_callback(input_callback, input_params, unpack=True)
|
|
339
|
+
execute_callback(output_callback, result, unpack=False)
|
|
340
|
+
|
|
341
|
+
return result
|
|
342
|
+
|
|
308
343
|
# Return the appropriate wrapper based on whether the function is async
|
|
309
344
|
return async_wrapper if is_async else sync_wrapper
|
|
310
345
|
|
|
311
346
|
return decorator
|
|
312
347
|
|
|
313
|
-
def stub(
|
|
314
|
-
|
|
348
|
+
def stub(
|
|
349
|
+
self,
|
|
350
|
+
return_value: Any, # noqa: ANN401
|
|
351
|
+
input_callback: Callable[..., Any] | None = None,
|
|
352
|
+
output_callback: Callable[[Any], Any] | None = None,
|
|
353
|
+
) -> Callable:
|
|
354
|
+
"""Decorator for stubbing tool calls.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
return_value: The value to return when the function is stubbed
|
|
358
|
+
input_callback: Callable that receives input parameters as individual arguments
|
|
359
|
+
output_callback: Callable that receives the output value
|
|
360
|
+
"""
|
|
315
361
|
|
|
316
362
|
def decorator(func: Callable) -> Callable:
|
|
317
363
|
# Check if the original function is async
|
|
@@ -327,7 +373,14 @@ class VerisSDK:
|
|
|
327
373
|
f"No session ID found, executing original function: {func.__name__}"
|
|
328
374
|
)
|
|
329
375
|
return await func(*args, **kwargs)
|
|
376
|
+
|
|
330
377
|
logger.info(f"Stubbing function: {func.__name__}")
|
|
378
|
+
|
|
379
|
+
# Launch callbacks as background tasks (non-blocking)
|
|
380
|
+
input_params = get_input_parameters(func, args, kwargs)
|
|
381
|
+
launch_callback_task(input_callback, input_params, unpack=True)
|
|
382
|
+
launch_callback_task(output_callback, return_value, unpack=False)
|
|
383
|
+
|
|
331
384
|
return return_value
|
|
332
385
|
|
|
333
386
|
@wraps(func)
|
|
@@ -337,7 +390,14 @@ class VerisSDK:
|
|
|
337
390
|
f"No session ID found, executing original function: {func.__name__}"
|
|
338
391
|
)
|
|
339
392
|
return func(*args, **kwargs)
|
|
393
|
+
|
|
340
394
|
logger.info(f"Stubbing function: {func.__name__}")
|
|
395
|
+
|
|
396
|
+
# Execute callbacks synchronously (can't use async tasks in sync context)
|
|
397
|
+
input_params = get_input_parameters(func, args, kwargs)
|
|
398
|
+
execute_callback(input_callback, input_params, unpack=True)
|
|
399
|
+
execute_callback(output_callback, return_value, unpack=False)
|
|
400
|
+
|
|
341
401
|
return return_value
|
|
342
402
|
|
|
343
403
|
# Return the appropriate wrapper based on whether the function is async
|
veris_ai/utils.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import inspect
|
|
3
|
+
import logging
|
|
2
4
|
import sys
|
|
3
5
|
import types
|
|
4
6
|
import typing
|
|
@@ -18,6 +20,8 @@ from collections.abc import Callable
|
|
|
18
20
|
|
|
19
21
|
from pydantic import BaseModel
|
|
20
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
def convert_to_type(value: object, target_type: type) -> object:
|
|
23
27
|
"""Convert a value to the specified type."""
|
|
@@ -303,3 +307,185 @@ def get_function_parameters(
|
|
|
303
307
|
"type": str(get_type_hints(func).get(param_name, Any)),
|
|
304
308
|
}
|
|
305
309
|
return params_info
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# Callback utility functions
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def get_input_parameters(func: Callable, args: tuple, kwargs: dict) -> dict[str, Any]:
|
|
316
|
+
"""Get the actual input parameters for callbacks.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
func: The function being called
|
|
320
|
+
args: Positional arguments
|
|
321
|
+
kwargs: Keyword arguments
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Dictionary of parameter names to their actual values (not stringified),
|
|
325
|
+
excluding self and cls parameters. Preserves ctx if present.
|
|
326
|
+
"""
|
|
327
|
+
sig = inspect.signature(func)
|
|
328
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
329
|
+
bound_args.apply_defaults()
|
|
330
|
+
|
|
331
|
+
# Remove only self and cls - preserve ctx and all other parameters
|
|
332
|
+
params = dict(bound_args.arguments)
|
|
333
|
+
params.pop("self", None)
|
|
334
|
+
params.pop("cls", None)
|
|
335
|
+
|
|
336
|
+
return params
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def filter_callback_parameters(callback: Callable, params: dict[str, Any]) -> dict[str, Any]:
|
|
340
|
+
"""Filter parameters to match what the callback can accept.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
callback: The callback function to inspect
|
|
344
|
+
params: Dictionary of all available parameters
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Filtered dictionary containing only parameters the callback accepts
|
|
348
|
+
"""
|
|
349
|
+
try:
|
|
350
|
+
sig = inspect.signature(callback)
|
|
351
|
+
|
|
352
|
+
# Check if callback accepts **kwargs (VAR_KEYWORD parameter)
|
|
353
|
+
has_var_keyword = any(
|
|
354
|
+
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# If callback accepts **kwargs, pass all parameters
|
|
358
|
+
if has_var_keyword:
|
|
359
|
+
return params
|
|
360
|
+
|
|
361
|
+
# Otherwise, filter to only include parameters the callback accepts
|
|
362
|
+
accepted_params = {}
|
|
363
|
+
for param_name in sig.parameters:
|
|
364
|
+
if param_name in params:
|
|
365
|
+
accepted_params[param_name] = params[param_name]
|
|
366
|
+
|
|
367
|
+
return accepted_params
|
|
368
|
+
except (ValueError, TypeError):
|
|
369
|
+
# If we can't inspect the signature, pass all parameters
|
|
370
|
+
# and let the callback handle it (will fail if incompatible)
|
|
371
|
+
return params
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def execute_callback(
|
|
375
|
+
callback: Callable | None,
|
|
376
|
+
data: Any, # noqa: ANN401
|
|
377
|
+
unpack: bool = False,
|
|
378
|
+
) -> None:
|
|
379
|
+
"""Execute a callback synchronously if provided.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
callback: The callback callable to execute
|
|
383
|
+
data: The data to pass to the callback
|
|
384
|
+
unpack: If True and data is a dict, unpack it as keyword arguments
|
|
385
|
+
|
|
386
|
+
Note:
|
|
387
|
+
Exceptions in callbacks are caught and logged to prevent breaking the main flow.
|
|
388
|
+
"""
|
|
389
|
+
if callback is None:
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
if unpack and isinstance(data, dict):
|
|
394
|
+
# Filter parameters to match callback signature
|
|
395
|
+
filtered_data = filter_callback_parameters(callback, data)
|
|
396
|
+
callback(**filtered_data)
|
|
397
|
+
else:
|
|
398
|
+
callback(data)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.warning(f"Callback execution failed: {e}", exc_info=True)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
async def execute_callback_async(
|
|
404
|
+
callback: Callable | None,
|
|
405
|
+
data: Any, # noqa: ANN401
|
|
406
|
+
unpack: bool = False,
|
|
407
|
+
) -> None:
|
|
408
|
+
"""Execute a callback asynchronously if provided.
|
|
409
|
+
|
|
410
|
+
Handles both sync and async callback callables.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
callback: The callback callable to execute (can be sync or async)
|
|
414
|
+
data: The data to pass to the callback
|
|
415
|
+
unpack: If True and data is a dict, unpack it as keyword arguments
|
|
416
|
+
|
|
417
|
+
Note:
|
|
418
|
+
Exceptions in callbacks are caught and logged to prevent breaking the main flow.
|
|
419
|
+
"""
|
|
420
|
+
if callback is None:
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
if inspect.iscoroutinefunction(callback):
|
|
425
|
+
if unpack and isinstance(data, dict):
|
|
426
|
+
# Filter parameters to match callback signature
|
|
427
|
+
filtered_data = filter_callback_parameters(callback, data)
|
|
428
|
+
await callback(**filtered_data)
|
|
429
|
+
else:
|
|
430
|
+
await callback(data)
|
|
431
|
+
else:
|
|
432
|
+
if unpack and isinstance(data, dict):
|
|
433
|
+
# Filter parameters to match callback signature
|
|
434
|
+
filtered_data = filter_callback_parameters(callback, data)
|
|
435
|
+
result = callback(**filtered_data)
|
|
436
|
+
else:
|
|
437
|
+
result = callback(data)
|
|
438
|
+
# If the result is a coroutine (can happen with functools.partial), await it
|
|
439
|
+
if inspect.iscoroutine(result):
|
|
440
|
+
await result
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.warning(f"Callback execution failed: {e}", exc_info=True)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def launch_callback_task(
|
|
446
|
+
callback: Callable | None,
|
|
447
|
+
data: Any, # noqa: ANN401
|
|
448
|
+
unpack: bool = False,
|
|
449
|
+
) -> None:
|
|
450
|
+
"""Launch a callback as a background task (fire-and-forget).
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
callback: The callback callable to execute (can be sync or async)
|
|
454
|
+
data: The data to pass to the callback
|
|
455
|
+
unpack: If True and data is a dict, unpack it as keyword arguments
|
|
456
|
+
|
|
457
|
+
Note:
|
|
458
|
+
This launches the callback without blocking. Errors are logged but won't
|
|
459
|
+
affect the main execution flow.
|
|
460
|
+
"""
|
|
461
|
+
if callback is None:
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
async def _run_callback() -> None:
|
|
465
|
+
"""Wrapper to run callback with error handling."""
|
|
466
|
+
try:
|
|
467
|
+
if inspect.iscoroutinefunction(callback):
|
|
468
|
+
if unpack and isinstance(data, dict):
|
|
469
|
+
# Filter parameters to match callback signature
|
|
470
|
+
filtered_data = filter_callback_parameters(callback, data)
|
|
471
|
+
await callback(**filtered_data)
|
|
472
|
+
else:
|
|
473
|
+
await callback(data)
|
|
474
|
+
else:
|
|
475
|
+
if unpack and isinstance(data, dict):
|
|
476
|
+
# Filter parameters to match callback signature
|
|
477
|
+
filtered_data = filter_callback_parameters(callback, data)
|
|
478
|
+
result = callback(**filtered_data)
|
|
479
|
+
else:
|
|
480
|
+
result = callback(data)
|
|
481
|
+
if inspect.iscoroutine(result):
|
|
482
|
+
await result
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logger.warning(f"Callback execution failed: {e}", exc_info=True)
|
|
485
|
+
|
|
486
|
+
# Create task without awaiting (fire-and-forget)
|
|
487
|
+
try:
|
|
488
|
+
asyncio.create_task(_run_callback())
|
|
489
|
+
except RuntimeError:
|
|
490
|
+
# If no event loop is running, log a warning
|
|
491
|
+
logger.warning("Cannot launch callback task: no event loop running")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-ai
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: A Python package for Veris AI tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
|
|
@@ -4,13 +4,13 @@ veris_ai/agents_wrapper.py,sha256=gLUd_0TyCVsqqilQLvsSJIpsU5uu2CdjjWOQ4QJjoJk,12
|
|
|
4
4
|
veris_ai/api_client.py,sha256=I1XyQ7J0ZU_JK9sZjF3XqFv5gGsrdKF38euOZmW8BG0,4150
|
|
5
5
|
veris_ai/models.py,sha256=xKeheSJQle2tBeJG1DsGJzMDwv24p5jECjX6RAa39n4,495
|
|
6
6
|
veris_ai/observability.py,sha256=eSIXmk6fpOAoWM-sDbsvzyUASh1ZwU6tRIPduy09RxY,4206
|
|
7
|
-
veris_ai/tool_mock.py,sha256=
|
|
8
|
-
veris_ai/utils.py,sha256=
|
|
7
|
+
veris_ai/tool_mock.py,sha256=olb_ywR88meK_FyoDVHpVnIi0h0B9oWM5IHV7k3hcko,24240
|
|
8
|
+
veris_ai/utils.py,sha256=2fzXcsKQGLm1Q1ntjtuC_Z5l6Atj56zQE723m3zXdGg,16357
|
|
9
9
|
veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
|
|
10
10
|
veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
|
|
11
11
|
veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
|
|
12
12
|
veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
|
|
13
|
-
veris_ai-1.
|
|
14
|
-
veris_ai-1.
|
|
15
|
-
veris_ai-1.
|
|
16
|
-
veris_ai-1.
|
|
13
|
+
veris_ai-1.13.0.dist-info/METADATA,sha256=5yQZuNyfZdG4QiJd30nTHkwm_e2B8WRp4DbCXequnmk,16684
|
|
14
|
+
veris_ai-1.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
veris_ai-1.13.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
16
|
+
veris_ai-1.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|