witdem-sdk 0.1.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.
Files changed (41) hide show
  1. witdem_sdk-0.1.0/.gitignore +27 -0
  2. witdem_sdk-0.1.0/PKG-INFO +49 -0
  3. witdem_sdk-0.1.0/pyproject.toml +95 -0
  4. witdem_sdk-0.1.0/src/witdem_sdk/__init__.py +189 -0
  5. witdem_sdk-0.1.0/src/witdem_sdk/__main__.py +3 -0
  6. witdem_sdk-0.1.0/src/witdem_sdk/_config.py +38 -0
  7. witdem_sdk-0.1.0/src/witdem_sdk/_contract.py +1130 -0
  8. witdem_sdk-0.1.0/src/witdem_sdk/_correlation.py +64 -0
  9. witdem_sdk-0.1.0/src/witdem_sdk/_errors.py +15 -0
  10. witdem_sdk-0.1.0/src/witdem_sdk/_payload.py +80 -0
  11. witdem_sdk-0.1.0/src/witdem_sdk/_protocol.py +7 -0
  12. witdem_sdk-0.1.0/src/witdem_sdk/_telemetry.py +1053 -0
  13. witdem_sdk-0.1.0/src/witdem_sdk/_transport.py +164 -0
  14. witdem_sdk-0.1.0/src/witdem_sdk/_workflow.py +68 -0
  15. witdem_sdk-0.1.0/src/witdem_sdk/cli.py +101 -0
  16. witdem_sdk-0.1.0/src/witdem_sdk/integrations/__init__.py +3 -0
  17. witdem_sdk-0.1.0/src/witdem_sdk/integrations/_common.py +73 -0
  18. witdem_sdk-0.1.0/src/witdem_sdk/integrations/anthropic.py +163 -0
  19. witdem_sdk-0.1.0/src/witdem_sdk/integrations/claude_agent.py +135 -0
  20. witdem_sdk-0.1.0/src/witdem_sdk/integrations/generic.py +145 -0
  21. witdem_sdk-0.1.0/src/witdem_sdk/integrations/haystack.py +553 -0
  22. witdem_sdk-0.1.0/src/witdem_sdk/integrations/langchain.py +412 -0
  23. witdem_sdk-0.1.0/src/witdem_sdk/integrations/langgraph.py +221 -0
  24. witdem_sdk-0.1.0/src/witdem_sdk/integrations/litellm.py +433 -0
  25. witdem_sdk-0.1.0/src/witdem_sdk/integrations/openai_agents.py +281 -0
  26. witdem_sdk-0.1.0/src/witdem_sdk/integrations/openrouter.py +365 -0
  27. witdem_sdk-0.1.0/src/witdem_sdk/integrations/smolagents.py +145 -0
  28. witdem_sdk-0.1.0/src/witdem_sdk/py.typed +0 -0
  29. witdem_sdk-0.1.0/tests/conftest.py +69 -0
  30. witdem_sdk-0.1.0/tests/test_contract_tutorial.py +42 -0
  31. witdem_sdk-0.1.0/tests/test_contracts.py +698 -0
  32. witdem_sdk-0.1.0/tests/test_correlation.py +95 -0
  33. witdem_sdk-0.1.0/tests/test_gateway_integrations.py +270 -0
  34. witdem_sdk-0.1.0/tests/test_haystack_observation.py +197 -0
  35. witdem_sdk-0.1.0/tests/test_high_level_integrations.py +412 -0
  36. witdem_sdk-0.1.0/tests/test_langgraph_instrumentation.py +159 -0
  37. witdem_sdk-0.1.0/tests/test_optional_integrations.py +301 -0
  38. witdem_sdk-0.1.0/tests/test_public_api.py +253 -0
  39. witdem_sdk-0.1.0/tests/test_telemetry.py +310 -0
  40. witdem_sdk-0.1.0/tests/test_transport.py +181 -0
  41. witdem_sdk-0.1.0/tests/test_workflow_config.py +65 -0
