veris-ai 0.2.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of veris-ai might be problematic. Click here for more details.
- veris_ai/tool_mock.py +81 -64
- veris_ai/utils.py +71 -0
- {veris_ai-0.2.0.dist-info → veris_ai-1.0.0.dist-info}/METADATA +107 -5
- veris_ai-1.0.0.dist-info/RECORD +7 -0
- veris_ai-0.2.0.dist-info/RECORD +0 -6
- {veris_ai-0.2.0.dist-info → veris_ai-1.0.0.dist-info}/WHEEL +0 -0
- {veris_ai-0.2.0.dist-info → veris_ai-1.0.0.dist-info}/licenses/LICENSE +0 -0
veris_ai/tool_mock.py
CHANGED
|
@@ -4,65 +4,91 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from contextlib import suppress
|
|
7
|
+
from contextvars import ContextVar
|
|
7
8
|
from functools import wraps
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
TypeVar,
|
|
12
|
+
get_type_hints,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
import httpx
|
|
11
16
|
|
|
17
|
+
from veris_ai.utils import convert_to_type
|
|
18
|
+
|
|
12
19
|
logger = logging.getLogger(__name__)
|
|
13
20
|
|
|
14
|
-
T = TypeVar("T")
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
# Context variable to store session_id for each call
|
|
24
|
+
_session_id_context: ContextVar[str | None] = ContextVar("veris_session_id", default=None)
|
|
15
25
|
|
|
16
26
|
|
|
17
|
-
class
|
|
27
|
+
class VerisSDK:
|
|
18
28
|
"""Class for mocking tool calls."""
|
|
19
29
|
|
|
20
30
|
def __init__(self) -> None:
|
|
21
31
|
"""Initialize the ToolMock class."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
self._mcp = None
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def session_id(self) -> str | None:
|
|
36
|
+
"""Get the session_id from context variable."""
|
|
37
|
+
return _session_id_context.get()
|
|
38
|
+
|
|
39
|
+
def set_session_id(self, session_id: str) -> None:
|
|
40
|
+
"""Set the session_id in context variable."""
|
|
41
|
+
_session_id_context.set(session_id)
|
|
42
|
+
logger.info(f"Session ID set to {session_id}")
|
|
43
|
+
|
|
44
|
+
def clear_session_id(self) -> None:
|
|
45
|
+
"""Clear the session_id from context variable."""
|
|
46
|
+
_session_id_context.set(None)
|
|
47
|
+
logger.info("Session ID cleared")
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def fastapi_mcp(self) -> Any | None: # noqa: ANN401
|
|
51
|
+
"""Get the FastAPI MCP server."""
|
|
52
|
+
return self._mcp
|
|
53
|
+
|
|
54
|
+
def set_fastapi_mcp(self, **params_dict: Any) -> None: # noqa: ANN401
|
|
55
|
+
"""Set the FastAPI MCP server."""
|
|
56
|
+
from fastapi import Depends, Request # noqa: PLC0415
|
|
57
|
+
from fastapi.security import OAuth2PasswordBearer # noqa: PLC0415
|
|
58
|
+
from fastapi_mcp import ( # type: ignore[import-untyped] # noqa: PLC0415
|
|
59
|
+
AuthConfig,
|
|
60
|
+
FastApiMCP,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
64
|
+
|
|
65
|
+
async def authenticate_request(
|
|
66
|
+
_: Request,
|
|
67
|
+
token: str = Depends(oauth2_scheme), # noqa: ARG001
|
|
68
|
+
) -> None:
|
|
69
|
+
self.set_session_id(token)
|
|
70
|
+
|
|
71
|
+
# Create auth config with dependencies
|
|
72
|
+
auth_config = AuthConfig(
|
|
73
|
+
dependencies=[Depends(authenticate_request)],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Merge the provided params with our auth config
|
|
77
|
+
if "auth_config" in params_dict:
|
|
78
|
+
# Merge the provided auth config with our dependencies
|
|
79
|
+
provided_auth_config = params_dict.pop("auth_config")
|
|
80
|
+
if provided_auth_config.dependencies:
|
|
81
|
+
auth_config.dependencies.extend(provided_auth_config.dependencies)
|
|
82
|
+
# Copy other auth config properties if they exist
|
|
83
|
+
for field, value in provided_auth_config.model_dump(exclude_none=True).items():
|
|
84
|
+
if field != "dependencies" and hasattr(auth_config, field):
|
|
85
|
+
setattr(auth_config, field, value)
|
|
86
|
+
|
|
87
|
+
# Create the FastApiMCP instance with merged parameters
|
|
88
|
+
self._mcp = FastApiMCP(
|
|
89
|
+
auth_config=auth_config,
|
|
90
|
+
**params_dict,
|
|
91
|
+
)
|
|
66
92
|
|
|
67
93
|
def mock(self, func: Callable) -> Callable:
|
|
68
94
|
"""Decorator for mocking tool calls."""
|
|
@@ -83,7 +109,6 @@ class ToolMock:
|
|
|
83
109
|
if env_mode != "simulation":
|
|
84
110
|
# If not in simulation mode, execute the original function
|
|
85
111
|
return await func(*args, **kwargs)
|
|
86
|
-
|
|
87
112
|
logger.info(f"Simulating function: {func.__name__}")
|
|
88
113
|
sig = inspect.signature(func)
|
|
89
114
|
type_hints = get_type_hints(func)
|
|
@@ -96,14 +121,6 @@ class ToolMock:
|
|
|
96
121
|
bound_args = sig.bind(*args, **kwargs)
|
|
97
122
|
bound_args.apply_defaults()
|
|
98
123
|
|
|
99
|
-
ctx = bound_args.arguments.pop("ctx", None)
|
|
100
|
-
session_id = None
|
|
101
|
-
if ctx:
|
|
102
|
-
try:
|
|
103
|
-
session_id = ctx.request_context.lifespan_context.session_id
|
|
104
|
-
except AttributeError:
|
|
105
|
-
logger.warning("Cannot get session_id from context.")
|
|
106
|
-
|
|
107
124
|
for param_name, param_value in bound_args.arguments.items():
|
|
108
125
|
params_info[param_name] = {
|
|
109
126
|
"value": param_value,
|
|
@@ -114,7 +131,7 @@ class ToolMock:
|
|
|
114
131
|
docstring = inspect.getdoc(func) or ""
|
|
115
132
|
# Prepare payload
|
|
116
133
|
payload = {
|
|
117
|
-
"session_id": session_id,
|
|
134
|
+
"session_id": self.session_id,
|
|
118
135
|
"tool_call": {
|
|
119
136
|
"function_name": func.__name__,
|
|
120
137
|
"parameters": params_info,
|
|
@@ -130,15 +147,15 @@ class ToolMock:
|
|
|
130
147
|
mock_result = response.json()["result"]
|
|
131
148
|
logger.info(f"Mock response: {mock_result}")
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
# Parse the mock result if it's a string
|
|
151
|
+
if isinstance(mock_result, str):
|
|
152
|
+
with suppress(json.JSONDecodeError):
|
|
153
|
+
mock_result = json.loads(mock_result)
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
# Convert the mock result to the expected return type
|
|
156
|
+
return convert_to_type(mock_result, return_type_obj)
|
|
140
157
|
|
|
141
158
|
return wrapper
|
|
142
159
|
|
|
143
160
|
|
|
144
|
-
veris =
|
|
161
|
+
veris = VerisSDK()
|
veris_ai/utils.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from typing import Any, Union, get_args, get_origin
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def convert_to_type(value: object, target_type: type) -> object:
|
|
6
|
+
"""Convert a value to the specified type."""
|
|
7
|
+
# Special case: Any type returns value as-is
|
|
8
|
+
if target_type is Any:
|
|
9
|
+
return value
|
|
10
|
+
|
|
11
|
+
origin = get_origin(target_type)
|
|
12
|
+
|
|
13
|
+
# Define conversion strategies for different type origins
|
|
14
|
+
converters = {
|
|
15
|
+
list: _convert_list,
|
|
16
|
+
dict: _convert_dict,
|
|
17
|
+
Union: _convert_union,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Use appropriate converter based on origin
|
|
21
|
+
if origin in converters:
|
|
22
|
+
return converters[origin](value, target_type)
|
|
23
|
+
|
|
24
|
+
# Handle primitives and custom types
|
|
25
|
+
return _convert_simple_type(value, target_type)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _convert_list(value: object, target_type: type) -> list:
|
|
29
|
+
"""Convert a value to a typed list."""
|
|
30
|
+
if not isinstance(value, list):
|
|
31
|
+
error_msg = f"Expected list but got {type(value)}"
|
|
32
|
+
raise ValueError(error_msg)
|
|
33
|
+
|
|
34
|
+
item_type = get_args(target_type)[0]
|
|
35
|
+
return [convert_to_type(item, item_type) for item in value]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _convert_dict(value: object, target_type: type) -> dict:
|
|
39
|
+
"""Convert a value to a typed dict."""
|
|
40
|
+
if not isinstance(value, dict):
|
|
41
|
+
error_msg = f"Expected dict but got {type(value)}"
|
|
42
|
+
raise ValueError(error_msg)
|
|
43
|
+
|
|
44
|
+
key_type, value_type = get_args(target_type)
|
|
45
|
+
return {convert_to_type(k, key_type): convert_to_type(v, value_type) for k, v in value.items()}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _convert_union(value: object, target_type: type) -> object:
|
|
49
|
+
"""Try to convert value to one of the union types."""
|
|
50
|
+
union_types = get_args(target_type)
|
|
51
|
+
|
|
52
|
+
for possible_type in union_types:
|
|
53
|
+
with suppress(ValueError, TypeError):
|
|
54
|
+
return convert_to_type(value, possible_type)
|
|
55
|
+
|
|
56
|
+
error_msg = f"Could not convert {value} to any of the union types {union_types}"
|
|
57
|
+
raise ValueError(error_msg)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _convert_simple_type(value: object, target_type: type) -> object:
|
|
61
|
+
"""Convert to primitive or custom types."""
|
|
62
|
+
# Primitive types
|
|
63
|
+
if target_type in (str, int, float, bool):
|
|
64
|
+
return target_type(value)
|
|
65
|
+
|
|
66
|
+
# Custom types - try kwargs for dicts, then direct instantiation
|
|
67
|
+
if isinstance(value, dict):
|
|
68
|
+
with suppress(TypeError):
|
|
69
|
+
return target_type(**value)
|
|
70
|
+
|
|
71
|
+
return target_type(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-ai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A Python package for Veris AI tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
|
|
@@ -17,11 +17,14 @@ Requires-Dist: pytest-asyncio>=0.21.1; extra == 'dev'
|
|
|
17
17
|
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
18
18
|
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
19
19
|
Requires-Dist: ruff>=0.11.4; extra == 'dev'
|
|
20
|
+
Provides-Extra: fastapi
|
|
21
|
+
Requires-Dist: fastapi; extra == 'fastapi'
|
|
22
|
+
Requires-Dist: fastapi-mcp; extra == 'fastapi'
|
|
20
23
|
Description-Content-Type: text/markdown
|
|
21
24
|
|
|
22
25
|
# Veris AI Python SDK
|
|
23
26
|
|
|
24
|
-
A Python package for Veris AI tools with simulation capabilities.
|
|
27
|
+
A Python package for Veris AI tools with simulation capabilities and FastAPI MCP (Model Context Protocol) integration.
|
|
25
28
|
|
|
26
29
|
## Installation
|
|
27
30
|
|
|
@@ -33,6 +36,9 @@ uv add veris-ai
|
|
|
33
36
|
|
|
34
37
|
# Install with development dependencies
|
|
35
38
|
uv add "veris-ai[dev]"
|
|
39
|
+
|
|
40
|
+
# Install with FastAPI MCP integration
|
|
41
|
+
uv add "veris-ai[fastapi]"
|
|
36
42
|
```
|
|
37
43
|
|
|
38
44
|
## Environment Setup
|
|
@@ -93,6 +99,78 @@ When `ENV=simulation` is set, the decorator will:
|
|
|
93
99
|
|
|
94
100
|
When not in simulation mode, the original function will be executed normally.
|
|
95
101
|
|
|
102
|
+
## FastAPI MCP Integration
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from fastapi import FastAPI
|
|
108
|
+
from veris_ai import veris
|
|
109
|
+
|
|
110
|
+
app = FastAPI()
|
|
111
|
+
|
|
112
|
+
# Set up FastAPI MCP with automatic session handling
|
|
113
|
+
veris.set_fastapi_mcp(
|
|
114
|
+
fastapi=app,
|
|
115
|
+
name="My API Server",
|
|
116
|
+
description="My FastAPI application exposed as MCP tools",
|
|
117
|
+
describe_all_responses=True,
|
|
118
|
+
include_operations=["get_users", "create_user"],
|
|
119
|
+
exclude_tags=["internal"]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# The MCP server is now available at veris.fastapi_mcp
|
|
123
|
+
mcp_server = veris.fastapi_mcp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Configuration Options
|
|
127
|
+
|
|
128
|
+
The `set_fastapi_mcp()` method accepts the following parameters:
|
|
129
|
+
|
|
130
|
+
- **fastapi** (required): Your FastAPI application instance
|
|
131
|
+
- **name**: Custom name for the MCP server (defaults to app.title)
|
|
132
|
+
- **description**: Custom description (defaults to app.description)
|
|
133
|
+
- **describe_all_responses**: Whether to include all response schemas in tool descriptions
|
|
134
|
+
- **describe_full_response_schema**: Whether to include full JSON schema for responses
|
|
135
|
+
- **http_client**: Optional custom httpx.AsyncClient instance
|
|
136
|
+
- **include_operations**: List of operation IDs to include as MCP tools
|
|
137
|
+
- **exclude_operations**: List of operation IDs to exclude (can't use with include_operations)
|
|
138
|
+
- **include_tags**: List of tags to include as MCP tools
|
|
139
|
+
- **exclude_tags**: List of tags to exclude (can't use with include_tags)
|
|
140
|
+
- **auth_config**: Optional FastAPI MCP AuthConfig for custom authentication
|
|
141
|
+
|
|
142
|
+
### Session Management
|
|
143
|
+
|
|
144
|
+
The SDK automatically handles session management through OAuth2 authentication:
|
|
145
|
+
- Session IDs are extracted from bearer tokens
|
|
146
|
+
- Context is maintained across API calls
|
|
147
|
+
- Sessions can be managed programmatically:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# Get current session ID
|
|
151
|
+
session_id = veris.session_id
|
|
152
|
+
|
|
153
|
+
# Set a new session ID
|
|
154
|
+
veris.set_session_id("new-session-123")
|
|
155
|
+
|
|
156
|
+
# Clear the session
|
|
157
|
+
veris.clear_session_id()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Project Structure
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
src/veris_ai/
|
|
164
|
+
├── __init__.py # Main entry point, exports the veris instance
|
|
165
|
+
├── tool_mock.py # VerisSDK class with @veris.mock() decorator
|
|
166
|
+
└── utils.py # Type conversion utilities
|
|
167
|
+
|
|
168
|
+
tests/
|
|
169
|
+
├── conftest.py # Pytest fixtures
|
|
170
|
+
├── test_tool_mock.py # Tests for VerisSDK functionality
|
|
171
|
+
└── test_utils.py # Tests for type conversion utilities
|
|
172
|
+
```
|
|
173
|
+
|
|
96
174
|
## Development
|
|
97
175
|
|
|
98
176
|
This project uses `pyproject.toml` for dependency management and `uv` for package installation.
|
|
@@ -121,16 +199,40 @@ This project uses [Ruff](https://github.com/charliermarsh/ruff) for linting and
|
|
|
121
199
|
To run Ruff:
|
|
122
200
|
|
|
123
201
|
```bash
|
|
202
|
+
# Lint code
|
|
124
203
|
ruff check .
|
|
204
|
+
|
|
205
|
+
# Auto-fix linting issues
|
|
206
|
+
ruff check --fix .
|
|
207
|
+
|
|
208
|
+
# Format code
|
|
209
|
+
ruff format .
|
|
210
|
+
|
|
211
|
+
# Check formatting only
|
|
212
|
+
ruff format --check .
|
|
125
213
|
```
|
|
126
214
|
|
|
127
|
-
|
|
215
|
+
The Ruff configuration is defined in `pyproject.toml` under the `[tool.ruff]` section.
|
|
216
|
+
|
|
217
|
+
### Running Tests
|
|
128
218
|
|
|
129
219
|
```bash
|
|
130
|
-
|
|
220
|
+
# Run all tests with coverage
|
|
221
|
+
pytest tests/ --cov=veris_ai --cov-report=xml --cov-report=term-missing
|
|
222
|
+
|
|
223
|
+
# Run specific test file
|
|
224
|
+
pytest tests/test_tool_mock.py
|
|
225
|
+
|
|
226
|
+
# Run tests with verbose output
|
|
227
|
+
pytest -v tests/
|
|
131
228
|
```
|
|
132
229
|
|
|
133
|
-
|
|
230
|
+
### Type Checking
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Run static type checking
|
|
234
|
+
mypy src/veris_ai tests
|
|
235
|
+
```
|
|
134
236
|
|
|
135
237
|
## License
|
|
136
238
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
veris_ai/__init__.py,sha256=7mEfVmqxQCn5FZN_1ua3iykCB1NUeJ4RfVXaAzdp4Hw,101
|
|
2
|
+
veris_ai/tool_mock.py,sha256=Yr702kl9ggdRUnR_iL37UXFjz-drvQqVpp7JdT_qlb4,5665
|
|
3
|
+
veris_ai/utils.py,sha256=-r4FZajHfrcjB0rhluQl2KDS68ER9pqHp8g_pmB1qAE,2322
|
|
4
|
+
veris_ai-1.0.0.dist-info/METADATA,sha256=mnB4E-ZMjyfrxiVVM2BQ27Ie7SDd-AZK-LuVjHj0cjo,6503
|
|
5
|
+
veris_ai-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
veris_ai-1.0.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
7
|
+
veris_ai-1.0.0.dist-info/RECORD,,
|
veris_ai-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
veris_ai/__init__.py,sha256=7mEfVmqxQCn5FZN_1ua3iykCB1NUeJ4RfVXaAzdp4Hw,101
|
|
2
|
-
veris_ai/tool_mock.py,sha256=7qFM35lz10k5i2X-XQVYcuFNF0bczEnHTr0vtineQrM,5172
|
|
3
|
-
veris_ai-0.2.0.dist-info/METADATA,sha256=rD-TyXPbDZfJcQCtNuUUA4KGYD6PAFXQIhB5SL1lyNg,3419
|
|
4
|
-
veris_ai-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
-
veris_ai-0.2.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
|
|
6
|
-
veris_ai-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|