veris-ai 1.1.0__tar.gz → 1.3.0__tar.gz

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.

Files changed (33) hide show
  1. {veris_ai-1.1.0 → veris_ai-1.3.0}/PKG-INFO +14 -5
  2. {veris_ai-1.1.0 → veris_ai-1.3.0}/README.md +13 -4
  3. {veris_ai-1.1.0 → veris_ai-1.3.0}/pyproject.toml +1 -1
  4. {veris_ai-1.1.0 → veris_ai-1.3.0}/src/veris_ai/__init__.py +3 -7
  5. veris_ai-1.3.0/src/veris_ai/jaeger_interface/README.md +137 -0
  6. veris_ai-1.3.0/src/veris_ai/jaeger_interface/__init__.py +39 -0
  7. veris_ai-1.3.0/src/veris_ai/jaeger_interface/client.py +236 -0
  8. veris_ai-1.3.0/src/veris_ai/jaeger_interface/models.py +78 -0
  9. veris_ai-1.3.0/src/veris_ai/models.py +11 -0
  10. {veris_ai-1.1.0 → veris_ai-1.3.0}/src/veris_ai/tool_mock.py +92 -31
  11. {veris_ai-1.1.0 → veris_ai-1.3.0}/src/veris_ai/utils.py +1 -0
  12. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/conftest.py +12 -2
  13. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/test_tool_mock.py +25 -25
  14. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/test_utils.py +22 -0
  15. {veris_ai-1.1.0 → veris_ai-1.3.0}/uv.lock +1 -1
  16. veris_ai-1.1.0/src/veris_ai/jaeger_interface/README.md +0 -109
  17. veris_ai-1.1.0/src/veris_ai/jaeger_interface/__init__.py +0 -26
  18. veris_ai-1.1.0/src/veris_ai/jaeger_interface/client.py +0 -133
  19. veris_ai-1.1.0/src/veris_ai/jaeger_interface/models.py +0 -153
  20. {veris_ai-1.1.0 → veris_ai-1.3.0}/.github/workflows/release.yml +0 -0
  21. {veris_ai-1.1.0 → veris_ai-1.3.0}/.github/workflows/test.yml +0 -0
  22. {veris_ai-1.1.0 → veris_ai-1.3.0}/.gitignore +0 -0
  23. {veris_ai-1.1.0 → veris_ai-1.3.0}/CHANGELOG.md +0 -0
  24. {veris_ai-1.1.0 → veris_ai-1.3.0}/CLAUDE.md +0 -0
  25. {veris_ai-1.1.0 → veris_ai-1.3.0}/LICENSE +0 -0
  26. {veris_ai-1.1.0 → veris_ai-1.3.0}/examples/__init__.py +0 -0
  27. {veris_ai-1.1.0 → veris_ai-1.3.0}/examples/import_options.py +0 -0
  28. {veris_ai-1.1.0 → veris_ai-1.3.0}/src/veris_ai/braintrust_tracing.py +0 -0
  29. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/__init__.py +0 -0
  30. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/fixtures/__init__.py +0 -0
  31. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/fixtures/simple_app.py +0 -0
  32. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/fixtures/sse_server.py +0 -0
  33. {veris_ai-1.1.0 → veris_ai-1.3.0}/tests/test_mcp_protocol_server_mocked.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-ai
3
- Version: 1.1.0
3
+ Version: 1.3.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
@@ -64,7 +64,7 @@ The SDK supports flexible import patterns to minimize dependencies:
64
64
 
65
65
  ```python
66
66
  # These imports only require base dependencies (httpx, pydantic, requests)
67
- from veris_ai import veris, JaegerClient, SearchQuery
67
+ from veris_ai import veris, JaegerClient
68
68
  ```
69
69
 
70
70
  ### Optional Imports (Require Extra Dependencies)
@@ -434,14 +434,23 @@ This project is licensed under the MIT License - see the LICENSE file for detail
434
434
  ## Jaeger Trace Interface
435
435
 
436
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 Jaegers default storage back-ends as well as **OpenSearch**, which powers the official Jaeger UI search page.
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
438
 
