spanforge 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.
- spanforge/__init__.py +815 -0
- spanforge/_ansi.py +93 -0
- spanforge/_batch_exporter.py +409 -0
- spanforge/_cli.py +2094 -0
- spanforge/_cli_audit.py +639 -0
- spanforge/_cli_compliance.py +711 -0
- spanforge/_cli_cost.py +243 -0
- spanforge/_cli_ops.py +791 -0
- spanforge/_cli_phase11.py +356 -0
- spanforge/_hooks.py +337 -0
- spanforge/_server.py +1708 -0
- spanforge/_span.py +1036 -0
- spanforge/_store.py +288 -0
- spanforge/_stream.py +664 -0
- spanforge/_trace.py +335 -0
- spanforge/_tracer.py +254 -0
- spanforge/actor.py +141 -0
- spanforge/alerts.py +469 -0
- spanforge/auto.py +464 -0
- spanforge/baseline.py +335 -0
- spanforge/cache.py +635 -0
- spanforge/compliance.py +325 -0
- spanforge/config.py +532 -0
- spanforge/consent.py +228 -0
- spanforge/consumer.py +377 -0
- spanforge/core/__init__.py +5 -0
- spanforge/core/compliance_mapping.py +1254 -0
- spanforge/cost.py +600 -0
- spanforge/debug.py +548 -0
- spanforge/deprecations.py +205 -0
- spanforge/drift.py +482 -0
- spanforge/egress.py +58 -0
- spanforge/eval.py +648 -0
- spanforge/event.py +1064 -0
- spanforge/exceptions.py +240 -0
- spanforge/explain.py +178 -0
- spanforge/export/__init__.py +69 -0
- spanforge/export/append_only.py +337 -0
- spanforge/export/cloud.py +357 -0
- spanforge/export/datadog.py +497 -0
- spanforge/export/grafana.py +320 -0
- spanforge/export/jsonl.py +195 -0
- spanforge/export/openinference.py +158 -0
- spanforge/export/otel_bridge.py +294 -0
- spanforge/export/otlp.py +811 -0
- spanforge/export/otlp_bridge.py +233 -0
- spanforge/export/redis_backend.py +282 -0
- spanforge/export/siem_schema.py +98 -0
- spanforge/export/siem_splunk.py +264 -0
- spanforge/export/siem_syslog.py +212 -0
- spanforge/export/webhook.py +299 -0
- spanforge/exporters/__init__.py +30 -0
- spanforge/exporters/console.py +271 -0
- spanforge/exporters/jsonl.py +144 -0
- spanforge/exporters/sqlite.py +142 -0
- spanforge/gate.py +1150 -0
- spanforge/governance.py +181 -0
- spanforge/hitl.py +295 -0
- spanforge/http.py +187 -0
- spanforge/inspect.py +427 -0
- spanforge/integrations/__init__.py +45 -0
- spanforge/integrations/_pricing.py +280 -0
- spanforge/integrations/anthropic.py +388 -0
- spanforge/integrations/azure_openai.py +133 -0
- spanforge/integrations/bedrock.py +292 -0
- spanforge/integrations/crewai.py +251 -0
- spanforge/integrations/gemini.py +351 -0
- spanforge/integrations/groq.py +442 -0
- spanforge/integrations/langchain.py +349 -0
- spanforge/integrations/langgraph.py +306 -0
- spanforge/integrations/llamaindex.py +373 -0
- spanforge/integrations/ollama.py +287 -0
- spanforge/integrations/openai.py +368 -0
- spanforge/integrations/together.py +483 -0
- spanforge/io.py +214 -0
- spanforge/lint.py +322 -0
- spanforge/metrics.py +417 -0
- spanforge/metrics_export.py +343 -0
- spanforge/migrate.py +402 -0
- spanforge/model_registry.py +278 -0
- spanforge/models.py +389 -0
- spanforge/namespaces/__init__.py +254 -0
- spanforge/namespaces/audit.py +256 -0
- spanforge/namespaces/cache.py +237 -0
- spanforge/namespaces/chain.py +77 -0
- spanforge/namespaces/confidence.py +72 -0
- spanforge/namespaces/consent.py +92 -0
- spanforge/namespaces/cost.py +179 -0
- spanforge/namespaces/decision.py +143 -0
- spanforge/namespaces/diff.py +157 -0
- spanforge/namespaces/drift.py +80 -0
- spanforge/namespaces/eval_.py +251 -0
- spanforge/namespaces/feedback.py +241 -0
- spanforge/namespaces/fence.py +193 -0
- spanforge/namespaces/guard.py +105 -0
- spanforge/namespaces/hitl.py +91 -0
- spanforge/namespaces/latency.py +72 -0
- spanforge/namespaces/prompt.py +190 -0
- spanforge/namespaces/redact.py +173 -0
- spanforge/namespaces/retrieval.py +379 -0
- spanforge/namespaces/runtime_governance.py +494 -0
- spanforge/namespaces/template.py +208 -0
- spanforge/namespaces/tool_call.py +77 -0
- spanforge/namespaces/trace.py +1029 -0
- spanforge/normalizer.py +171 -0
- spanforge/plugins.py +82 -0
- spanforge/presidio_backend.py +349 -0
- spanforge/processor.py +258 -0
- spanforge/prompt_registry.py +418 -0
- spanforge/py.typed +0 -0
- spanforge/redact.py +914 -0
- spanforge/regression.py +192 -0
- spanforge/runtime_policy.py +159 -0
- spanforge/sampling.py +511 -0
- spanforge/schema.py +183 -0
- spanforge/schemas/v1.0/schema.json +170 -0
- spanforge/schemas/v2.0/schema.json +536 -0
- spanforge/sdk/__init__.py +625 -0
- spanforge/sdk/_base.py +584 -0
- spanforge/sdk/_base.pyi +71 -0
- spanforge/sdk/_exceptions.py +1096 -0
- spanforge/sdk/_types.py +2184 -0
- spanforge/sdk/alert.py +1514 -0
- spanforge/sdk/alert.pyi +56 -0
- spanforge/sdk/audit.py +1196 -0
- spanforge/sdk/audit.pyi +67 -0
- spanforge/sdk/cec.py +1215 -0
- spanforge/sdk/cec.pyi +37 -0
- spanforge/sdk/config.py +641 -0
- spanforge/sdk/config.pyi +55 -0
- spanforge/sdk/enterprise.py +714 -0
- spanforge/sdk/enterprise.pyi +79 -0
- spanforge/sdk/explain.py +170 -0
- spanforge/sdk/fallback.py +432 -0
- spanforge/sdk/feedback.py +351 -0
- spanforge/sdk/gate.py +874 -0
- spanforge/sdk/gate.pyi +51 -0
- spanforge/sdk/identity.py +2114 -0
- spanforge/sdk/identity.pyi +47 -0
- spanforge/sdk/lineage.py +175 -0
- spanforge/sdk/observe.py +1065 -0
- spanforge/sdk/observe.pyi +50 -0
- spanforge/sdk/operator.py +338 -0
- spanforge/sdk/pii.py +1473 -0
- spanforge/sdk/pii.pyi +119 -0
- spanforge/sdk/pipelines.py +458 -0
- spanforge/sdk/pipelines.pyi +39 -0
- spanforge/sdk/policy.py +930 -0
- spanforge/sdk/rag.py +594 -0
- spanforge/sdk/rbac.py +280 -0
- spanforge/sdk/registry.py +430 -0
- spanforge/sdk/registry.pyi +46 -0
- spanforge/sdk/scope.py +279 -0
- spanforge/sdk/secrets.py +293 -0
- spanforge/sdk/secrets.pyi +25 -0
- spanforge/sdk/security.py +560 -0
- spanforge/sdk/security.pyi +57 -0
- spanforge/sdk/trust.py +472 -0
- spanforge/sdk/trust.pyi +41 -0
- spanforge/secrets.py +799 -0
- spanforge/signing.py +1179 -0
- spanforge/stats.py +100 -0
- spanforge/stream.py +560 -0
- spanforge/testing.py +378 -0
- spanforge/testing_mocks.py +1052 -0
- spanforge/trace.py +199 -0
- spanforge/types.py +696 -0
- spanforge/ulid.py +300 -0
- spanforge/validate.py +379 -0
- spanforge-1.0.0.dist-info/METADATA +1509 -0
- spanforge-1.0.0.dist-info/RECORD +174 -0
- spanforge-1.0.0.dist-info/WHEEL +4 -0
- spanforge-1.0.0.dist-info/entry_points.txt +5 -0
- spanforge-1.0.0.dist-info/licenses/LICENSE +128 -0
spanforge/models.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Pydantic v2 model layer for spanforge events.
|
|
2
|
+
|
|
3
|
+
This module provides Pydantic v2 models that mirror the :class:`~spanforge.event.Event`
|
|
4
|
+
envelope with strict field-level validation and bidirectional conversion.
|
|
5
|
+
|
|
6
|
+
The model layer is **optional** — it requires ``pydantic>=2.7`` which is not a
|
|
7
|
+
core dependency. Install it with::
|
|
8
|
+
|
|
9
|
+
pip install "spanforge[pydantic]"
|
|
10
|
+
|
|
11
|
+
Design goals
|
|
12
|
+
------------
|
|
13
|
+
* All field validation is equivalent to :meth:`~spanforge.event.Event.validate`,
|
|
14
|
+
giving callers a familiar API while leveraging Pydantic's declarative style.
|
|
15
|
+
* :class:`EventModel` is immutable (``frozen=True``).
|
|
16
|
+
* :meth:`EventModel.from_event` and :meth:`EventModel.to_event` provide lossless
|
|
17
|
+
round-trips.
|
|
18
|
+
* :meth:`EventModel.model_json_schema` exports a full JSON Schema (for Phase 5
|
|
19
|
+
schema publication).
|
|
20
|
+
|
|
21
|
+
Example::
|
|
22
|
+
|
|
23
|
+
from spanforge import Event, EventType
|
|
24
|
+
from spanforge.models import EventModel
|
|
25
|
+
|
|
26
|
+
event = Event(
|
|
27
|
+
event_type=EventType.TRACE_SPAN_COMPLETED,
|
|
28
|
+
source="llm-trace@0.3.1",
|
|
29
|
+
payload={"status": "ok"},
|
|
30
|
+
)
|
|
31
|
+
model = EventModel.from_event(event)
|
|
32
|
+
print(model.model_json_schema())
|
|
33
|
+
restored = model.to_event()
|
|
34
|
+
assert restored.event_id == event.event_id
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import re
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
44
|
+
from pydantic import ValidationError as _PydanticValidationError
|
|
45
|
+
except ImportError as _import_err: # pragma: no cover
|
|
46
|
+
raise ImportError(
|
|
47
|
+
"pydantic>=2.7 is required for spanforge.models. "
|
|
48
|
+
'Install it: pip install "spanforge[pydantic]"'
|
|
49
|
+
) from _import_err
|
|
50
|
+
|
|
51
|
+
from spanforge.event import SCHEMA_VERSION, Event, Tags
|
|
52
|
+
from spanforge.types import EVENT_TYPE_PATTERN
|
|
53
|
+
from spanforge.ulid import validate as _validate_ulid
|
|
54
|
+
|
|
55
|
+
__all__ = ["EventModel", "TagsModel"]
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Validation patterns (must stay in sync with spanforge/event.py)
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
_SEMVER_RE: re.Pattern[str] = re.compile(r"^\d+\.\d+(?:\.\d+)?(?:[.-][a-zA-Z0-9.]+)?$")
|
|
62
|
+
_SOURCE_RE: re.Pattern[str] = re.compile(r"^[a-z][a-z0-9\-]*@\d+\.\d+\.\d+$")
|
|
63
|
+
_TIMESTAMP_RE: re.Pattern[str] = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$")
|
|
64
|
+
_TRACE_ID_RE: re.Pattern[str] = re.compile(r"^[0-9a-f]{32}$")
|
|
65
|
+
_SPAN_ID_RE: re.Pattern[str] = re.compile(r"^[0-9a-f]{16}$")
|
|
66
|
+
_EVENT_TYPE_RE: re.Pattern[str] = re.compile(EVENT_TYPE_PATTERN)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# TagsModel
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TagsModel(BaseModel):
|
|
75
|
+
"""Pydantic model for event tags.
|
|
76
|
+
|
|
77
|
+
Allows arbitrary ``str → str`` key-value pairs as extra fields. All
|
|
78
|
+
values must be strings; non-string values are rejected by Pydantic.
|
|
79
|
+
|
|
80
|
+
Example::
|
|
81
|
+
|
|
82
|
+
tags = TagsModel(env="production", model="gpt-4o")
|
|
83
|
+
tags.model_dump() # {"env": "production", "model": "gpt-4o"}
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
model_config = ConfigDict(frozen=True, extra="allow")
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_tags(cls, tags: Tags) -> TagsModel:
|
|
90
|
+
"""Construct from a :class:`~spanforge.event.Tags` instance.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
tags: A :class:`~spanforge.event.Tags` instance.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A corresponding :class:`TagsModel`.
|
|
97
|
+
"""
|
|
98
|
+
return cls(**dict(tags))
|
|
99
|
+
|
|
100
|
+
def to_tags(self) -> Tags:
|
|
101
|
+
"""Convert back to a :class:`~spanforge.event.Tags` instance.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A new :class:`~spanforge.event.Tags` with the same key-value pairs.
|
|
105
|
+
"""
|
|
106
|
+
return Tags(**self.model_dump())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# EventModel
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class EventModel(BaseModel):
|
|
115
|
+
"""Pydantic v2 model for the spanforge event envelope.
|
|
116
|
+
|
|
117
|
+
Each field carries a Pydantic ``Field`` description and is validated by a
|
|
118
|
+
``@field_validator``. The model is frozen (immutable after construction).
|
|
119
|
+
|
|
120
|
+
Validation rules are equivalent to those enforced by
|
|
121
|
+
:meth:`~spanforge.event.Event.validate`, so ``EventModel.from_event(event)``
|
|
122
|
+
succeeds for any event that passes :meth:`~spanforge.event.Event.validate`.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
schema_version: Schema version string (e.g. ``"1.0"``).
|
|
126
|
+
event_id: 26-character ULID.
|
|
127
|
+
event_type: Namespaced event type (e.g. ``"llm.trace.span.completed"``).
|
|
128
|
+
timestamp: UTC ISO-8601 timestamp (e.g. ``"2026-03-01T12:00:00.000000Z"``).
|
|
129
|
+
source: Tool name + version (e.g. ``"llm-trace@0.3.1"``).
|
|
130
|
+
payload: Non-empty dict of event-type-specific data.
|
|
131
|
+
trace_id: Optional 32-char hex OpenTelemetry trace ID.
|
|
132
|
+
span_id: Optional 16-char hex OpenTelemetry span ID.
|
|
133
|
+
parent_span_id: Optional 16-char hex parent span ID.
|
|
134
|
+
org_id: Optional organisation identifier.
|
|
135
|
+
team_id: Optional team identifier.
|
|
136
|
+
actor_id: Optional user/service identifier.
|
|
137
|
+
session_id: Optional session/conversation identifier.
|
|
138
|
+
tags: Optional :class:`TagsModel` with arbitrary metadata.
|
|
139
|
+
checksum: Optional SHA-256 payload checksum.
|
|
140
|
+
signature: Optional HMAC-SHA256 audit chain signature.
|
|
141
|
+
prev_id: Optional ULID of preceding event in audit chain.
|
|
142
|
+
|
|
143
|
+
Example::
|
|
144
|
+
|
|
145
|
+
from spanforge.models import EventModel
|
|
146
|
+
|
|
147
|
+
model = EventModel(
|
|
148
|
+
event_id="01ARYZ3NDEKTSV4RRFFQ69G5FAV",
|
|
149
|
+
event_type="llm.trace.span.completed",
|
|
150
|
+
timestamp="2026-03-01T12:00:00.000000Z",
|
|
151
|
+
source="llm-trace@0.3.1",
|
|
152
|
+
payload={"status": "ok"},
|
|
153
|
+
)
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
model_config = ConfigDict(frozen=True, populate_by_name=True)
|
|
157
|
+
|
|
158
|
+
schema_version: str = Field(
|
|
159
|
+
default=SCHEMA_VERSION,
|
|
160
|
+
description="Schema version string, e.g. '1.0'.",
|
|
161
|
+
)
|
|
162
|
+
event_id: str = Field(
|
|
163
|
+
description="26-character Crockford Base32 ULID event identifier.",
|
|
164
|
+
)
|
|
165
|
+
event_type: str = Field(
|
|
166
|
+
description="Namespaced event type, e.g. 'llm.trace.span.completed'.",
|
|
167
|
+
)
|
|
168
|
+
timestamp: str = Field(
|
|
169
|
+
description="UTC ISO-8601 timestamp, e.g. '2026-03-01T12:00:00.000000Z'.",
|
|
170
|
+
)
|
|
171
|
+
source: str = Field(
|
|
172
|
+
description="Source tool and version, e.g. 'llm-trace@0.3.1'.",
|
|
173
|
+
)
|
|
174
|
+
payload: dict[str, Any] = Field(
|
|
175
|
+
description="Non-empty dict of event-type-specific data.",
|
|
176
|
+
)
|
|
177
|
+
trace_id: str | None = Field(
|
|
178
|
+
default=None,
|
|
179
|
+
description="OpenTelemetry trace ID — 32 lowercase hex characters.",
|
|
180
|
+
)
|
|
181
|
+
span_id: str | None = Field(
|
|
182
|
+
default=None,
|
|
183
|
+
description="OpenTelemetry span ID — 16 lowercase hex characters.",
|
|
184
|
+
)
|
|
185
|
+
parent_span_id: str | None = Field(
|
|
186
|
+
default=None,
|
|
187
|
+
description="Parent span ID — 16 lowercase hex characters.",
|
|
188
|
+
)
|
|
189
|
+
org_id: str | None = Field(
|
|
190
|
+
default=None,
|
|
191
|
+
description="Organisation identifier (non-empty string).",
|
|
192
|
+
)
|
|
193
|
+
team_id: str | None = Field(
|
|
194
|
+
default=None,
|
|
195
|
+
description="Team identifier within the organisation (non-empty string).",
|
|
196
|
+
)
|
|
197
|
+
actor_id: str | None = Field(
|
|
198
|
+
default=None,
|
|
199
|
+
description="User or service actor identifier (non-empty string).",
|
|
200
|
+
)
|
|
201
|
+
session_id: str | None = Field(
|
|
202
|
+
default=None,
|
|
203
|
+
description="Session or conversation identifier (non-empty string).",
|
|
204
|
+
)
|
|
205
|
+
tags: TagsModel | None = Field(
|
|
206
|
+
default=None,
|
|
207
|
+
description="Arbitrary string key-value metadata tags.",
|
|
208
|
+
)
|
|
209
|
+
checksum: str | None = Field(
|
|
210
|
+
default=None,
|
|
211
|
+
description="SHA-256 payload checksum (prefixed 'sha256:').",
|
|
212
|
+
)
|
|
213
|
+
signature: str | None = Field(
|
|
214
|
+
default=None,
|
|
215
|
+
description="HMAC-SHA256 audit chain signature (set by spanforge.signing).",
|
|
216
|
+
)
|
|
217
|
+
prev_id: str | None = Field(
|
|
218
|
+
default=None,
|
|
219
|
+
description="ULID of the preceding event in the tamper-evident audit chain.",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
# Field validators
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
@field_validator("schema_version")
|
|
227
|
+
@classmethod
|
|
228
|
+
def _check_schema_version(cls, v: str) -> str:
|
|
229
|
+
if not _SEMVER_RE.match(v):
|
|
230
|
+
raise ValueError(f"schema_version must match SemVer pattern (e.g. '1.0'), got {v!r}")
|
|
231
|
+
return v
|
|
232
|
+
|
|
233
|
+
@field_validator("event_id")
|
|
234
|
+
@classmethod
|
|
235
|
+
def _check_event_id(cls, v: str) -> str:
|
|
236
|
+
if not _validate_ulid(v):
|
|
237
|
+
raise ValueError("event_id must be a valid 26-character ULID (Crockford Base32)")
|
|
238
|
+
return v
|
|
239
|
+
|
|
240
|
+
@field_validator("event_type")
|
|
241
|
+
@classmethod
|
|
242
|
+
def _check_event_type(cls, v: str) -> str:
|
|
243
|
+
if not _EVENT_TYPE_RE.match(v):
|
|
244
|
+
raise ValueError(
|
|
245
|
+
"event_type must follow 'llm.<namespace>.<entity>.<action>' "
|
|
246
|
+
"or 'x.<company>.<…>' pattern"
|
|
247
|
+
)
|
|
248
|
+
return v
|
|
249
|
+
|
|
250
|
+
@field_validator("timestamp")
|
|
251
|
+
@classmethod
|
|
252
|
+
def _check_timestamp(cls, v: str) -> str:
|
|
253
|
+
if not _TIMESTAMP_RE.match(v):
|
|
254
|
+
raise ValueError(
|
|
255
|
+
"timestamp must be a UTC ISO-8601 string ending in 'Z', "
|
|
256
|
+
f"e.g. '2026-03-01T12:00:00.000000Z', got {v!r}"
|
|
257
|
+
)
|
|
258
|
+
return v
|
|
259
|
+
|
|
260
|
+
@field_validator("source")
|
|
261
|
+
@classmethod
|
|
262
|
+
def _check_source(cls, v: str) -> str:
|
|
263
|
+
if not _SOURCE_RE.match(v):
|
|
264
|
+
raise ValueError(
|
|
265
|
+
"source must match 'tool-name@semver' pattern (full 3-part semver), "
|
|
266
|
+
f"e.g. 'llm-trace@0.3.1', got {v!r}"
|
|
267
|
+
)
|
|
268
|
+
return v
|
|
269
|
+
|
|
270
|
+
@field_validator("payload")
|
|
271
|
+
@classmethod
|
|
272
|
+
def _check_payload(cls, v: dict[str, Any]) -> dict[str, Any]:
|
|
273
|
+
if not v:
|
|
274
|
+
raise ValueError("payload must be a non-empty dict")
|
|
275
|
+
return v
|
|
276
|
+
|
|
277
|
+
@field_validator("trace_id")
|
|
278
|
+
@classmethod
|
|
279
|
+
def _check_trace_id(cls, v: str | None) -> str | None:
|
|
280
|
+
if v is not None and not _TRACE_ID_RE.match(v):
|
|
281
|
+
raise ValueError("trace_id must be exactly 32 lowercase hex characters")
|
|
282
|
+
return v
|
|
283
|
+
|
|
284
|
+
@field_validator("span_id", "parent_span_id")
|
|
285
|
+
@classmethod
|
|
286
|
+
def _check_span_id(cls, v: str | None) -> str | None:
|
|
287
|
+
if v is not None and not _SPAN_ID_RE.match(v):
|
|
288
|
+
raise ValueError("span_id / parent_span_id must be exactly 16 lowercase hex characters")
|
|
289
|
+
return v
|
|
290
|
+
|
|
291
|
+
@field_validator("org_id", "team_id", "actor_id", "session_id")
|
|
292
|
+
@classmethod
|
|
293
|
+
def _check_string_id(cls, v: str | None) -> str | None:
|
|
294
|
+
if v is not None and not v.strip():
|
|
295
|
+
raise ValueError("org_id / team_id / actor_id / session_id must be non-empty")
|
|
296
|
+
return v
|
|
297
|
+
|
|
298
|
+
@field_validator("prev_id")
|
|
299
|
+
@classmethod
|
|
300
|
+
def _check_prev_id(cls, v: str | None) -> str | None:
|
|
301
|
+
if v is not None and not _validate_ulid(v):
|
|
302
|
+
raise ValueError("prev_id must be a valid 26-character ULID (Crockford Base32)")
|
|
303
|
+
return v
|
|
304
|
+
|
|
305
|
+
# ------------------------------------------------------------------
|
|
306
|
+
# Conversion helpers
|
|
307
|
+
# ------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
def from_event(cls, event: Event) -> EventModel:
|
|
311
|
+
"""Construct an :class:`EventModel` from an :class:`~spanforge.event.Event`.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
event: A validated or unvalidated :class:`~spanforge.event.Event`.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
A new :class:`EventModel` with all fields populated.
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
pydantic.ValidationError: If the event contains invalid field values.
|
|
321
|
+
|
|
322
|
+
Example::
|
|
323
|
+
|
|
324
|
+
model = EventModel.from_event(event)
|
|
325
|
+
"""
|
|
326
|
+
tags_model: TagsModel | None = (
|
|
327
|
+
TagsModel.from_tags(event.tags) if event.tags is not None else None
|
|
328
|
+
)
|
|
329
|
+
return cls(
|
|
330
|
+
schema_version=event.schema_version,
|
|
331
|
+
event_id=event.event_id,
|
|
332
|
+
event_type=event.event_type,
|
|
333
|
+
timestamp=event.timestamp,
|
|
334
|
+
source=event.source,
|
|
335
|
+
payload=dict(event.payload),
|
|
336
|
+
trace_id=event.trace_id,
|
|
337
|
+
span_id=event.span_id,
|
|
338
|
+
parent_span_id=event.parent_span_id,
|
|
339
|
+
org_id=event.org_id,
|
|
340
|
+
team_id=event.team_id,
|
|
341
|
+
actor_id=event.actor_id,
|
|
342
|
+
session_id=event.session_id,
|
|
343
|
+
tags=tags_model,
|
|
344
|
+
checksum=event.checksum,
|
|
345
|
+
signature=event.signature,
|
|
346
|
+
prev_id=event.prev_id,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def to_event(self) -> Event:
|
|
350
|
+
"""Convert this model back to an :class:`~spanforge.event.Event`.
|
|
351
|
+
|
|
352
|
+
The returned event has the same field values as this model. Call
|
|
353
|
+
:meth:`~spanforge.event.Event.validate` if you want to re-run all
|
|
354
|
+
built-in validators (they are equivalent to those already applied by
|
|
355
|
+
Pydantic during construction of this model).
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
A new :class:`~spanforge.event.Event` instance.
|
|
359
|
+
|
|
360
|
+
Example::
|
|
361
|
+
|
|
362
|
+
event = model.to_event()
|
|
363
|
+
assert event.event_id == model.event_id
|
|
364
|
+
"""
|
|
365
|
+
tags: Tags | None = self.tags.to_tags() if self.tags is not None else None
|
|
366
|
+
kwargs: dict[str, Any] = {
|
|
367
|
+
k: v
|
|
368
|
+
for k, v in {
|
|
369
|
+
"schema_version": self.schema_version,
|
|
370
|
+
"event_id": self.event_id,
|
|
371
|
+
"event_type": self.event_type,
|
|
372
|
+
"timestamp": self.timestamp,
|
|
373
|
+
"source": self.source,
|
|
374
|
+
"payload": dict(self.payload),
|
|
375
|
+
"trace_id": self.trace_id,
|
|
376
|
+
"span_id": self.span_id,
|
|
377
|
+
"parent_span_id": self.parent_span_id,
|
|
378
|
+
"org_id": self.org_id,
|
|
379
|
+
"team_id": self.team_id,
|
|
380
|
+
"actor_id": self.actor_id,
|
|
381
|
+
"session_id": self.session_id,
|
|
382
|
+
"tags": tags,
|
|
383
|
+
"checksum": self.checksum,
|
|
384
|
+
"signature": self.signature,
|
|
385
|
+
"prev_id": self.prev_id,
|
|
386
|
+
}.items()
|
|
387
|
+
if v is not None
|
|
388
|
+
}
|
|
389
|
+
return Event(**kwargs)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""spanforge.namespaces — Namespace-specific payload dataclasses (v2.0).
|
|
2
|
+
|
|
3
|
+
Each sub-module provides dataclasses that model the ``payload`` field of
|
|
4
|
+
:class:`~spanforge.event.Event` for a given namespace.
|
|
5
|
+
|
|
6
|
+
All payload classes share the same contract:
|
|
7
|
+
|
|
8
|
+
* ``to_dict() -> dict`` — serialise to a plain dict for ``Event.payload``.
|
|
9
|
+
* ``from_dict(data) -> cls`` — reconstruct from a plain dict.
|
|
10
|
+
* ``__post_init__`` — validates every field at construction time.
|
|
11
|
+
|
|
12
|
+
Sub-modules
|
|
13
|
+
-----------
|
|
14
|
+
audit
|
|
15
|
+
:class:`AuditKeyRotatedPayload`, :class:`AuditChainVerifiedPayload`,
|
|
16
|
+
:class:`AuditChainTamperedPayload`, :class:`AuditChainPayload`
|
|
17
|
+
cache
|
|
18
|
+
:class:`CacheHitPayload`, :class:`CacheMissPayload`,
|
|
19
|
+
:class:`CacheEvictedPayload`, :class:`CacheWrittenPayload`
|
|
20
|
+
chain (RFC-0001 SPANFORGE)
|
|
21
|
+
:class:`ChainPayload`
|
|
22
|
+
confidence (RFC-0001 SPANFORGE)
|
|
23
|
+
:class:`ConfidencePayload`
|
|
24
|
+
consent (RFC-0001 SPANFORGE)
|
|
25
|
+
:class:`ConsentPayload`
|
|
26
|
+
hitl (RFC-0001 SPANFORGE)
|
|
27
|
+
:class:`HITLPayload`
|
|
28
|
+
playbook (RFC-0001 SPANFORGE)
|
|
29
|
+
*removed*
|
|
30
|
+
cost
|
|
31
|
+
:class:`CostTokenRecordedPayload`, :class:`CostSessionRecordedPayload`,
|
|
32
|
+
:class:`CostAttributedPayload`
|
|
33
|
+
decision (RFC-0001 SPANFORGE)
|
|
34
|
+
:class:`DecisionDriver`, :class:`DecisionPayload`
|
|
35
|
+
diff
|
|
36
|
+
:class:`DiffComputedPayload`, :class:`DiffRegressionFlaggedPayload`
|
|
37
|
+
drift (RFC-0001 SPANFORGE)
|
|
38
|
+
:class:`DriftPayload`
|
|
39
|
+
eval_
|
|
40
|
+
:class:`EvalScoreRecordedPayload`, :class:`EvalRegressionDetectedPayload`,
|
|
41
|
+
:class:`EvalScenarioStartedPayload`, :class:`EvalScenarioCompletedPayload`
|
|
42
|
+
fence
|
|
43
|
+
:class:`FenceValidatedPayload`, :class:`FenceRetryTriggeredPayload`,
|
|
44
|
+
:class:`FenceMaxRetriesExceededPayload`
|
|
45
|
+
guard
|
|
46
|
+
:class:`GuardPayload`
|
|
47
|
+
latency (RFC-0001 SPANFORGE)
|
|
48
|
+
:class:`LatencyPayload`
|
|
49
|
+
playbook (RFC-0001 SPANFORGE)
|
|
50
|
+
*removed*
|
|
51
|
+
prompt
|
|
52
|
+
:class:`PromptRenderedPayload`, :class:`PromptTemplateLoadedPayload`,
|
|
53
|
+
:class:`PromptVersionChangedPayload`
|
|
54
|
+
redact
|
|
55
|
+
:class:`RedactPiiDetectedPayload`, :class:`RedactPhiDetectedPayload`,
|
|
56
|
+
:class:`RedactAppliedPayload`
|
|
57
|
+
template
|
|
58
|
+
:class:`TemplateRegisteredPayload`, :class:`TemplateVariableBoundPayload`,
|
|
59
|
+
:class:`TemplateValidationFailedPayload`
|
|
60
|
+
tool_call (RFC-0001 SPANFORGE)
|
|
61
|
+
:class:`ToolCallPayload`
|
|
62
|
+
trace
|
|
63
|
+
:class:`GenAISystem`, :class:`GenAIOperationName`, :class:`SpanKind`,
|
|
64
|
+
:class:`TokenUsage`, :class:`ModelInfo`, :class:`CostBreakdown`,
|
|
65
|
+
:class:`PricingTier`, :class:`ToolCall`, :class:`ReasoningStep`,
|
|
66
|
+
:class:`DecisionPoint`, :class:`SpanPayload`, :class:`AgentStepPayload`,
|
|
67
|
+
:class:`AgentRunPayload`
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
from spanforge.namespaces.audit import (
|
|
71
|
+
AuditChainPayload,
|
|
72
|
+
AuditChainTamperedPayload,
|
|
73
|
+
AuditChainVerifiedPayload,
|
|
74
|
+
AuditKeyRotatedPayload,
|
|
75
|
+
)
|
|
76
|
+
from spanforge.namespaces.cache import (
|
|
77
|
+
CacheEvictedPayload,
|
|
78
|
+
CacheHitPayload,
|
|
79
|
+
CacheMissPayload,
|
|
80
|
+
CacheWrittenPayload,
|
|
81
|
+
)
|
|
82
|
+
from spanforge.namespaces.chain import ChainPayload
|
|
83
|
+
from spanforge.namespaces.confidence import ConfidencePayload
|
|
84
|
+
from spanforge.namespaces.consent import ConsentPayload
|
|
85
|
+
from spanforge.namespaces.cost import (
|
|
86
|
+
CostAttributedPayload,
|
|
87
|
+
CostSessionRecordedPayload,
|
|
88
|
+
CostTokenRecordedPayload,
|
|
89
|
+
)
|
|
90
|
+
from spanforge.namespaces.decision import DecisionDriver, DecisionPayload
|
|
91
|
+
from spanforge.namespaces.diff import (
|
|
92
|
+
DiffComputedPayload,
|
|
93
|
+
DiffRegressionFlaggedPayload,
|
|
94
|
+
)
|
|
95
|
+
from spanforge.namespaces.drift import DriftPayload
|
|
96
|
+
from spanforge.namespaces.eval_ import (
|
|
97
|
+
EvalRegressionDetectedPayload,
|
|
98
|
+
EvalScenarioCompletedPayload,
|
|
99
|
+
EvalScenarioStartedPayload,
|
|
100
|
+
EvalScoreRecordedPayload,
|
|
101
|
+
)
|
|
102
|
+
from spanforge.namespaces.feedback import (
|
|
103
|
+
FeedbackRating,
|
|
104
|
+
FeedbackSubmittedPayload,
|
|
105
|
+
FeedbackSummaryPayload,
|
|
106
|
+
)
|
|
107
|
+
from spanforge.namespaces.fence import (
|
|
108
|
+
FenceMaxRetriesExceededPayload,
|
|
109
|
+
FenceRetryTriggeredPayload,
|
|
110
|
+
FenceValidatedPayload,
|
|
111
|
+
)
|
|
112
|
+
from spanforge.namespaces.guard import GuardPayload
|
|
113
|
+
from spanforge.namespaces.hitl import HITLPayload
|
|
114
|
+
from spanforge.namespaces.latency import LatencyPayload
|
|
115
|
+
from spanforge.namespaces.prompt import (
|
|
116
|
+
PromptRenderedPayload,
|
|
117
|
+
PromptTemplateLoadedPayload,
|
|
118
|
+
PromptVersionChangedPayload,
|
|
119
|
+
)
|
|
120
|
+
from spanforge.namespaces.redact import (
|
|
121
|
+
RedactAppliedPayload,
|
|
122
|
+
RedactPhiDetectedPayload,
|
|
123
|
+
RedactPiiDetectedPayload,
|
|
124
|
+
)
|
|
125
|
+
from spanforge.namespaces.retrieval import (
|
|
126
|
+
RAGSessionPayload,
|
|
127
|
+
RAGSpanPayload,
|
|
128
|
+
RetrievalQueryPayload,
|
|
129
|
+
RetrievalResultPayload,
|
|
130
|
+
RetrievedChunk,
|
|
131
|
+
)
|
|
132
|
+
from spanforge.namespaces.runtime_governance import (
|
|
133
|
+
ExplanationFactor,
|
|
134
|
+
ExplanationPayload,
|
|
135
|
+
GroundingClaim,
|
|
136
|
+
GroundingPayload,
|
|
137
|
+
LineagePayload,
|
|
138
|
+
RBACDecisionPayload,
|
|
139
|
+
ScopeDecisionPayload,
|
|
140
|
+
)
|
|
141
|
+
from spanforge.namespaces.template import (
|
|
142
|
+
TemplateRegisteredPayload,
|
|
143
|
+
TemplateValidationFailedPayload,
|
|
144
|
+
TemplateVariableBoundPayload,
|
|
145
|
+
)
|
|
146
|
+
from spanforge.namespaces.tool_call import ToolCallPayload
|
|
147
|
+
from spanforge.namespaces.trace import (
|
|
148
|
+
AgentRunPayload,
|
|
149
|
+
AgentStepPayload,
|
|
150
|
+
CostBreakdown,
|
|
151
|
+
DecisionPoint,
|
|
152
|
+
GenAIOperationName,
|
|
153
|
+
GenAISystem,
|
|
154
|
+
ModelInfo,
|
|
155
|
+
PricingTier,
|
|
156
|
+
ReasoningStep,
|
|
157
|
+
SpanKind,
|
|
158
|
+
SpanPayload,
|
|
159
|
+
TokenUsage,
|
|
160
|
+
ToolCall,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
__all__: list[str] = [
|
|
164
|
+
"AgentRunPayload",
|
|
165
|
+
"AgentStepPayload",
|
|
166
|
+
# audit (legacy + RFC-0001 SPANFORGE)
|
|
167
|
+
"AuditChainPayload",
|
|
168
|
+
"AuditChainTamperedPayload",
|
|
169
|
+
"AuditChainVerifiedPayload",
|
|
170
|
+
"AuditKeyRotatedPayload",
|
|
171
|
+
# cache
|
|
172
|
+
"CacheEvictedPayload",
|
|
173
|
+
"CacheHitPayload",
|
|
174
|
+
"CacheMissPayload",
|
|
175
|
+
"CacheWrittenPayload",
|
|
176
|
+
# chain (RFC-0001 SPANFORGE)
|
|
177
|
+
"ChainPayload",
|
|
178
|
+
# confidence (RFC-0001 SPANFORGE)
|
|
179
|
+
"ConfidencePayload",
|
|
180
|
+
# consent (RFC-0001 SPANFORGE)
|
|
181
|
+
"ConsentPayload",
|
|
182
|
+
# cost
|
|
183
|
+
"CostAttributedPayload",
|
|
184
|
+
"CostBreakdown",
|
|
185
|
+
"CostSessionRecordedPayload",
|
|
186
|
+
"CostTokenRecordedPayload",
|
|
187
|
+
# decision (RFC-0001 SPANFORGE)
|
|
188
|
+
"DecisionDriver",
|
|
189
|
+
"DecisionPayload",
|
|
190
|
+
# Backward-compat trace value object
|
|
191
|
+
"DecisionPoint",
|
|
192
|
+
# diff
|
|
193
|
+
"DiffComputedPayload",
|
|
194
|
+
"DiffRegressionFlaggedPayload",
|
|
195
|
+
# drift (RFC-0001 SPANFORGE)
|
|
196
|
+
"DriftPayload",
|
|
197
|
+
# eval
|
|
198
|
+
"EvalRegressionDetectedPayload",
|
|
199
|
+
"EvalScenarioCompletedPayload",
|
|
200
|
+
"EvalScenarioStartedPayload",
|
|
201
|
+
"EvalScoreRecordedPayload",
|
|
202
|
+
# fence
|
|
203
|
+
"FenceMaxRetriesExceededPayload",
|
|
204
|
+
"FenceRetryTriggeredPayload",
|
|
205
|
+
"FenceValidatedPayload",
|
|
206
|
+
# trace — value objects and payloads
|
|
207
|
+
"GenAIOperationName",
|
|
208
|
+
"GenAISystem",
|
|
209
|
+
# guard
|
|
210
|
+
"GuardPayload",
|
|
211
|
+
# hitl (RFC-0001 SPANFORGE)
|
|
212
|
+
"HITLPayload",
|
|
213
|
+
# latency (RFC-0001 SPANFORGE)
|
|
214
|
+
"LatencyPayload",
|
|
215
|
+
"ModelInfo",
|
|
216
|
+
"PricingTier",
|
|
217
|
+
# prompt
|
|
218
|
+
"PromptRenderedPayload",
|
|
219
|
+
"PromptTemplateLoadedPayload",
|
|
220
|
+
"PromptVersionChangedPayload",
|
|
221
|
+
"ReasoningStep",
|
|
222
|
+
# redact
|
|
223
|
+
"RedactAppliedPayload",
|
|
224
|
+
"RedactPhiDetectedPayload",
|
|
225
|
+
"RedactPiiDetectedPayload",
|
|
226
|
+
"SpanKind",
|
|
227
|
+
"SpanPayload",
|
|
228
|
+
# template
|
|
229
|
+
"TemplateRegisteredPayload",
|
|
230
|
+
"TemplateValidationFailedPayload",
|
|
231
|
+
"TemplateVariableBoundPayload",
|
|
232
|
+
"TokenUsage",
|
|
233
|
+
"ToolCall",
|
|
234
|
+
# tool_call (RFC-0001 SPANFORGE)
|
|
235
|
+
"ToolCallPayload",
|
|
236
|
+
# retrieval / RAG
|
|
237
|
+
"RAGSessionPayload",
|
|
238
|
+
"RAGSpanPayload",
|
|
239
|
+
"RetrievalQueryPayload",
|
|
240
|
+
"RetrievalResultPayload",
|
|
241
|
+
"RetrievedChunk",
|
|
242
|
+
# runtime governance GA payloads
|
|
243
|
+
"ExplanationFactor",
|
|
244
|
+
"ExplanationPayload",
|
|
245
|
+
"GroundingClaim",
|
|
246
|
+
"GroundingPayload",
|
|
247
|
+
"LineagePayload",
|
|
248
|
+
"ScopeDecisionPayload",
|
|
249
|
+
"RBACDecisionPayload",
|
|
250
|
+
# feedback
|
|
251
|
+
"FeedbackRating",
|
|
252
|
+
"FeedbackSubmittedPayload",
|
|
253
|
+
"FeedbackSummaryPayload",
|
|
254
|
+
]
|