veris-ai 1.7.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/README.md +27 -2
- veris_ai/__init__.py +54 -1
- veris_ai/agents_wrapper.py +283 -0
- veris_ai/api_client.py +60 -0
- veris_ai/logging.py +13 -120
- veris_ai/models.py +11 -0
- veris_ai/observability.py +8 -10
- veris_ai/tool_mock.py +130 -225
- veris_ai/utils.py +16 -0
- {veris_ai-1.7.0.dist-info → veris_ai-1.8.1.dist-info}/METADATA +43 -11
- veris_ai-1.8.1.dist-info/RECORD +17 -0
- veris_ai-1.7.0.dist-info/RECORD +0 -15
- {veris_ai-1.7.0.dist-info → veris_ai-1.8.1.dist-info}/WHEEL +0 -0
- {veris_ai-1.7.0.dist-info → veris_ai-1.8.1.dist-info}/licenses/LICENSE +0 -0
veris_ai/README.md
CHANGED
|
@@ -49,8 +49,33 @@ Environment variables are processed in [`tool_mock.py`](tool_mock.py):
|
|
|
49
49
|
- `VERIS_ENDPOINT_URL`: Mock server endpoint
|
|
50
50
|
- `VERIS_MOCK_TIMEOUT`: Request timeout (default: 90s)
|
|
51
51
|
- `ENV`: Set to `"simulation"` for mock mode
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
|
|
53
|
+
### Observability (OTLP / Logfire)
|
|
54
|
+
|
|
55
|
+
When using the observability helpers (`init_observability`, `instrument_fastapi_app`), configure the following environment variables so traces export correctly and are attributed to the right service name:
|
|
56
|
+
|
|
57
|
+
- `OTEL_SERVICE_NAME` — e.g. `simulation-server` (keep consistent with any `VERIS_SERVICE_NAME` you use)
|
|
58
|
+
- `OTEL_EXPORTER_OTLP_ENDPOINT` — e.g. `https://logfire-api.pydantic.dev`
|
|
59
|
+
- `LOGFIRE_TOKEN` — API token for Logfire
|
|
60
|
+
- `OTEL_EXPORTER_OTLP_HEADERS` — e.g. `Authorization=Bearer <LOGFIRE_TOKEN>` (quote the value)
|
|
61
|
+
|
|
62
|
+
Minimal shell setup:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export OTEL_SERVICE_NAME="simulation-server"
|
|
66
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT="https://logfire-api.pydantic.dev"
|
|
67
|
+
export LOGFIRE_TOKEN="<your-token>"
|
|
68
|
+
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=${LOGFIRE_TOKEN}"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then in code:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from veris_ai import init_observability, instrument_fastapi_app
|
|
75
|
+
init_observability()
|
|
76
|
+
app = FastAPI()
|
|
77
|
+
instrument_fastapi_app(app)
|
|
78
|
+
```
|
|
54
79
|
|
|
55
80
|
## Development Notes
|
|
56
81
|
|
veris_ai/__init__.py
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
"""Veris AI Python SDK."""
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
3
5
|
__version__ = "0.1.0"
|
|
4
6
|
|
|
5
7
|
# Import lightweight modules that only use base dependencies
|
|
6
8
|
from .jaeger_interface import JaegerClient
|
|
7
9
|
from .models import ResponseExpectation
|
|
8
|
-
from .tool_mock import veris
|
|
9
10
|
from .observability import init_observability, instrument_fastapi_app
|
|
11
|
+
from .tool_mock import veris
|
|
12
|
+
|
|
13
|
+
# Lazy import for modules with heavy dependencies
|
|
14
|
+
_veris_runner = None
|
|
15
|
+
_VerisConfig = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def veris_runner(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
|
|
19
|
+
"""Lazy loader for the veris_runner function from agents_wrapper.
|
|
20
|
+
|
|
21
|
+
This function wraps OpenAI agents Runner.run to intercept tool calls
|
|
22
|
+
through the Veris SDK's mocking infrastructure.
|
|
23
|
+
|
|
24
|
+
This function requires the 'agents' extra dependencies:
|
|
25
|
+
pip install veris-ai[agents]
|
|
26
|
+
"""
|
|
27
|
+
global _veris_runner # noqa: PLW0603
|
|
28
|
+
if _veris_runner is None:
|
|
29
|
+
try:
|
|
30
|
+
from .agents_wrapper import veris_runner as _veris_runner_impl # noqa: PLC0415
|
|
31
|
+
|
|
32
|
+
_veris_runner = _veris_runner_impl
|
|
33
|
+
except ImportError as e:
|
|
34
|
+
error_msg = (
|
|
35
|
+
"The 'veris_runner' function requires additional dependencies. "
|
|
36
|
+
"Please install them with: pip install veris-ai[agents]"
|
|
37
|
+
)
|
|
38
|
+
raise ImportError(error_msg) from e
|
|
39
|
+
return _veris_runner(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __getattr__(name: str) -> Any: # noqa: ANN401
|
|
43
|
+
"""Lazy load VerisConfig class."""
|
|
44
|
+
global _VerisConfig # noqa: PLW0603
|
|
45
|
+
if name == "VerisConfig":
|
|
46
|
+
if _VerisConfig is None:
|
|
47
|
+
try:
|
|
48
|
+
from .agents_wrapper import VerisConfig as _VerisConfig_impl # noqa: PLC0415
|
|
49
|
+
|
|
50
|
+
_VerisConfig = _VerisConfig_impl
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
error_msg = (
|
|
53
|
+
"The 'VerisConfig' class requires additional dependencies. "
|
|
54
|
+
"Please install them with: pip install veris-ai[agents]"
|
|
55
|
+
)
|
|
56
|
+
raise ImportError(error_msg) from e
|
|
57
|
+
return _VerisConfig
|
|
58
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
59
|
+
raise AttributeError(msg)
|
|
60
|
+
|
|
10
61
|
|
|
11
62
|
__all__ = [
|
|
12
63
|
"veris",
|
|
@@ -14,4 +65,6 @@ __all__ = [
|
|
|
14
65
|
"ResponseExpectation",
|
|
15
66
|
"init_observability",
|
|
16
67
|
"instrument_fastapi_app",
|
|
68
|
+
"veris_runner",
|
|
69
|
+
"VerisConfig",
|
|
17
70
|
]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""OpenAI Agents wrapper for automatic tool mocking via Veris SDK."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agents import RunContextWrapper, RunResult, Runner
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from veris_ai import veris
|
|
11
|
+
from veris_ai.tool_mock import mock_tool_call
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _wrap(
|
|
17
|
+
include_tools: list[str] | None = None,
|
|
18
|
+
exclude_tools: list[str] | None = None,
|
|
19
|
+
) -> Callable:
|
|
20
|
+
"""Private wrapper for OpenAI agents Runner to intercept tool calls through Veris SDK.
|
|
21
|
+
|
|
22
|
+
This function transparently intercepts tool calls from OpenAI agents and
|
|
23
|
+
routes them through the Veris SDK's mocking infrastructure.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
include_tools: Optional list of tool names to intercept (only these if provided)
|
|
27
|
+
exclude_tools: Optional list of tool names to NOT intercept (these run normally)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A wrapped Runner.run function
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If both include_tools and exclude_tools are specified
|
|
34
|
+
ImportError: If agents package is not installed
|
|
35
|
+
"""
|
|
36
|
+
if include_tools and exclude_tools:
|
|
37
|
+
msg = "Cannot specify both include_tools and exclude_tools"
|
|
38
|
+
raise ValueError(msg)
|
|
39
|
+
|
|
40
|
+
def wrapped_run_func(run_func: Callable) -> Callable:
|
|
41
|
+
"""Inner wrapper that takes the actual Runner.run function."""
|
|
42
|
+
try:
|
|
43
|
+
from agents import FunctionTool # type: ignore[import-untyped] # noqa: PLC0415
|
|
44
|
+
except ImportError as e:
|
|
45
|
+
msg = "openai-agents package not installed. Install with: pip install veris-ai[agents]"
|
|
46
|
+
raise ImportError(msg) from e
|
|
47
|
+
|
|
48
|
+
async def wrapped_run(starting_agent: Any, input_text: str, **kwargs: Any) -> Any: # noqa: ANN401
|
|
49
|
+
"""Wrapped version of Runner.run that intercepts tool calls."""
|
|
50
|
+
# Store a mapping of tools to their original functions
|
|
51
|
+
tool_functions = {}
|
|
52
|
+
|
|
53
|
+
if hasattr(starting_agent, "tools") and starting_agent.tools:
|
|
54
|
+
for tool in starting_agent.tools:
|
|
55
|
+
if isinstance(tool, FunctionTool):
|
|
56
|
+
tool_name = getattr(tool, "name", None)
|
|
57
|
+
|
|
58
|
+
# Check if we should patch this tool
|
|
59
|
+
if tool_name and _should_intercept_tool(
|
|
60
|
+
tool_name, include_tools, exclude_tools
|
|
61
|
+
):
|
|
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
|
|
93
|
+
)
|
|
94
|
+
return await run_func(starting_agent, input_text, **kwargs)
|
|
95
|
+
|
|
96
|
+
return wrapped_run
|
|
97
|
+
|
|
98
|
+
return wrapped_run_func
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _should_intercept_tool(
|
|
102
|
+
tool_name: str,
|
|
103
|
+
include_tools: list[str] | None,
|
|
104
|
+
exclude_tools: list[str] | None,
|
|
105
|
+
) -> bool:
|
|
106
|
+
"""Determine if a tool should be intercepted based on include/exclude lists.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
tool_name: Name of the tool
|
|
110
|
+
include_tools: If provided, only these tools are intercepted
|
|
111
|
+
exclude_tools: If provided, these tools are NOT intercepted
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
True if the tool should be intercepted, False otherwise
|
|
115
|
+
"""
|
|
116
|
+
if include_tools:
|
|
117
|
+
return tool_name in include_tools
|
|
118
|
+
if exclude_tools:
|
|
119
|
+
return tool_name not in exclude_tools
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
|
|
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
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
on_invoke_tool: The on_invoke_tool function from FunctionTool
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The original user function if found, None otherwise
|
|
136
|
+
"""
|
|
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__:
|
|
153
|
+
try:
|
|
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
|
+
|
|
173
|
+
|
|
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.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
closure: The closure tuple to search
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The user function if found, None otherwise
|
|
182
|
+
"""
|
|
183
|
+
import inspect
|
|
184
|
+
|
|
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
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class VerisConfig(BaseModel):
|
|
228
|
+
"""Configuration for the Veris SDK."""
|
|
229
|
+
|
|
230
|
+
include_tools: list[str] | None = None
|
|
231
|
+
exclude_tools: list[str] | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def veris_runner(
|
|
235
|
+
starting_agent: Any, # noqa: ANN401
|
|
236
|
+
input_text: str,
|
|
237
|
+
veris_config: VerisConfig | None = None,
|
|
238
|
+
**kwargs: Any, # noqa: ANN401
|
|
239
|
+
) -> RunResult: # noqa: ANN401
|
|
240
|
+
"""Veris-wrapped version of OpenAI agents Runner.run.
|
|
241
|
+
|
|
242
|
+
This function wraps the OpenAI agents Runner.run to intercept tool calls
|
|
243
|
+
and route them through the Veris SDK's mocking infrastructure. It can be
|
|
244
|
+
used as a drop-in replacement for Runner.run with an additional veris_config parameter.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
starting_agent: The OpenAI agent to run
|
|
248
|
+
input_text: The input text to process
|
|
249
|
+
veris_config: Optional configuration for Veris SDK tool interception
|
|
250
|
+
**kwargs: Additional arguments to pass to Runner.run
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The result from Runner.run
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
```python
|
|
257
|
+
from veris_ai import veris_runner, VerisConfig
|
|
258
|
+
from agents import Agent, FunctionTool
|
|
259
|
+
|
|
260
|
+
# Define your agent with tools
|
|
261
|
+
agent = Agent(...)
|
|
262
|
+
|
|
263
|
+
# Use veris_runner instead of Runner.run
|
|
264
|
+
result = await veris_runner(agent, "Process this input")
|
|
265
|
+
|
|
266
|
+
# Or with specific tool configuration
|
|
267
|
+
config = VerisConfig(include_tools=["calculator", "search"])
|
|
268
|
+
result = await veris_runner(agent, "Calculate 2+2", veris_config=config)
|
|
269
|
+
```
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# Extract config values
|
|
273
|
+
include_tools = None
|
|
274
|
+
exclude_tools = None
|
|
275
|
+
if veris_config:
|
|
276
|
+
include_tools = veris_config.include_tools
|
|
277
|
+
exclude_tools = veris_config.exclude_tools
|
|
278
|
+
|
|
279
|
+
# Create the wrapped version of Runner.run with the config
|
|
280
|
+
wrapped_run = _wrap(include_tools=include_tools, exclude_tools=exclude_tools)(Runner.run)
|
|
281
|
+
|
|
282
|
+
# Execute the wrapped run function
|
|
283
|
+
return wrapped_run(starting_agent, input_text, **kwargs)
|
veris_ai/api_client.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Centralized API client for VERIS simulation endpoints."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimulatorAPIClient:
|
|
13
|
+
"""Centralized client for making requests to VERIS simulation endpoints."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize the API client with configuration from environment variables."""
|
|
17
|
+
self.base_url = os.getenv("VERIS_API_URL", "https://simulation.api.veris.ai/")
|
|
18
|
+
self.api_key = os.getenv("VERIS_API_KEY")
|
|
19
|
+
self.timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
20
|
+
|
|
21
|
+
def _build_headers(self) -> dict[str, str] | None:
|
|
22
|
+
"""Build headers including OpenTelemetry tracing and API key."""
|
|
23
|
+
headers: dict[str, str] | None = None
|
|
24
|
+
# Add API key header if available
|
|
25
|
+
if self.api_key:
|
|
26
|
+
if headers is None:
|
|
27
|
+
headers = {}
|
|
28
|
+
headers["x-api-key"] = self.api_key
|
|
29
|
+
|
|
30
|
+
return headers
|
|
31
|
+
|
|
32
|
+
def post(self, endpoint: str, payload: dict[str, Any]) -> Any: # noqa: ANN401
|
|
33
|
+
"""Make a synchronous POST request to the specified endpoint."""
|
|
34
|
+
headers = self._build_headers()
|
|
35
|
+
with httpx.Client(timeout=self.timeout) as client:
|
|
36
|
+
response = client.post(endpoint, json=payload, headers=headers)
|
|
37
|
+
response.raise_for_status()
|
|
38
|
+
return response.json() if response.content else None
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def tool_mock_endpoint(self) -> str:
|
|
42
|
+
"""Get the tool mock endpoint URL."""
|
|
43
|
+
return f"{self.base_url}/v2/tool_mock"
|
|
44
|
+
|
|
45
|
+
def get_log_tool_call_endpoint(self, session_id: str) -> str:
|
|
46
|
+
"""Get the log tool call endpoint URL."""
|
|
47
|
+
return f"{self.base_url}/v2/simulations/{session_id}/log_tool_call"
|
|
48
|
+
|
|
49
|
+
def get_log_tool_response_endpoint(self, session_id: str) -> str:
|
|
50
|
+
"""Get the log tool response endpoint URL."""
|
|
51
|
+
return f"{self.base_url}/v2/simulations/{session_id}/log_tool_response"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Global singleton instance
|
|
55
|
+
_api_client = SimulatorAPIClient()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_api_client() -> SimulatorAPIClient:
|
|
59
|
+
"""Get the global API client instance."""
|
|
60
|
+
return _api_client
|
veris_ai/logging.py
CHANGED
|
@@ -2,152 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
-
import os
|
|
6
5
|
from typing import Any
|
|
7
6
|
|
|
8
|
-
import
|
|
7
|
+
from veris_ai.api_client import get_api_client
|
|
9
8
|
|
|
10
9
|
logger = logging.getLogger(__name__)
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
def log_tool_call(
|
|
14
13
|
session_id: str,
|
|
15
14
|
function_name: str,
|
|
16
|
-
parameters:
|
|
17
|
-
docstring: str,
|
|
18
|
-
) -> None:
|
|
19
|
-
"""Log tool call asynchronously to the VERIS logging endpoint."""
|
|
20
|
-
base_url = os.getenv("VERIS_ENDPOINT_URL")
|
|
21
|
-
if not base_url:
|
|
22
|
-
logger.warning("VERIS_ENDPOINT_URL not set, skipping tool call logging")
|
|
23
|
-
return
|
|
24
|
-
base_url = base_url.rstrip("/")
|
|
25
|
-
|
|
26
|
-
endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_call"
|
|
27
|
-
payload = {
|
|
28
|
-
"function_name": function_name,
|
|
29
|
-
"parameters": parameters,
|
|
30
|
-
"docstring": docstring,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
headers: dict[str, str] | None = None
|
|
37
|
-
try:
|
|
38
|
-
from opentelemetry.propagate import get_global_textmap
|
|
39
|
-
|
|
40
|
-
headers = {}
|
|
41
|
-
get_global_textmap().inject(headers)
|
|
42
|
-
except Exception: # pragma: no cover - otel optional
|
|
43
|
-
headers = None
|
|
44
|
-
|
|
45
|
-
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
46
|
-
response = await client.post(endpoint, json=payload, headers=headers)
|
|
47
|
-
response.raise_for_status()
|
|
48
|
-
logger.debug(f"Tool call logged for {function_name}")
|
|
49
|
-
except Exception as e:
|
|
50
|
-
logger.warning(f"Failed to log tool call for {function_name}: {e}")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def log_tool_call_sync(
|
|
54
|
-
session_id: str,
|
|
55
|
-
function_name: str,
|
|
56
|
-
parameters: dict[str, Any],
|
|
15
|
+
parameters: str,
|
|
57
16
|
docstring: str,
|
|
58
17
|
) -> None:
|
|
59
18
|
"""Log tool call synchronously to the VERIS logging endpoint."""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
logger.warning("VERIS_ENDPOINT_URL not set, skipping tool call logging")
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_call"
|
|
19
|
+
api_client = get_api_client()
|
|
20
|
+
endpoint = api_client.get_log_tool_call_endpoint(session_id)
|
|
66
21
|
payload = {
|
|
67
22
|
"function_name": function_name,
|
|
68
|
-
"parameters": parameters,
|
|
23
|
+
"parameters": json.loads(parameters),
|
|
69
24
|
"docstring": docstring,
|
|
70
25
|
}
|
|
71
|
-
|
|
72
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
73
|
-
|
|
74
26
|
try:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
from opentelemetry.propagate import get_global_textmap # type: ignore[import-not-found]
|
|
78
|
-
|
|
79
|
-
headers = {}
|
|
80
|
-
get_global_textmap().inject(headers)
|
|
81
|
-
except Exception: # pragma: no cover - otel optional
|
|
82
|
-
headers = None
|
|
83
|
-
|
|
84
|
-
with httpx.Client(timeout=timeout) as client:
|
|
85
|
-
response = client.post(endpoint, json=payload, headers=headers)
|
|
86
|
-
response.raise_for_status()
|
|
87
|
-
logger.debug(f"Tool call logged for {function_name}")
|
|
27
|
+
api_client.post(endpoint, payload)
|
|
28
|
+
logger.debug(f"Tool call logged for {function_name}")
|
|
88
29
|
except Exception as e:
|
|
89
30
|
logger.warning(f"Failed to log tool call for {function_name}: {e}")
|
|
90
31
|
|
|
91
32
|
|
|
92
|
-
|
|
93
|
-
"""Log tool response asynchronously to the VERIS logging endpoint."""
|
|
94
|
-
base_url = os.getenv("VERIS_ENDPOINT_URL")
|
|
95
|
-
if not base_url:
|
|
96
|
-
logger.warning("VERIS_ENDPOINT_URL not set, skipping tool response logging")
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_response"
|
|
100
|
-
payload = {
|
|
101
|
-
"response": json.dumps(response, default=str),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
headers: dict[str, str] | None = None
|
|
108
|
-
try:
|
|
109
|
-
from opentelemetry.propagate import get_global_textmap # type: ignore[import-not-found]
|
|
110
|
-
|
|
111
|
-
headers = {}
|
|
112
|
-
get_global_textmap().inject(headers)
|
|
113
|
-
except Exception: # pragma: no cover - otel optional
|
|
114
|
-
headers = None
|
|
115
|
-
|
|
116
|
-
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
117
|
-
http_response = await client.post(endpoint, json=payload, headers=headers)
|
|
118
|
-
http_response.raise_for_status()
|
|
119
|
-
logger.debug("Tool response logged")
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger.warning(f"Failed to log tool response: {e}")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
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
|
|
125
34
|
"""Log tool response synchronously to the VERIS logging endpoint."""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
logger.warning("VERIS_ENDPOINT_URL not set, skipping tool response logging")
|
|
129
|
-
return
|
|
35
|
+
api_client = get_api_client()
|
|
36
|
+
endpoint = api_client.get_log_tool_response_endpoint(session_id)
|
|
130
37
|
|
|
131
|
-
endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_response"
|
|
132
38
|
payload = {
|
|
133
39
|
"response": json.dumps(response, default=str),
|
|
134
40
|
}
|
|
135
41
|
|
|
136
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
137
|
-
|
|
138
42
|
try:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
from opentelemetry.propagate import get_global_textmap # type: ignore[import-not-found]
|
|
142
|
-
|
|
143
|
-
headers = {}
|
|
144
|
-
get_global_textmap().inject(headers)
|
|
145
|
-
except Exception: # pragma: no cover - otel optional
|
|
146
|
-
headers = None
|
|
147
|
-
|
|
148
|
-
with httpx.Client(timeout=timeout) as client:
|
|
149
|
-
http_response = client.post(endpoint, json=payload, headers=headers)
|
|
150
|
-
http_response.raise_for_status()
|
|
151
|
-
logger.debug("Tool response logged")
|
|
43
|
+
api_client.post(endpoint, payload)
|
|
44
|
+
logger.debug("Tool response logged")
|
|
152
45
|
except Exception as e:
|
|
153
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/observability.py
CHANGED
|
@@ -7,12 +7,13 @@ enable consistent tracing without duplicating setup code.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import os
|
|
11
|
-
|
|
12
10
|
from fastapi import FastAPI
|
|
11
|
+
from logging import getLogger
|
|
12
|
+
|
|
13
|
+
logger = getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def init_observability(
|
|
16
|
+
def init_observability() -> None: # noqa: PLR0912
|
|
16
17
|
"""Initialize tracing/export and set W3C propagation.
|
|
17
18
|
|
|
18
19
|
- Initializes Traceloop if available (acts as OTel bootstrap/exporter)
|
|
@@ -22,17 +23,14 @@ def init_observability(service_name: str | None = None) -> None:
|
|
|
22
23
|
|
|
23
24
|
This function is safe to call even if instrumentation packages are not installed.
|
|
24
25
|
"""
|
|
25
|
-
|
|
26
|
-
resolved_service_name = service_name or os.getenv("VERIS_SERVICE_NAME", "veris-service")
|
|
27
|
-
|
|
28
|
-
# Initialize Traceloop SDK first (acts as OTel bootstrap)
|
|
29
26
|
try:
|
|
30
|
-
|
|
27
|
+
import logfire
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
logfire.configure(scrubbing=False)
|
|
30
|
+
logfire.instrument_openai_agents()
|
|
33
31
|
except Exception as e:
|
|
34
32
|
# Tracing is optional; continue without Traceloop
|
|
35
|
-
msg = "
|
|
33
|
+
msg = "Logfire not found: " + str(e)
|
|
36
34
|
raise RuntimeError(msg) from e
|
|
37
35
|
|
|
38
36
|
# Ensure W3C propagation (TraceContext + optional Baggage), tolerant to OTel versions
|
veris_ai/tool_mock.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import os
|
|
5
4
|
from collections.abc import Callable
|
|
6
5
|
from contextlib import suppress
|
|
7
6
|
from contextvars import ContextVar
|
|
@@ -13,16 +12,14 @@ from typing import (
|
|
|
13
12
|
get_type_hints,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
|
-
import httpx
|
|
17
15
|
|
|
18
16
|
from veris_ai.logging import (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
log_tool_response_async,
|
|
22
|
-
log_tool_response_sync,
|
|
17
|
+
log_tool_call,
|
|
18
|
+
log_tool_response,
|
|
23
19
|
)
|
|
24
|
-
from veris_ai.models import ResponseExpectation
|
|
25
|
-
from veris_ai.
|
|
20
|
+
from veris_ai.models import ResponseExpectation, ToolCallOptions
|
|
21
|
+
from veris_ai.api_client import get_api_client
|
|
22
|
+
from veris_ai.utils import convert_to_type, extract_json_schema, get_function_parameters
|
|
26
23
|
|
|
27
24
|
logger = logging.getLogger(__name__)
|
|
28
25
|
|
|
@@ -98,243 +95,109 @@ class VerisSDK:
|
|
|
98
95
|
**params_dict,
|
|
99
96
|
)
|
|
100
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
|
+
|
|
101
145
|
def mock( # noqa: C901, PLR0915
|
|
102
146
|
self,
|
|
103
|
-
mode: Literal["tool", "function"
|
|
147
|
+
mode: Literal["tool", "function"] = "tool",
|
|
104
148
|
expects_response: bool | None = None,
|
|
105
149
|
cache_response: bool | None = None,
|
|
106
150
|
) -> Callable:
|
|
107
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
|
+
)
|
|
108
163
|
|
|
109
164
|
def decorator(func: Callable) -> Callable: # noqa: C901, PLR0915
|
|
110
165
|
"""Decorator for mocking tool calls."""
|
|
111
|
-
# Check if the original function is async
|
|
112
166
|
is_async = inspect.iscoroutinefunction(func)
|
|
113
167
|
|
|
114
|
-
def create_mock_payload(
|
|
115
|
-
*args: tuple[object, ...],
|
|
116
|
-
**kwargs: dict[str, object],
|
|
117
|
-
) -> tuple[dict[str, Any], Any]:
|
|
118
|
-
"""Create the mock payload - shared logic for both sync and async."""
|
|
119
|
-
sig = inspect.signature(func)
|
|
120
|
-
type_hints = get_type_hints(func)
|
|
121
|
-
|
|
122
|
-
# Extract return type object (not just the name)
|
|
123
|
-
return_type_obj = type_hints.pop("return", Any)
|
|
124
|
-
# Create parameter info
|
|
125
|
-
params_info = {}
|
|
126
|
-
bound_args = sig.bind(*args, **kwargs)
|
|
127
|
-
bound_args.apply_defaults()
|
|
128
|
-
_ = bound_args.arguments.pop("ctx", None)
|
|
129
|
-
_ = bound_args.arguments.pop("self", None)
|
|
130
|
-
_ = bound_args.arguments.pop("cls", None)
|
|
131
|
-
|
|
132
|
-
for param_name, param_value in bound_args.arguments.items():
|
|
133
|
-
params_info[param_name] = {
|
|
134
|
-
"value": str(param_value),
|
|
135
|
-
"type": str(type_hints.get(param_name, Any)),
|
|
136
|
-
}
|
|
137
|
-
# Get function docstring
|
|
138
|
-
docstring = inspect.getdoc(func) or ""
|
|
139
|
-
nonlocal expects_response
|
|
140
|
-
if expects_response is None and mode == "function":
|
|
141
|
-
expects_response = False
|
|
142
|
-
# Prepare payload
|
|
143
|
-
# Convert expects_response to response_expectation enum
|
|
144
|
-
if expects_response is False:
|
|
145
|
-
response_expectation = ResponseExpectation.NONE
|
|
146
|
-
elif expects_response is True:
|
|
147
|
-
response_expectation = ResponseExpectation.REQUIRED
|
|
148
|
-
else:
|
|
149
|
-
response_expectation = ResponseExpectation.AUTO
|
|
150
|
-
|
|
151
|
-
payload = {
|
|
152
|
-
"session_id": self.session_id,
|
|
153
|
-
"response_expectation": response_expectation.value,
|
|
154
|
-
"cache_response": bool(cache_response) if cache_response is not None else False,
|
|
155
|
-
"tool_call": {
|
|
156
|
-
"function_name": func.__name__,
|
|
157
|
-
"parameters": params_info,
|
|
158
|
-
"return_type": json.dumps(extract_json_schema(return_type_obj)),
|
|
159
|
-
"docstring": docstring,
|
|
160
|
-
},
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return payload, return_type_obj
|
|
164
|
-
|
|
165
168
|
@wraps(func)
|
|
166
169
|
async def async_wrapper(
|
|
167
170
|
*args: tuple[object, ...],
|
|
168
|
-
**kwargs:
|
|
171
|
+
**kwargs: Any, # noqa: ANN401
|
|
169
172
|
) -> object:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
"""Async wrapper."""
|
|
174
|
+
session_id = _session_id_context.get()
|
|
175
|
+
if not session_id:
|
|
173
176
|
return await func(*args, **kwargs)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
sig = inspect.signature(func)
|
|
182
|
-
bound_args = sig.bind(*args, **kwargs)
|
|
183
|
-
bound_args.apply_defaults()
|
|
184
|
-
_ = bound_args.arguments.pop("ctx", None)
|
|
185
|
-
_ = bound_args.arguments.pop("self", None)
|
|
186
|
-
_ = bound_args.arguments.pop("cls", None)
|
|
187
|
-
|
|
188
|
-
await log_tool_call_async(
|
|
189
|
-
session_id=session_id,
|
|
190
|
-
function_name=func.__name__,
|
|
191
|
-
parameters=bound_args.arguments,
|
|
192
|
-
docstring=inspect.getdoc(func) or "",
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
# Execute the original function
|
|
196
|
-
result = await func(*args, **kwargs)
|
|
197
|
-
|
|
198
|
-
# Log the response
|
|
199
|
-
await log_tool_response_async(session_id=session_id, response=result)
|
|
200
|
-
|
|
201
|
-
return result
|
|
202
|
-
|
|
203
|
-
# Regular mock mode
|
|
204
|
-
base_url = os.getenv("VERIS_ENDPOINT_URL")
|
|
205
|
-
if not base_url:
|
|
206
|
-
error_msg = "VERIS_ENDPOINT_URL environment variable is not set"
|
|
207
|
-
raise ValueError(error_msg)
|
|
208
|
-
endpoint = f"{base_url.rstrip('/')}/api/v2/tool_mock"
|
|
209
|
-
# Default timeout of 30 seconds
|
|
210
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
211
|
-
|
|
212
|
-
logger.info(f"Simulating function: {func.__name__}")
|
|
213
|
-
payload, return_type_obj = create_mock_payload(*args, **kwargs)
|
|
214
|
-
|
|
215
|
-
# Send request to endpoint with timeout
|
|
216
|
-
# Inject current trace context headers if OpenTelemetry is available
|
|
217
|
-
headers: dict[str, str] | None = None
|
|
218
|
-
try:
|
|
219
|
-
from opentelemetry.propagate import get_global_textmap # type: ignore[import-not-found]
|
|
220
|
-
|
|
221
|
-
headers = {}
|
|
222
|
-
get_global_textmap().inject(headers)
|
|
223
|
-
except Exception: # pragma: no cover - otel optional
|
|
224
|
-
headers = None
|
|
225
|
-
|
|
226
|
-
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
227
|
-
response = await client.post(endpoint, json=payload, headers=headers)
|
|
228
|
-
response.raise_for_status()
|
|
229
|
-
mock_result = response.json()
|
|
230
|
-
logger.info(f"Mock response: {mock_result}")
|
|
231
|
-
|
|
232
|
-
if isinstance(mock_result, str):
|
|
233
|
-
with suppress(json.JSONDecodeError):
|
|
234
|
-
mock_result = json.loads(mock_result)
|
|
235
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
236
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
237
|
-
|
|
238
|
-
# Create a top-level span for the simulated mock call if OpenTelemetry is available
|
|
239
|
-
try:
|
|
240
|
-
from opentelemetry import trace # type: ignore[import-not-found]
|
|
241
|
-
|
|
242
|
-
tracer = trace.get_tracer("veris_ai.tool_mock")
|
|
243
|
-
span_name = f"mock.{func.__name__}"
|
|
244
|
-
with tracer.start_as_current_span(span_name) as span: # type: ignore[attr-defined]
|
|
245
|
-
span.set_attribute("veris_ai.session.id", self.session_id or "") # type: ignore[attr-defined]
|
|
246
|
-
span.set_attribute("veris_ai.mock.mode", mode) # type: ignore[attr-defined]
|
|
247
|
-
return await _execute_mock_logic(self.session_id)
|
|
248
|
-
except Exception:
|
|
249
|
-
# If OpenTelemetry is not available, run without span
|
|
250
|
-
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
|
+
)
|
|
251
184
|
|
|
252
185
|
@wraps(func)
|
|
253
186
|
def sync_wrapper(
|
|
254
187
|
*args: tuple[object, ...],
|
|
255
|
-
**kwargs:
|
|
188
|
+
**kwargs: Any, # noqa: ANN401
|
|
256
189
|
) -> object:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
190
|
+
"""Sync wrapper."""
|
|
191
|
+
session_id = _session_id_context.get()
|
|
192
|
+
if not session_id:
|
|
260
193
|
return func(*args, **kwargs)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
sig = inspect.signature(func)
|
|
269
|
-
bound_args = sig.bind(*args, **kwargs)
|
|
270
|
-
bound_args.apply_defaults()
|
|
271
|
-
_ = bound_args.arguments.pop("ctx", None)
|
|
272
|
-
_ = bound_args.arguments.pop("self", None)
|
|
273
|
-
_ = bound_args.arguments.pop("cls", None)
|
|
274
|
-
|
|
275
|
-
log_tool_call_sync(
|
|
276
|
-
session_id=session_id,
|
|
277
|
-
function_name=func.__name__,
|
|
278
|
-
parameters=bound_args.arguments,
|
|
279
|
-
docstring=inspect.getdoc(func) or "",
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
# Execute the original function
|
|
283
|
-
result = func(*args, **kwargs)
|
|
284
|
-
|
|
285
|
-
# Log the response
|
|
286
|
-
log_tool_response_sync(session_id=session_id, response=result)
|
|
287
|
-
|
|
288
|
-
return result
|
|
289
|
-
|
|
290
|
-
# Regular mock mode
|
|
291
|
-
base_url = os.getenv("VERIS_ENDPOINT_URL")
|
|
292
|
-
if not base_url:
|
|
293
|
-
error_msg = "VERIS_ENDPOINT_URL environment variable is not set"
|
|
294
|
-
raise ValueError(error_msg)
|
|
295
|
-
endpoint = f"{base_url.rstrip('/')}/api/v2/tool_mock"
|
|
296
|
-
# Default timeout of 30 seconds
|
|
297
|
-
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
|
|
298
|
-
|
|
299
|
-
logger.info(f"Simulating function: {func.__name__}")
|
|
300
|
-
payload, return_type_obj = create_mock_payload(*args, **kwargs)
|
|
301
|
-
|
|
302
|
-
# Send request to endpoint with timeout (synchronous)
|
|
303
|
-
# Inject current trace context headers if OpenTelemetry is available
|
|
304
|
-
headers: dict[str, str] | None = None
|
|
305
|
-
try:
|
|
306
|
-
from opentelemetry.propagate import get_global_textmap # type: ignore[import-not-found]
|
|
307
|
-
|
|
308
|
-
headers = {}
|
|
309
|
-
get_global_textmap().inject(headers)
|
|
310
|
-
except Exception: # pragma: no cover - otel optional
|
|
311
|
-
headers = None
|
|
312
|
-
|
|
313
|
-
with httpx.Client(timeout=timeout) as client:
|
|
314
|
-
response = client.post(endpoint, json=payload, headers=headers)
|
|
315
|
-
response.raise_for_status()
|
|
316
|
-
mock_result = response.json()
|
|
317
|
-
logger.info(f"Mock response: {mock_result}")
|
|
318
|
-
|
|
319
|
-
if isinstance(mock_result, str):
|
|
320
|
-
with suppress(json.JSONDecodeError):
|
|
321
|
-
mock_result = json.loads(mock_result)
|
|
322
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
323
|
-
return convert_to_type(mock_result, return_type_obj)
|
|
324
|
-
|
|
325
|
-
# Create a top-level span for the simulated mock call if OpenTelemetry is available
|
|
326
|
-
try:
|
|
327
|
-
from opentelemetry import trace # type: ignore[import-not-found]
|
|
328
|
-
|
|
329
|
-
tracer = trace.get_tracer("veris_ai.tool_mock")
|
|
330
|
-
span_name = f"mock.{func.__name__}"
|
|
331
|
-
with tracer.start_as_current_span(span_name) as span: # type: ignore[attr-defined]
|
|
332
|
-
span.set_attribute("veris_ai.session.id", self.session_id or "") # type: ignore[attr-defined]
|
|
333
|
-
span.set_attribute("veris_ai.mock.mode", mode) # type: ignore[attr-defined]
|
|
334
|
-
return _execute_mock_logic(self.session_id)
|
|
335
|
-
except Exception:
|
|
336
|
-
# If OpenTelemetry is not available, run without span
|
|
337
|
-
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
|
+
)
|
|
338
201
|
|
|
339
202
|
# Return the appropriate wrapper based on whether the function is async
|
|
340
203
|
return async_wrapper if is_async else sync_wrapper
|
|
@@ -351,7 +214,7 @@ class VerisSDK:
|
|
|
351
214
|
@wraps(func)
|
|
352
215
|
async def async_wrapper(
|
|
353
216
|
*args: tuple[object, ...],
|
|
354
|
-
**kwargs:
|
|
217
|
+
**kwargs: Any, # noqa: ANN401
|
|
355
218
|
) -> object:
|
|
356
219
|
if not self.session_id:
|
|
357
220
|
# If not in simulation mode, execute the original function
|
|
@@ -360,7 +223,7 @@ class VerisSDK:
|
|
|
360
223
|
return return_value
|
|
361
224
|
|
|
362
225
|
@wraps(func)
|
|
363
|
-
def sync_wrapper(*args: tuple[object, ...], **kwargs:
|
|
226
|
+
def sync_wrapper(*args: tuple[object, ...], **kwargs: Any) -> object: # noqa: ANN401
|
|
364
227
|
if not self.session_id:
|
|
365
228
|
# If not in simulation mode, execute the original function
|
|
366
229
|
return func(*args, **kwargs)
|
|
@@ -373,4 +236,46 @@ class VerisSDK:
|
|
|
373
236
|
return decorator
|
|
374
237
|
|
|
375
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
|
+
|
|
376
281
|
veris = VerisSDK()
|
veris_ai/utils.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
1
3
|
import sys
|
|
2
4
|
import types
|
|
3
5
|
import typing
|
|
4
6
|
from contextlib import suppress
|
|
5
7
|
from typing import Any, ForwardRef, Literal, NotRequired, Required, Union, get_args, get_origin
|
|
8
|
+
from collections.abc import Callable
|
|
6
9
|
|
|
7
10
|
from pydantic import BaseModel
|
|
8
11
|
|
|
@@ -272,3 +275,16 @@ def extract_json_schema(target_type: Any) -> dict: # noqa: PLR0911, PLR0912, C9
|
|
|
272
275
|
|
|
273
276
|
# Default case for unknown types
|
|
274
277
|
return {"type": "object"}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_function_parameters(
|
|
281
|
+
func: Callable, args: tuple[object, ...], kwargs: dict[str, object]
|
|
282
|
+
) -> str:
|
|
283
|
+
"""Get the parameters for a function."""
|
|
284
|
+
sig = inspect.signature(func)
|
|
285
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
286
|
+
bound_args.apply_defaults()
|
|
287
|
+
_ = bound_args.arguments.pop("ctx", None)
|
|
288
|
+
_ = bound_args.arguments.pop("self", None)
|
|
289
|
+
_ = bound_args.arguments.pop("cls", None)
|
|
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.
|
|
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
|
|
@@ -9,6 +9,7 @@ License-Expression: MIT
|
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: httpx>=0.24.0
|
|
12
|
+
Requires-Dist: logfire>=4.3.3
|
|
12
13
|
Requires-Dist: opentelemetry-api>=1.34.1
|
|
13
14
|
Requires-Dist: opentelemetry-exporter-otlp>=1.34.1
|
|
14
15
|
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.55b1
|
|
@@ -19,11 +20,12 @@ Requires-Dist: opentelemetry-instrumentation>=0.55b1
|
|
|
19
20
|
Requires-Dist: opentelemetry-sdk>=1.34.1
|
|
20
21
|
Requires-Dist: pydantic>=2.0.0
|
|
21
22
|
Requires-Dist: requests>=2.31.0
|
|
22
|
-
|
|
23
|
+
Provides-Extra: agents
|
|
24
|
+
Requires-Dist: openai-agents>=0.0.1; extra == 'agents'
|
|
23
25
|
Provides-Extra: dev
|
|
24
26
|
Requires-Dist: black>=23.7.0; extra == 'dev'
|
|
25
27
|
Requires-Dist: mypy>=1.5.1; extra == 'dev'
|
|
26
|
-
Requires-Dist: openai-agents>=0.
|
|
28
|
+
Requires-Dist: openai-agents>=0.2.5; extra == 'dev'
|
|
27
29
|
Requires-Dist: pre-commit>=3.3.3; extra == 'dev'
|
|
28
30
|
Requires-Dist: pytest-asyncio>=0.21.1; extra == 'dev'
|
|
29
31
|
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
@@ -45,7 +47,7 @@ A Python package for Veris AI tools with simulation capabilities and FastAPI MCP
|
|
|
45
47
|
## Quick Reference
|
|
46
48
|
|
|
47
49
|
**Purpose**: Tool mocking, tracing, and FastAPI MCP integration for AI agent development
|
|
48
|
-
**Core Components**: [`tool_mock`](#function-mocking) • [`observability`](#sdk-observability-helpers) • [`fastapi_mcp`](#fastapi-mcp-integration) • [`jaeger_interface`](#jaeger-trace-interface)
|
|
50
|
+
**Core Components**: [`tool_mock`](#function-mocking) • [`api_client`](src/veris_ai/api_client.py) • [`observability`](#sdk-observability-helpers) • [`fastapi_mcp`](#fastapi-mcp-integration) • [`jaeger_interface`](#jaeger-trace-interface)
|
|
49
51
|
**Deep Dive**: [`Module Architecture`](src/veris_ai/README.md) • [`Testing Guide`](tests/README.md) • [`Usage Examples`](examples/README.md)
|
|
50
52
|
**Source of Truth**: Implementation details in [`src/veris_ai/`](src/veris_ai/) source code
|
|
51
53
|
|
|
@@ -84,13 +86,14 @@ from veris_ai import init_observability, instrument_fastapi_app # Provided by S
|
|
|
84
86
|
|
|
85
87
|
| Variable | Purpose | Default |
|
|
86
88
|
|----------|---------|---------|
|
|
87
|
-
| `
|
|
89
|
+
| `VERIS_API_KEY` | API authentication key | None |
|
|
88
90
|
| `VERIS_MOCK_TIMEOUT` | Request timeout (seconds) | `90.0` |
|
|
89
91
|
| `ENV` | Set to `"simulation"` for mock mode | Production |
|
|
90
|
-
| `VERIS_SERVICE_NAME` | Tracing service name | Auto-detected |
|
|
91
|
-
| `VERIS_OTLP_ENDPOINT` | OpenTelemetry collector | *Required for tracing* |
|
|
92
92
|
|
|
93
|
-
**Configuration
|
|
93
|
+
**Advanced Configuration** (rarely needed):
|
|
94
|
+
- `VERIS_API_URL`: Override default API endpoint (defaults to production)
|
|
95
|
+
|
|
96
|
+
**Configuration Details**: See [`src/veris_ai/api_client.py`](src/veris_ai/api_client.py) for API configuration and [`src/veris_ai/tool_mock.py`](src/veris_ai/tool_mock.py) for environment handling logic.
|
|
94
97
|
|
|
95
98
|
|
|
96
99
|
### SDK Observability Helpers
|
|
@@ -102,7 +105,7 @@ from fastapi import FastAPI
|
|
|
102
105
|
from veris_ai import init_observability, instrument_fastapi_app
|
|
103
106
|
|
|
104
107
|
# Initialize tracing/export early (no-op if dependencies are absent)
|
|
105
|
-
init_observability(
|
|
108
|
+
init_observability()
|
|
106
109
|
|
|
107
110
|
app = FastAPI()
|
|
108
111
|
|
|
@@ -110,6 +113,35 @@ app = FastAPI()
|
|
|
110
113
|
instrument_fastapi_app(app)
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
#### Observability Environment
|
|
117
|
+
|
|
118
|
+
Set these environment variables to enable exporting traces via OTLP (Logfire) and ensure consistent service naming:
|
|
119
|
+
|
|
120
|
+
| Variable | Example | Notes |
|
|
121
|
+
|----------|---------|-------|
|
|
122
|
+
| `OTEL_SERVICE_NAME` | `simulation-server` | Should match `VERIS_SERVICE_NAME` used elsewhere to keep traces aligned |
|
|
123
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `https://logfire-api.pydantic.dev` | OTLP HTTP endpoint |
|
|
124
|
+
| `LOGFIRE_TOKEN` | `FILL_IN` | Logfire API token used by the exporter |
|
|
125
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | `'Authorization=FILL_IN'` | Include quotes to preserve the `=`; often `Authorization=Bearer <LOGFIRE_TOKEN>` |
|
|
126
|
+
|
|
127
|
+
Quick setup example:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
export OTEL_SERVICE_NAME="simulation-server"
|
|
131
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT="https://logfire-api.pydantic.dev"
|
|
132
|
+
export LOGFIRE_TOKEN="<your-token>"
|
|
133
|
+
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=${LOGFIRE_TOKEN}"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then initialize in code early in your process:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from veris_ai import init_observability, instrument_fastapi_app
|
|
140
|
+
init_observability()
|
|
141
|
+
app = FastAPI()
|
|
142
|
+
instrument_fastapi_app(app)
|
|
143
|
+
```
|
|
144
|
+
|
|
113
145
|
What this enables:
|
|
114
146
|
- Sets global W3C propagator (TraceContext + Baggage)
|
|
115
147
|
- Optionally instruments FastAPI, requests, httpx, MCP client if installed
|
|
@@ -136,7 +168,7 @@ async def your_function(param1: str, param2: int) -> dict:
|
|
|
136
168
|
return {"result": "actual implementation"}
|
|
137
169
|
|
|
138
170
|
# Spy mode: Executes function but logs calls/responses
|
|
139
|
-
@veris.
|
|
171
|
+
@veris.spy()
|
|
140
172
|
async def monitored_function(data: str) -> dict:
|
|
141
173
|
return process_data(data)
|
|
142
174
|
|
|
@@ -222,7 +254,7 @@ pytest --cov=veris_ai # Test with coverage
|
|
|
222
254
|
|
|
223
255
|
**Semantic Tag**: `module-architecture`
|
|
224
256
|
|
|
225
|
-
**Core Modules**: `tool_mock` (mocking), `jaeger_interface` (trace queries), `utils` (schema conversion)
|
|
257
|
+
**Core Modules**: `tool_mock` (mocking), `api_client` (centralized API), `jaeger_interface` (trace queries), `utils` (schema conversion)
|
|
226
258
|
|
|
227
259
|
**Complete Architecture**: See [`src/veris_ai/README.md`](src/veris_ai/README.md) for module overview, implementation flows, and configuration details.
|
|
228
260
|
|
|
@@ -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.7.0.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
veris_ai/README.md,sha256=iMSwSIrBO2zEdUhImkhZucUnTNO0kSQ-_zxWjZCmp4I,2730
|
|
2
|
-
veris_ai/__init__.py,sha256=Gh52-XfFpsxX37uqp8vuX0V3Np7f-Rlf5k3MMANu6e0,425
|
|
3
|
-
veris_ai/logging.py,sha256=srEdVimcQSCsnwGhyzCydehD2JW1cQmwSRd3X20NQs0,5233
|
|
4
|
-
veris_ai/models.py,sha256=6HINPxNFCakCVPcyEbUswWkXwb2K4lF0A8g8EvTMal4,213
|
|
5
|
-
veris_ai/observability.py,sha256=fG5ixAiVDykLCdHUScKQNCsePLphKQ7PcaKkAGBVdF0,5113
|
|
6
|
-
veris_ai/tool_mock.py,sha256=C1BANT3LCMKOIwJm_nOFuNdHnaO9_tbjeDTtBcI41RI,16512
|
|
7
|
-
veris_ai/utils.py,sha256=aqFFNuNiBehil6874nOHtU7G_bWHbFpVuubcz2AIx6I,9267
|
|
8
|
-
veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
|
|
9
|
-
veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
|
|
10
|
-
veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
|
|
11
|
-
veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
|
|
12
|
-
veris_ai-1.7.0.dist-info/METADATA,sha256=dmWFBpwU2MK9tjnJtlrkQdqnR9C0yn0Lhb1TMWb-XvA,8679
|
|
13
|
-
veris_ai-1.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
veris_ai-1.7.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
15
|
-
veris_ai-1.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|