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,,
|