witdem-sdk 0.2.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 (39) hide show
  1. witdem_sdk-0.2.0/.gitignore +27 -0
  2. witdem_sdk-0.2.0/PKG-INFO +44 -0
  3. witdem_sdk-0.2.0/pyproject.toml +84 -0
  4. witdem_sdk-0.2.0/src/witdem_sdk/__init__.py +185 -0
  5. witdem_sdk-0.2.0/src/witdem_sdk/__main__.py +3 -0
  6. witdem_sdk-0.2.0/src/witdem_sdk/_config.py +38 -0
  7. witdem_sdk-0.2.0/src/witdem_sdk/_contract.py +1161 -0
  8. witdem_sdk-0.2.0/src/witdem_sdk/_correlation.py +64 -0
  9. witdem_sdk-0.2.0/src/witdem_sdk/_errors.py +15 -0
  10. witdem_sdk-0.2.0/src/witdem_sdk/_payload.py +80 -0
  11. witdem_sdk-0.2.0/src/witdem_sdk/_protocol.py +7 -0
  12. witdem_sdk-0.2.0/src/witdem_sdk/_telemetry.py +879 -0
  13. witdem_sdk-0.2.0/src/witdem_sdk/_transport.py +164 -0
  14. witdem_sdk-0.2.0/src/witdem_sdk/cli.py +101 -0
  15. witdem_sdk-0.2.0/src/witdem_sdk/integrations/__init__.py +3 -0
  16. witdem_sdk-0.2.0/src/witdem_sdk/integrations/_common.py +73 -0
  17. witdem_sdk-0.2.0/src/witdem_sdk/integrations/anthropic.py +163 -0
  18. witdem_sdk-0.2.0/src/witdem_sdk/integrations/claude_agent.py +135 -0
  19. witdem_sdk-0.2.0/src/witdem_sdk/integrations/generic.py +145 -0
  20. witdem_sdk-0.2.0/src/witdem_sdk/integrations/haystack.py +557 -0
  21. witdem_sdk-0.2.0/src/witdem_sdk/integrations/langchain.py +414 -0
  22. witdem_sdk-0.2.0/src/witdem_sdk/integrations/langgraph.py +221 -0
  23. witdem_sdk-0.2.0/src/witdem_sdk/integrations/litellm.py +396 -0
  24. witdem_sdk-0.2.0/src/witdem_sdk/integrations/openai_agents.py +281 -0
  25. witdem_sdk-0.2.0/src/witdem_sdk/integrations/openrouter.py +368 -0
  26. witdem_sdk-0.2.0/src/witdem_sdk/integrations/smolagents.py +145 -0
  27. witdem_sdk-0.2.0/src/witdem_sdk/py.typed +0 -0
  28. witdem_sdk-0.2.0/tests/conftest.py +69 -0
  29. witdem_sdk-0.2.0/tests/test_contract_tutorial.py +42 -0
  30. witdem_sdk-0.2.0/tests/test_contracts.py +696 -0
  31. witdem_sdk-0.2.0/tests/test_correlation.py +95 -0
  32. witdem_sdk-0.2.0/tests/test_gateway_integrations.py +241 -0
  33. witdem_sdk-0.2.0/tests/test_haystack_observation.py +194 -0
  34. witdem_sdk-0.2.0/tests/test_high_level_integrations.py +412 -0
  35. witdem_sdk-0.2.0/tests/test_langgraph_instrumentation.py +161 -0
  36. witdem_sdk-0.2.0/tests/test_optional_integrations.py +301 -0
  37. witdem_sdk-0.2.0/tests/test_public_api.py +253 -0
  38. witdem_sdk-0.2.0/tests/test_telemetry.py +282 -0
  39. witdem_sdk-0.2.0/tests/test_transport.py +181 -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,44 @@