@@ -0,0 +1,27 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ *.duckdb
8
+ /data/backups/
9
+ .env
10
+ .DS_Store
11
+ !data/synthetic-ui-v2/
12
+ !data/synthetic-ui-v2/**
13
+ !data/synthetic-ui-v2/analytics.duckdb
14
+ data/live/
15
+ .claude/
16
+ data/**/.DS_Store
17
+
18
+ # Dashboard contributor build artifacts
19
+ web/node_modules/
20
+ web/*.tsbuildinfo
21
+ web/vite.config.js
22
+ web/vite.config.d.ts
23
+ web/tailwind.config.js
24
+ web/tailwind.config.d.ts
25
+
26
+ # Locally generated live-matrix reports
27
+ examples/product-factory/reports/
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.5
2
+ Name: witdem-sdk
3
+ Version: 0.1.0
4
+ Summary: Public client SDK for emitting Witdem business-semantic records correlated to the caller's active OpenTelemetry execution.
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.10
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Requires-Python: <3.14,>=3.10
11
+ Requires-Dist: httpx<1,>=0.27
12
+ Requires-Dist: opentelemetry-api<2,>=1.27
13
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27
14
+ Requires-Dist: opentelemetry-sdk<2,>=1.27
15
+ Requires-Dist: pydantic<3,>=2.10
16
+ Requires-Dist: pyyaml<7,>=6.0
17
+ Requires-Dist: typing-extensions<5,>=4.12
18
+ Provides-Extra: all
19
+ Requires-Dist: anthropic<1,>=0.50; extra == 'all'
20
+ Requires-Dist: haystack-ai<4,>=3.0; extra == 'all'
21
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'all'
22
+ Requires-Dist: langgraph<2,>=0.2; extra == 'all'
23
+ Requires-Dist: litellm<2,>=1.86; extra == 'all'
24
+ Requires-Dist: openai-agents<0.21,>=0.0.10; extra == 'all'
25
+ Requires-Dist: openai<4,>=1.68; extra == 'all'
26
+ Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'all'
27
+ Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'all'
28
+ Requires-Dist: smolagents<2,>=1.18; extra == 'all'
29
+ Provides-Extra: anthropic
30
+ Requires-Dist: anthropic<1,>=0.50; extra == 'anthropic'
31
+ Provides-Extra: haystack
32
+ Requires-Dist: haystack-ai<4,>=3.0; extra == 'haystack'
33
+ Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'haystack'
34
+ Provides-Extra: langchain
35
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'langchain'
36
+ Provides-Extra: langgraph
37
+ Requires-Dist: langgraph<2,>=0.2; extra == 'langgraph'
38
+ Provides-Extra: litellm
39
+ Requires-Dist: litellm<2,>=1.86; extra == 'litellm'
40
+ Provides-Extra: openai
41
+ Requires-Dist: openai-agents<0.21,>=0.0.10; extra == 'openai'
42
+ Provides-Extra: openrouter
43
+ Requires-Dist: openai<4,>=1.68; extra == 'openrouter'
44
+ Provides-Extra: smolagents
45
+ Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'smolagents'
46
+ Requires-Dist: smolagents<2,>=1.18; extra == 'smolagents'
47
+ Description-Content-Type: text/markdown
48
+
49
+ Witdem SDK provides optional application semantics and framework integrations for Witdem Analytics.
@@ -0,0 +1,95 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.32.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "witdem-sdk"
7
+ version = "0.1.0"
8
+ description = "Public client SDK for emitting Witdem business-semantic records correlated to the caller's active OpenTelemetry execution."
9
+ readme = { text = "Witdem SDK provides optional application semantics and framework integrations for Witdem Analytics.", content-type = "text/markdown" }
10
+ requires-python = ">=3.10,<3.14"
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.10",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ ]
18
+ dependencies = [
19
+ "opentelemetry-api>=1.27,<2",
20
+ "opentelemetry-exporter-otlp-proto-http>=1.27,<2",
21
+ "opentelemetry-sdk>=1.27,<2",
22
+ "httpx>=0.27,<1",
23
+ "pydantic>=2.10,<3",
24
+ "pyyaml>=6.0,<7",
25
+ "typing-extensions>=4.12,<5",
26
+ ]
27
+
28
+ [project.scripts]
29
+ witdem-sdk = "witdem_sdk.cli:main"
30
+
31
+ [project.optional-dependencies]
32
+ anthropic = ["anthropic>=0.50,<1"]
33
+ # openai-agents 0.21+ requires openai 3.x, while the supported LiteLLM
34
+ # integration still requires openai <3. Keep the combined `all` extra solvable.
35
+ openai = ["openai-agents>=0.0.10,<0.21"]
36
+ langchain = ["langchain-core>=0.3,<2"]
37
+ langgraph = ["langgraph>=0.2,<2"]
38
+ haystack = ["haystack-ai>=3.0,<4", "opentelemetry-haystack>=1,<2"]
39
+ litellm = ["litellm>=1.86,<2"]
40
+ openrouter = ["openai>=1.68,<4"]
41
+ smolagents = [
42
+ "smolagents>=1.18,<2",
43
+ "openinference-instrumentation-smolagents>=0.1.20,<1",
44
+ ]
45
+ all = [
46
+ "anthropic>=0.50,<1",
47
+ "openai-agents>=0.0.10,<0.21",
48
+ "langchain-core>=0.3,<2",
49
+ "langgraph>=0.2,<2",
50
+ "haystack-ai>=3.0,<4",
51
+ "opentelemetry-haystack>=1,<2",
52
+ "litellm>=1.86,<2",
53
+ "openai>=1.68,<4",
54
+ "smolagents>=1.18,<2",
55
+ "openinference-instrumentation-smolagents>=0.1.20,<1",
56
+ ]
57
+
58
+ [dependency-groups]
59
+ dev = [
60
+ "pytest>=8.3,<9",
61
+ "pytest-cov>=6,<7",
62
+ "ruff>=0.9,<1",
63
+ "mypy>=1.15,<2",
64
+ "types-PyYAML>=6.0,<7",
65
+ ]
66
+
67
+ [tool.hatch.build.targets.wheel]
68
+ packages = ["src/witdem_sdk"]
69
+
70
+ [tool.hatch.build.targets.sdist]
71
+ include = ["src/witdem_sdk", "tests", "pyproject.toml"]
72
+
73
+ [tool.uv]
74
+ required-version = "==0.12.5"
75
+
76
+ [tool.pytest.ini_options]
77
+ addopts = "--strict-markers"
78
+ testpaths = ["tests"]
79
+
80
+ [tool.ruff]
81
+ line-length = 120
82
+ target-version = "py310"
83
+
84
+ [tool.ruff.lint]
85
+ select = ["E", "F", "I", "B", "UP", "SIM"]
86
+
87
+ [tool.mypy]
88
+ python_version = "3.10"
89
+ strict = true
90
+ packages = ["witdem_sdk"]
91
+ follow_imports_for_stubs = true
92
+
93
+ [[tool.mypy.overrides]]
94
+ module = ["numpy", "numpy.*"]
95
+ follow_imports = "skip"
@@ -0,0 +1,189 @@
1
+ """witdem_sdk -- the public Witdem client SDK.
2
+
3
+ ``configure(...)`` provides one setup for OpenTelemetry traces and Witdem's
4
+ business-semantic records. ``WITDEM_ENDPOINT`` is the common base URL for the
5
+ trace and SDK ingest routes and defaults to ``http://localhost:4318``.
6
+
7
+ The five typed semantic functions (events, decisions, evaluations, outcomes,
8
+ metrics) remain available independently for applications that already own
9
+ their OpenTelemetry setup.
10
+
11
+ Correlation is automatic: each call reads the active OTel span
12
+ (``trace_id``/``span_id``) and ``"witdem.execution_id"`` baggage
13
+ (``execution_id``) from the current OpenTelemetry context -- callers never
14
+ copy ids by hand. If no execution id can be resolved (no baggage and no
15
+ explicit ``execution_id=`` keyword argument), :class:`WitdemSDKError` is raised
16
+ instead of sending an uncorrelated record.
17
+
18
+ Sending is fire-and-forget on a small bounded background thread pool: a
19
+ network error, or Witdem being unreachable, is logged and dropped (after one
20
+ quick retry) and never raised into caller code -- the one hard reliability
21
+ requirement for this package.
22
+
23
+ The unified client exposes execution, model, tool, and generic operation
24
+ contexts. It records standard GenAI attributes and flushes traces and semantic
25
+ records at shutdown; provider pricing remains a server-side concern.
26
+
27
+ See ``docs/sdk.md`` §5 in the Witdem AI repository for the wire
28
+ contract this package implements.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from collections.abc import Mapping
34
+ from importlib.metadata import PackageNotFoundError, version
35
+ from typing import Any
36
+
37
+ from witdem_sdk._contract import ContractResult, WitdemProjectConfig, load_project_config
38
+ from witdem_sdk._correlation import resolve_correlation
39
+ from witdem_sdk._errors import WitdemSDKError
40
+ from witdem_sdk._payload import Kind as _Kind
41
+ from witdem_sdk._payload import build_payload
42
+ from witdem_sdk._protocol import SEMANTIC_RECORD_PROTOCOL_VERSION
43
+ from witdem_sdk._telemetry import Operation, Witdem, configure
44
+ from witdem_sdk._transport import DeliveryStatus, flush, submit_record
45
+
46
+ __all__ = [
47
+ "WitdemSDKError",
48
+ "Witdem",
49
+ "Operation",
50
+ "configure",
51
+ "decision",
52
+ "event",
53
+ "evaluation",
54
+ "metric",
55
+ "outcome",
56
+ "flush",
57
+ "DeliveryStatus",
58
+ "SEMANTIC_RECORD_PROTOCOL_VERSION",
59
+ "ContractResult",
60
+ "WitdemProjectConfig",
61
+ "load_project_config",
62
+ ]
63
+
64
+ try:
65
+ __version__ = version("witdem-sdk")
66
+ except PackageNotFoundError: # pragma: no cover - only an unpackaged source tree
67
+ __version__ = "0+unknown"
68
+
69
+
70
+ def _emit(
71
+ kind: _Kind,
72
+ name: str,
73
+ value: Any,
74
+ attributes: dict[str, Any],
75
+ *,
76
+ execution_id: str | None,
77
+ ) -> None:
78
+ """Shared plumbing: resolve correlation, build the wire payload, send it."""
79
+
80
+ resolved_execution_id, trace_id, span_id = resolve_correlation(execution_id)
81
+ payload = build_payload(
82
+ kind=kind,
83
+ name=name,
84
+ value=value,
85
+ execution_id=resolved_execution_id,
86
+ trace_id=trace_id,
87
+ span_id=span_id,
88
+ attributes=attributes,
89
+ )
90
+ submit_record(payload)
91
+
92
+
93
+ def event(
94
+ name: str,
95
+ payload: dict[str, Any] | None = None,
96
+ *,
97
+ attributes: Mapping[str, Any] | None = None,
98
+ execution_id: str | None = None,
99
+ ) -> None:
100
+ """Record a domain event that happened during the active execution.
101
+
102
+ ``payload`` is an arbitrary JSON-serializable dict describing what
103
+ happened; it is sent as the wire record's ``attributes``. Raises
104
+ :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see module
105
+ docs). Never raises when Witdem is unavailable on the network.
106
+ """
107
+
108
+ _emit(
109
+ "event",
110
+ name,
111
+ None,
112
+ {**dict(payload or {}), **dict(attributes or {})},
113
+ execution_id=execution_id,
114
+ )
115
+
116
+
117
+ def decision(
118
+ name: str,
119
+ value: Any,
120
+ *,
121
+ attributes: Mapping[str, Any] | None = None,
122
+ execution_id: str | None = None,
123
+ ) -> None:
124
+ """Record a business decision (e.g. a chosen route/branch) and its value.
125
+
126
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
127
+ module docs). Never raises when Witdem is unavailable on the network.
128
+ """
129
+
130
+ _emit("decision", name, value, dict(attributes or {}), execution_id=execution_id)
131
+
132
+
133
+ def evaluation(
134
+ name: str,
135
+ *,
136
+ score: float | None = None,
137
+ label: str | None = None,
138
+ value: Any = None,
139
+ attributes: Mapping[str, Any] | None = None,
140
+ execution_id: str | None = None,
141
+ ) -> None:
142
+ """Record a structured assessment: a ``score``, a ``label``, or both.
143
+
144
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
145
+ module docs). Never raises when Witdem is unavailable on the network.
146
+ """
147
+
148
+ resolved_attributes = dict(attributes or {})
149
+ if score is not None:
150
+ resolved_attributes["score"] = score
151
+ if label is not None:
152
+ resolved_attributes["label"] = label
153
+ _emit("evaluation", name, value, resolved_attributes, execution_id=execution_id)
154
+
155
+
156
+ def outcome(
157
+ name: str,
158
+ *,
159
+ status: str | None = None,
160
+ value: Any = None,
161
+ attributes: Mapping[str, Any] | None = None,
162
+ execution_id: str | None = None,
163
+ ) -> None:
164
+ """Record an externally meaningful result of the active execution.
165
+
166
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
167
+ module docs). Never raises when Witdem is unavailable on the network.
168
+ """
169
+
170
+ resolved_attributes = dict(attributes or {})
171
+ if status is not None:
172
+ resolved_attributes["status"] = status
173
+ _emit("outcome", name, value, resolved_attributes, execution_id=execution_id)
174
+
175
+
176
+ def metric(
177
+ name: str,
178
+ value: Any,
179
+ *,
180
+ attributes: Mapping[str, Any] | None = None,
181
+ execution_id: str | None = None,
182
+ ) -> None:
183
+ """Record a numeric (or otherwise quantitative) measurement.
184
+
185
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
186
+ module docs). Never raises when Witdem is unavailable on the network.
187
+ """
188
+
189
+ _emit("metric", name, value, dict(attributes or {}), execution_id=execution_id)
@@ -0,0 +1,3 @@
1
+ from witdem_sdk.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,38 @@
1
+ """Environment-based configuration for :mod:`witdem_sdk`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ _ENDPOINT_ENV_VAR = "WITDEM_ENDPOINT"
8
+ _DEFAULT_ENDPOINT = "http://localhost:4318"
9
+ _endpoint_override: str | None = None
10
+
11
+
12
+ def configure_records_endpoint(endpoint: str | None) -> str | None:
13
+ """Set the process-local endpoint override and return its previous value."""
14
+
15
+ global _endpoint_override
16
+ previous = _endpoint_override
17
+ _endpoint_override = endpoint.rstrip("/") if endpoint else None
18
+ return previous
19
+
20
+
21
+ def records_endpoint() -> str:
22
+ """Base URL of the Witdem service.
23
+
24
+ Records are POSTed to ``f"{records_endpoint()}/sdk/v1/records"``. Read
25
+ fresh on every call (not cached at import time) so tests can repoint it
26
+ per-test via ``monkeypatch.setenv`` without reloading this module.
27
+ """
28
+
29
+ if _endpoint_override is not None:
30
+ return _endpoint_override
31
+ value = os.environ.get(_ENDPOINT_ENV_VAR)
32
+ if value:
33
+ return value.rstrip("/")
34
+ return _DEFAULT_ENDPOINT
35
+
36
+
37
+ def api_key(explicit: str | None = None) -> str | None:
38
+ return explicit or os.getenv("WITDEM_API_KEY")