forgesight-langfuse 0.1.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.
- forgesight_langfuse/__init__.py +9 -0
- forgesight_langfuse/exporter.py +105 -0
- forgesight_langfuse/py.typed +0 -0
- forgesight_langfuse-0.1.0.dist-info/METADATA +62 -0
- forgesight_langfuse-0.1.0.dist-info/RECORD +7 -0
- forgesight_langfuse-0.1.0.dist-info/WHEEL +4 -0
- forgesight_langfuse-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""ForgeSight Langfuse exporter — OTLP ingest with native langfuse.* observation mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .exporter import LangfuseExporter, basic_auth_header
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
__all__ = ["LangfuseExporter", "__version__", "basic_auth_header"]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""``LangfuseExporter`` — ForgeSight records → Langfuse via OTLP + ``langfuse.*`` attrs.
|
|
2
|
+
|
|
3
|
+
Wraps ``forgesight-otel``'s ``OTelExporter`` pointed at Langfuse's OTLP ingest endpoint
|
|
4
|
+
(``/api/public/otel``, HTTP, Basic auth) and enriches each record with the native
|
|
5
|
+
``langfuse.*`` attributes Langfuse reads (observation type; trace name / user / session /
|
|
6
|
+
tags). LLM calls land as ``generation`` observations, tools as ``tool`` observations,
|
|
7
|
+
steps as ``span`` — with the SDK's computed ``forgesight.usage.cost_usd`` ingested.
|
|
8
|
+
|
|
9
|
+
Content (prompts/completions) is captured only when ``capture_content`` is on (P7).
|
|
10
|
+
``export`` never raises (P6). Runs on the pipeline worker, never the hot path.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import base64
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from dataclasses import replace
|
|
18
|
+
from types import MappingProxyType
|
|
19
|
+
|
|
20
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
|
21
|
+
|
|
22
|
+
from forgesight_api import ExportResult, Kind, Record
|
|
23
|
+
from forgesight_otel import OTelExporter
|
|
24
|
+
|
|
25
|
+
_REGION_HOSTS = {
|
|
26
|
+
"us": "https://us.cloud.langfuse.com",
|
|
27
|
+
"eu": "https://cloud.langfuse.com",
|
|
28
|
+
}
|
|
29
|
+
_OBSERVATION_TYPE = {
|
|
30
|
+
Kind.AGENT: "agent",
|
|
31
|
+
Kind.WORKFLOW: "chain",
|
|
32
|
+
Kind.STEP: "span",
|
|
33
|
+
Kind.LLM: "generation",
|
|
34
|
+
Kind.TOOL: "tool",
|
|
35
|
+
Kind.MCP: "tool",
|
|
36
|
+
}
|
|
37
|
+
# business-metadata keys lifted to trace-level langfuse attributes (on the root span)
|
|
38
|
+
_TRACE_LIFTS = {
|
|
39
|
+
"user_id": "langfuse.user.id",
|
|
40
|
+
"session_id": "langfuse.session.id",
|
|
41
|
+
"tags": "langfuse.trace.tags",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
LANGFUSE_OBSERVATION_TYPE = "langfuse.observation.type"
|
|
45
|
+
LANGFUSE_TRACE_NAME = "langfuse.trace.name"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def basic_auth_header(public_key: str, secret_key: str) -> str:
|
|
49
|
+
"""Return the ``Basic base64(pk:sk)`` value Langfuse's OTLP endpoint expects."""
|
|
50
|
+
token = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode("ascii")
|
|
51
|
+
return f"Basic {token}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def otlp_traces_endpoint(host: str) -> str:
|
|
55
|
+
"""Langfuse's OTLP/HTTP traces URL: the ``/v1/traces`` signal under ``/api/public/otel``.
|
|
56
|
+
The signal path is required — posting to the bare ``/api/public/otel`` base 404s."""
|
|
57
|
+
return f"{host.rstrip('/')}/api/public/otel/v1/traces"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LangfuseExporter:
|
|
61
|
+
"""Export records to Langfuse over OTLP with native ``langfuse.*`` enrichment."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
public_key: str,
|
|
67
|
+
secret_key: str,
|
|
68
|
+
host: str | None = None,
|
|
69
|
+
region: str | None = None,
|
|
70
|
+
capture_content: bool = False,
|
|
71
|
+
span_exporter: SpanExporter | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
if not public_key or not secret_key:
|
|
74
|
+
raise ValueError("LangfuseExporter requires public_key and secret_key")
|
|
75
|
+
resolved_host = host or _REGION_HOSTS.get((region or "").lower(), _REGION_HOSTS["eu"])
|
|
76
|
+
self._host = resolved_host.rstrip("/")
|
|
77
|
+
self._otel = OTelExporter(
|
|
78
|
+
endpoint=otlp_traces_endpoint(self._host),
|
|
79
|
+
protocol="http/protobuf",
|
|
80
|
+
service_name="forgesight",
|
|
81
|
+
capture_content=capture_content,
|
|
82
|
+
headers={"Authorization": basic_auth_header(public_key, secret_key)},
|
|
83
|
+
span_exporter=span_exporter,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# --- TelemetryExporter Protocol --------------------------------------
|
|
87
|
+
def export(self, records: Sequence[Record]) -> ExportResult:
|
|
88
|
+
return self._otel.export([self._enrich(r) for r in records])
|
|
89
|
+
|
|
90
|
+
def force_flush(self, timeout_millis: int = 30_000) -> bool:
|
|
91
|
+
return self._otel.force_flush(timeout_millis)
|
|
92
|
+
|
|
93
|
+
def shutdown(self, timeout_millis: int = 30_000) -> None:
|
|
94
|
+
self._otel.shutdown(timeout_millis)
|
|
95
|
+
|
|
96
|
+
# --- internals --------------------------------------------------------
|
|
97
|
+
def _enrich(self, record: Record) -> Record:
|
|
98
|
+
attrs: dict[str, object] = dict(record.attributes)
|
|
99
|
+
attrs[LANGFUSE_OBSERVATION_TYPE] = _OBSERVATION_TYPE[record.kind]
|
|
100
|
+
if record.parent_span_id is None and record.kind in (Kind.AGENT, Kind.WORKFLOW):
|
|
101
|
+
attrs[LANGFUSE_TRACE_NAME] = record.name
|
|
102
|
+
for meta_key, lf_key in _TRACE_LIFTS.items():
|
|
103
|
+
if meta_key in attrs:
|
|
104
|
+
attrs[lf_key] = attrs[meta_key]
|
|
105
|
+
return replace(record, attributes=MappingProxyType(attrs))
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forgesight-langfuse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ForgeSight Langfuse exporter — OTLP ingest with native langfuse.* observation mapping.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Scaffoldic/forgesight
|
|
6
|
+
Project-URL: Repository, https://github.com/Scaffoldic/forgesight
|
|
7
|
+
Project-URL: Issues, https://github.com/Scaffoldic/forgesight/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Scaffoldic/forgesight/blob/main/docs/releases/v0.1.md
|
|
9
|
+
Author: kjoshi
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: ai-agents,forgesight,langfuse,llm,observability,otlp
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: forgesight-core
|
|
24
|
+
Requires-Dist: forgesight-otel
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# forgesight-langfuse
|
|
28
|
+
|
|
29
|
+
The Langfuse exporter for [ForgeSight](https://github.com/Scaffoldic/forgesight).
|
|
30
|
+
Ships ForgeSight records to Langfuse over its OTLP ingest endpoint
|
|
31
|
+
(`/api/public/otel`, Basic auth), enriched with the native **`langfuse.*`**
|
|
32
|
+
attributes so LLM calls render as **generation** observations, tools as **tool**
|
|
33
|
+
observations, and the run's `user`/`session`/`tags` lift to the trace.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install forgesight-langfuse
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import forgesight
|
|
41
|
+
from forgesight_langfuse import LangfuseExporter
|
|
42
|
+
|
|
43
|
+
forgesight.configure(exporters=[
|
|
44
|
+
LangfuseExporter(public_key="pk-lf-...", secret_key="sk-lf-...",
|
|
45
|
+
host="https://cloud.langfuse.com"),
|
|
46
|
+
])
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or by name: `exporters: [{name: langfuse, config: {public_key: …, secret_key: …}}]`.
|
|
50
|
+
|
|
51
|
+
## Two paths
|
|
52
|
+
|
|
53
|
+
- **First-party (this package):** native `langfuse.*` observation mapping + the SDK's
|
|
54
|
+
computed cost (`forgesight.usage.cost_usd`) ingested.
|
|
55
|
+
- **OTLP-native (no package):** point `forgesight-otel` at
|
|
56
|
+
`https://cloud.langfuse.com/api/public/otel` with `Authorization: Basic base64(pk:sk)`.
|
|
57
|
+
|
|
58
|
+
Prompt/response content is captured only with `capture_content=True` (off by default).
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
Apache-2.0
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
forgesight_langfuse/__init__.py,sha256=dYv94RTIlLkv8_j3rsRjeKuVW3YpFzSIw5Ylyo256Ro,281
|
|
2
|
+
forgesight_langfuse/exporter.py,sha256=feKec4_FCNifhMoYzHhai78-tI1uFyI_SPTZMu7OIns,4199
|
|
3
|
+
forgesight_langfuse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
forgesight_langfuse-0.1.0.dist-info/METADATA,sha256=nAOAivZQEwC0vyoSNA_47_49qfNIW0e8aOgYOK1YLOk,2396
|
|
5
|
+
forgesight_langfuse-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
6
|
+
forgesight_langfuse-0.1.0.dist-info/entry_points.txt,sha256=0_LafV4GTGCVN2HrpR7qDWsV8QMCxe_3NMZlHI6Icas,80
|
|
7
|
+
forgesight_langfuse-0.1.0.dist-info/RECORD,,
|