veris-ai 1.2.0__py3-none-any.whl → 1.4.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/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Veris AI Module Architecture
2
+
3
+ This module contains the core implementation of the Veris AI Python SDK. Each component focuses on a specific aspect of tool mocking, tracing, and MCP integration.
4
+
5
+ ## Quick Reference
6
+
7
+ **Purpose**: Core SDK implementation with modular architecture
8
+ **Entry Point**: [`__init__.py`](__init__.py) handles lazy imports and public API exports
9
+ **Source of Truth**: Individual module files contain implementation details
10
+
11
+ ## Module Overview
12
+
13
+ **Semantic Tag**: `core-modules`
14
+
15
+ | Module | Purpose | Key Classes/Functions | Lines |
16
+ |--------|---------|----------------------|-------|
17
+ | [`tool_mock.py`](tool_mock.py) | Function mocking & FastAPI MCP | `VerisSDK`, `@mock`, `@stub` | 327 |
18
+ | [`braintrust_tracing.py`](braintrust_tracing.py) | Dual tracing instrumentation | `instrument()` | 283 |
19
+ | [`utils.py`](utils.py) | Type utilities & JSON schema | `extract_json_schema()` | 272 |
20
+ | [`logging.py`](logging.py) | Logging configuration | `setup_logging()` | 116 |
21
+ | [`models.py`](models.py) | Data models | Type definitions | 12 |
22
+ | [`jaeger_interface/`](jaeger_interface/) | Jaeger Query API wrapper | `JaegerClient` | See module README |
23
+
24
+ ## Core Workflows
25
+
26
+ **Semantic Tag**: `implementation-flows`
27
+
28
+ ### Mock Flow
29
+ 1. **Decoration**: `@veris.mock()` captures function metadata
30
+ 2. **Environment Check**: `ENV=simulation` determines behavior
31
+ 3. **API Call**: POST to `{VERIS_ENDPOINT_URL}/api/v2/tool_mock`
32
+ 4. **Type Conversion**: Response converted using `extract_json_schema()`
33
+
34
+ **Implementation**: [`tool_mock.py:200-250`](tool_mock.py)
35
+
36
+ ### Spy Flow
37
+ 1. **Pre-execution Logging**: Call details sent to `/api/v2/log_tool_call`
38
+ 2. **Function Execution**: Original function runs normally
39
+ 3. **Post-execution Logging**: Response sent to `/api/v2/log_tool_response`
40
+
41
+ **Implementation**: [`tool_mock.py:250-300`](tool_mock.py)
42
+
43
+ ### Tracing Flow
44
+ 1. **Dual Setup**: Braintrust + OpenTelemetry instrumentation
45
+ 2. **Session Tagging**: Bearer tokens → session IDs
46
+ 3. **Span Attribution**: All operations tagged with `veris.session_id`
47
+
48
+ **Implementation**: [`braintrust_tracing.py:50-150`](braintrust_tracing.py)
49
+
50
+ ## Configuration
51
+
52
+ **Semantic Tag**: `module-config`
53
+
54
+ Environment variables are processed in [`tool_mock.py`](tool_mock.py):
55
+
56
+ - `VERIS_ENDPOINT_URL`: Mock server endpoint
57
+ - `VERIS_MOCK_TIMEOUT`: Request timeout (default: 90s)
58
+ - `ENV`: Set to `"simulation"` for mock mode
59
+ - `VERIS_SERVICE_NAME`: Tracing service identifier
60
+ - `VERIS_OTLP_ENDPOINT`: OpenTelemetry collector endpoint
61
+
62
+ ## Development Notes
63
+
64
+ **Semantic Tag**: `development-patterns`
65
+
66
+ - **Lazy Imports**: [`__init__.py`](__init__.py) minimizes startup dependencies
67
+ - **Type Safety**: Extensive use of Pydantic models and type hints
68
+ - **Error Handling**: Comprehensive exception handling with timeouts
69
+ - **Testing**: Module-specific tests in [`../tests/`](../tests/)
70
+
71
+ **Architecture Principle**: Each module is self-contained with minimal cross-dependencies, enabling selective imports and reduced memory footprint.
72
+
73
+ ---
74
+
75
+ **Parent Documentation**: See [main README](../../README.md) for installation and usage patterns.
veris_ai/__init__.py CHANGED
@@ -5,9 +5,9 @@ from typing import Any
5
5
  __version__ = "0.1.0"
