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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""spanforge.namespaces.template — Template payload types (RFC-0001).
|
|
2
|
+
|
|
3
|
+
Classes
|
|
4
|
+
-------
|
|
5
|
+
TemplateRegisteredPayload llm.template.registered
|
|
6
|
+
TemplateVariableBoundPayload llm.template.variable.bound
|
|
7
|
+
TemplateValidationFailedPayload llm.template.validation.failed
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"TemplateRegisteredPayload",
|
|
17
|
+
"TemplateValidationFailedPayload",
|
|
18
|
+
"TemplateVariableBoundPayload",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
_VALID_VALUE_TYPES = frozenset({"string", "integer", "float", "boolean", "array", "object", "null"})
|
|
22
|
+
_VALID_FAILURE_TYPES = frozenset(
|
|
23
|
+
{
|
|
24
|
+
"missing_variable",
|
|
25
|
+
"type_mismatch",
|
|
26
|
+
"hash_mismatch",
|
|
27
|
+
"version_not_found",
|
|
28
|
+
"syntax_error",
|
|
29
|
+
"schema_violation",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_SHA256_HEX_LEN = 64 # SHA-256 hex digest length (characters)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TemplateRegisteredPayload:
|
|
38
|
+
"""RFC-0001 — A prompt template was registered in the registry."""
|
|
39
|
+
|
|
40
|
+
template_id: str
|
|
41
|
+
version: str
|
|
42
|
+
template_hash: str # 64 lowercase hex chars, SHA-256 of template source
|
|
43
|
+
variable_names: list[str] = field(default_factory=list)
|
|
44
|
+
variable_count: int | None = None
|
|
45
|
+
language: str | None = None
|
|
46
|
+
char_count: int | None = None
|
|
47
|
+
registered_by: str | None = None
|
|
48
|
+
is_active: bool | None = None
|
|
49
|
+
tags: dict[str, str] | None = None
|
|
50
|
+
|
|
51
|
+
def __post_init__(self) -> None:
|
|
52
|
+
if not self.template_id:
|
|
53
|
+
raise ValueError("TemplateRegisteredPayload.template_id must be non-empty")
|
|
54
|
+
if not self.version:
|
|
55
|
+
raise ValueError("TemplateRegisteredPayload.version must be non-empty")
|
|
56
|
+
if not self.template_hash or len(self.template_hash) != _SHA256_HEX_LEN:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"TemplateRegisteredPayload.template_hash must be 64 hex chars (SHA-256)"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict[str, Any]:
|
|
62
|
+
"""Serialise the payload to a plain ``dict``."""
|
|
63
|
+
d: dict[str, Any] = {
|
|
64
|
+
"template_id": self.template_id,
|
|
65
|
+
"version": self.version,
|
|
66
|
+
"template_hash": self.template_hash,
|
|
67
|
+
}
|
|
68
|
+
if self.variable_names:
|
|
69
|
+
d["variable_names"] = list(self.variable_names)
|
|
70
|
+
if self.variable_count is not None:
|
|
71
|
+
d["variable_count"] = self.variable_count
|
|
72
|
+
if self.language is not None:
|
|
73
|
+
d["language"] = self.language
|
|
74
|
+
if self.char_count is not None:
|
|
75
|
+
d["char_count"] = self.char_count
|
|
76
|
+
if self.registered_by is not None:
|
|
77
|
+
d["registered_by"] = self.registered_by
|
|
78
|
+
if self.is_active is not None:
|
|
79
|
+
d["is_active"] = self.is_active
|
|
80
|
+
if self.tags is not None:
|
|
81
|
+
d["tags"] = dict(self.tags)
|
|
82
|
+
return d
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_dict(cls, data: dict[str, Any]) -> TemplateRegisteredPayload:
|
|
86
|
+
"""Deserialise from a plain ``dict``."""
|
|
87
|
+
return cls(
|
|
88
|
+
template_id=data["template_id"],
|
|
89
|
+
version=data["version"],
|
|
90
|
+
template_hash=data["template_hash"],
|
|
91
|
+
variable_names=list(data.get("variable_names", [])),
|
|
92
|
+
variable_count=int(data["variable_count"]) if "variable_count" in data else None,
|
|
93
|
+
language=data.get("language"),
|
|
94
|
+
char_count=int(data["char_count"]) if "char_count" in data else None,
|
|
95
|
+
registered_by=data.get("registered_by"),
|
|
96
|
+
is_active=bool(data["is_active"]) if "is_active" in data else None,
|
|
97
|
+
tags=dict(data["tags"]) if "tags" in data else None,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class TemplateVariableBoundPayload:
|
|
103
|
+
"""RFC-0001 — A variable was bound to a value for template rendering.
|
|
104
|
+
|
|
105
|
+
``value_hash`` stores a SHA-256 hash of the value. For sensitive variables,
|
|
106
|
+
the raw value MUST NOT be stored.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
template_id: str
|
|
110
|
+
version: str
|
|
111
|
+
variable_name: str
|
|
112
|
+
value_type: str | None = None # "string"|"integer"|"float"|"boolean"|"array"|"object"|"null"
|
|
113
|
+
value_length: int | None = None
|
|
114
|
+
value_hash: str | None = None # 64 hex chars, SHA-256
|
|
115
|
+
is_sensitive: bool | None = None
|
|
116
|
+
span_id: str | None = None
|
|
117
|
+
|
|
118
|
+
def __post_init__(self) -> None:
|
|
119
|
+
if not self.template_id:
|
|
120
|
+
raise ValueError("TemplateVariableBoundPayload.template_id must be non-empty")
|
|
121
|
+
if not self.version:
|
|
122
|
+
raise ValueError("TemplateVariableBoundPayload.version must be non-empty")
|
|
123
|
+
if not self.variable_name:
|
|
124
|
+
raise ValueError("TemplateVariableBoundPayload.variable_name must be non-empty")
|
|
125
|
+
if self.value_type is not None and self.value_type not in _VALID_VALUE_TYPES:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"TemplateVariableBoundPayload.value_type must be one of {sorted(_VALID_VALUE_TYPES)}"
|
|
128
|
+
)
|
|
129
|
+
if self.value_hash is not None and len(self.value_hash) != _SHA256_HEX_LEN:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
"TemplateVariableBoundPayload.value_hash must be 64 hex chars (SHA-256)"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def to_dict(self) -> dict[str, Any]:
|
|
135
|
+
"""Serialise the payload to a plain ``dict``."""
|
|
136
|
+
d: dict[str, Any] = {
|
|
137
|
+
"template_id": self.template_id,
|
|
138
|
+
"version": self.version,
|
|
139
|
+
"variable_name": self.variable_name,
|
|
140
|
+
}
|
|
141
|
+
if self.value_type is not None:
|
|
142
|
+
d["value_type"] = self.value_type
|
|
143
|
+
if self.value_length is not None:
|
|
144
|
+
d["value_length"] = self.value_length
|
|
145
|
+
if self.value_hash is not None:
|
|
146
|
+
d["value_hash"] = self.value_hash
|
|
147
|
+
if self.is_sensitive is not None:
|
|
148
|
+
d["is_sensitive"] = self.is_sensitive
|
|
149
|
+
if self.span_id is not None:
|
|
150
|
+
d["span_id"] = self.span_id
|
|
151
|
+
return d
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_dict(cls, data: dict[str, Any]) -> TemplateVariableBoundPayload:
|
|
155
|
+
"""Deserialise from a plain ``dict``."""
|
|
156
|
+
return cls(
|
|
157
|
+
template_id=data["template_id"],
|
|
158
|
+
version=data["version"],
|
|
159
|
+
variable_name=data["variable_name"],
|
|
160
|
+
value_type=data.get("value_type"),
|
|
161
|
+
value_length=int(data["value_length"]) if "value_length" in data else None,
|
|
162
|
+
value_hash=data.get("value_hash"),
|
|
163
|
+
is_sensitive=bool(data["is_sensitive"]) if "is_sensitive" in data else None,
|
|
164
|
+
span_id=data.get("span_id"),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass
|
|
169
|
+
class TemplateValidationFailedPayload:
|
|
170
|
+
"""RFC-0001 — Template validation failed during rendering or registration."""
|
|
171
|
+
|
|
172
|
+
template_id: str
|
|
173
|
+
version: str
|
|
174
|
+
failure_reason: str
|
|
175
|
+
failure_type: str | None = None
|
|
176
|
+
|
|
177
|
+
def __post_init__(self) -> None:
|
|
178
|
+
if not self.template_id:
|
|
179
|
+
raise ValueError("TemplateValidationFailedPayload.template_id must be non-empty")
|
|
180
|
+
if not self.version:
|
|
181
|
+
raise ValueError("TemplateValidationFailedPayload.version must be non-empty")
|
|
182
|
+
if not self.failure_reason:
|
|
183
|
+
raise ValueError("TemplateValidationFailedPayload.failure_reason must be non-empty")
|
|
184
|
+
if self.failure_type is not None and self.failure_type not in _VALID_FAILURE_TYPES:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"TemplateValidationFailedPayload.failure_type must be one of {sorted(_VALID_FAILURE_TYPES)}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def to_dict(self) -> dict[str, Any]:
|
|
190
|
+
"""Serialise the payload to a plain ``dict``."""
|
|
191
|
+
d: dict[str, Any] = {
|
|
192
|
+
"template_id": self.template_id,
|
|
193
|
+
"version": self.version,
|
|
194
|
+
"failure_reason": self.failure_reason,
|
|
195
|
+
}
|
|
196
|
+
if self.failure_type is not None:
|
|
197
|
+
d["failure_type"] = self.failure_type
|
|
198
|
+
return d
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def from_dict(cls, data: dict[str, Any]) -> TemplateValidationFailedPayload:
|
|
202
|
+
"""Deserialise from a plain ``dict``."""
|
|
203
|
+
return cls(
|
|
204
|
+
template_id=data["template_id"],
|
|
205
|
+
version=data["version"],
|
|
206
|
+
failure_reason=data["failure_reason"],
|
|
207
|
+
failure_type=data.get("failure_type"),
|
|
208
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""spanforge.namespaces.tool_call \u2014 Tool call namespace payload types (RFC-0001 SPANFORGE).
|
|
2
|
+
|
|
3
|
+
Classes
|
|
4
|
+
-------
|
|
5
|
+
ToolCallPayload tool_call.invoked / tool_call.completed / tool_call.failed
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Literal
|
|
12
|
+
|
|
13
|
+
__all__ = ["ToolCallPayload"]
|
|
14
|
+
|
|
15
|
+
_VALID_STATUSES = frozenset({"success", "failure", "timeout"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ToolCallPayload:
|
|
20
|
+
"""RFC-0001 SPANFORGE \u2014 payload for tool_call.* events.
|
|
21
|
+
|
|
22
|
+
Captures all external tool invocations with inputs, outputs, latency, and
|
|
23
|
+
consent-check status (U \u2014 User Rights).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
call_id: str
|
|
27
|
+
tool_name: str
|
|
28
|
+
latency_ms: float
|
|
29
|
+
status: Literal["success", "failure", "timeout"]
|
|
30
|
+
consent_checked: bool
|
|
31
|
+
tool_version: str | None = None
|
|
32
|
+
inputs: dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
outputs: dict[str, Any] | None = None
|
|
34
|
+
error_message: str | None = None
|
|
35
|
+
|
|
36
|
+
def __post_init__(self) -> None:
|
|
37
|
+
if not self.call_id:
|
|
38
|
+
raise ValueError("ToolCallPayload.call_id must be non-empty")
|
|
39
|
+
if not self.tool_name:
|
|
40
|
+
raise ValueError("ToolCallPayload.tool_name must be non-empty")
|
|
41
|
+
if self.status not in _VALID_STATUSES:
|
|
42
|
+
raise ValueError(f"ToolCallPayload.status must be one of {sorted(_VALID_STATUSES)}")
|
|
43
|
+
if self.latency_ms < 0:
|
|
44
|
+
raise ValueError("ToolCallPayload.latency_ms must be >= 0")
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict[str, Any]:
|
|
47
|
+
"""Serialise to a plain dict."""
|
|
48
|
+
d: dict[str, Any] = {
|
|
49
|
+
"call_id": self.call_id,
|
|
50
|
+
"tool_name": self.tool_name,
|
|
51
|
+
"latency_ms": self.latency_ms,
|
|
52
|
+
"status": self.status,
|
|
53
|
+
"consent_checked": self.consent_checked,
|
|
54
|
+
"inputs": self.inputs,
|
|
55
|
+
}
|
|
56
|
+
if self.tool_version is not None:
|
|
57
|
+
d["tool_version"] = self.tool_version
|
|
58
|
+
if self.outputs is not None:
|
|
59
|
+
d["outputs"] = self.outputs
|
|
60
|
+
if self.error_message is not None:
|
|
61
|
+
d["error_message"] = self.error_message
|
|
62
|
+
return d
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_dict(cls, data: dict[str, Any]) -> ToolCallPayload:
|
|
66
|
+
"""Deserialise from a plain dict."""
|
|
67
|
+
return cls(
|
|
68
|
+
call_id=data["call_id"],
|
|
69
|
+
tool_name=data["tool_name"],
|
|
70
|
+
latency_ms=float(data["latency_ms"]),
|
|
71
|
+
status=data["status"],
|
|
72
|
+
consent_checked=bool(data["consent_checked"]),
|
|
73
|
+
tool_version=data.get("tool_version"),
|
|
74
|
+
inputs=dict(data.get("inputs", {})),
|
|
75
|
+
outputs=data.get("outputs"),
|
|
76
|
+
error_message=data.get("error_message"),
|
|
77
|
+
)
|