439
439
  ```python
440
- from veris_ai.jaeger_interface import JaegerClient, SearchQuery
440
+ from veris_ai.jaeger_interface import JaegerClient
441
441
 
442
442
  client = JaegerClient("http://localhost:16686")
443
443
 
444
- traces = client.search(SearchQuery(service="veris-agent", limit=2))
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
+
445
454
  first_trace = client.get_trace(traces.data[0].traceID)
446
455
  ```
447
456
 
@@ -28,7 +28,7 @@ The SDK supports flexible import patterns to minimize dependencies:
28
28
 
29
29
  ```python
30
30
  # These imports only require base dependencies (httpx, pydantic, requests)
31
- from veris_ai import veris, JaegerClient, SearchQuery
31
+ from veris_ai import veris, JaegerClient
32
32
  ```
33
33
 
34
34
  ### Optional Imports (Require Extra Dependencies)
@@ -398,14 +398,23 @@ This project is licensed under the MIT License - see the LICENSE file for detail
398
398
  ## Jaeger Trace Interface
399
399
 
400
400
  A lightweight, fully-typed wrapper around the Jaeger **Query Service** HTTP API lives under `veris_ai.jaeger_interface`.
401
- It allows you to **search and retrieve traces** from both Jaegers default storage back-ends as well as **OpenSearch**, which powers the official Jaeger UI search page.
401
+ 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.
402
402
 
403
403
  ```python
404
- from veris_ai.jaeger_interface import JaegerClient, SearchQuery
404
+ from veris_ai.jaeger_interface import JaegerClient
405
405
 
406
406
  client = JaegerClient("http://localhost:16686")
407
407
 
408
- traces = client.search(SearchQuery(service="veris-agent", limit=2))
408
+ # Search with trace-level filters (server-side)
409
+ traces = client.search(service="veris-agent", limit=2, tags={"error": "true"})
410
+
411
+ # Search with span-level filters (client-side, OR logic)
412
+ filtered = client.search(
413
+ service="veris-agent",
414
+ limit=10,
415
+ span_tags={"http.status_code": 500, "db.error": "timeout"}
416
+ )
417
+
409
418
  first_trace = client.get_trace(traces.data[0].traceID)
410
419
  ```
411
420
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "veris-ai"
7
- version = "1.1.0"
7
+ version = "1.3.0"
8
8
  description = "A Python package for Veris AI tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -5,7 +5,8 @@ from typing import Any
5
5
  __version__ = "0.1.0"
6
6
 
7
7
  # Import lightweight modules that only use base dependencies
8
- from .jaeger_interface import JaegerClient, SearchQuery
8
+ from .jaeger_interface import JaegerClient
9
+ from .models import ResponseExpectation
9
10
  from .tool_mock import veris
10
11
 
11
12
  # Lazy import for modules with heavy dependencies
