veris-ai 1.0.0__py3-none-any.whl → 1.2.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/__init__.py +36 -1
- veris_ai/braintrust_tracing.py +282 -0
- veris_ai/jaeger_interface/README.md +137 -0
- veris_ai/jaeger_interface/__init__.py +39 -0
- veris_ai/jaeger_interface/client.py +233 -0
- veris_ai/jaeger_interface/models.py +79 -0
- veris_ai/models.py +11 -0
- veris_ai/tool_mock.py +162 -64
- veris_ai/utils.py +201 -1
- veris_ai-1.2.0.dist-info/METADATA +457 -0
- veris_ai-1.2.0.dist-info/RECORD +13 -0
- veris_ai-1.0.0.dist-info/METADATA +0 -239
- veris_ai-1.0.0.dist-info/RECORD +0 -7
- {veris_ai-1.0.0.dist-info → veris_ai-1.2.0.dist-info}/WHEEL +0 -0
- {veris_ai-1.0.0.dist-info → veris_ai-1.2.0.dist-info}/licenses/LICENSE +0 -0
veris_ai/utils.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import types
|
|
3
|
+
import typing
|
|
1
4
|
from contextlib import suppress
|
|
2
|
-
from typing import Any, Union, get_args, get_origin
|
|
5
|
+
from typing import Any, ForwardRef, Literal, NotRequired, Required, Union, get_args, get_origin
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
3
8
|
|
|
4
9
|
|
|
5
10
|
def convert_to_type(value: object, target_type: type) -> object:
|
|
@@ -15,6 +20,7 @@ def convert_to_type(value: object, target_type: type) -> object:
|
|
|
15
20
|
list: _convert_list,
|
|
16
21
|
dict: _convert_dict,
|
|
17
22
|
Union: _convert_union,
|
|
23
|
+
types.UnionType: _convert_union, # Handle Python 3.10+ union syntax (str | int)
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
# Use appropriate converter based on origin
|
|
@@ -69,3 +75,197 @@ def _convert_simple_type(value: object, target_type: type) -> object:
|
|
|
69
75
|
return target_type(**value)
|
|
70
76
|
|
|
71
77
|
return target_type(value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_forward_ref(ref: ForwardRef, module_context: types.ModuleType | None = None) -> Any: # noqa: ANN401
|
|
81
|
+
"""Resolve a ForwardRef to its actual type."""
|
|
82
|
+
if not isinstance(ref, ForwardRef):
|
|
83
|
+
return ref
|
|
84
|
+
|
|
85
|
+
# Try to evaluate the forward reference
|
|
86
|
+
try:
|
|
87
|
+
# Get the module's namespace for evaluation
|
|
88
|
+
namespace = dict(vars(module_context)) if module_context else {}
|
|
89
|
+
|
|
90
|
+
# Add common typing imports to namespace
|
|
91
|
+
namespace.update(
|
|
92
|
+
{
|
|
93
|
+
"Union": Union,
|
|
94
|
+
"Any": Any,
|
|
95
|
+
"Literal": Literal,
|
|
96
|
+
"Required": Required,
|
|
97
|
+
"NotRequired": NotRequired,
|
|
98
|
+
"List": list,
|
|
99
|
+
"Dict": dict,
|
|
100
|
+
"Optional": typing.Optional,
|
|
101
|
+
"Iterable": typing.Iterable,
|
|
102
|
+
"str": str,
|
|
103
|
+
"int": int,
|
|
104
|
+
"float": float,
|
|
105
|
+
"bool": bool,
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Try to import from the same module to resolve local references
|
|
110
|
+
if module_context and hasattr(module_context, "__name__"):
|
|
111
|
+
with suppress(Exception):
|
|
112
|
+
# Import all from the module to get access to local types
|
|
113
|
+
exec(f"from {module_context.__name__} import *", namespace) # noqa: S102
|
|
114
|
+
|
|
115
|
+
# Get the forward reference string
|
|
116
|
+
ref_string = ref.__forward_arg__ if hasattr(ref, "__forward_arg__") else str(ref)
|
|
117
|
+
|
|
118
|
+
# Try to evaluate the forward reference string
|
|
119
|
+
return eval(ref_string, namespace, namespace) # noqa: S307
|
|
120
|
+
except Exception:
|
|
121
|
+
# If we can't resolve it, return the ref itself
|
|
122
|
+
return ref
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _unwrap_required(field_type: Any) -> tuple[Any, bool]: # noqa: ANN401
|
|
126
|
+
"""Unwrap Required/NotRequired and return the inner type and whether it's required."""
|
|
127
|
+
origin = get_origin(field_type)
|
|
128
|
+
|
|
129
|
+
# Check if it's Required or NotRequired
|
|
130
|
+
if origin is Required:
|
|
131
|
+
args = get_args(field_type)
|
|
132
|
+
return args[0] if args else field_type, True
|
|
133
|
+
if origin is NotRequired:
|
|
134
|
+
args = get_args(field_type)
|
|
135
|
+
return args[0] if args else field_type, False
|
|
136
|
+
|
|
137
|
+
# Default to required for TypedDict fields
|
|
138
|
+
return field_type, True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def extract_json_schema(target_type: Any) -> dict: # noqa: PLR0911, PLR0912, C901, ANN401
|
|
142
|
+
"""Extract the JSON schema from a type or pydantic model.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
target_type: The type or pydantic model to extract the JSON schema from.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A dictionary representing the JSON schema.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> extract_json_schema(int)
|
|
152
|
+
{"type": "integer"}
|
|
153
|
+
|
|
154
|
+
>>> extract_json_schema(list[int])
|
|
155
|
+
{"type": "array", "items": {"type": "integer"}}
|
|
156
|
+
|
|
157
|
+
>>> extract_json_schema(list[User])
|
|
158
|
+
{"type": "array", "items": {"type": "object", "properties": {...}}}
|
|
159
|
+
"""
|
|
160
|
+
# Handle Pydantic BaseModel instances or classes
|
|
161
|
+
if isinstance(target_type, type) and issubclass(target_type, BaseModel):
|
|
162
|
+
return target_type.model_json_schema()
|
|
163
|
+
if isinstance(target_type, BaseModel):
|
|
164
|
+
return target_type.model_json_schema()
|
|
165
|
+
|
|
166
|
+
# Handle TypedDict
|
|
167
|
+
if (
|
|
168
|
+
isinstance(target_type, type)
|
|
169
|
+
and hasattr(target_type, "__annotations__")
|
|
170
|
+
and hasattr(target_type, "__total__")
|
|
171
|
+
):
|
|
172
|
+
# This is a TypedDict
|
|
173
|
+
properties = {}
|
|
174
|
+
required = []
|
|
175
|
+
|
|
176
|
+
# Get the module context for resolving forward references
|
|
177
|
+
module = sys.modules.get(target_type.__module__)
|
|
178
|
+
|
|
179
|
+
for field_name, field_type_annotation in target_type.__annotations__.items():
|
|
180
|
+
# Resolve forward references if present
|
|
181
|
+
resolved_type = field_type_annotation
|
|
182
|
+
if isinstance(resolved_type, ForwardRef):
|
|
183
|
+
resolved_type = _resolve_forward_ref(resolved_type, module)
|
|
184
|
+
|
|
185
|
+
# Unwrap Required/NotRequired
|
|
186
|
+
unwrapped_type, is_required = _unwrap_required(resolved_type)
|
|
187
|
+
|
|
188
|
+
# Extract schema for the unwrapped type
|
|
189
|
+
properties[field_name] = extract_json_schema(unwrapped_type)
|
|
190
|
+
|
|
191
|
+
# Add to required list if necessary
|
|
192
|
+
if is_required and getattr(target_type, "__total__", True):
|
|
193
|
+
required.append(field_name)
|
|
194
|
+
|
|
195
|
+
schema = {"type": "object", "properties": properties}
|
|
196
|
+
if required:
|
|
197
|
+
schema["required"] = required
|
|
198
|
+
return schema
|
|
199
|
+
|
|
200
|
+
# Handle built-in types
|
|
201
|
+
type_mapping = {
|
|
202
|
+
str: {"type": "string"},
|
|
203
|
+
int: {"type": "integer"},
|
|
204
|
+
float: {"type": "number"},
|
|
205
|
+
bool: {"type": "boolean"},
|
|
206
|
+
type(None): {"type": "null"},
|
|
207
|
+
Any: {}, # Empty schema for Any type
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if target_type in type_mapping:
|
|
211
|
+
return type_mapping[target_type]
|
|
212
|
+
|
|
213
|
+
# Handle generic types
|
|
214
|
+
origin = get_origin(target_type)
|
|
215
|
+
|
|
216
|
+
# Handle bare collection types
|
|
217
|
+
if target_type is list:
|
|
218
|
+
return {"type": "array"}
|
|
219
|
+
if target_type is dict:
|
|
220
|
+
return {"type": "object"}
|
|
221
|
+
if target_type is tuple:
|
|
222
|
+
return {"type": "array"}
|
|
223
|
+
|
|
224
|
+
# Handle Literal types
|
|
225
|
+
if origin is Literal:
|
|
226
|
+
values = get_args(target_type)
|
|
227
|
+
if len(values) == 1:
|
|
228
|
+
# Single literal value - use const
|
|
229
|
+
return {"const": values[0]}
|
|
230
|
+
# Multiple literal values - use enum
|
|
231
|
+
return {"enum": list(values)}
|
|
232
|
+
|
|
233
|
+
if origin is list:
|
|
234
|
+
args = get_args(target_type)
|
|
235
|
+
if args:
|
|
236
|
+
return {"type": "array", "items": extract_json_schema(args[0])}
|
|
237
|
+
return {"type": "array"}
|
|
238
|
+
|
|
239
|
+
if origin is dict:
|
|
240
|
+
args = get_args(target_type)
|
|
241
|
+
if len(args) == 2: # noqa: PLR2004
|
|
242
|
+
# For typed dicts like dict[str, int]
|
|
243
|
+
return {
|
|
244
|
+
"type": "object",
|
|
245
|
+
"additionalProperties": extract_json_schema(args[1]),
|
|
246
|
+
}
|
|
247
|
+
return {"type": "object"}
|
|
248
|
+
|
|
249
|
+
if origin is Union:
|
|
250
|
+
args = get_args(target_type)
|
|
251
|
+
# Handle Optional types (Union[T, None])
|
|
252
|
+
if len(args) == 2 and type(None) in args: # noqa: PLR2004
|
|
253
|
+
non_none_type = args[0] if args[1] is type(None) else args[1]
|
|
254
|
+
schema = extract_json_schema(non_none_type)
|
|
255
|
+
return {"anyOf": [schema, {"type": "null"}]}
|
|
256
|
+
# Handle general Union types
|
|
257
|
+
return {"anyOf": [extract_json_schema(arg) for arg in args]}
|
|
258
|
+
|
|
259
|
+
if origin is tuple:
|
|
260
|
+
args = get_args(target_type)
|
|
261
|
+
if args:
|
|
262
|
+
return {
|
|
263
|
+
"type": "array",
|
|
264
|
+
"prefixItems": [extract_json_schema(arg) for arg in args],
|
|
265
|
+
"minItems": len(args),
|
|
266
|
+
"maxItems": len(args),
|
|
267
|
+
}
|
|
268
|
+
return {"type": "array"}
|
|
269
|
+
|
|
270
|
+
# Default case for unknown types
|
|
271
|
+
return {"type": "object"}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veris-ai
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A Python package for Veris AI tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
|
|
7
|
+
Author-email: Mehdi Jamei <mehdi@veris.ai>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: httpx>=0.24.0
|
|
12
|
+
Requires-Dist: pydantic>=2.0.0
|
|
13
|
+
Requires-Dist: requests>=2.31.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: black>=23.7.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: mypy>=1.5.1; extra == 'dev'
|
|
17
|
+
Requires-Dist: openai-agents>=0.0.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: pre-commit>=3.3.3; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.21.1; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.11.4; extra == 'dev'
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi; extra == 'fastapi'
|
|
25
|
+
Requires-Dist: fastapi-mcp; extra == 'fastapi'
|
|
26
|
+
Provides-Extra: instrument
|
|
27
|
+
Requires-Dist: braintrust; extra == 'instrument'
|
|
28
|
+
Requires-Dist: opentelemetry-api; extra == 'instrument'
|
|
29
|
+
Requires-Dist: opentelemetry-exporter-otlp; extra == 'instrument'
|
|
30
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common; extra == 'instrument'
|
|
31
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc; extra == 'instrument'
|
|
32
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http; extra == 'instrument'
|
|
33
|
+
Requires-Dist: opentelemetry-sdk; extra == 'instrument'
|
|
34
|
+
Requires-Dist: wrapt; extra == 'instrument'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Veris AI Python SDK
|
|
38
|
+
|
|
39
|
+
A Python package for Veris AI tools with simulation capabilities and FastAPI MCP (Model Context Protocol) integration.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
You can install the package using `uv`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Install the package
|
|
47
|
+
uv add veris-ai
|
|
48
|
+
|
|
49
|
+
# Install with development dependencies
|
|
50
|
+
uv add "veris-ai[dev]"
|
|
51
|
+
|
|
52
|
+
# Install with FastAPI MCP integration
|
|
53
|
+
uv add "veris-ai[fastapi]"
|
|
54
|
+
|
|
55
|
+
# Install with tracing/instrumentation support
|
|
56
|
+
uv add "veris-ai[instrument]"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Import Options
|
|
60
|
+
|
|
61
|
+
The SDK supports flexible import patterns to minimize dependencies:
|
|
62
|
+
|
|
63
|
+
### Default Imports (Base Dependencies Only)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# These imports only require base dependencies (httpx, pydantic, requests)
|
|
67
|
+
from veris_ai import veris, JaegerClient
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Optional Imports (Require Extra Dependencies)
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# The instrument function requires the 'instrument' extra
|
|
74
|
+
# Install with: pip install veris-ai[instrument] or uv add "veris-ai[instrument]"
|
|
75
|
+
from veris_ai import instrument # Lazy-loaded, will error if deps missing
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Direct Submodule Imports
|
|
79
|
+
|
|
80
|
+
For maximum control over dependencies, you can import directly from submodules:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# Import only what you need
|
|
84
|
+
from veris_ai.tool_mock import veris
|
|
85
|
+
from veris_ai.jaeger_interface import JaegerClient
|
|
86
|
+
from veris_ai.braintrust_tracing import instrument # Requires [instrument] extra
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Environment Setup
|
|
90
|
+
|
|
91
|
+
The package requires the following environment variables:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Required: URL for the mock endpoint
|
|
95
|
+
VERIS_MOCK_ENDPOINT_URL=http://your-mock-endpoint.com
|
|
96
|
+
|
|
97
|
+
# Optional: Timeout in seconds (default: 30.0)
|
|
98
|
+
VERIS_MOCK_TIMEOUT=30.0
|
|
99
|
+
|
|
100
|
+
# Optional: Set to "simulation" to enable mock mode
|
|
101
|
+
ENV=simulation
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Tracing Integration
|
|
105
|
+
|
|
106
|
+
The SDK provides seamless integration with Braintrust and Jaeger/OpenTelemetry for distributed tracing.
|
|
107
|
+
|
|
108
|
+
### Setting up Tracing
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from veris_ai import instrument
|
|
112
|
+
|
|
113
|
+
# Initialize tracing instrumentation
|
|
114
|
+
instrument(
|
|
115
|
+
service_name="my-service", # or via VERIS_SERVICE_NAME env var
|
|
116
|
+
otlp_endpoint="http://localhost:4317" # or via VERIS_OTLP_ENDPOINT env var
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Session ID Tracking
|
|
121
|
+
|
|
122
|
+
When using the SDK with FastAPI MCP integration, session IDs are automatically embedded in all traces:
|
|
123
|
+
|
|
124
|
+
1. **Automatic Session Extraction**: When a request includes an Authorization header with a bearer token, the token is automatically used as the session ID.
|
|
125
|
+
|
|
126
|
+
2. **Trace Attribution**: All spans created during a request will include the `veris.session_id` attribute, allowing you to:
|
|
127
|
+
- Filter traces by session in Jaeger
|
|
128
|
+
- Correlate all operations within a single user session
|
|
129
|
+
- Debug issues specific to a particular session
|
|
130
|
+
|
|
131
|
+
3. **Example**:
|
|
132
|
+
```python
|
|
133
|
+
# FastAPI app with MCP integration
|
|
134
|
+
from fastapi import FastAPI
|
|
135
|
+
from veris_ai import veris, instrument
|
|
136
|
+
|
|
137
|
+
app = FastAPI()
|
|
138
|
+
|
|
139
|
+
# Set up tracing
|
|
140
|
+
instrument()
|
|
141
|
+
|
|
142
|
+
# Set up FastAPI MCP with session handling
|
|
143
|
+
veris.set_fastapi_mcp(
|
|
144
|
+
fastapi=app,
|
|
145
|
+
name="My API Server"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Now all traces will automatically include session IDs from bearer tokens
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
4. **Manual Session Management**: You can also manually set session IDs:
|
|
152
|
+
```python
|
|
153
|
+
veris.set_session_id("custom-session-123")
|
|
154
|
+
# All subsequent operations will be tagged with this session ID
|
|
155
|
+
veris.clear_session_id() # Clear when done
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The session ID tracking works seamlessly with both the mock decorators and the tracing system, providing end-to-end visibility of user sessions across your application.
|
|
159
|
+
|
|
160
|
+
## Python Version
|
|
161
|
+
|
|
162
|
+
This project requires Python 3.11 or higher. We use [pyenv](https://github.com/pyenv/pyenv) for Python version management.
|
|
163
|
+
|
|
164
|
+
To set up the correct Python version:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Install Python 3.11.0 using pyenv
|
|
168
|
+
pyenv install 3.11.0
|
|
169
|
+
|
|
170
|
+
# Set the local Python version for this project
|
|
171
|
+
pyenv local 3.11.0
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Usage
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from veris_ai import veris
|
|
178
|
+
|
|
179
|
+
@veris.mock()
|
|
180
|
+
async def your_function(param1: str, param2: int) -> dict:
|
|
181
|
+
"""
|
|
182
|
+
Your function documentation here.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
param1: Description of param1
|
|
186
|
+
param2: Description of param2
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
A dictionary containing the results
|
|
190
|
+
"""
|
|
191
|
+
# Your implementation here
|
|
192
|
+
return {"result": "actual implementation"}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
When `ENV=simulation` is set, the decorator will:
|
|
196
|
+
1. Capture the function signature, type hints, and docstring
|
|
197
|
+
2. Send this information to the mock endpoint
|
|
198
|
+
3. Convert the mock response to the expected return type
|
|
199
|
+
4. Return the mock result
|
|
200
|
+
|
|
201
|
+
When not in simulation mode, the original function will be executed normally.
|
|
202
|
+
|
|
203
|
+
### Stub Decorator
|
|
204
|
+
|
|
205
|
+
For simple test scenarios where you want to return a fixed value in simulation mode, use the `@veris.stub()` decorator:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from veris_ai import veris
|
|
209
|
+
|
|
210
|
+
@veris.stub(return_value={"status": "success", "data": [1, 2, 3]})
|
|
211
|
+
async def get_data() -> dict:
|
|
212
|
+
"""Get some data from external service."""
|
|
213
|
+
# This implementation is only called in production mode
|
|
214
|
+
return await fetch_from_api()
|
|
215
|
+
|
|
216
|
+
# In simulation mode: always returns {"status": "success", "data": [1, 2, 3]}
|
|
217
|
+
# In production mode: calls fetch_from_api()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The stub decorator is useful for:
|
|
221
|
+
- Quick prototyping with fixed responses
|
|
222
|
+
- Testing with predictable data
|
|
223
|
+
- Bypassing external dependencies in development
|
|
224
|
+
|
|
225
|
+
## FastAPI MCP Integration
|
|
226
|
+
|
|
227
|
+
The SDK provides integration with FastAPI applications through the Model Context Protocol (MCP). This allows your FastAPI endpoints to be exposed as MCP tools that can be used by AI agents.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from fastapi import FastAPI
|
|
231
|
+
from veris_ai import veris
|
|
232
|
+
|
|
233
|
+
app = FastAPI()
|
|
234
|
+
|
|
235
|
+
# Set up FastAPI MCP with automatic session handling
|
|
236
|
+
veris.set_fastapi_mcp(
|
|
237
|
+
fastapi=app,
|
|
238
|
+
name="My API Server",
|
|
239
|
+
description="My FastAPI application exposed as MCP tools",
|
|
240
|
+
describe_all_responses=True,
|
|
241
|
+
include_operations=["get_users", "create_user"],
|
|
242
|
+
exclude_tags=["internal"]
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# The MCP server is now available at veris.fastapi_mcp
|
|
246
|
+
mcp_server = veris.fastapi_mcp
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Configuration Options
|
|
250
|
+
|
|
251
|
+
The `set_fastapi_mcp()` method accepts the following parameters:
|
|
252
|
+
|
|
253
|
+
- **fastapi** (required): Your FastAPI application instance
|
|
254
|
+
- **name**: Custom name for the MCP server (defaults to app.title)
|
|
255
|
+
- **description**: Custom description (defaults to app.description)
|
|
256
|
+
- **describe_all_responses**: Whether to include all response schemas in tool descriptions
|
|
257
|
+
- **describe_full_response_schema**: Whether to include full JSON schema for responses
|
|
258
|
+
- **http_client**: Optional custom httpx.AsyncClient instance
|
|
259
|
+
- **include_operations**: List of operation IDs to include as MCP tools
|
|
260
|
+
- **exclude_operations**: List of operation IDs to exclude (can't use with include_operations)
|
|
261
|
+
- **include_tags**: List of tags to include as MCP tools
|
|
262
|
+
- **exclude_tags**: List of tags to exclude (can't use with include_tags)
|
|
263
|
+
- **auth_config**: Optional FastAPI MCP AuthConfig for custom authentication
|
|
264
|
+
|
|
265
|
+
### Session Management
|
|
266
|
+
|
|
267
|
+
The SDK automatically handles session management through OAuth2 authentication:
|
|
268
|
+
- Session IDs are extracted from bearer tokens
|
|
269
|
+
- Context is maintained across API calls
|
|
270
|
+
- Sessions can be managed programmatically:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
# Get current session ID
|
|
274
|
+
session_id = veris.session_id
|
|
275
|
+
|
|
276
|
+
# Set a new session ID
|
|
277
|
+
veris.set_session_id("new-session-123")
|
|
278
|
+
|
|
279
|
+
# Clear the session
|
|
280
|
+
veris.clear_session_id()
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Braintrust & Jaeger Tracing
|
|
284
|
+
|
|
285
|
+
The SDK includes a non-invasive instrumentation helper for sending traces to both Braintrust and a Jaeger-compatible OpenTelemetry (OTEL) collector simultaneously. This allows you to leverage Braintrust's powerful monitoring and evaluation tools while also storing and visualizing traces in your own Jaeger instance.
|
|
286
|
+
|
|
287
|
+
### Usage
|
|
288
|
+
|
|
289
|
+
To enable parallel tracing, simply import the `braintrust_tracing` module and call the `instrument()` function at the beginning of your application's lifecycle.
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from veris_ai import braintrust_tracing
|
|
293
|
+
|
|
294
|
+
# Enable Braintrust and Jaeger tracing
|
|
295
|
+
braintrust_tracing.instrument()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Configuration
|
|
299
|
+
|
|
300
|
+
The `instrument()` function is configured through environment variables:
|
|
301
|
+
|
|
302
|
+
- **`VERIS_SERVICE_NAME`** (required): The name of your service as it will appear in Jaeger.
|
|
303
|
+
- **`VERIS_OTLP_ENDPOINT`** (required): The gRPC endpoint of your Jaeger OTLP collector (e.g., `http://host.docker.internal:4317`).
|
|
304
|
+
|
|
305
|
+
When initialized, the SDK automatically patches the `agents` library to send traces to both systems without any further code changes required.
|
|
306
|
+
|
|
307
|
+
## Utility Functions
|
|
308
|
+
|
|
309
|
+
### extract_json_schema
|
|
310
|
+
|
|
311
|
+
The SDK provides a utility function to extract JSON schemas from Python types and Pydantic models:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from veris_ai.utils import extract_json_schema
|
|
315
|
+
from pydantic import BaseModel
|
|
316
|
+
from typing import List, Optional
|
|
317
|
+
|
|
318
|
+
# Extract schema from built-in types
|
|
319
|
+
int_schema = extract_json_schema(int)
|
|
320
|
+
# {"type": "integer"}
|
|
321
|
+
|
|
322
|
+
list_schema = extract_json_schema(List[str])
|
|
323
|
+
# {"type": "array", "items": {"type": "string"}}
|
|
324
|
+
|
|
325
|
+
# Extract schema from Pydantic models
|
|
326
|
+
class User(BaseModel):
|
|
327
|
+
name: str
|
|
328
|
+
age: int
|
|
329
|
+
email: Optional[str] = None
|
|
330
|
+
|
|
331
|
+
user_schema = extract_json_schema(User)
|
|
332
|
+
# Returns full JSON schema with properties, required fields, etc.
|
|
333
|
+
|
|
334
|
+
# Extract schema from TypedDict
|
|
335
|
+
from typing import TypedDict
|
|
336
|
+
|
|
337
|
+
class Config(TypedDict):
|
|
338
|
+
host: str
|
|
339
|
+
port: int
|
|
340
|
+
debug: bool
|
|
341
|
+
|
|
342
|
+
config_schema = extract_json_schema(Config)
|
|
343
|
+
# {"type": "object", "properties": {...}, "required": ["host", "port", "debug"]}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
This function supports:
|
|
347
|
+
- Built-in types (str, int, float, bool, None)
|
|
348
|
+
- Generic types (List, Dict, Union, Optional, Literal)
|
|
349
|
+
- Pydantic models
|
|
350
|
+
- TypedDict classes
|
|
351
|
+
- Forward references and complex nested types
|
|
352
|
+
|
|
353
|
+
## Project Structure
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
src/veris_ai/
|
|
357
|
+
├── __init__.py # Main entry point, exports the veris instance
|
|
358
|
+
├── tool_mock.py # VerisSDK class with @veris.mock() decorator
|
|
359
|
+
└── utils.py # Type conversion and JSON schema utilities
|
|
360
|
+
|
|
361
|
+
tests/
|
|
362
|
+
├── conftest.py # Pytest fixtures
|
|
363
|
+
├── test_tool_mock.py # Tests for VerisSDK functionality
|
|
364
|
+
└── test_utils.py # Tests for type conversion and schema extraction
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
This project uses `pyproject.toml` for dependency management and `uv` for package installation.
|
|
370
|
+
|
|
371
|
+
### Development Dependencies
|
|
372
|
+
|
|
373
|
+
To install the package with development dependencies:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
uv add "veris-ai[dev]"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This will install the following development tools:
|
|
380
|
+
- **Ruff**: Fast Python linter
|
|
381
|
+
- **pytest**: Testing framework
|
|
382
|
+
- **pytest-asyncio**: Async support for pytest
|
|
383
|
+
- **pytest-cov**: Coverage reporting for pytest
|
|
384
|
+
- **black**: Code formatter
|
|
385
|
+
- **mypy**: Static type checker
|
|
386
|
+
- **pre-commit**: Git hooks for code quality
|
|
387
|
+
|
|
388
|
+
### Code Quality
|
|
389
|
+
|
|
390
|
+
This project uses [Ruff](https://github.com/charliermarsh/ruff) for linting and code quality checks. Ruff is a fast Python linter written in Rust.
|
|
391
|
+
|
|
392
|
+
To run Ruff:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Lint code
|
|
396
|
+
ruff check .
|
|
397
|
+
|
|
398
|
+
# Auto-fix linting issues
|
|
399
|
+
ruff check --fix .
|
|
400
|
+
|
|
401
|
+
# Format code
|
|
402
|
+
ruff format .
|
|
403
|
+
|
|
404
|
+
# Check formatting only
|
|
405
|
+
ruff format --check .
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The Ruff configuration is defined in `pyproject.toml` under the `[tool.ruff]` section.
|
|
409
|
+
|
|
410
|
+
### Running Tests
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
# Run all tests with coverage
|
|
414
|
+
pytest tests/ --cov=veris_ai --cov-report=xml --cov-report=term-missing
|
|
415
|
+
|
|
416
|
+
# Run specific test file
|
|
417
|
+
pytest tests/test_tool_mock.py
|
|
418
|
+
|
|
419
|
+
# Run tests with verbose output
|
|
420
|
+
pytest -v tests/
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Type Checking
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Run static type checking
|
|
427
|
+
mypy src/veris_ai tests
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## License
|
|
431
|
+
|
|
432
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
433
|
+
|
|
434
|
+
## Jaeger Trace Interface
|
|
435
|
+
|
|
436
|
+
A lightweight, fully-typed wrapper around the Jaeger **Query Service** HTTP API lives under `veris_ai.jaeger_interface`.
|
|
437
|
+
It allows you to **search and retrieve traces** from both Jaeger's default storage back-ends as well as **OpenSearch**, with additional **client-side span filtering** capabilities.
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
from veris_ai.jaeger_interface import JaegerClient
|
|
441
|
+
|
|
442
|
+
client = JaegerClient("http://localhost:16686")
|
|
443
|
+
|
|
444
|
+
# Search with trace-level filters (server-side)
|
|
445
|
+
traces = client.search(service="veris-agent", limit=2, tags={"error": "true"})
|
|
446
|
+
|
|
447
|
+
# Search with span-level filters (client-side, OR logic)
|
|
448
|
+
filtered = client.search(
|
|
449
|
+
service="veris-agent",
|
|
450
|
+
limit=10,
|
|
451
|
+
span_tags={"http.status_code": 500, "db.error": "timeout"}
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
first_trace = client.get_trace(traces.data[0].traceID)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
See `src/veris_ai/jaeger_interface/README.md` for a complete walkthrough and API reference.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
veris_ai/__init__.py,sha256=FkpF1jAEX9y5mZjICuxv0-uc_pTgWuCzK5D5fEQ-b8s,1195
|
|
2
|
+
veris_ai/braintrust_tracing.py,sha256=0i-HR6IuK3-Q5ujMjT1FojxESRZLgqEvJ44bfJfDHaw,11938
|
|
3
|
+
veris_ai/models.py,sha256=wj1avahszKrErFbWVar9xvFrp_pVkQUCaJUc_gFzu3k,216
|
|
4
|
+
veris_ai/tool_mock.py,sha256=UO-6tYvfreLXfOxHFKFl-GJ1nI7bLoz93uuv_yBehWc,10507
|
|
5
|
+
veris_ai/utils.py,sha256=3R2J9ko5t1UATiF1R6Hox9IPbtUz59EHsMDFg1-i7sk,9208
|
|
6
|
+
veris_ai/jaeger_interface/README.md,sha256=te5z3MWLqd2ECVV-a0MImBwTKRgQuuSuDB3bwIIsHi0,4437
|
|
7
|
+
veris_ai/jaeger_interface/__init__.py,sha256=d873a0zq3eUYU2Y77MtdjCwIARjAsAP7WDqGXDMWpYs,1158
|
|
8
|
+
veris_ai/jaeger_interface/client.py,sha256=AU9qP_pzutwantcLiKaml5JW_AHrXHH7X-ALYVkbNZc,8777
|
|
9
|
+
veris_ai/jaeger_interface/models.py,sha256=ZYL2vlJbObAiAowsashuAuT6tUV9HaEmGKuxLPi_ye8,1876
|
|
10
|
+
veris_ai-1.2.0.dist-info/METADATA,sha256=ZmgJqKZUnL2G0ANQxX6SiYX2UK3HqeG7FjGgCttAQPM,13832
|
|
11
|
+
veris_ai-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
veris_ai-1.2.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
13
|
+
veris_ai-1.2.0.dist-info/RECORD,,
|