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.
@@ -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,,
@@ -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
+ [forgesight.exporters]
2
+ langfuse = forgesight_langfuse.exporter:LangfuseExporter