@@ -33,9 +34,4 @@ def instrument(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
33
34
  return _instrument(*args, **kwargs)
34
35
 
35
36
 
36
- __all__ = [
37
- "veris",
38
- "JaegerClient",
39
- "SearchQuery",
40
- "instrument",
41
- ]
37
+ __all__ = ["veris", "JaegerClient", "instrument", "ResponseExpectation"]
@@ -0,0 +1,137 @@
1
+ # Jaeger Interface
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.
8
+
9
+ > The client relies on `requests` (already included in the SDK's
10
+ > dependencies) and uses *pydantic* for full type-safety.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ `veris-ai` already lists both `requests` and `pydantic` as hard
17
+ requirements, so **no additional dependencies are required**.
18
+
19
+ ```bash
20
+ pip install veris-ai
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Quick-start
26
+
27
+ ```python
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
32
+ client = JaegerClient("http://localhost:16686")
33
+
34
+ # --- 1. Search traces --------------------------------------------------
35
+ resp = client.search(
36
+ service="veris-agent",
37
+ 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"}
41
+ )
42
+
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))
67
+ ```
68
+
69
+ ---
70
+
71
+ ## API Reference
72
+
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:
83
+
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. |
93
+
94
+ ### Filter Logic
95
+
96
+ The interface provides two levels of filtering:
97
+
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
102
+
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
110
+
111
+ ```python
112
+ # Find traces in service that have errors, then filter to show only
113
+ # spans with specific HTTP status codes or database errors
114
+ 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
122
+ )
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Compatibility
128
+
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!
132
+
133
+ ---
134
+
135
+ ## License
136
+
137
+ This package is released under the **MIT license**.
@@ -0,0 +1,39 @@
1
+ """Jaeger interface for searching and retrieving traces.
2
+
3
+ This sub-package provides a thin synchronous wrapper around the Jaeger
4
+ Query Service HTTP API with client-side span filtering capabilities.
5
+
6
+ Typical usage example::
7
+
8
+ from veris_ai.jaeger_interface import JaegerClient
9
+
10
+ client = JaegerClient("http://localhost:16686")
11
+
12
+ # Search traces with trace-level filters
13
+ traces = client.search(
14
+ service="veris-agent",
15
+ limit=20,
16
+ tags={"error": "true"} # AND logic at trace level
17
+ )
18
+
19
+ # Search with span-level filtering
20
+ traces_filtered = client.search(
21
+ service="veris-agent",
22
+ limit=20,
23
+ span_tags={
24
+ "http.status_code": 404,
25
+ "db.error": "timeout"
26
+ } # OR logic: spans with either tag are included
27
+ )
28
+
29
+ # Get a specific trace
30
+ trace = client.get_trace(traces.data[0].traceID)
31
+
32
+ The implementation uses *requests* under the hood and all public functions
33
+ are fully typed using *pydantic* models so that IDEs can provide proper
34
+ autocomplete and type checking.
35
+ """
36
+
37
+ from .client import JaegerClient as JaegerClient # noqa: F401
38
+
39
+ __all__ = ["JaegerClient"]
@@ -0,0 +1,236 @@
1
+ """Synchronous Jaeger Query Service client built on **requests**.
2
+
3
+ This implementation keeps dependencies minimal while providing fully-typed
4
+ *pydantic* models for both **request** and **response** bodies.
5
+ """
6
+
7
+ import json
8
+ import types
9
+ from typing import Any, Self
10
+
11
+ import requests
12
+
13
+ from .models import GetTraceResponse, SearchResponse, Span, Trace
14
+
15
+ __all__ = ["JaegerClient"]
16
+
17
+
18
+ class JaegerClient: # noqa: D101
19
+ def __init__(
20
+ self,
21
+ base_url: str,
22
+ *,
23
+ timeout: float | None = 10.0,
24
+ session: requests.Session | None = None,
25
+ headers: dict[str, str] | None = None,
26
+ ) -> None:
27
+ """Create a new *JaegerClient* instance.
28
+
29
+ Args:
30
+ base_url: Base URL of the Jaeger Query Service (e.g. ``http://localhost:16686``).
31
+ timeout: Request timeout in **seconds** (applied to every call).
32
+ session: Optional pre-configured :class:`requests.Session` to reuse.
33
+ headers: Optional default headers to send with every request.
34
+ """
35
+ # Normalise to avoid trailing slash duplicates
36
+ self._base_url = base_url.rstrip("/")
37
+ self._timeout = timeout
38
+ self._external_session = session # If provided we won't close it
39
+ self._headers = headers or {}
40
+
41
+ # ---------------------------------------------------------------------
42
+ # Internal helpers
43
+ # ---------------------------------------------------------------------
44
+
45
+ def _make_session(self) -> tuple[requests.Session, bool]: # noqa: D401
46
+ """Return a *(session, should_close)* tuple.
47
+
48
+ If an external session was supplied we **must not** close it after the
49
+ request, hence the boolean flag letting callers know whether they are
50
+ responsible for closing the session.
51
+ """
52
+ if self._external_session is not None:
53
+ return self._external_session, False
54
+
55
+ # Reuse the session opened via the context manager if available
56
+ if hasattr(self, "_session_ctx"):
57
+ return self._session_ctx, False
58
+
59
+ session = requests.Session()
60
+ session.headers.update(self._headers)
61
+ return session, True
62
+
63
+ def _span_matches_tags(self, span: Span, span_tags: dict[str, Any]) -> bool:
64
+ """Check if a span matches any of the provided tags (OR logic)."""
65
+ if not span.tags or not span_tags:
66
+ return False
67
+
68
+ # Convert span tags to a dict for easier lookup
69
+ span_tag_dict = {tag.key: tag.value for tag in span.tags}
70
+
71
+ # OR logic: return True if ANY tag matches
72
+ return any(span_tag_dict.get(key) == value for key, value in span_tags.items())
73
+
74
+ def _filter_spans(
75
+ self,
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.
84
+ """
85
+ if not span_tags and not span_operations:
86
+ return traces
87
+
88
+ filtered_traces = []
89
+ for trace in traces:
90
+ filtered_spans = []
91
+ for span in trace.spans:
92
+ tag_match = True
93
+ op_match = True
94
+
95
+ if span_tags:
96
+ tag_match = self._span_matches_tags(span, span_tags)
97
+ if span_operations:
98
+ op_match = span.operationName in span_operations
99
+
100
+ # If both filters are provided, require both to match (AND logic)
101
+ if tag_match and op_match:
102
+ filtered_spans.append(span)
103
+
104
+ if filtered_spans:
105
+ filtered_trace = Trace(
106
+ traceID=trace.traceID,
107
+ spans=filtered_spans,
108
+ process=trace.process,
109
+ warnings=trace.warnings,
110
+ )
111
+ filtered_traces.append(filtered_trace)
112
+
113
+ return filtered_traces
114
+
115
+ # ---------------------------------------------------------------------
116
+ # Public API
117
+ # ---------------------------------------------------------------------
118
+
119
+ def search( # noqa: PLR0913
120
+ self,
121
+ service: str | None = None,
122
+ *,
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
129
+ ) -> SearchResponse: # noqa: D401
130
+ """Search traces using the *v1* ``/api/traces`` endpoint with optional span filtering.
131
+
132
+ Args:
133
+ service: Service name to search for. If not provided, searches across all services.
134
+ limit: Maximum number of traces to return.
135
+ tags: Dictionary of tag filters for trace-level filtering (AND-combined).
136
+ operation: Operation name to search for.
137
+ span_tags: Dictionary of tag filters for span-level filtering.
138
+ Uses OR logic. Combined with span_operations using AND.
139
+ Applied client-side after retrieving traces.
140
+ span_operations: List of operation names to search for.
141
+ Uses OR logic. Combined with span_tags using AND.
142
+ **kwargs: Additional parameters to pass to the Jaeger API.
143
+
144
+ Returns:
145
+ Parsed :class:`~veris_ai.jaeger_interface.models.SearchResponse` model
146
+ with spans filtered according to span_tags if provided.
147
+ """
148
+ # Build params for the Jaeger API (excluding span_tags)
149
+ params: dict[str, Any] = {}
150
+
151
+ if service is not None:
152
+ params["service"] = service
153
+
154
+ if limit is not None:
155
+ params["limit"] = limit
156
+
157
+ if operation is not None:
158
+ params["operation"] = operation
159
+
160
+ if tags:
161
+ # Convert tags to JSON string as expected by Jaeger API
162
+ params["tags"] = json.dumps(tags)
163
+
164
+ # Add any additional parameters
165
+ params.update(kwargs)
166
+
167
+ session, should_close = self._make_session()
168
+ try:
169
+ url = f"{self._base_url}/api/traces"
170
+ response = session.get(url, params=params, timeout=self._timeout)
171
+ response.raise_for_status()
172
+ data = response.json()
173
+ finally:
174
+ if should_close:
175
+ session.close()
176
+
177
+ # Parse the response
178
+ search_response = SearchResponse.model_validate(data) # type: ignore[arg-type]
179
+
180
+ # Apply span-level filtering if span_tags is provided
181
+ if (
182
+ (span_tags or span_operations)
183
+ and search_response.data
184
+ and isinstance(search_response.data, list)
185
+ ):
186
+ filtered_traces = self._filter_spans(search_response.data, span_tags, span_operations)
187
+ search_response.data = filtered_traces
188
+ # Update the total to reflect filtered results
189
+ if search_response.total is not None:
190
+ search_response.total = len(filtered_traces)
191
+
192
+ return search_response
193
+
194
+ def get_trace(self, trace_id: str) -> GetTraceResponse: # noqa: D401
195
+ """Retrieve a single trace by *trace_id*.
196
+
197
+ Args:
198
+ trace_id: The Jaeger trace identifier.
199
+
200
+ Returns:
201
+ Parsed :class:`~veris_ai.jaeger_interface.models.GetTraceResponse` model.
202
+ """
203
+ if not trace_id:
204
+ error_msg = "trace_id must be non-empty"
205
+ raise ValueError(error_msg)
206
+
207
+ session, should_close = self._make_session()
208
+ try:
209
+ url = f"{self._base_url}/api/traces/{trace_id}"
210
+ response = session.get(url, timeout=self._timeout)
211
+ response.raise_for_status()
212
+ data = response.json()
213
+ finally:
214
+ if should_close:
215
+ session.close()
216
+ return GetTraceResponse.model_validate(data) # type: ignore[arg-type]
217
+
218
+ # ------------------------------------------------------------------
219
+ # Context-manager helpers (optional but convenient)
220
+ # ------------------------------------------------------------------
221
+
222
+ def __enter__(self) -> Self:
223
+ """Enter the context manager."""
224
+ self._session_ctx, self._should_close_ctx = self._make_session()
225
+ return self
226
+
227
+ def __exit__(
228
+ self,
229
+ exc_type: type[BaseException] | None,
230
+ exc: BaseException | None,
231
+ tb: types.TracebackType | None,
232
+ ) -> None:
233
+ """Exit the context manager."""
234
+ # Only close if we created the session
235
+ if getattr(self, "_should_close_ctx", False):
236
+ self._session_ctx.close()
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ __all__ = [
8
+ "Tag",
9
+ "Process",
10
+ "Span",
11
+ "Trace",
12
+ "SearchResponse",
13
+ "GetTraceResponse",
14
+ ]
15
+
16
+
17
+ class Tag(BaseModel):
18
+ """A Jaeger tag key/value pair."""
19
+
20
+ key: str
21
+ value: Any
22
+ type: str | None = None # Jaeger uses an optional *type* field in v1
23
+
24
+
25
+ class Process(BaseModel):
26
+ """Represents the *process* section of a Jaeger trace."""
27
+
28
+ serviceName: str = Field(alias="serviceName") # noqa: N815
29
+ tags: list[Tag] | None = None
30
+
31
+
32
+ class Span(BaseModel):
33
+ """Represents a single Jaeger span."""
34
+
35
+ traceID: str # noqa: N815
36
+ spanID: str # noqa: N815
37
+ operationName: str # noqa: N815
38
+ startTime: int # noqa: N815
39
+ duration: int
40
+ tags: list[Tag] | None = None
41
+ references: list[dict[str, Any]] | None = None
42
+ processID: str | None = None # noqa: N815
43
+
44
+ model_config = ConfigDict(extra="allow")
45
+
46
+
47
+ class Trace(BaseModel):
48
+ """A full Jaeger trace as returned by the Query API."""
49
+
50
+ traceID: str # noqa: N815
51
+ spans: list[Span]
52
+ process: Process | dict[str, Process] | None = None
53
+ warnings: list[str] | None = None
54
+
55
+ model_config = ConfigDict(extra="allow")
56
+
57
+
58
+ class _BaseResponse(BaseModel):
59
+ data: list[Trace] | Trace | None = None
60
+ errors: list[str] | None = None
61
+
62
+ # Allow any additional keys returned by Jaeger so that nothing gets
63
+ # silently dropped if the backend adds new fields we don't know about.
64
+
65
+ model_config = ConfigDict(extra="allow")
66
+
67
+
68
+ class SearchResponse(_BaseResponse):
69
+ """Response model for *search* or *find traces* requests."""
70
+
71
+ total: int | None = None
72
+ limit: int | None = None
73
+
74
+
75
+ class GetTraceResponse(_BaseResponse):
76
+ """Response model for *get trace by id* requests."""
77
+
78
+ # Same as base but alias for clarity
@@ -0,0 +1,11 @@
1
+ """Models for the VERIS SDK."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class ResponseExpectation(str, Enum):
7
+ """Expected response behavior for tool mocking."""
8
+
9
+ AUTO = "auto"
10
+ REQUIRED = "required"
11
+ NONE = "none"