veris-ai 1.8.0__py3-none-any.whl → 1.8.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 veris-ai might be problematic. Click here for more details.
- veris_ai/agents_wrapper.py +128 -111
- veris_ai/api_client.py +1 -9
- veris_ai/logging.py +6 -47
- veris_ai/models.py +11 -0
- veris_ai/tool_mock.py +125 -160
- veris_ai/utils.py +3 -2
- {veris_ai-1.8.0.dist-info → veris_ai-1.8.1.dist-info}/METADATA +2 -2
- veris_ai-1.8.1.dist-info/RECORD +17 -0
- veris_ai-1.8.0.dist-info/RECORD +0 -17
- {veris_ai-1.8.0.dist-info → veris_ai-1.8.1.dist-info}/WHEEL +0 -0
- {veris_ai-1.8.0.dist-info → veris_ai-1.8.1.dist-info}/licenses/LICENSE +0 -0
veris_ai/agents_wrapper.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"""OpenAI Agents wrapper for automatic tool mocking via Veris SDK."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
|
-
import inspect
|
|
5
|
-
import json
|
|
6
3
|
import logging
|
|
7
4
|
from collections.abc import Callable
|
|
8
5
|
from typing import Any
|
|
9
6
|
|
|
10
|
-
from agents import RunResult, Runner
|
|
7
|
+
from agents import RunContextWrapper, RunResult, Runner
|
|
11
8
|
from pydantic import BaseModel
|
|
12
9
|
|
|
13
10
|
from veris_ai import veris
|
|
11
|
+
from veris_ai.tool_mock import mock_tool_call
|
|
14
12
|
|
|
15
13
|
logger = logging.getLogger(__name__)
|
|
16
14
|
|
|
@@ -49,8 +47,8 @@ def _wrap(
|
|
|
49
47
|
|
|
50
48
|
async def wrapped_run(starting_agent: Any, input_text: str, **kwargs: Any) -> Any: # noqa: ANN401
|
|
51
49
|
"""Wrapped version of Runner.run that intercepts tool calls."""
|
|
52
|
-
#
|
|
53
|
-
|
|
50
|
+
# Store a mapping of tools to their original functions
|
|
51
|
+
tool_functions = {}
|
|
54
52
|
|
|
55
53
|
if hasattr(starting_agent, "tools") and starting_agent.tools:
|
|
56
54
|
for tool in starting_agent.tools:
|
|
@@ -61,34 +59,39 @@ def _wrap(
|
|
|
61
59
|
if tool_name and _should_intercept_tool(
|
|
62
60
|
tool_name, include_tools, exclude_tools
|
|
63
61
|
):
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
# Extract the original function before patching
|
|
63
|
+
original_func = _extract_the_func(tool.on_invoke_tool)
|
|
64
|
+
if original_func:
|
|
65
|
+
tool_functions[id(tool)] = original_func
|
|
66
|
+
|
|
67
|
+
# Store original on_invoke_tool
|
|
68
|
+
original_on_invoke = tool.on_invoke_tool
|
|
69
|
+
|
|
70
|
+
def make_wrapped_on_invoke_tool(
|
|
71
|
+
tool_id: int, orig_invoke: Callable
|
|
72
|
+
) -> Callable:
|
|
73
|
+
"""Create a wrapped on_invoke_tool with proper closure."""
|
|
74
|
+
|
|
75
|
+
async def wrapped_on_invoke_tool(
|
|
76
|
+
ctx: RunContextWrapper[Any], parameters: str
|
|
77
|
+
) -> Any: # noqa: ANN401
|
|
78
|
+
"""Wrapped on_invoke_tool that intercepts the tool function."""
|
|
79
|
+
session_id = veris.session_id
|
|
80
|
+
the_func = tool_functions.get(tool_id)
|
|
81
|
+
if the_func and session_id:
|
|
82
|
+
# mock_tool_call is synchronous, don't await it
|
|
83
|
+
return mock_tool_call(
|
|
84
|
+
the_func, session_id, parameters, None
|
|
85
|
+
)
|
|
86
|
+
# Fall back to original if we couldn't extract the function
|
|
87
|
+
return await orig_invoke(ctx, parameters)
|
|
88
|
+
|
|
89
|
+
return wrapped_on_invoke_tool
|
|
90
|
+
|
|
91
|
+
tool.on_invoke_tool = make_wrapped_on_invoke_tool(
|
|
92
|
+
id(tool), original_on_invoke
|
|
71
93
|
)
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
# Call the original Runner.run with the patched agent
|
|
75
|
-
return await run_func(starting_agent, input_text, **kwargs)
|
|
76
|
-
finally:
|
|
77
|
-
# Restore all original tool functions
|
|
78
|
-
for tool, original_invoke in original_tools:
|
|
79
|
-
tool.on_invoke_tool = original_invoke # type: ignore[attr-defined]
|
|
80
|
-
|
|
81
|
-
# Preserve function metadata
|
|
82
|
-
wrapped_run.__name__ = getattr(run_func, "__name__", "wrapped_run")
|
|
83
|
-
wrapped_run.__doc__ = getattr(run_func, "__doc__", "Wrapped Runner.run function")
|
|
84
|
-
|
|
85
|
-
# Also provide a sync version
|
|
86
|
-
def wrapped_run_sync(starting_agent: Any, input_text: str, **kwargs: Any) -> Any: # noqa: ANN401
|
|
87
|
-
"""Sync version of wrapped Runner.run."""
|
|
88
|
-
return asyncio.run(wrapped_run(starting_agent, input_text, **kwargs))
|
|
89
|
-
|
|
90
|
-
# Add sync version as an attribute
|
|
91
|
-
wrapped_run.run_sync = wrapped_run_sync # type: ignore[attr-defined]
|
|
94
|
+
return await run_func(starting_agent, input_text, **kwargs)
|
|
92
95
|
|
|
93
96
|
return wrapped_run
|
|
94
97
|
|
|
@@ -117,94 +120,108 @@ def _should_intercept_tool(
|
|
|
117
120
|
return True
|
|
118
121
|
|
|
119
122
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
def _extract_the_func(on_invoke_tool: Callable) -> Callable | None:
|
|
124
|
+
"""Extract the original user function from the on_invoke_tool closure.
|
|
125
|
+
|
|
126
|
+
This function attempts multiple strategies to extract the original function:
|
|
127
|
+
1. Direct attribute access (if the tool stores it)
|
|
128
|
+
2. Closure inspection for known patterns
|
|
129
|
+
3. Deep closure traversal as a fallback
|
|
125
130
|
|
|
126
131
|
Args:
|
|
127
|
-
|
|
128
|
-
original_invoke: The original on_invoke_tool function
|
|
132
|
+
on_invoke_tool: The on_invoke_tool function from FunctionTool
|
|
129
133
|
|
|
130
134
|
Returns:
|
|
131
|
-
|
|
135
|
+
The original user function if found, None otherwise
|
|
132
136
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# No session, run original
|
|
150
|
-
return await original_invoke(context, arguments)
|
|
151
|
-
|
|
152
|
-
# Parse arguments
|
|
153
|
-
try:
|
|
154
|
-
args_dict = json.loads(arguments) if arguments else {}
|
|
155
|
-
except json.JSONDecodeError:
|
|
156
|
-
args_dict = {"raw_arguments": arguments}
|
|
157
|
-
|
|
158
|
-
# Set up the function with proper metadata for veris.mock()
|
|
159
|
-
veris_tool_function.__name__ = tool_name
|
|
160
|
-
veris_tool_function.__doc__ = getattr(tool, "description", "")
|
|
161
|
-
|
|
162
|
-
# Add type hints based on schema if available
|
|
163
|
-
if tool_schema and "properties" in tool_schema:
|
|
164
|
-
# Create a simple signature based on schema
|
|
165
|
-
params = []
|
|
166
|
-
for param_name, param_info in tool_schema["properties"].items():
|
|
167
|
-
# Simplified type mapping
|
|
168
|
-
param_type: type[Any] = Any
|
|
169
|
-
if "type" in param_info:
|
|
170
|
-
type_map = {
|
|
171
|
-
"string": str,
|
|
172
|
-
"number": float,
|
|
173
|
-
"integer": int,
|
|
174
|
-
"boolean": bool,
|
|
175
|
-
"array": list,
|
|
176
|
-
"object": dict,
|
|
177
|
-
}
|
|
178
|
-
param_type = type_map.get(param_info["type"], Any)
|
|
179
|
-
params.append(
|
|
180
|
-
inspect.Parameter(
|
|
181
|
-
param_name,
|
|
182
|
-
inspect.Parameter.KEYWORD_ONLY,
|
|
183
|
-
annotation=param_type,
|
|
184
|
-
)
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
if params:
|
|
188
|
-
veris_tool_function.__signature__ = inspect.Signature(params)
|
|
189
|
-
|
|
190
|
-
# Call the veris-wrapped function with the arguments
|
|
137
|
+
|
|
138
|
+
# Strategy 1: Check if the tool has stored the original function as an attribute
|
|
139
|
+
# (This would be the cleanest approach if the agents library supported it)
|
|
140
|
+
if hasattr(on_invoke_tool, "__wrapped__"):
|
|
141
|
+
return on_invoke_tool.__wrapped__
|
|
142
|
+
|
|
143
|
+
# Strategy 2: Look for the function in the closure using known structure
|
|
144
|
+
# Based on the agents library implementation, we know:
|
|
145
|
+
# - on_invoke_tool has _on_invoke_tool_impl in its closure
|
|
146
|
+
# - _on_invoke_tool_impl has the_func in its closure
|
|
147
|
+
|
|
148
|
+
if not hasattr(on_invoke_tool, "__closure__") or not on_invoke_tool.__closure__:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Find _on_invoke_tool_impl by looking for a function with that name pattern
|
|
152
|
+
for cell in on_invoke_tool.__closure__:
|
|
191
153
|
try:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
154
|
+
obj = cell.cell_contents
|
|
155
|
+
if not callable(obj):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Check if this looks like _on_invoke_tool_impl
|
|
159
|
+
if (
|
|
160
|
+
hasattr(obj, "__name__")
|
|
161
|
+
and "_on_invoke_tool_impl" in obj.__name__
|
|
162
|
+
and hasattr(obj, "__closure__")
|
|
163
|
+
and obj.__closure__
|
|
164
|
+
):
|
|
165
|
+
# Now look for the_func in its closure
|
|
166
|
+
return _find_user_function_in_closure(obj.__closure__)
|
|
167
|
+
except (ValueError, AttributeError):
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Strategy 3: Fallback - do a broader search in the closure
|
|
171
|
+
return _find_user_function_in_closure(on_invoke_tool.__closure__)
|
|
172
|
+
|
|
197
173
|
|
|
198
|
-
|
|
199
|
-
|
|
174
|
+
def _find_user_function_in_closure(closure: tuple) -> Callable | None:
|
|
175
|
+
"""Find the user function in a closure by filtering out known library functions.
|
|
200
176
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return asyncio.run(wrapped_invoke(context, arguments))
|
|
177
|
+
Args:
|
|
178
|
+
closure: The closure tuple to search
|
|
204
179
|
|
|
205
|
-
|
|
180
|
+
Returns:
|
|
181
|
+
The user function if found, None otherwise
|
|
182
|
+
"""
|
|
183
|
+
import inspect
|
|
206
184
|
|
|
207
|
-
|
|
185
|
+
# List of module prefixes that indicate library/framework code
|
|
186
|
+
library_modules = ("json", "inspect", "agents", "pydantic", "openai", "typing")
|
|
187
|
+
|
|
188
|
+
for cell in closure:
|
|
189
|
+
try:
|
|
190
|
+
obj = cell.cell_contents
|
|
191
|
+
|
|
192
|
+
# Must be callable but not a class
|
|
193
|
+
if not callable(obj) or isinstance(obj, type):
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# Skip private/internal functions
|
|
197
|
+
if hasattr(obj, "__name__") and obj.__name__.startswith("_"):
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# Check the module to filter out library code
|
|
201
|
+
module = inspect.getmodule(obj)
|
|
202
|
+
if module:
|
|
203
|
+
# Skip if it's from a known library
|
|
204
|
+
if module.__name__.startswith(library_modules):
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# Skip if it's from site-packages (library code)
|
|
208
|
+
if (
|
|
209
|
+
hasattr(module, "__file__")
|
|
210
|
+
and module.__file__
|
|
211
|
+
and "site-packages" in module.__file__
|
|
212
|
+
# Unless it's user code installed as a package
|
|
213
|
+
# (this is a heuristic - may need adjustment)
|
|
214
|
+
and not any(pkg in module.__name__ for pkg in ["my_", "custom_", "app_"])
|
|
215
|
+
):
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
# If we made it here, this is likely the user function
|
|
219
|
+
return obj
|
|
220
|
+
|
|
221
|
+
except (ValueError, AttributeError):
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
return None
|
|
208
225
|
|
|
209
226
|
|
|
210
227
|
class VerisConfig(BaseModel):
|
veris_ai/api_client.py
CHANGED
|
@@ -29,15 +29,7 @@ class SimulatorAPIClient:
|
|
|
29
29
|
|
|
30
30
|
return headers
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
"""Make an async POST request to the specified endpoint."""
|
|
34
|
-
headers = self._build_headers()
|
|
35
|
-
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
36
|
-
response = await client.post(endpoint, json=payload, headers=headers)
|
|
37
|
-
response.raise_for_status()
|
|
38
|
-
return response.json() if response.content else None
|
|
39
|
-
|
|
40
|
-
def post_sync(self, endpoint: str, payload: dict[str, Any]) -> Any: # noqa: ANN401
|
|
32
|
+
def post(self, endpoint: str, payload: dict[str, Any]) -> Any: # noqa: ANN401
|
|
41
33
|
"""Make a synchronous POST request to the specified endpoint."""
|
|
42
34
|
headers = self._build_headers()
|
|
43
35
|
with httpx.Client(timeout=self.timeout) as client:
|
veris_ai/logging.py
CHANGED
|
@@ -9,69 +9,28 @@ from veris_ai.api_client import get_api_client
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
def log_tool_call(
|
|
13
13
|
session_id: str,
|
|
14
14
|
function_name: str,
|
|
15
|
-
parameters:
|
|
16
|
-
docstring: str,
|
|
17
|
-
) -> None:
|
|
18
|
-
"""Log tool call asynchronously to the VERIS logging endpoint."""
|
|
19
|
-
api_client = get_api_client()
|
|
20
|
-
endpoint = api_client.get_log_tool_call_endpoint(session_id)
|
|
21
|
-
|
|
22
|
-
payload = {
|
|
23
|
-
"function_name": function_name,
|
|
24
|
-
"parameters": parameters,
|
|
25
|
-
"docstring": docstring,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
await api_client.post_async(endpoint, payload)
|
|
30
|
-
logger.debug(f"Tool call logged for {function_name}")
|
|
31
|
-
except Exception as e:
|
|
32
|
-
logger.warning(f"Failed to log tool call for {function_name}: {e}")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def log_tool_call_sync(
|
|
36
|
-
session_id: str,
|
|
37
|
-
function_name: str,
|
|
38
|
-
parameters: dict[str, Any],
|
|
15
|
+
parameters: str,
|
|
39
16
|
docstring: str,
|
|
40
17
|
) -> None:
|
|
41
18
|
"""Log tool call synchronously to the VERIS logging endpoint."""
|
|
42
19
|
api_client = get_api_client()
|
|
43
20
|
endpoint = api_client.get_log_tool_call_endpoint(session_id)
|
|
44
|
-
|
|
45
21
|
payload = {
|
|
46
22
|
"function_name": function_name,
|
|
47
|
-
"parameters": parameters,
|
|
23
|
+
"parameters": json.loads(parameters),
|
|
48
24
|
"docstring": docstring,
|
|
49
25
|
}
|
|
50
|
-
|
|
51
26
|
try:
|
|
52
|
-
api_client.
|
|
27
|
+
api_client.post(endpoint, payload)
|
|
53
28
|
logger.debug(f"Tool call logged for {function_name}")
|
|
54
29
|
except Exception as e:
|
|
55
30
|
logger.warning(f"Failed to log tool call for {function_name}: {e}")
|
|
56
31
|
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
"""Log tool response asynchronously to the VERIS logging endpoint."""
|
|
60
|
-
api_client = get_api_client()
|
|
61
|
-
endpoint = api_client.get_log_tool_response_endpoint(session_id)
|
|
62
|
-
|
|
63
|
-
payload = {
|
|
64
|
-
"response": json.dumps(response, default=str),
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
await api_client.post_async(endpoint, payload)
|
|
69
|
-
logger.debug("Tool response logged")
|
|
70
|
-
except Exception as e:
|
|
71
|
-
logger.warning(f"Failed to log tool response: {e}")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def log_tool_response_sync(session_id: str, response: Any) -> None: # noqa: ANN401
|
|
33
|
+
def log_tool_response(session_id: str, response: Any) -> None: # noqa: ANN401
|
|
75
34
|
"""Log tool response synchronously to the VERIS logging endpoint."""
|
|
76
35
|
api_client = get_api_client()
|
|
77
36
|
endpoint = api_client.get_log_tool_response_endpoint(session_id)
|
|
@@ -81,7 +40,7 @@ def log_tool_response_sync(session_id: str, response: Any) -> None: # noqa: ANN
|
|
|
81
40
|
}
|
|
82
41
|
|
|
83
42
|
try:
|
|
84
|
-
api_client.
|
|
43
|
+
api_client.post(endpoint, payload)
|
|
85
44
|
logger.debug("Tool response logged")
|
|
86
45
|
except Exception as e:
|
|
87
46
|
logger.warning(f"Failed to log tool response: {e}")
|
veris_ai/models.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""Models for the VERIS SDK."""
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class ResponseExpectation(str, Enum):
|
|
@@ -9,3 +12,11 @@ class ResponseExpectation(str, Enum):
|
|
|
9
12
|
AUTO = "auto"
|
|
10
13
|
REQUIRED = "required"
|
|
11
14
|
NONE = "none"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToolCallOptions(BaseModel):
|
|
18
|
+
"""Options for tool call."""
|
|
19
|
+
|
|
20
|
+
response_expectation: ResponseExpectation = ResponseExpectation.AUTO
|
|
21
|
+
cache_response: bool = False
|
|
22
|
+
mode: Literal["tool", "function", "spy"] = "tool"
|
veris_ai/tool_mock.py
CHANGED
|
@@ -12,13 +12,12 @@ from typing import (
|
|
|
12
12
|
get_type_hints,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
from veris_ai.logging import (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
log_tool_response_async,
|
|
19
|
-
log_tool_response_sync,
|
|
17
|
+
log_tool_call,
|
|
18
|
+
log_tool_response,
|
|
20
19
|
)
|
|
21
|
-
from veris_ai.models import ResponseExpectation
|
|
20
|
+
from veris_ai.models import ResponseExpectation, ToolCallOptions
|
|
22
21
|
from veris_ai.api_client import get_api_client
|
|
23
22
|
from veris_ai.utils import convert_to_type, extract_json_schema, get_function_parameters
|
|
24
23
|
|
|
@@ -96,185 +95,109 @@ class VerisSDK:
|
|
|
96
95
|
**params_dict,
|
|
97
96
|
)
|
|
98
97
|
|
|
98
|
+
def spy(self) -> Callable:
|
|
99
|
+
"""Decorator for spying on tool calls."""
|
|
100
|
+
|
|
101
|
+
def decorator(func: Callable) -> Callable:
|
|
102
|
+
"""Decorator for spying on tool calls."""
|
|
103
|
+
is_async = inspect.iscoroutinefunction(func)
|
|
104
|
+
|
|
105
|
+
@wraps(func)
|
|
106
|
+
async def async_wrapper(*args: tuple[object, ...], **kwargs: Any) -> object: # noqa: ANN401
|
|
107
|
+
"""Async wrapper."""
|
|
108
|
+
session_id = _session_id_context.get()
|
|
109
|
+
if not session_id:
|
|
110
|
+
return await func(*args, **kwargs)
|
|
111
|
+
parameters = get_function_parameters(func, args, kwargs)
|
|
112
|
+
logger.info(f"Spying on function: {func.__name__}")
|
|
113
|
+
log_tool_call(
|
|
114
|
+
session_id=session_id,
|
|
115
|
+
function_name=func.__name__,
|
|
116
|
+
parameters=parameters,
|
|
117
|
+
docstring=inspect.getdoc(func) or "",
|
|
118
|
+
)
|
|
119
|
+
result = await func(*args, **kwargs)
|
|
120
|
+
log_tool_response(session_id=session_id, response=result)
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
@wraps(func)
|
|
124
|
+
def sync_wrapper(*args: tuple[object, ...], **kwargs: Any) -> object: # noqa: ANN401
|
|
125
|
+
"""Sync wrapper."""
|
|
126
|
+
session_id = _session_id_context.get()
|
|
127
|
+
if not session_id:
|
|
128
|
+
return func(*args, **kwargs)
|
|
129
|
+
parameters = get_function_parameters(func, args, kwargs)
|
|
130
|
+
logger.info(f"Spying on function: {func.__name__}")
|
|
131
|
+
log_tool_call(
|
|
132
|
+
session_id=session_id,
|
|
133
|
+
function_name=func.__name__,
|
|
134
|
+
parameters=parameters,
|
|
135
|
+
docstring=inspect.getdoc(func) or "",
|
|
136
|
+
)
|
|
137
|
+
result = func(*args, **kwargs)
|
|
138
|
+
log_tool_response(session_id=session_id, response=result)
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
return async_wrapper if is_async else sync_wrapper
|
|
142
|
+
|
|
143
|
+
return decorator
|
|
144
|
+
|
|
99
145
|
def mock( # noqa: C901, PLR0915
|
|
100
146
|
self,
|
|
101
|
-
mode: Literal["tool", "function"
|
|
147
|
+
mode: Literal["tool", "function"] = "tool",
|
|
102
148
|
expects_response: bool | None = None,
|
|
103
149
|
cache_response: bool | None = None,
|
|
104
150
|
) -> Callable:
|
|
105
151
|
"""Decorator for mocking tool calls."""
|
|
152
|
+
response_expectation = (
|
|
153
|
+
ResponseExpectation.NONE
|
|
154
|
+
if (expects_response is False or (expects_response is None and mode == "function"))
|
|
155
|
+
else ResponseExpectation.REQUIRED
|
|
156
|
+
if expects_response is True
|
|
157
|
+
else ResponseExpectation.AUTO
|
|
158
|
+
)
|
|
159
|
+
cache_response = cache_response or False
|
|
160
|
+
options = ToolCallOptions(
|
|
161
|
+
mode=mode, response_expectation=response_expectation, cache_response=cache_response
|
|
162
|
+
)
|
|
106
163
|
|
|
107
164
|
def decorator(func: Callable) -> Callable: # noqa: C901, PLR0915
|
|
108
165
|
"""Decorator for mocking tool calls."""
|
|
109
|
-
# Check if the original function is async
|
|
110
166
|
is_async = inspect.iscoroutinefunction(func)
|
|
111
167
|
|
|
112
|
-
def create_mock_payload(
|
|
113
|
-
*args: tuple[object, ...],
|
|
114
|
-
**kwargs: Any, # noqa: ANN401
|
|
115
|
-
) -> tuple[dict[str, Any], Any]:
|
|
116
|
-
"""Create the mock payload - shared logic for both sync and async."""
|
|
117
|
-
type_hints = get_type_hints(func)
|
|
118
|
-
|
|
119
|
-
# Extract return type object (not just the name)
|
|
120
|
-
return_type_obj = type_hints.pop("return", Any)
|
|
121
|
-
# Create parameter info
|
|
122
|
-
parameters = get_function_parameters(func, args, kwargs)
|
|
123
|
-
# Get function docstring
|
|
124
|
-
docstring = inspect.getdoc(func) or ""
|
|
125
|
-
nonlocal expects_response
|
|
126
|
-
if expects_response is None and mode == "function":
|
|
127
|
-
expects_response = False
|
|
128
|
-
# Prepare payload
|
|
129
|
-
# Convert expects_response to response_expectation enum
|
|
130
|
-
if expects_response is False:
|
|
131
|
-
response_expectation = ResponseExpectation.NONE
|
|
132
|
-
elif expects_response is True:
|
|
133
|
-
response_expectation = ResponseExpectation.REQUIRED
|
|
134
|
-
else:
|
|
135
|
-
response_expectation = ResponseExpectation.AUTO
|
|
136
|
-
|
|
137
|
-
payload = {
|
|
138
|
-
"session_id": self.session_id,
|
|
139
|
-
"response_expectation": response_expectation.value,
|
|
140
|
-
"cache_response": bool(cache_response) if cache_response is not None else False,
|
|
141
|
-
"tool_call": {
|
|
142
|
-
"function_name": func.__name__,
|
|
143
|
-
"parameters": parameters,
|
|
144
|
-
"return_type": json.dumps(extract_json_schema(return_type_obj)),
|
|
145
|
-
"docstring": docstring,
|
|
146
|
-
},
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return payload, return_type_obj
|
|
150
|
-
|
|
151
168
|
@wraps(func)
|
|
152
169
|
async def async_wrapper(
|
|
153
170
|
*args: tuple[object, ...],
|
|
154
171
|
**kwargs: Any, # noqa: ANN401
|
|
155
172
|
) -> object:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
"""Async wrapper."""
|
|
174
|
+
session_id = _session_id_context.get()
|
|
175
|
+
if not session_id:
|
|
159
176
|
return await func(*args, **kwargs)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
parameters = get_function_parameters(func, args, kwargs)
|
|
168
|
-
|
|
169
|
-
await log_tool_call_async(
|
|
170
|
-
session_id=session_id,
|
|
171
|
-
function_name=func.__name__,
|
|
172
|
-
parameters=parameters,
|
|
173
|
-
docstring=inspect.getdoc(func) or "",
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
# Execute the original function
|
|
177
|
-
result = await func(*args, **kwargs)
|
|
178
|
-
|
|
179
|
-
# Log the response
|
|
180
|
-
await log_tool_response_async(session_id=session_id, response=result)
|
|
181
|
-
|
|
182
|
-
return result
|
|
183
|
-
|
|
184
|
-
# Regular mock mode
|
|
185
|
-
api_client = get_api_client()
|
|
186
|
-
endpoint = api_client.tool_mock_endpoint
|
|
187
|
-
|
|
188
|
-
logger.info(f"Simulating function: {func.__name__}")
|
|
189
|
-
payload, return_type_obj = create_mock_payload(*args, **kwargs)
|
|
190
|
-
|
|
191
|
-
# Send request to endpoint
|
|
192
|
-
mock_result = await api_client.post_async(endpoint, payload)
|
|
193
|
-
logger.info(f"Mock response: {mock_result}")
|
|
194
|
-
|
|
195
|
-
if isinstance(mock_result, str):
|
|
196
|
-
with suppress(json.JSONDecodeError):
|
|
197
|
-
mock_result = json.loads(mock_result)
|
|
198
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
199
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
200
|
-
|
|
201
|
-
# Create a top-level span for the simulated mock call if OpenTelemetry is available
|
|
202
|
-
try:
|
|
203
|
-
from opentelemetry import trace # type: ignore[import-not-found]
|
|
204
|
-
|
|
205
|
-
tracer = trace.get_tracer("veris_ai.tool_mock")
|
|
206
|
-
span_name = f"mock.{func.__name__}"
|
|
207
|
-
with tracer.start_as_current_span(span_name) as span: # type: ignore[attr-defined]
|
|
208
|
-
span.set_attribute("veris_ai.session.id", self.session_id or "") # type: ignore[attr-defined]
|
|
209
|
-
span.set_attribute("veris_ai.mock.mode", mode) # type: ignore[attr-defined]
|
|
210
|
-
return await _execute_mock_logic(self.session_id)
|
|
211
|
-
except Exception:
|
|
212
|
-
# If OpenTelemetry is not available, run without span
|
|
213
|
-
return await _execute_mock_logic(self.session_id)
|
|
177
|
+
parameters = get_function_parameters(func, args, kwargs)
|
|
178
|
+
return mock_tool_call(
|
|
179
|
+
func,
|
|
180
|
+
session_id,
|
|
181
|
+
parameters,
|
|
182
|
+
options,
|
|
183
|
+
)
|
|
214
184
|
|
|
215
185
|
@wraps(func)
|
|
216
186
|
def sync_wrapper(
|
|
217
187
|
*args: tuple[object, ...],
|
|
218
188
|
**kwargs: Any, # noqa: ANN401
|
|
219
189
|
) -> object:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
190
|
+
"""Sync wrapper."""
|
|
191
|
+
session_id = _session_id_context.get()
|
|
192
|
+
if not session_id:
|
|
223
193
|
return func(*args, **kwargs)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
parameters = get_function_parameters(func, args, kwargs)
|
|
232
|
-
|
|
233
|
-
log_tool_call_sync(
|
|
234
|
-
session_id=session_id,
|
|
235
|
-
function_name=func.__name__,
|
|
236
|
-
parameters=parameters,
|
|
237
|
-
docstring=inspect.getdoc(func) or "",
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
# Execute the original function
|
|
241
|
-
result = func(*args, **kwargs)
|
|
242
|
-
|
|
243
|
-
# Log the response
|
|
244
|
-
log_tool_response_sync(session_id=session_id, response=result)
|
|
245
|
-
|
|
246
|
-
return result
|
|
247
|
-
|
|
248
|
-
# Regular mock mode
|
|
249
|
-
api_client = get_api_client()
|
|
250
|
-
endpoint = api_client.tool_mock_endpoint
|
|
251
|
-
|
|
252
|
-
logger.info(f"Simulating function: {func.__name__}")
|
|
253
|
-
payload, return_type_obj = create_mock_payload(*args, **kwargs)
|
|
254
|
-
|
|
255
|
-
# Send request to endpoint
|
|
256
|
-
mock_result = api_client.post_sync(endpoint, payload)
|
|
257
|
-
logger.info(f"Mock response: {mock_result}")
|
|
258
|
-
|
|
259
|
-
if isinstance(mock_result, str):
|
|
260
|
-
with suppress(json.JSONDecodeError):
|
|
261
|
-
mock_result = json.loads(mock_result)
|
|
262
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
263
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
264
|
-
|
|
265
|
-
# Create a top-level span for the simulated mock call if OpenTelemetry is available
|
|
266
|
-
try:
|
|
267
|
-
from opentelemetry import trace # type: ignore[import-not-found]
|
|
268
|
-
|
|
269
|
-
tracer = trace.get_tracer("veris_ai.tool_mock")
|
|
270
|
-
span_name = f"mock.{func.__name__}"
|
|
271
|
-
with tracer.start_as_current_span(span_name) as span: # type: ignore[attr-defined]
|
|
272
|
-
span.set_attribute("veris_ai.session.id", self.session_id or "") # type: ignore[attr-defined]
|
|
273
|
-
span.set_attribute("veris_ai.mock.mode", mode) # type: ignore[attr-defined]
|
|
274
|
-
return _execute_mock_logic(self.session_id)
|
|
275
|
-
except Exception:
|
|
276
|
-
# If OpenTelemetry is not available, run without span
|
|
277
|
-
return _execute_mock_logic(self.session_id)
|
|
194
|
+
parameters = get_function_parameters(func, args, kwargs)
|
|
195
|
+
return mock_tool_call(
|
|
196
|
+
func,
|
|
197
|
+
session_id,
|
|
198
|
+
parameters,
|
|
199
|
+
options,
|
|
200
|
+
)
|
|
278
201
|
|
|
279
202
|
# Return the appropriate wrapper based on whether the function is async
|
|
280
203
|
return async_wrapper if is_async else sync_wrapper
|
|
@@ -313,4 +236,46 @@ class VerisSDK:
|
|
|
313
236
|
return decorator
|
|
314
237
|
|
|
315
238
|
|
|
239
|
+
def mock_tool_call(
|
|
240
|
+
func: Callable,
|
|
241
|
+
session_id: str,
|
|
242
|
+
parameters: str,
|
|
243
|
+
options: ToolCallOptions | None = None,
|
|
244
|
+
) -> object:
|
|
245
|
+
"""Mock tool call."""
|
|
246
|
+
options = options or ToolCallOptions()
|
|
247
|
+
api_client = get_api_client()
|
|
248
|
+
endpoint = api_client.tool_mock_endpoint
|
|
249
|
+
|
|
250
|
+
logger.info(f"Simulating function: {func.__name__}")
|
|
251
|
+
|
|
252
|
+
type_hints = get_type_hints(func)
|
|
253
|
+
|
|
254
|
+
# Extract return type object (not just the name)
|
|
255
|
+
return_type_obj = type_hints.pop("return", Any)
|
|
256
|
+
# Get function docstring
|
|
257
|
+
docstring = inspect.getdoc(func) or ""
|
|
258
|
+
# Determine response expectation
|
|
259
|
+
payload = {
|
|
260
|
+
"session_id": session_id,
|
|
261
|
+
"response_expectation": options.response_expectation.value,
|
|
262
|
+
"cache_response": bool(options.cache_response),
|
|
263
|
+
"tool_call": {
|
|
264
|
+
"function_name": func.__name__,
|
|
265
|
+
"parameters": json.loads(parameters),
|
|
266
|
+
"return_type": json.dumps(extract_json_schema(return_type_obj)),
|
|
267
|
+
"docstring": docstring,
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
mock_result = api_client.post(endpoint, payload)
|
|
272
|
+
logger.info(f"Mock response: {mock_result}")
|
|
273
|
+
|
|
274
|
+
if isinstance(mock_result, str):
|
|
275
|
+
with suppress(json.JSONDecodeError):
|
|
276
|
+
mock_result = json.loads(mock_result)
|
|
277
|
+
return convert_to_type(mock_result, return_type_obj)
|
|
278
|
+
return convert_to_type(mock_result, return_type_obj)
|
|
279
|
+
|
|
280
|
+
|
|
316
281
|
veris = VerisSDK()
|
veris_ai/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
import json
|
|
2
3
|
import sys
|
|
3
4
|
import types
|
|
4
5
|
import typing
|
|
@@ -278,7 +279,7 @@ def extract_json_schema(target_type: Any) -> dict: # noqa: PLR0911, PLR0912, C9
|
|
|
278
279
|
|
|
279
280
|
def get_function_parameters(
|
|
280
281
|
func: Callable, args: tuple[object, ...], kwargs: dict[str, object]
|
|
281
|
-
) ->
|
|
282
|
+
) -> str:
|
|
282
283
|
"""Get the parameters for a function."""
|
|
283
284
|
sig = inspect.signature(func)
|
|
284
285
|
bound_args = sig.bind(*args, **kwargs)
|
|
@@ -286,4 +287,4 @@ def get_function_parameters(
|
|
|
286
287
|
_ = bound_args.arguments.pop("ctx", None)
|
|
287
288
|
_ = bound_args.arguments.pop("self", None)
|
|
288
289
|
_ = bound_args.arguments.pop("cls", None)
|
|
289
|
-
return bound_args.arguments
|
|
290
|
+
return json.dumps(bound_args.arguments)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-ai
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.1
|
|
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
|
|
@@ -168,7 +168,7 @@ async def your_function(param1: str, param2: int) -> dict:
|
|
|
168
168
|
return {"result": "actual implementation"}
|
|
169
169
|
|
|
170
170
|
# Spy mode: Executes function but logs calls/responses
|
|
171
|
-
@veris.
|
|
171
|
+
@veris.spy()
|
|
172
172
|
async def monitored_function(data: str) -> dict:
|
|
173
173
|
return process_data(data)
|
|
174
174
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
veris_ai/README.md,sha256=0EC-yWB8FtqAhtcE5Q204bS1JWJPIvfpp1bPDo6iwwc,3601
|
|
2
|
+
veris_ai/__init__.py,sha256=mFEF2pHXOAXoXuBh9IOQgqpKGvzLOsy8b0vUpMRJggU,2260
|
|
3
|
+
veris_ai/agents_wrapper.py,sha256=LmxPkOg3KLICgTrl0Bouy7enhyO4tF6_evq2cb8I_ig,10718
|
|
4
|
+
veris_ai/api_client.py,sha256=lpokGrDqC222eM8J65814a85DfEL_iAlHRatf9BFy2Y,2136
|
|
5
|
+
veris_ai/logging.py,sha256=ENduOHnc5UmzciTDZDJCZqUOXb5We-bdtwN9f9Qqwl0,1360
|
|
6
|
+
veris_ai/models.py,sha256=TiQJuME2pGDE6EY5J8TYKNSeajaN2Xk9ISsLoIwgtgU,502
|
|
7
|
+
veris_ai/observability.py,sha256=fDtWeUexfzaIQArE5YbWsja8Y-bcE_h0dXQWYbXbupY,4929
|
|
8
|
+
veris_ai/tool_mock.py,sha256=L2p6Yg8S98floahDTrf6cnmDqC6Dq3YKA7Q6RRvv7BQ,10253
|
|
9
|
+
veris_ai/utils.py,sha256=Ud4k2jKAJ6-nqSsFXIJWYrOmPGmvl5RSp36cQtgnMpg,9781
|
|
10
|
+
veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
|
|
11
|
+
veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
|
|
12
|
+
veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
|
|
13
|
+
veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
|
|
14
|
+
veris_ai-1.8.1.dist-info/METADATA,sha256=SCSuZSupecfwozColHl09XlE4Hy5W_R7xqHw8Wco560,9924
|
|
15
|
+
veris_ai-1.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
veris_ai-1.8.1.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
17
|
+
veris_ai-1.8.1.dist-info/RECORD,,
|
veris_ai-1.8.0.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
veris_ai/README.md,sha256=0EC-yWB8FtqAhtcE5Q204bS1JWJPIvfpp1bPDo6iwwc,3601
|
|
2
|
-
veris_ai/__init__.py,sha256=mFEF2pHXOAXoXuBh9IOQgqpKGvzLOsy8b0vUpMRJggU,2260
|
|
3
|
-
veris_ai/agents_wrapper.py,sha256=-8R2yTkEQjHQJxkWBK5tqMHzmlPjfDpybD9_UpsSjLk,9971
|
|
4
|
-
veris_ai/api_client.py,sha256=afvVvg9DXSHgnr0p411yXo3_8UM7EeiIoqm1q_goYgI,2602
|
|
5
|
-
veris_ai/logging.py,sha256=Dr7dQtTK_WvUAtSpif79Srf5YpSLwDVlWs0XsQGOdu4,2595
|
|
6
|
-
veris_ai/models.py,sha256=6HINPxNFCakCVPcyEbUswWkXwb2K4lF0A8g8EvTMal4,213
|
|
7
|
-
veris_ai/observability.py,sha256=fDtWeUexfzaIQArE5YbWsja8Y-bcE_h0dXQWYbXbupY,4929
|
|
8
|
-
veris_ai/tool_mock.py,sha256=77KxBy0mAxC58sXYs_jPHBT_2oEkXybsUGKzOOLH93w,13334
|
|
9
|
-
veris_ai/utils.py,sha256=bSutoMYQAboo9nHZ4yAsOGIDEoc1zaoIXCoJr4yTvBw,9771
|
|
10
|
-
veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
|
|
11
|
-
veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
|
|
12
|
-
veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
|
|
13
|
-
veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
|
|
14
|
-
veris_ai-1.8.0.dist-info/METADATA,sha256=r79b39D4sUnlT2sJWNxbMk2Y1ES4pRVLkaO0K7BYsDc,9935
|
|
15
|
-
veris_ai-1.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
-
veris_ai-1.8.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
17
|
-
veris_ai-1.8.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|