openwright-langfuse 0.3.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.
@@ -0,0 +1,98 @@
1
+ """Langfuse forwarder for OpenWright (CONN-2).
2
+
3
+ The downstream-observability pattern as a :class:`Forwarder`:
4
+
5
+ * ``forward`` — co-ingest a telemetry batch to Langfuse **unchanged**, injecting
6
+ Basic auth + ``x-langfuse-ingestion-version: 4`` (Langfuse is HTTP-only).
7
+ * ``backlink`` — push ``openwright:<control>`` **scores** (the verdicts) onto the
8
+ Langfuse trace via the public API, with the reason as a comment, and return a
9
+ deep-link to the trace.
10
+ * ``pull`` — read Langfuse scores/evals and yield them as ``ComplianceEvent``s
11
+ (evals-as-evidence).
12
+
13
+ No crypto here; payloads are hashed by the source connector before they become
14
+ events. ``config`` carries ``host``, ``public_key``, ``secret_key`` and per-call
15
+ fields (``trace_id`` for ``backlink``). Pass ``_transport`` (an httpx transport)
16
+ to talk to a stub in tests.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import base64
22
+ from typing import Any, Iterable
23
+
24
+ from openwright.connectors import CONTRACT_VERSION, ExportResult
25
+
26
+ __all__ = ["LangfuseForwarder", "forwarder", "CONTRACT_VERSION"]
27
+
28
+
29
+ class LangfuseForwarder:
30
+ name = "langfuse"
31
+ CONTRACT_VERSION = CONTRACT_VERSION
32
+
33
+ def _client(self, config: dict):
34
+ import httpx
35
+
36
+ host = str(config["host"]).rstrip("/")
37
+ token = base64.b64encode(
38
+ f"{config['public_key']}:{config['secret_key']}".encode("utf-8")
39
+ ).decode("ascii")
40
+ headers = {
41
+ "Authorization": f"Basic {token}",
42
+ "x-langfuse-ingestion-version": "4",
43
+ }
44
+ return httpx.Client(
45
+ base_url=host, headers=headers, timeout=config.get("timeout", 10),
46
+ transport=config.get("_transport"),
47
+ )
48
+
49
+ def forward(self, payload: Any, *, config: dict) -> ExportResult:
50
+ path = config.get("ingest_path", "/api/public/otel/v1/traces")
51
+ with self._client(config) as c:
52
+ if isinstance(payload, (bytes, bytearray)):
53
+ r = c.post(path, content=bytes(payload),
54
+ headers={"Content-Type": "application/x-protobuf"})
55
+ else:
56
+ r = c.post(path, json=payload)
57
+ return ExportResult(ok=r.is_success, detail=f"forward -> HTTP {r.status_code}")
58
+
59
+ def backlink(self, report: dict, *, config: dict) -> ExportResult:
60
+ trace_id = config.get("trace_id")
61
+ oks: list[bool] = []
62
+ with self._client(config) as c:
63
+ for ctrl in report.get("controls", []):
64
+ body = {
65
+ "traceId": trace_id,
66
+ "name": f"openwright:{ctrl['control_id']}",
67
+ "value": ctrl["status"],
68
+ "dataType": "CATEGORICAL",
69
+ "comment": (ctrl.get("reason") or "")[:500],
70
+ }
71
+ oks.append(c.post("/api/public/scores", json=body).is_success)
72
+ url = f"{str(config['host']).rstrip('/')}/trace/{trace_id}" if trace_id else None
73
+ ok = all(oks) if oks else True
74
+ return ExportResult(ok=ok, detail=f"back-linked {sum(oks)}/{len(oks)} verdict(s)", url=url)
75
+
76
+ def pull(self, *, config: dict) -> Iterable[Any]:
77
+ from openwright.canonical import to_rfc3339
78
+ from openwright.events import ComplianceEvent, EventKind
79
+
80
+ with self._client(config) as c:
81
+ r = c.get("/api/public/scores", params={"limit": config.get("limit", 50)})
82
+ r.raise_for_status()
83
+ for sc in r.json().get("data", []):
84
+ yield ComplianceEvent(
85
+ timestamp=to_rfc3339(sc.get("timestamp") or "1970-01-01T00:00:00.000000000Z"),
86
+ kind=EventKind.CONFORMANCE_FINDING,
87
+ actor={"agent_id": "langfuse"},
88
+ source={"format": "langfuse"},
89
+ attributes={
90
+ "score_name": sc.get("name"),
91
+ "value": str(sc.get("value")),
92
+ "trace_id": sc.get("traceId"),
93
+ },
94
+ )
95
+
96
+
97
+ #: The object the ``openwright.forwarders:langfuse`` entry point loads to.
98
+ forwarder = LangfuseForwarder()
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: openwright-langfuse
3
+ Version: 0.3.0
4
+ Summary: Langfuse forwarder for OpenWright — co-ingest + openwright:* verdict back-link + evals-as-evidence.
5
+ License: Apache-2.0
6
+ Keywords: evidence,forwarder,langfuse,observability,openwright
7
+ Requires-Python: <3.15,>=3.10
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: openwright-core<0.7,>=0.6
10
+ Provides-Extra: test
11
+ Requires-Dist: openwright-conformance; extra == 'test'
12
+ Requires-Dist: pytest>=8; extra == 'test'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # openwright-langfuse
16
+
17
+ A **Langfuse forwarder** for [OpenWright](https://github.com/allthingsN/openwright):
18
+ keep your traces in Langfuse *and* attach the OpenWright verdicts back to them.
19
+
20
+ - **forward** — co-ingest a telemetry batch to Langfuse unchanged (Basic auth +
21
+ `x-langfuse-ingestion-version: 4`).
22
+ - **backlink** — push `openwright:<control>` scores (the verdicts) onto the trace,
23
+ with the reason as a comment; returns a deep-link.
24
+ - **pull** — read Langfuse scores/evals back as `ComplianceEvent`s (evals-as-evidence).
25
+
26
+ ```python
27
+ from openwright.connectors import load
28
+ fwd = load("openwright.forwarders", "langfuse")
29
+ cfg = {"host": "https://cloud.langfuse.com", "public_key": "pk-…", "secret_key": "sk-…", "trace_id": trace_id}
30
+ fwd.backlink(report, config=cfg) # openwright:* verdicts now on the Langfuse trace
31
+ ```
32
+
33
+ No partnership or endorsement implied; built on Langfuse's public HTTP API. No
34
+ crypto is reimplemented here.
@@ -0,0 +1,5 @@
1
+ openwright_langfuse/__init__.py,sha256=-Rpbz0jYFYzhTjzGv5OrPBX5MPsbhKVohfuGfo-QWl4,4084
2
+ openwright_langfuse-0.3.0.dist-info/METADATA,sha256=rukJgofiURlXVGM2FToagU3nRydK0KE9vsbggxbrZug,1463
3
+ openwright_langfuse-0.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
4
+ openwright_langfuse-0.3.0.dist-info/entry_points.txt,sha256=2Yf_qcZsEjHtzvwkOeTXk7brLRPM5-d7Ky3WMSYrXPs,65
5
+ openwright_langfuse-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [openwright.forwarders]
2
+ langfuse = openwright_langfuse:forwarder