1
+ Metadata-Version: 2.5
2
+ Name: witdem-sdk
3
+ Version: 0.2.0
4
+ Summary: Public client SDK for emitting Witdem business-semantic records correlated to the caller's active OpenTelemetry execution.
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: httpx<1,>=0.27
7
+ Requires-Dist: opentelemetry-api<2,>=1.27
8
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27
9
+ Requires-Dist: opentelemetry-sdk<2,>=1.27
10
+ Requires-Dist: pydantic<3,>=2.10
11
+ Requires-Dist: pyyaml<7,>=6.0
12
+ Requires-Dist: typing-extensions<5,>=4.12
13
+ Provides-Extra: all
14
+ Requires-Dist: anthropic<1,>=0.50; extra == 'all'
15
+ Requires-Dist: haystack-ai<4,>=3.0; extra == 'all'
16
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'all'
17
+ Requires-Dist: langgraph<2,>=0.2; extra == 'all'
18
+ Requires-Dist: litellm<2,>=1.86; extra == 'all'
19
+ Requires-Dist: openai-agents<1,>=0.0.10; extra == 'all'
20
+ Requires-Dist: openai<4,>=1.68; extra == 'all'
21
+ Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'all'
22
+ Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'all'
23
+ Requires-Dist: smolagents<2,>=1.18; extra == 'all'
24
+ Provides-Extra: anthropic
25
+ Requires-Dist: anthropic<1,>=0.50; extra == 'anthropic'
26
+ Provides-Extra: haystack
27
+ Requires-Dist: haystack-ai<4,>=3.0; extra == 'haystack'
28
+ Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'haystack'
29
+ Provides-Extra: langchain
30
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'langchain'
31
+ Provides-Extra: langgraph
32
+ Requires-Dist: langgraph<2,>=0.2; extra == 'langgraph'
33
+ Provides-Extra: litellm
34
+ Requires-Dist: litellm<2,>=1.86; extra == 'litellm'
35
+ Provides-Extra: openai
36
+ Requires-Dist: openai-agents<1,>=0.0.10; extra == 'openai'
37
+ Provides-Extra: openrouter
38
+ Requires-Dist: openai<4,>=1.68; extra == 'openrouter'
39
+ Provides-Extra: smolagents
40
+ Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'smolagents'
41
+ Requires-Dist: smolagents<2,>=1.18; extra == 'smolagents'
42
+ Description-Content-Type: text/markdown
43
+
44
+ Witdem SDK provides optional application semantics and framework integrations for Witdem Analytics.
@@ -0,0 +1,84 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "witdem-sdk"
7
+ version = "0.2.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"
11
+ dependencies = [
12
+ "opentelemetry-api>=1.27,<2",
13
+ "opentelemetry-exporter-otlp-proto-http>=1.27,<2",
14
+ "opentelemetry-sdk>=1.27,<2",
15
+ "httpx>=0.27,<1",
16
+ "pydantic>=2.10,<3",
17
+ "pyyaml>=6.0,<7",
18
+ "typing-extensions>=4.12,<5",
19
+ ]
20
+
21
+ [project.scripts]
22
+ witdem = "witdem_sdk.cli:main"
23
+ witdem-sdk = "witdem_sdk.cli:main"
24
+
25
+ [project.optional-dependencies]
26
+ anthropic = ["anthropic>=0.50,<1"]
27
+ openai = ["openai-agents>=0.0.10,<1"]
28
+ langchain = ["langchain-core>=0.3,<2"]
29
+ langgraph = ["langgraph>=0.2,<2"]
30
+ haystack = ["haystack-ai>=3.0,<4", "opentelemetry-haystack>=1,<2"]
31
+ litellm = ["litellm>=1.86,<2"]
32
+ openrouter = ["openai>=1.68,<4"]
33
+ smolagents = [
34
+ "smolagents>=1.18,<2",
35
+ "openinference-instrumentation-smolagents>=0.1.20,<1",
36
+ ]
37
+ all = [
38
+ "anthropic>=0.50,<1",
39
+ "openai-agents>=0.0.10,<1",
40
+ "langchain-core>=0.3,<2",
41
+ "langgraph>=0.2,<2",
42
+ "haystack-ai>=3.0,<4",
43
+ "opentelemetry-haystack>=1,<2",
44
+ "litellm>=1.86,<2",
45
+ "openai>=1.68,<4",
46
+ "smolagents>=1.18,<2",
47
+ "openinference-instrumentation-smolagents>=0.1.20,<1",
48
+ ]
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=8.3,<9",
53
+ "pytest-cov>=6,<7",
54
+ "ruff>=0.9,<1",
55
+ "mypy>=1.15,<2",
56
+ "types-PyYAML>=6.0,<7",
57
+ ]
58
+
59
+ [tool.hatch.build.targets.wheel]
60
+ packages = ["src/witdem_sdk"]
61
+
62
+ [tool.hatch.build.targets.sdist]
63
+ include = ["src/witdem_sdk", "tests", "pyproject.toml"]
64
+
65
+ [tool.pytest.ini_options]
66
+ addopts = "--strict-markers"
67
+ testpaths = ["tests"]
68
+
69
+ [tool.ruff]
70
+ line-length = 120
71
+ target-version = "py310"
72
+
73
+ [tool.ruff.lint]
74
+ select = ["E", "F", "I", "B", "UP", "SIM"]
75
+
76
+ [tool.mypy]
77
+ python_version = "3.10"
78
+ strict = true
79
+ packages = ["witdem_sdk"]
80
+ follow_imports_for_stubs = true
81
+
82
+ [[tool.mypy.overrides]]
83
+ module = ["numpy", "numpy.*"]
84
+ follow_imports = "skip"
@@ -0,0 +1,185 @@
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 typing import Any
35
+
36
+ from witdem_sdk._contract import ContractResult, WitdemProjectConfig, load_project_config
37
+ from witdem_sdk._correlation import resolve_correlation
38
+ from witdem_sdk._errors import WitdemSDKError
39
+ from witdem_sdk._payload import Kind as _Kind
40
+ from witdem_sdk._payload import build_payload
41
+ from witdem_sdk._protocol import SEMANTIC_RECORD_PROTOCOL_VERSION
42
+ from witdem_sdk._telemetry import Operation, Witdem, configure
43
+ from witdem_sdk._transport import DeliveryStatus, flush, submit_record
44
+
45
+ __all__ = [
46
+ "WitdemSDKError",
47
+ "Witdem",
48
+ "Operation",
49
+ "configure",
50
+ "decision",
51
+ "event",
52
+ "evaluation",
53
+ "metric",
54
+ "outcome",
55
+ "flush",
56
+ "DeliveryStatus",
57
+ "SEMANTIC_RECORD_PROTOCOL_VERSION",
58
+ "ContractResult",
59
+ "WitdemProjectConfig",
60
+ "load_project_config",
61
+ ]
62
+
63
+ __version__ = "0.2.0"
64
+
65
+
66
+ def _emit(
67
+ kind: _Kind,
68
+ name: str,
69
+ value: Any,
70
+ attributes: dict[str, Any],
71
+ *,
72
+ execution_id: str | None,
73
+ ) -> None:
74
+ """Shared plumbing: resolve correlation, build the wire payload, send it."""
75
+
76
+ resolved_execution_id, trace_id, span_id = resolve_correlation(execution_id)
77
+ payload = build_payload(
78
+ kind=kind,
79
+ name=name,
80
+ value=value,
81
+ execution_id=resolved_execution_id,
82
+ trace_id=trace_id,
83
+ span_id=span_id,
84
+ attributes=attributes,
85
+ )
86
+ submit_record(payload)
87
+
88
+
89
+ def event(
90
+ name: str,
91
+ payload: dict[str, Any] | None = None,
92
+ *,
93
+ attributes: Mapping[str, Any] | None = None,
94
+ execution_id: str | None = None,
95
+ ) -> None:
96
+ """Record a domain event that happened during the active execution.
97
+
98
+ ``payload`` is an arbitrary JSON-serializable dict describing what
99
+ happened; it is sent as the wire record's ``attributes``. Raises
100
+ :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see module
101
+ docs). Never raises when Witdem is unavailable on the network.
102
+ """
103
+
104
+ _emit(
105
+ "event",
106
+ name,
107
+ None,
108
+ {**dict(payload or {}), **dict(attributes or {})},
109
+ execution_id=execution_id,
110
+ )
111
+
112
+
113
+ def decision(
114
+ name: str,
115
+ value: Any,
116
+ *,
117
+ attributes: Mapping[str, Any] | None = None,
118
+ execution_id: str | None = None,
119
+ ) -> None:
120
+ """Record a business decision (e.g. a chosen route/branch) and its value.
121
+
122
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
123
+ module docs). Never raises when Witdem is unavailable on the network.
124
+ """
125
+
126
+ _emit("decision", name, value, dict(attributes or {}), execution_id=execution_id)
127
+
128
+
129
+ def evaluation(
130
+ name: str,
131
+ *,
132
+ score: float | None = None,
133
+ label: str | None = None,
134
+ value: Any = None,
135
+ attributes: Mapping[str, Any] | None = None,
136
+ execution_id: str | None = None,
137
+ ) -> None:
138
+ """Record a structured assessment: a ``score``, a ``label``, or both.
139
+
140
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
141
+ module docs). Never raises when Witdem is unavailable on the network.
142
+ """
143
+
144
+ resolved_attributes = dict(attributes or {})
145
+ if score is not None:
146
+ resolved_attributes["score"] = score
147
+ if label is not None:
148
+ resolved_attributes["label"] = label
149
+ _emit("evaluation", name, value, resolved_attributes, execution_id=execution_id)
150
+
151
+
152
+ def outcome(
153
+ name: str,
154
+ *,
155
+ status: str | None = None,
156
+ value: Any = None,
157
+ attributes: Mapping[str, Any] | None = None,
158
+ execution_id: str | None = None,
159
+ ) -> None:
160
+ """Record an externally meaningful result of the active execution.
161
+
162
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
163
+ module docs). Never raises when Witdem is unavailable on the network.
164
+ """
165
+
166
+ resolved_attributes = dict(attributes or {})
167
+ if status is not None:
168
+ resolved_attributes["status"] = status
169
+ _emit("outcome", name, value, resolved_attributes, execution_id=execution_id)
170
+
171
+
172
+ def metric(
173
+ name: str,
174
+ value: Any,
175
+ *,
176
+ attributes: Mapping[str, Any] | None = None,
177
+ execution_id: str | None = None,
178
+ ) -> None:
179
+ """Record a numeric (or otherwise quantitative) measurement.
180
+
181
+ Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
182
+ module docs). Never raises when Witdem is unavailable on the network.
183
+ """
184
+
185
+ _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")