6
6
 
7
7
  # Import lightweight modules that only use base dependencies
8
- from .tool_mock import veris
9
8
  from .jaeger_interface import JaegerClient
10
9
  from .models import ResponseExpectation
10
+ from .tool_mock import veris
11
11
 
12
12
  # Lazy import for modules with heavy dependencies
13
13
  _instrument = None
@@ -34,9 +34,4 @@ def instrument(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
34
34
  return _instrument(*args, **kwargs)
35
35
 
36
36
 
37
- __all__ = [
38
- "veris",
39
- "JaegerClient",
40
- "instrument",
41
- "ResponseExpectation"
42
- ]
37
+ __all__ = ["veris", "JaegerClient", "instrument", "ResponseExpectation"]
@@ -1,137 +1,101 @@
1
1
  # Jaeger Interface
2
2
 
3
- This sub-package ships a **thin synchronous wrapper** around the
4
- [Jaeger Query Service](https://www.jaegertracing.io/docs/) HTTP API so
5
- that you can **search for and retrieve traces** directly from Python
6
- with minimal boilerplate. It also provides **client-side span filtering**
7
- capabilities for more granular control over the returned data.
3
+ Typed Python wrapper for the Jaeger Query Service HTTP API with client-side span filtering capabilities.
8
4
 
9
- > The client relies on `requests` (already included in the SDK's
10
- > dependencies) and uses *pydantic* for full type-safety.
5
+ ## Quick Reference
11
6
 
12
- ---
7
+ **Purpose**: Search and retrieve traces from Jaeger with minimal boilerplate
8
+ **Core Component**: [`JaegerClient`](client.py) class with `search()` and `get_trace()` methods
9
+ **Dependencies**: Uses `requests` and `pydantic` (included in base SDK)
10
+ **Compatibility**: Jaeger v1.x REST endpoints, OpenSearch storage backends
13
11
 
14
12
  ## Installation
15
13
 
16
- `veris-ai` already lists both `requests` and `pydantic` as hard
17
- requirements, so **no additional dependencies are required**.
14
+ **Semantic Tag**: `jaeger-setup`
18
15
 
16
+ No additional dependencies required - included with base `veris-ai` package:
19
17
  ```bash
20
18
  pip install veris-ai
21
19
  ```
22
20
 
23
21
  ---
24
22
 
25
- ## Quick-start
23
+ ## Basic Usage
24
+
25
+ **Semantic Tag**: `jaeger-client-usage`
26
26
 
27
27
  ```python
28
28
  from veris_ai.jaeger_interface import JaegerClient
29
- import json
30
- from veris_ai.jaeger_interface.models import Trace
31
- # Replace with the URL of your Jaeger Query Service instance
29
+
32
30
  client = JaegerClient("http://localhost:16686")
33
31
 
34
- # --- 1. Search traces --------------------------------------------------
35
- resp = client.search(
32
+ # Search traces with filtering
33
+ traces = client.search(
36
34
  service="veris-agent",
37
35
  limit=10,
38
- # operation="CustomSpanData",
39
- tags={"veris.session_id":"088b5aaf-84bd-4768-9a62-5e981222a9f2"},
40
- span_tags={"bt.metadata.model":"gpt-4.1-2025-04-14"}
36
+ tags={"veris.session_id": "session-123"},
37
+ span_tags={"http.status_code": 500}
41
38
  )
42
39
 
43
- # save to json
44
- with open("resp.json", "w") as f:
45
- f.write(resp.model_dump_json(indent=2))
46
-
47
- # Guard clause
48
- if not resp or not resp.data:
49
- print("No data found")
50
- exit(1)
51
-
52
- # Print trace ids
53
- for trace in resp.data:
54
- if isinstance(trace, Trace):
55
- print("TRACE ID:", trace.traceID, len(trace.spans), "spans")
56
-
57
- # --- 2. Retrieve a specific trace -------------------------------------
58
- if isinstance(resp.data, list):
59
- trace_id = resp.data[0].traceID
60
- else:
61
- trace_id = resp.data.traceID
62
-
63
- detailed = client.get_trace(trace_id)
64
- # save detailed to json
65
- with open("detailed.json", "w") as f:
66
- f.write(detailed.model_dump_json(indent=2))
40
+ # Retrieve specific trace
41
+ if traces.data:
42
+ detailed = client.get_trace(traces.data[0].traceID)
67
43
  ```
68
44
 
45
+ **Data Models**: See [`models.py`](models.py) for `Trace`, `Span`, and response type definitions.
46
+
69
47
  ---
70
48
 
71
49
  ## API Reference
72
50
 
73
- ### `JaegerClient`
74
-
75
- | Method | Description |
76
- | -------- | ----------- |
77
- | `search(service, *, limit=None, tags=None, operation=None, span_tags=None, **kwargs) -> SearchResponse` | Search for traces with optional span-level filtering. |
78
- | `get_trace(trace_id: str) -> GetTraceResponse` | Fetch a single trace by ID (wrapper around `/api/traces/{id}`). |
79
-
80
- ### `search()` Parameters
81
-
82
- The `search()` method now uses a flattened parameter structure:
51
+ **Semantic Tag**: `jaeger-api-methods`
83
52
 
84
- | Parameter | Type | Description |
85
- | --------- | ---- | ----------- |
86
- | `service` | `str` | Service name to search for. Optional - if not provided, searches across all services. |
87
- | `limit` | `int` | Maximum number of traces to return. |
88
- | `tags` | `Dict[str, Any]` | Trace-level tag filters (AND logic). A trace must have a span matching ALL tags. |
89
- | `operation` | `str` | Filter by operation name. |
90
- | `span_tags` | `Dict[str, Any]` | Span-level tag filters (OR logic). Returns only spans matching ANY of these tags. |
91
- | `span_operations` | `List[str]` | Span-level operation name filters (OR logic). Returns only spans matching ANY of these operations. |
92
- | `**kwargs` | `Any` | Additional parameters passed directly to Jaeger API. |
53
+ ### Core Methods
93
54
 
94
- ### Filter Logic
55
+ | Method | Purpose | Returns |
56
+ |--------|---------|---------|
57
+ | `search(service, **filters)` | Search traces with optional filtering | `SearchResponse` |
58
+ | `get_trace(trace_id)` | Retrieve single trace by ID | `GetTraceResponse` |
95
59
 
96
- The interface provides two levels of filtering:
60
+ **Implementation**: See [`client.py`](client.py) for method signatures and error handling.
97
61
 
98
- 1. **Trace-level filtering** (`tags` parameter):
99
- - Sent directly to Jaeger API
100
- - Uses AND logic: all tag key-value pairs must match on a single span
101
- - Efficient server-side filtering
62
+ ### Filtering Strategy
102
63
 
103
- 2. **Span-level filtering** (`span_tags` parameter):
104
- - Applied client-side after retrieving traces
105
- - Uses OR logic: spans matching ANY of the provided tags are included
106
- - Traces with no matching spans are excluded from results
107
- - Useful for finding spans with specific characteristics across different traces
108
-
109
- ### Example: Combining Filters
64
+ **Semantic Tag**: `filtering-logic`
110
65
 
111
66
  ```python
112
- # Find traces in service that have errors, then filter to show only
113
- # spans with specific HTTP status codes or database errors
67
+ # Server-side filtering (efficient)
68
+ traces = client.search(
69
+ service="my-service",
70
+ tags={"error": "true"}, # AND logic: trace must match ALL tags
71
+ operation="specific_op"
72
+ )
73
+
74
+ # Client-side filtering (granular)
114
75
  traces = client.search(
115
- service="my-api",
116
- tags={"error": "true"}, # Trace must contain an error
117
- span_tags={
118
- "http.status_code": 500,
119
- "http.status_code": 503,
120
- "db.error": "connection_timeout"
121
- } # Show only spans with these specific errors
76
+ service="my-service",
77
+ span_tags={"http.status_code": [500, 503]}, # OR logic: ANY span match
78
+ span_operations=["db_query", "api_call"]
122
79
  )
123
80
  ```
124
81
 
82
+ **Filter Types**:
83
+ - **`tags`**: Trace-level filters (server-side, AND logic)
84
+ - **`span_tags`**: Span-level filters (client-side, OR logic)
85
+ - **`span_operations`**: Operation name filters (client-side, OR logic)
86
+
125
87
  ---
126
88
 
127
- ## Compatibility
89
+ ## Architecture
128
90
 
129
- The implementation targets **Jaeger v1.x** REST endpoints. For clusters
130
- backed by **OpenSearch** storage the same endpoints apply. Should you
131
- need API v3 support feel free to open an issue or contribution—thanks!
91
+ **Semantic Tag**: `jaeger-architecture`
132
92
 
133
- ---
93
+ - **Client Implementation**: [`client.py`](client.py) - HTTP requests to Jaeger API
94
+ - **Data Models**: [`models.py`](models.py) - Pydantic models for type safety
95
+ - **Compatibility**: Jaeger v1.x REST endpoints, OpenSearch backends
134
96
 
135
- ## License
97
+ **Design Principle**: Thin wrapper maintaining Jaeger's native API structure while adding client-side span filtering capabilities.
98
+
99
+ ---
136
100
 
137
- This package is released under the **MIT license**.
101
+ **Parent Documentation**: See [module README](../README.md) for integration with other SDK components.
@@ -5,12 +5,12 @@ This implementation keeps dependencies minimal while providing fully-typed
5
5
  """
6
6
 
7
7
  import json
8
- from typing import Any, Dict, List, Optional, Self
8
+ import types
9
+ from typing import Any, Self
9
10
 
10
11
  import requests
11
12
 
12
- from .models import GetTraceResponse, SearchResponse, Span, Tag, Trace
13
-
13
+ from .models import GetTraceResponse, SearchResponse, Span, Trace
14
14
 
15
15
  __all__ = ["JaegerClient"]
16
16
 
@@ -60,30 +60,27 @@ class JaegerClient: # noqa: D101
60
60
  session.headers.update(self._headers)
61
61
  return session, True
62
62
 
63
- def _span_matches_tags(self, span: Span, span_tags: Dict[str, Any]) -> bool:
63
+ def _span_matches_tags(self, span: Span, span_tags: dict[str, Any]) -> bool:
64
64
  """Check if a span matches any of the provided tags (OR logic)."""
65
65
  if not span.tags or not span_tags:
66
66
  return False
67
-
67
+
68
68
  # Convert span tags to a dict for easier lookup
69
69
  span_tag_dict = {tag.key: tag.value for tag in span.tags}
70
-
70
+
71
71
  # OR logic: return True if ANY tag matches
72
- for key, value in span_tags.items():
73
- if span_tag_dict.get(key) == value:
74
- return True
75
-
76
- return False
72
+ return any(span_tag_dict.get(key) == value for key, value in span_tags.items())
77
73
 
78
74
  def _filter_spans(
79
75
  self,
80
- traces: List[Trace],
81
- span_tags: Optional[Dict[str, Any]],
82
- span_operations: Optional[List[str]] = None
83
- ) -> List[Trace]:
84
- """
85
- Filter spans within traces based on span_tags (OR logic) and/or span_operations (OR logic).
86
- If both are provided, a span must match at least one tag AND at least one operation.
76
+ traces: list[Trace],
77
+ span_tags: dict[str, Any] | None,
78
+ span_operations: list[str] | None = None,
79
+ ) -> list[Trace]:
80
+ """Filter spans within traces based on span_tags and/or span_operations.
81
+
82
+ Uses OR logic within each filter type. If both are provided, a span must
83
+ match at least one tag AND at least one operation.
87
84
  """
88
85
  if not span_tags and not span_operations:
89
86
  return traces
@@ -109,7 +106,7 @@ class JaegerClient: # noqa: D101
109
106
  traceID=trace.traceID,
110
107
  spans=filtered_spans,
111
108
  process=trace.process,
112
- warnings=trace.warnings
109
+ warnings=trace.warnings,
113
110
  )
