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 +75 -0
- veris_ai/__init__.py +2 -7
- veris_ai/jaeger_interface/README.md +55 -91
- veris_ai/jaeger_interface/client.py +44 -41
- veris_ai/jaeger_interface/models.py +1 -2
- veris_ai/logging.py +116 -0
- veris_ai/models.py +2 -2
- veris_ai/tool_mock.py +93 -26
- veris_ai/utils.py +3 -0
- veris_ai-1.4.0.dist-info/METADATA +223 -0
- veris_ai-1.4.0.dist-info/RECORD +15 -0
- veris_ai-1.2.0.dist-info/METADATA +0 -457
- veris_ai-1.2.0.dist-info/RECORD +0 -13
- {veris_ai-1.2.0.dist-info → veris_ai-1.4.0.dist-info}/WHEEL +0 -0
- {veris_ai-1.2.0.dist-info → veris_ai-1.4.0.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
#
|
|
35
|
-
|
|
32
|
+
# Search traces with filtering
|
|
33
|
+
traces = client.search(
|
|
36
34
|
service="veris-agent",
|
|
37
35
|
limit=10,
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
**Implementation**: See [`client.py`](client.py) for method signatures and error handling.
|
|
97
61
|
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
113
|
-
|
|
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-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
##
|
|
89
|
+
## Architecture
|
|
128
90
|
|
|
129
|
-
|
|
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
|
-
|
|
97
|
+
**Design Principle**: Thin wrapper maintaining Jaeger's native API structure while adding client-side span filtering capabilities.
|
|
98
|
+
|
|
99
|
+
---
|
|
136
100
|
|
|
137
|
-
|
|
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
|
-
|
|
8
|
+
import types
|
|
9
|
+
from typing import Any, Self
|
|
9
10
|
|
|
10
11
|
import requests
|
|
11
12
|
|
|
12
|
-
from .models import GetTraceResponse, SearchResponse, Span,
|
|
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:
|
|
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:
|
|
81
|
-
span_tags:
|
|
82
|
-
span_operations:
|
|
83
|
-
) ->
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
If both are provided, a span must
|
|
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:
|
|
121
|
+
service: str | None = None,
|
|
125
122
|
*,
|
|
126
|
-
limit:
|
|
127
|
-
tags:
|
|
128
|
-
operation:
|
|
129
|
-
span_tags:
|
|
130
|
-
span_operations:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
231
|
+
tb: types.TracebackType | None,
|
|
229
232
|
) -> None:
|
|
230
233
|
"""Exit the context manager."""
|
|
231
234
|
# Only close if we created the session
|
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}")
|