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.
Files changed (174) hide show
  1. spanforge/__init__.py +815 -0
  2. spanforge/_ansi.py +93 -0
  3. spanforge/_batch_exporter.py +409 -0
  4. spanforge/_cli.py +2094 -0
  5. spanforge/_cli_audit.py +639 -0
  6. spanforge/_cli_compliance.py +711 -0
  7. spanforge/_cli_cost.py +243 -0
  8. spanforge/_cli_ops.py +791 -0
  9. spanforge/_cli_phase11.py +356 -0
  10. spanforge/_hooks.py +337 -0
  11. spanforge/_server.py +1708 -0
  12. spanforge/_span.py +1036 -0
  13. spanforge/_store.py +288 -0
  14. spanforge/_stream.py +664 -0
  15. spanforge/_trace.py +335 -0
  16. spanforge/_tracer.py +254 -0
  17. spanforge/actor.py +141 -0
  18. spanforge/alerts.py +469 -0
  19. spanforge/auto.py +464 -0
  20. spanforge/baseline.py +335 -0
  21. spanforge/cache.py +635 -0
  22. spanforge/compliance.py +325 -0
  23. spanforge/config.py +532 -0
  24. spanforge/consent.py +228 -0
  25. spanforge/consumer.py +377 -0
  26. spanforge/core/__init__.py +5 -0
  27. spanforge/core/compliance_mapping.py +1254 -0
  28. spanforge/cost.py +600 -0
  29. spanforge/debug.py +548 -0
  30. spanforge/deprecations.py +205 -0
  31. spanforge/drift.py +482 -0
  32. spanforge/egress.py +58 -0
  33. spanforge/eval.py +648 -0
  34. spanforge/event.py +1064 -0
  35. spanforge/exceptions.py +240 -0
  36. spanforge/explain.py +178 -0
  37. spanforge/export/__init__.py +69 -0
  38. spanforge/export/append_only.py +337 -0
  39. spanforge/export/cloud.py +357 -0
  40. spanforge/export/datadog.py +497 -0
  41. spanforge/export/grafana.py +320 -0
  42. spanforge/export/jsonl.py +195 -0
  43. spanforge/export/openinference.py +158 -0
  44. spanforge/export/otel_bridge.py +294 -0
  45. spanforge/export/otlp.py +811 -0
  46. spanforge/export/otlp_bridge.py +233 -0
  47. spanforge/export/redis_backend.py +282 -0
  48. spanforge/export/siem_schema.py +98 -0
  49. spanforge/export/siem_splunk.py +264 -0
  50. spanforge/export/siem_syslog.py +212 -0
  51. spanforge/export/webhook.py +299 -0
  52. spanforge/exporters/__init__.py +30 -0
  53. spanforge/exporters/console.py +271 -0
  54. spanforge/exporters/jsonl.py +144 -0
  55. spanforge/exporters/sqlite.py +142 -0
  56. spanforge/gate.py +1150 -0
  57. spanforge/governance.py +181 -0
  58. spanforge/hitl.py +295 -0
  59. spanforge/http.py +187 -0
  60. spanforge/inspect.py +427 -0
  61. spanforge/integrations/__init__.py +45 -0
  62. spanforge/integrations/_pricing.py +280 -0
  63. spanforge/integrations/anthropic.py +388 -0
  64. spanforge/integrations/azure_openai.py +133 -0
  65. spanforge/integrations/bedrock.py +292 -0
  66. spanforge/integrations/crewai.py +251 -0
  67. spanforge/integrations/gemini.py +351 -0
  68. spanforge/integrations/groq.py +442 -0
  69. spanforge/integrations/langchain.py +349 -0
  70. spanforge/integrations/langgraph.py +306 -0
  71. spanforge/integrations/llamaindex.py +373 -0
  72. spanforge/integrations/ollama.py +287 -0
  73. spanforge/integrations/openai.py +368 -0
  74. spanforge/integrations/together.py +483 -0
  75. spanforge/io.py +214 -0
  76. spanforge/lint.py +322 -0
  77. spanforge/metrics.py +417 -0
  78. spanforge/metrics_export.py +343 -0
  79. spanforge/migrate.py +402 -0
  80. spanforge/model_registry.py +278 -0
  81. spanforge/models.py +389 -0
  82. spanforge/namespaces/__init__.py +254 -0
  83. spanforge/namespaces/audit.py +256 -0
  84. spanforge/namespaces/cache.py +237 -0
  85. spanforge/namespaces/chain.py +77 -0
  86. spanforge/namespaces/confidence.py +72 -0
  87. spanforge/namespaces/consent.py +92 -0
  88. spanforge/namespaces/cost.py +179 -0
  89. spanforge/namespaces/decision.py +143 -0
  90. spanforge/namespaces/diff.py +157 -0
  91. spanforge/namespaces/drift.py +80 -0
  92. spanforge/namespaces/eval_.py +251 -0
  93. spanforge/namespaces/feedback.py +241 -0
  94. spanforge/namespaces/fence.py +193 -0
  95. spanforge/namespaces/guard.py +105 -0
  96. spanforge/namespaces/hitl.py +91 -0
  97. spanforge/namespaces/latency.py +72 -0
  98. spanforge/namespaces/prompt.py +190 -0
  99. spanforge/namespaces/redact.py +173 -0
  100. spanforge/namespaces/retrieval.py +379 -0
  101. spanforge/namespaces/runtime_governance.py +494 -0
  102. spanforge/namespaces/template.py +208 -0
  103. spanforge/namespaces/tool_call.py +77 -0
  104. spanforge/namespaces/trace.py +1029 -0
  105. spanforge/normalizer.py +171 -0
  106. spanforge/plugins.py +82 -0
  107. spanforge/presidio_backend.py +349 -0
  108. spanforge/processor.py +258 -0
  109. spanforge/prompt_registry.py +418 -0
  110. spanforge/py.typed +0 -0
  111. spanforge/redact.py +914 -0
  112. spanforge/regression.py +192 -0
  113. spanforge/runtime_policy.py +159 -0
  114. spanforge/sampling.py +511 -0
  115. spanforge/schema.py +183 -0
  116. spanforge/schemas/v1.0/schema.json +170 -0
  117. spanforge/schemas/v2.0/schema.json +536 -0
  118. spanforge/sdk/__init__.py +625 -0
  119. spanforge/sdk/_base.py +584 -0
  120. spanforge/sdk/_base.pyi +71 -0
  121. spanforge/sdk/_exceptions.py +1096 -0
  122. spanforge/sdk/_types.py +2184 -0
  123. spanforge/sdk/alert.py +1514 -0
  124. spanforge/sdk/alert.pyi +56 -0
  125. spanforge/sdk/audit.py +1196 -0
  126. spanforge/sdk/audit.pyi +67 -0
  127. spanforge/sdk/cec.py +1215 -0
  128. spanforge/sdk/cec.pyi +37 -0
  129. spanforge/sdk/config.py +641 -0
  130. spanforge/sdk/config.pyi +55 -0
  131. spanforge/sdk/enterprise.py +714 -0
  132. spanforge/sdk/enterprise.pyi +79 -0
  133. spanforge/sdk/explain.py +170 -0
  134. spanforge/sdk/fallback.py +432 -0
  135. spanforge/sdk/feedback.py +351 -0
  136. spanforge/sdk/gate.py +874 -0
  137. spanforge/sdk/gate.pyi +51 -0
  138. spanforge/sdk/identity.py +2114 -0
  139. spanforge/sdk/identity.pyi +47 -0
  140. spanforge/sdk/lineage.py +175 -0
  141. spanforge/sdk/observe.py +1065 -0
  142. spanforge/sdk/observe.pyi +50 -0
  143. spanforge/sdk/operator.py +338 -0
  144. spanforge/sdk/pii.py +1473 -0
  145. spanforge/sdk/pii.pyi +119 -0
  146. spanforge/sdk/pipelines.py +458 -0
  147. spanforge/sdk/pipelines.pyi +39 -0
  148. spanforge/sdk/policy.py +930 -0
  149. spanforge/sdk/rag.py +594 -0
  150. spanforge/sdk/rbac.py +280 -0
  151. spanforge/sdk/registry.py +430 -0
  152. spanforge/sdk/registry.pyi +46 -0
  153. spanforge/sdk/scope.py +279 -0
  154. spanforge/sdk/secrets.py +293 -0
  155. spanforge/sdk/secrets.pyi +25 -0
  156. spanforge/sdk/security.py +560 -0
  157. spanforge/sdk/security.pyi +57 -0
  158. spanforge/sdk/trust.py +472 -0
  159. spanforge/sdk/trust.pyi +41 -0
  160. spanforge/secrets.py +799 -0
  161. spanforge/signing.py +1179 -0
  162. spanforge/stats.py +100 -0
  163. spanforge/stream.py +560 -0
  164. spanforge/testing.py +378 -0
  165. spanforge/testing_mocks.py +1052 -0
  166. spanforge/trace.py +199 -0
  167. spanforge/types.py +696 -0
  168. spanforge/ulid.py +300 -0
  169. spanforge/validate.py +379 -0
  170. spanforge-1.0.0.dist-info/METADATA +1509 -0
  171. spanforge-1.0.0.dist-info/RECORD +174 -0
  172. spanforge-1.0.0.dist-info/WHEEL +4 -0
  173. spanforge-1.0.0.dist-info/entry_points.txt +5 -0
  174. spanforge-1.0.0.dist-info/licenses/LICENSE +128 -0