114
111
  filtered_traces.append(filtered_trace)
115
112
 
@@ -119,16 +116,16 @@ class JaegerClient: # noqa: D101
119
116
  # Public API
120
117
  # ---------------------------------------------------------------------
121
118
 
122
- def search(
119
+ def search( # noqa: PLR0913
123
120
  self,
124
- service: Optional[str] = None,
121
+ service: str | None = None,
125
122
  *,
126
- limit: Optional[int] = None,
127
- tags: Optional[Dict[str, Any]] = None,
128
- operation: Optional[str] = None,
129
- span_tags: Optional[Dict[str, Any]] = None,
130
- span_operations: Optional[List[str]] = None,
131
- **kwargs: Any
123
+ limit: int | None = None,
124
+ tags: dict[str, Any] | None = None,
125
+ operation: str | None = None,
126
+ span_tags: dict[str, Any] | None = None,
127
+ span_operations: list[str] | None = None,
128
+ **kwargs: Any, # noqa: ANN401
132
129
  ) -> SearchResponse: # noqa: D401
133
130
  """Search traces using the *v1* ``/api/traces`` endpoint with optional span filtering.
134
131
 
@@ -137,9 +134,11 @@ class JaegerClient: # noqa: D101
137
134
  limit: Maximum number of traces to return.
138
135
  tags: Dictionary of tag filters for trace-level filtering (AND-combined).
139
136
  operation: Operation name to search for.
140
- span_tags: Dictionary of tag filters for span-level filtering (OR-combined AND-combined with span_operations).
137
+ span_tags: Dictionary of tag filters for span-level filtering.
138
+ Uses OR logic. Combined with span_operations using AND.
141
139
  Applied client-side after retrieving traces.
142
- span_operations: List of operation names to search for (OR-combined AND-combined with span_tags).
140
+ span_operations: List of operation names to search for.
141
+ Uses OR logic. Combined with span_tags using AND.
143
142
  **kwargs: Additional parameters to pass to the Jaeger API.
144
143
 
145
144
  Returns:
@@ -147,24 +146,24 @@ class JaegerClient: # noqa: D101
147
146
  with spans filtered according to span_tags if provided.
148
147
  """
149
148
  # Build params for the Jaeger API (excluding span_tags)
150
- params: Dict[str, Any] = {}
151
-
149
+ params: dict[str, Any] = {}
150
+
152
151
  if service is not None:
153
152
  params["service"] = service
154
-
153
+
155
154
  if limit is not None:
156
155
  params["limit"] = limit
157
-
156
+
158
157
  if operation is not None:
159
158
  params["operation"] = operation
160
-
159
+
161
160
  if tags:
162
161
  # Convert tags to JSON string as expected by Jaeger API
163
162
  params["tags"] = json.dumps(tags)
164
-
163
+
165
164
  # Add any additional parameters
166
165
  params.update(kwargs)
167
-
166
+
168
167
  session, should_close = self._make_session()
169
168
  try:
170
169
  url = f"{self._base_url}/api/traces"
@@ -174,18 +173,22 @@ class JaegerClient: # noqa: D101
174
173
  finally:
175
174
  if should_close:
176
175
  session.close()
177
-
176
+
178
177
  # Parse the response
179
178
  search_response = SearchResponse.model_validate(data) # type: ignore[arg-type]
180
-
179
+
181
180
  # Apply span-level filtering if span_tags is provided
182
- if span_tags or span_operations and search_response.data and isinstance(search_response.data, list):
181
+ if (
182
+ (span_tags or span_operations)
183
+ and search_response.data
184
+ and isinstance(search_response.data, list)
185
+ ):
183
186
  filtered_traces = self._filter_spans(search_response.data, span_tags, span_operations)
184
187
  search_response.data = filtered_traces
185
188
  # Update the total to reflect filtered results
186
189
  if search_response.total is not None:
187
190
  search_response.total = len(filtered_traces)
188
-
191
+
189
192
  return search_response
190
193
 
191
194
  def get_trace(self, trace_id: str) -> GetTraceResponse: # noqa: D401
@@ -225,7 +228,7 @@ class JaegerClient: # noqa: D101
225
228
  self,
226
229
  exc_type: type[BaseException] | None,
227
230
  exc: BaseException | None,
228
- tb: Any | None,
231
+ tb: types.TracebackType | None,
229
232
  ) -> None:
