@cat-factory/observability-langfuse 0.6.0
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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +168 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Savin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @cat-factory/observability-langfuse
|
|
2
|
+
|
|
3
|
+
Opt-in [Langfuse](https://langfuse.com) trace sink for the Agent Architecture Board.
|
|
4
|
+
|
|
5
|
+
It implements the runtime-neutral `LlmTraceSink` port from `@cat-factory/kernel`, so
|
|
6
|
+
when wired into a facade every LLM call — container-agent calls (through the LLM proxy)
|
|
7
|
+
**and** inline calls (requirements review, document planner, fragment selector, inline
|
|
8
|
+
agent) — surfaces in Langfuse as a generation grouped under its run's trace, plus
|
|
9
|
+
optional container tool spans.
|
|
10
|
+
|
|
11
|
+
## Why fetch-only
|
|
12
|
+
|
|
13
|
+
The sink talks to Langfuse's public **ingestion API** (`POST /api/public/ingestion`,
|
|
14
|
+
HTTP Basic auth, batched JSON events) using only the global `fetch` / `crypto` /
|
|
15
|
+
`btoa`. It deliberately does **not** depend on the official `langfuse` Node SDK or any
|
|
16
|
+
`@opentelemetry/*` package — those rely on Node-only APIs that are unavailable on the
|
|
17
|
+
Cloudflare Worker runtime (workerd). Using the raw ingestion API keeps the sink
|
|
18
|
+
byte-for-byte identical on both the Worker and Node facades.
|
|
19
|
+
|
|
20
|
+
## Behaviour
|
|
21
|
+
|
|
22
|
+
- Never throws into the caller: every flush swallows its own errors (logging at most a
|
|
23
|
+
warning). Observability must never break agent work.
|
|
24
|
+
- Honours the same `LLM_RECORD_PROMPTS` privacy switch as the local metric store: when
|
|
25
|
+
prompt recording is off, generations carry usage/timing/metadata but no prompt or
|
|
26
|
+
response bodies.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createLangfuseSink } from '@cat-factory/observability-langfuse'
|
|
32
|
+
|
|
33
|
+
const sink = createLangfuseSink({
|
|
34
|
+
publicKey: env.LANGFUSE_PUBLIC_KEY,
|
|
35
|
+
secretKey: env.LANGFUSE_SECRET_KEY,
|
|
36
|
+
baseUrl: env.LANGFUSE_BASE_URL, // optional; defaults to Langfuse Cloud
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Wired into a facade via its container's `selectLangfuseSink(config)`; absent config ⇒
|
|
41
|
+
the sink is never built and there is no external emission or behaviour change.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LlmGenerationEvent, LlmToolSpan, LlmToolSpanContext, LlmTraceSink } from '@cat-factory/kernel';
|
|
2
|
+
/** Minimal structured logger (pino-compatible); optional. */
|
|
3
|
+
export interface LangfuseLogger {
|
|
4
|
+
warn(obj: Record<string, unknown>, msg?: string): void;
|
|
5
|
+
}
|
|
6
|
+
export interface LangfuseSinkConfig {
|
|
7
|
+
/** Langfuse public key (`pk-lf-…`). */
|
|
8
|
+
publicKey: string;
|
|
9
|
+
/** Langfuse secret key (`sk-lf-…`). */
|
|
10
|
+
secretKey: string;
|
|
11
|
+
/** Host of the Langfuse instance. Default: Langfuse Cloud (`https://cloud.langfuse.com`). */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Optional logger for swallowed errors. */
|
|
14
|
+
logger?: LangfuseLogger;
|
|
15
|
+
/** Injectable fetch (tests); defaults to the global `fetch`. */
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
export declare class LangfuseTraceSink implements LlmTraceSink {
|
|
19
|
+
private readonly endpoint;
|
|
20
|
+
private readonly authorization;
|
|
21
|
+
private readonly logger?;
|
|
22
|
+
private readonly fetchImpl;
|
|
23
|
+
constructor(config: LangfuseSinkConfig);
|
|
24
|
+
recordGeneration(event: LlmGenerationEvent): Promise<void>;
|
|
25
|
+
recordToolSpans(context: LlmToolSpanContext, spans: LlmToolSpan[]): Promise<void>;
|
|
26
|
+
private send;
|
|
27
|
+
}
|
|
28
|
+
/** Build a {@link LangfuseTraceSink}. Returns the opt-in sink wired into a facade. */
|
|
29
|
+
export declare function createLangfuseSink(config: LangfuseSinkConfig): LangfuseTraceSink;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAkD5B,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAkBD,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,YAAY,MAAM,EAAE,kBAAkB,EAMrC;IAEK,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD/D;IAEK,eAAe,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtF;YAEa,IAAI;CA4BnB;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,iBAAiB,CAEhF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// A fetch-based Langfuse trace sink. It speaks Langfuse's public **ingestion API**
|
|
2
|
+
// (`POST /api/public/ingestion`, HTTP Basic auth = public:secret key, batched JSON
|
|
3
|
+
// events) using only the global `fetch`/`crypto`/`btoa` — NO `langfuse` Node SDK and
|
|
4
|
+
// NO `@opentelemetry/*`, both of which depend on Node-only APIs that are unavailable on
|
|
5
|
+
// the Cloudflare Worker runtime (workerd). This keeps the sink byte-for-byte identical
|
|
6
|
+
// across the Worker and Node facades, which is the whole reason it exists as its own
|
|
7
|
+
// package: see `LlmTraceSink` in `@cat-factory/kernel`.
|
|
8
|
+
//
|
|
9
|
+
// Observability must never break the product, so every method swallows its own errors
|
|
10
|
+
// (logging at most a warning) and the caller additionally schedules the call off the
|
|
11
|
+
// response path. A failed flush drops that batch; it never propagates.
|
|
12
|
+
//
|
|
13
|
+
// Each `recordGeneration` posts its own small ingestion batch rather than buffering
|
|
14
|
+
// across calls. This is deliberate: the Worker runtime is stateless per request (there
|
|
15
|
+
// is no durable cross-request buffer to flush, and a `waitUntil`-scheduled POST can't
|
|
16
|
+
// outlive its request), so a per-call POST is the only shape that stays identical across
|
|
17
|
+
// the Worker and Node facades. Tool spans, which the backend already accumulates per
|
|
18
|
+
// poll, ARE sent as one batch. Langfuse's ingestion API is built for this volume.
|
|
19
|
+
//
|
|
20
|
+
// IMPACT ANALYSIS — why per-call POST is safe for the execution hot path:
|
|
21
|
+
// - NOT on the hot path. The proxied feeder runs under `executionCtx.waitUntil`
|
|
22
|
+
// (`LlmProxyController`), scheduled AFTER the container's chat-completion response
|
|
23
|
+
// is returned (on Node `waitUntil` is a plain fire-and-forget); the inline feeder
|
|
24
|
+
// (`InstrumentedModelProvider`) dispatches AFTER `generateText` resolves. Inside
|
|
25
|
+
// `LlmObservabilityService.record` the POST is then dispatched detached (not
|
|
26
|
+
// awaited), so even the `waitUntil` window never blocks on the Langfuse round trip.
|
|
27
|
+
// The only synchronous cost added to any path is one object build + `JSON.stringify`
|
|
28
|
+
// — microseconds, never the network call.
|
|
29
|
+
// - NOT a source of execution brittleness. Every error is swallowed + logged, the
|
|
30
|
+
// fetch is bounded by SEND_TIMEOUT_MS, and nothing in the run lifecycle reads the
|
|
31
|
+
// sink's result — a Langfuse outage / slowness / 4xx drops a batch and nothing else.
|
|
32
|
+
// - The costs that DO exist are telemetry-side, not run-side: +1 detached subrequest
|
|
33
|
+
// per proxy invocation (~2 of the Worker's 1000-subrequest budget), negligible
|
|
34
|
+
// `waitUntil` CPU (I/O-bound, timeout-capped), and N calls ⇒ N POSTs — a very chatty
|
|
35
|
+
// run could brush Langfuse ingestion rate limits and drop some batches, degrading
|
|
36
|
+
// telemetry COMPLETENESS only, never the run. (Tool spans are batched per poll, so
|
|
37
|
+
// they don't multiply.)
|
|
38
|
+
const DEFAULT_BASE_URL = 'https://cloud.langfuse.com';
|
|
39
|
+
/**
|
|
40
|
+
* Hard ceiling on a single ingestion POST. Observability must never tie up the LLM
|
|
41
|
+
* path: the proxied feeder records under the platform's `waitUntil` budget and the
|
|
42
|
+
* inline feeder dispatches without awaiting, so a hung Langfuse endpoint must abort
|
|
43
|
+
* rather than dangle. A dropped batch is the documented best-effort worst case.
|
|
44
|
+
*/
|
|
45
|
+
const SEND_TIMEOUT_MS = 10_000;
|
|
46
|
+
function iso(ms) {
|
|
47
|
+
return new Date(ms).toISOString();
|
|
48
|
+
}
|
|
49
|
+
function basicAuth(publicKey, secretKey) {
|
|
50
|
+
return `Basic ${btoa(`${publicKey}:${secretKey}`)}`;
|
|
51
|
+
}
|
|
52
|
+
export class LangfuseTraceSink {
|
|
53
|
+
endpoint;
|
|
54
|
+
authorization;
|
|
55
|
+
logger;
|
|
56
|
+
fetchImpl;
|
|
57
|
+
constructor(config) {
|
|
58
|
+
const base = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
59
|
+
this.endpoint = `${base}/api/public/ingestion`;
|
|
60
|
+
this.authorization = basicAuth(config.publicKey, config.secretKey);
|
|
61
|
+
this.logger = config.logger;
|
|
62
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
63
|
+
}
|
|
64
|
+
async recordGeneration(event) {
|
|
65
|
+
// Group every call of a run under one trace (keyed by the execution id); inline
|
|
66
|
+
// single-shot calls (no execution) become their own standalone trace.
|
|
67
|
+
const traceId = event.executionId ?? crypto.randomUUID();
|
|
68
|
+
const generation = {
|
|
69
|
+
id: crypto.randomUUID(),
|
|
70
|
+
traceId,
|
|
71
|
+
name: event.agentKind,
|
|
72
|
+
startTime: iso(event.startedAt),
|
|
73
|
+
endTime: iso(event.endedAt),
|
|
74
|
+
model: event.model,
|
|
75
|
+
usage: {
|
|
76
|
+
input: event.promptTokens,
|
|
77
|
+
output: event.completionTokens,
|
|
78
|
+
total: event.totalTokens,
|
|
79
|
+
unit: 'TOKENS',
|
|
80
|
+
},
|
|
81
|
+
level: event.ok ? 'DEFAULT' : 'ERROR',
|
|
82
|
+
metadata: {
|
|
83
|
+
provider: event.provider,
|
|
84
|
+
agentKind: event.agentKind,
|
|
85
|
+
finishReason: event.finishReason,
|
|
86
|
+
workspaceId: event.workspaceId,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
// Prompt/response bodies are present only when prompt recording is on (the same
|
|
90
|
+
// `LLM_RECORD_PROMPTS` switch the local store honours): omit empty bodies entirely.
|
|
91
|
+
if (event.input)
|
|
92
|
+
generation.input = event.input;
|
|
93
|
+
if (event.output)
|
|
94
|
+
generation.output = event.output;
|
|
95
|
+
if (event.errorMessage)
|
|
96
|
+
generation.statusMessage = event.errorMessage;
|
|
97
|
+
await this.send([
|
|
98
|
+
{
|
|
99
|
+
id: crypto.randomUUID(),
|
|
100
|
+
type: 'trace-create',
|
|
101
|
+
timestamp: iso(event.endedAt),
|
|
102
|
+
body: {
|
|
103
|
+
id: traceId,
|
|
104
|
+
// A run trace is upserted by every call it groups, so keep the trace body
|
|
105
|
+
// stable across them: the per-call agent kind lives on each generation
|
|
106
|
+
// (its `name` + metadata), NOT as a trace tag that the next call would clobber.
|
|
107
|
+
name: event.executionId ? `run ${event.executionId}` : event.agentKind,
|
|
108
|
+
metadata: { workspaceId: event.workspaceId },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: crypto.randomUUID(),
|
|
113
|
+
type: 'generation-create',
|
|
114
|
+
timestamp: iso(event.endedAt),
|
|
115
|
+
body: generation,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
async recordToolSpans(context, spans) {
|
|
120
|
+
// Tool spans are only meaningful as children of a run's trace.
|
|
121
|
+
if (!context.executionId || spans.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
const traceId = context.executionId;
|
|
124
|
+
const batch = spans.map((span) => ({
|
|
125
|
+
id: crypto.randomUUID(),
|
|
126
|
+
type: 'span-create',
|
|
127
|
+
timestamp: iso(span.endedAt),
|
|
128
|
+
body: {
|
|
129
|
+
id: crypto.randomUUID(),
|
|
130
|
+
traceId,
|
|
131
|
+
name: span.tool,
|
|
132
|
+
startTime: iso(span.startedAt),
|
|
133
|
+
endTime: iso(span.endedAt),
|
|
134
|
+
level: span.ok ? 'DEFAULT' : 'ERROR',
|
|
135
|
+
metadata: { agentKind: context.agentKind },
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
await this.send(batch);
|
|
139
|
+
}
|
|
140
|
+
async send(batch) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await this.fetchImpl(this.endpoint, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'content-type': 'application/json',
|
|
146
|
+
authorization: this.authorization,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({ batch }),
|
|
149
|
+
// Bound the request so a hung endpoint can't tie up the caller's waitUntil
|
|
150
|
+
// budget; an abort lands in the catch below and drops the batch (best-effort).
|
|
151
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
152
|
+
});
|
|
153
|
+
// 207 = partial success (per-event errors in the body); anything else non-2xx is
|
|
154
|
+
// a hard failure. Either way we only log — observability never breaks the caller.
|
|
155
|
+
if (!res.ok && res.status !== 207) {
|
|
156
|
+
this.logger?.warn({ scope: 'langfuse', status: res.status }, 'langfuse: ingestion rejected batch');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
this.logger?.warn({ scope: 'langfuse', err: err instanceof Error ? err.message : String(err) }, 'langfuse: failed to post ingestion batch');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Build a {@link LangfuseTraceSink}. Returns the opt-in sink wired into a facade. */
|
|
165
|
+
export function createLangfuseSink(config) {
|
|
166
|
+
return new LangfuseTraceSink(config);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cat-factory/observability-langfuse",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Opt-in Langfuse trace sink for the Agent Architecture Board. A fetch-based LlmTraceSink that streams LLM generations (and container tool spans) to Langfuse's ingestion API — runs unchanged on both the Cloudflare Worker (workerd) and Node facades.",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@cat-factory/kernel": "0.6.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "7.0.1-rc",
|
|
26
|
+
"vitest": "^3.2.4"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -b tsconfig.build.json",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
31
|
+
"test:run": "vitest run"
|
|
32
|
+
}
|
|
33
|
+
}
|