spanforge/trace.py ADDED
@@ -0,0 +1,199 @@
1
+ """spanforge.trace — @trace() function decorator and trace engine (Tool 1 / llm-trace).
2
+
3
+ Single decorator that instruments any Python function as an SpanForge span::
4
+
5
+ from spanforge import trace
6
+
7
+ @trace(name="search", model="gpt-4o")
8
+ def call_llm(prompt: str) -> str: ...
9
+
10
+ @trace(name="async-step")
11
+ async def async_step(x: int) -> dict: ...
12
+
13
+ @trace(name="web-search", tool=True)
14
+ def search_web(query: str) -> list[str]: ...
15
+
16
+ Supports:
17
+ - Sync and async functions/methods
18
+ - Auto-capture of call arguments and return values (opt-in)
19
+ - Parent-child span relationships via contextvars
20
+ - ``tool=True`` to emit spans with ``operation="execute_tool"``
21
+ - Pytest fixture integration via :func:`~spanforge.testing.captured_spans`
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import functools
27
+ import inspect
28
+ from typing import Any, Callable, TypeVar
29
+
30
+ from spanforge._span import SpanContextManager
31
+
32
+ __all__ = ["trace"]
33
+
34
+ _F = TypeVar("_F", bound=Callable[..., Any])
35
+
36
+
37
+ def _safe_repr(value: Any, max_len: int = 200) -> str:
38
+ """Return a repr of *value* truncated to *max_len* characters."""
39
+ try:
40
+ r = repr(value)
41
+ except Exception:
42
+ r = "<unrepresentable>"
43
+ return r[:max_len] + "..." if len(r) > max_len else r
44
+
45
+
46
+ class _TraceDecorator:
47
+ """Wraps a callable so every invocation is recorded as an SpanForge span.
48
+
49
+ Created by :func:`trace`; use :func:`trace` rather than instantiating
50
+ this class directly.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ fn: Callable[..., Any],
56
+ name: str | None,
57
+ model: str | None,
58
+ operation: str,
59
+ tool: bool,
60
+ capture_args: bool,
61
+ capture_return: bool,
62
+ attributes: dict[str, Any] | None,
63
+ ) -> None:
64
+ self._fn = fn
65
+ self._name = name or fn.__qualname__
66
+ self._model = model
67
+ self._operation = operation
68
+ self._tool = tool
69
+ self._capture_args = capture_args
70
+ self._capture_return = capture_return
71
+ self._attributes = dict(attributes or {})
72
+ # Preserve __name__, __doc__, __module__, etc. on the wrapper.
73
+ functools.update_wrapper(self, fn)
74
+
75
+ # ------------------------------------------------------------------
76
+ # Internal helpers
77
+ # ------------------------------------------------------------------
78
+
79
+ def _build_attrs(self, args: tuple[Any, ...], kwargs: dict[str, Any]) -> dict[str, Any]:
80
+ """Build the initial span attributes dict from static attrs + captured args."""
81
+ attrs: dict[str, Any] = dict(self._attributes)
82
+ if self._tool:
83
+ # Mark span so InspectorSession can detect it even without checking operation.
84
+ attrs["tool"] = True
85
+ if self._capture_args or self._tool:
86
+ try:
87
+ sig = inspect.signature(self._fn)
88
+ bound = sig.bind(*args, **kwargs)
89
+ bound.apply_defaults()
90
+ for k, v in bound.arguments.items():
91
+ attrs[f"arg.{k}"] = _safe_repr(v)
92
+ except (TypeError, ValueError):
93
+ pass
94
+ return attrs
95
+
96
+ def _record_return(self, span: Any, result: Any) -> None:
97
+ if self._capture_return or self._tool:
98
+ span.set_attribute("return_value", _safe_repr(result))
99
+
100
+ # ------------------------------------------------------------------
101
+ # Sync call
102
+ # ------------------------------------------------------------------
103
+
104
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
105
+ attrs = self._build_attrs(args, kwargs)
106
+ cm = SpanContextManager(
107
+ name=self._name,
108
+ model=self._model,
109
+ operation=self._operation,
110
+ attributes=attrs,
111
+ )
112
+ with cm as span:
113
+ result = self._fn(*args, **kwargs)
114
+ self._record_return(span, result)
115
+ return result
116
+
117
+
118
+ def _make_async_wrapper(decorator: _TraceDecorator, fn: Callable[..., Any]) -> Callable[..., Any]:
119
+ """Return an async wrapper that runs *fn* inside a span context."""
120
+
121
+ @functools.wraps(fn)
122
+ async def _async_wrapper(*args: Any, **kwargs: Any) -> Any:
123
+ attrs = decorator._build_attrs(args, kwargs)
124
+ cm = SpanContextManager(
125
+ name=decorator._name,
126
+ model=decorator._model,
127
+ operation=decorator._operation,
128
+ attributes=attrs,
129
+ )
130
+ async with cm as span:
131
+ result = await fn(*args, **kwargs)
132
+ decorator._record_return(span, result)
133
+ return result
134
+
135
+ return _async_wrapper
136
+
137
+
138
+ def trace(
139
+ fn: Callable[..., Any] | None = None,
140
+ *,
141
+ name: str | None = None,
142
+ model: str | None = None,
143
+ operation: str = "chat",
144
+ tool: bool = False,
145
+ capture_args: bool = False,
146
+ capture_return: bool = False,
147
+ attributes: dict[str, Any] | None = None,
148
+ ) -> Any:
149
+ """Decorator that instruments a function as an SpanForge span.
150
+
151
+ Works with or without parentheses::
152
+
153
+ @trace
154
+ def my_fn(): ...
155
+
156
+ @trace(name="custom-name", model="gpt-4o")
157
+ def my_fn(): ...
158
+
159
+ Args:
160
+ fn: The function to wrap (only when used bare as ``@trace``).
161
+ name: Span name. Defaults to ``fn.__qualname__``.
162
+ model: Model identifier string forwarded to ``SpanPayload``.
163
+ operation: GenAI operation name (default ``"chat"``). Any
164
+ :class:`~spanforge.namespaces.trace.GenAIOperationName`
165
+ value or a custom string.
166
+ tool: When ``True``, marks this as a tool call; sets
167
+ ``operation="execute_tool"`` regardless of *operation*.
168
+ capture_args: When ``True``, records call arguments as ``arg.<name>``
169
+ span attributes (values truncated to 200 chars).
170
+ capture_return: When ``True``, records the return value as a
171
+ ``return_value`` span attribute (truncated to 200 chars).
172
+ attributes: Static key-value attributes added to every span.
173
+
174
+ Returns:
175
+ Decorated callable (sync or async), or a single-argument decorator
176
+ when keyword arguments are supplied.
177
+ """
178
+
179
+ def _decorate(f: Callable[..., Any]) -> Callable[..., Any]:
180
+ effective_op = "execute_tool" if tool else operation
181
+ dec = _TraceDecorator(
182
+ fn=f,
183
+ name=name,
184
+ model=model,
185
+ operation=effective_op,
186
+ tool=tool,
187
+ capture_args=capture_args,
188
+ capture_return=capture_return,
189
+ attributes=attributes,
190
+ )
191
+ if inspect.iscoroutinefunction(f):
192
+ return _make_async_wrapper(dec, f)
193
+ return dec
194
+
195
+ if fn is not None:
196
+ # @trace — bare decorator, no parentheses
197
+ return _decorate(fn)
198
+ # @trace(...) — decorator factory
199
+ return _decorate