230
233
  """Exit the context manager."""
231
234
  # Only close if we created the session
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any
5
4
 
6
- from pydantic import BaseModel, Field, ConfigDict
5
+ from pydantic import BaseModel, ConfigDict, Field
7
6
 
8
7
  __all__ = [
9
8
  "Tag",
veris_ai/logging.py ADDED
@@ -0,0 +1,116 @@
1
+ """Logging utilities for VERIS tool calls and responses."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ async def log_tool_call_async(
14
+ session_id: str,
15
+ function_name: str,
16
+ parameters: dict[str, Any],
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
+
25
+ endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_call"
26
+ payload = {
27
+ "function_name": function_name,
28
+ "parameters": parameters,
29
+ "docstring": docstring,
30
+ }
31
+
32
+ timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
33
+
34
+ try:
35
+ async with httpx.AsyncClient(timeout=timeout) as client:
36
+ response = await client.post(endpoint, json=payload)
37
+ response.raise_for_status()
38
+ logger.debug(f"Tool call logged for {function_name}")
39
+ except Exception as e:
40
+ logger.warning(f"Failed to log tool call for {function_name}: {e}")
41
+
42
+
43
+ def log_tool_call_sync(
44
+ session_id: str,
45
+ function_name: str,
46
+ parameters: dict[str, Any],
47
+ docstring: str,
48
+ ) -> None:
49
+ """Log tool call synchronously to the VERIS logging endpoint."""
50
+ base_url = os.getenv("VERIS_ENDPOINT_URL")
51
+ if not base_url:
52
+ logger.warning("VERIS_ENDPOINT_URL not set, skipping tool call logging")
53
+ return
54
+
55
+ endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_call"
56
+ payload = {
57
+ "function_name": function_name,
58
+ "parameters": parameters,
59
+ "docstring": docstring,
60
+ }
61
+
62
+ timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
63
+
64
+ try:
65
+ with httpx.Client(timeout=timeout) as client:
66
+ response = client.post(endpoint, json=payload)
67
+ response.raise_for_status()
68
+ logger.debug(f"Tool call logged for {function_name}")
69
+ except Exception as e:
70
+ logger.warning(f"Failed to log tool call for {function_name}: {e}")
71
+
72
+
73
+ async def log_tool_response_async(session_id: str, response: object) -> None:
74
+ """Log tool response asynchronously to the VERIS logging endpoint."""
75
+ base_url = os.getenv("VERIS_ENDPOINT_URL")
76
+ if not base_url:
77
+ logger.warning("VERIS_ENDPOINT_URL not set, skipping tool response logging")
78
+ return
79
+
80
+ endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_response"
81
+ payload = {
82
+ "response": json.dumps(response, default=str),
83
+ }
84
+
85
+ timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
86
+
87
+ try:
88
+ async with httpx.AsyncClient(timeout=timeout) as client:
89
+ http_response = await client.post(endpoint, json=payload)
90
+ http_response.raise_for_status()
91
+ logger.debug("Tool response logged")
92
+ except Exception as e:
93
+ logger.warning(f"Failed to log tool response: {e}")
94
+
95
+
96
+ def log_tool_response_sync(session_id: str, response: object) -> None:
97
+ """Log tool response synchronously to the VERIS logging endpoint."""
98
+ base_url = os.getenv("VERIS_ENDPOINT_URL")
99
+ if not base_url:
100
+ logger.warning("VERIS_ENDPOINT_URL not set, skipping tool response logging")
101
+ return
102
+
103
+ endpoint = f"{base_url}/api/v2/simulations/{session_id}/log_tool_response"
104
+ payload = {
105
+ "response": json.dumps(response, default=str),
106
+ }
107
+
108
+ timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "90.0"))
109
+
110
+ try:
111
+ with httpx.Client(timeout=timeout) as client:
112
+ http_response = client.post(endpoint, json=payload)
113
+ http_response.raise_for_status()
114
+ logger.debug("Tool response logged")
115
+ except Exception as e:
116
+ logger.warning(f"Failed to log tool response: {e}")
veris_ai/models.py CHANGED
@@ -5,7 +5,7 @@ from enum import Enum
5
5
 
6
6
  class ResponseExpectation(str, Enum):
7
7
  """Expected response behavior for tool mocking."""
8
-
8
+
9
9
  AUTO = "auto"
10
10
  REQUIRED = "required"
11
- NONE = "none"
11
+ NONE = "none"