pi-langfuse 1.5.18 → 1.5.19
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/package.json +1 -1
- package/src/langfuse.ts +76 -20
- package/src/types.ts +8 -0
package/package.json
CHANGED
package/src/langfuse.ts
CHANGED
|
@@ -463,25 +463,73 @@ function observationType(asType?: string): FallbackObservationType {
|
|
|
463
463
|
* `LangfuseSpanProcessor.onStart(span, parentContext)`, reading them from the
|
|
464
464
|
* OTel context that was active when the span was created. `propagateAttributes`
|
|
465
465
|
* only seeds that context for the duration of its callback, so a child created
|
|
466
|
-
* later — from a separate event handler, outside the callback — is written
|
|
467
|
-
*
|
|
466
|
+
* later — from a separate event handler, outside the callback — is written
|
|
467
|
+
* without `session.id`, `langfuse.trace.name`, or `langfuse.trace.metadata.*`.
|
|
468
468
|
*
|
|
469
|
-
* That is invisible in the legacy data model, where
|
|
470
|
-
*
|
|
471
|
-
* session
|
|
472
|
-
* `
|
|
473
|
-
*
|
|
469
|
+
* That is invisible in the legacy data model, where those live on the trace,
|
|
470
|
+
* but Langfuse v4 stores them per event row and filters and aggregates over
|
|
471
|
+
* those rows: a session is `WHERE session_id != ''`, and cost by trace name
|
|
472
|
+
* matches `trace_name` on the generation itself. Unstamped children drop out
|
|
473
|
+
* of `sumMap(cost_details)` and `sumMap(usage_details)`, which is why such
|
|
474
|
+
* sessions and trace-name filters report a correct trace count but no cost
|
|
475
|
+
* and no usage at all.
|
|
474
476
|
*
|
|
475
|
-
* Re-entering the propagated context for every child keeps the whole tree
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
477
|
+
* Re-entering the propagated context for every child keeps the whole tree
|
|
478
|
+
* queryable. The attributes are read back off the parent span rather than
|
|
479
|
+
* from state, so a child created outside an active session scope still
|
|
480
|
+
* inherits whatever its parent was actually stamped with, and grandchildren
|
|
481
|
+
* inherit transitively.
|
|
482
|
+
*
|
|
483
|
+
* The re-entry runs on the OTel root context, not the ambient one.
|
|
484
|
+
* `propagateAttributes` starts from `context.active()`, merges `metadata`
|
|
485
|
+
* and `tags` with whatever that context already carries, and stamps the
|
|
486
|
+
* active span if there is one. Any attributes propagated by the caller —
|
|
487
|
+
* another instrumentation, a foreign `propagateAttributes` scope — would
|
|
488
|
+
* otherwise leak into the child or onto an unrelated span. The parent is
|
|
489
|
+
* still explicit: `observation.startObservation` passes the parent span
|
|
490
|
+
* context to the child, so a clean ambient context changes nothing else.
|
|
479
491
|
*/
|
|
480
|
-
|
|
492
|
+
type PropagatedAttributes = Parameters<LangfuseRuntime["propagateAttributes"]>[0];
|
|
493
|
+
|
|
494
|
+
const PROPAGATED_STRING_ATTRIBUTES = {
|
|
495
|
+
sessionId: "session.id",
|
|
496
|
+
userId: "user.id",
|
|
497
|
+
traceName: "langfuse.trace.name",
|
|
498
|
+
version: "langfuse.version",
|
|
499
|
+
} as const;
|
|
500
|
+
const OTEL_TRACE_TAGS_ATTRIBUTE = "langfuse.trace.tags";
|
|
501
|
+
const OTEL_TRACE_METADATA_PREFIX = "langfuse.trace.metadata.";
|
|
502
|
+
|
|
503
|
+
function readPropagatedAttributes(observation: any): PropagatedAttributes {
|
|
504
|
+
const attributes: Record<string, unknown> = observation?.otelSpan?.attributes ?? {};
|
|
505
|
+
const propagated: PropagatedAttributes = {};
|
|
506
|
+
|
|
507
|
+
for (const [key, attribute] of Object.entries(PROPAGATED_STRING_ATTRIBUTES)) {
|
|
508
|
+
const value = attributes[attribute];
|
|
509
|
+
if (typeof value === "string" && value) {
|
|
510
|
+
propagated[key as keyof typeof PROPAGATED_STRING_ATTRIBUTES] = value;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const tags = attributes[OTEL_TRACE_TAGS_ATTRIBUTE];
|
|
515
|
+
if (Array.isArray(tags)) {
|
|
516
|
+
const validTags = tags.filter((tag): tag is string => typeof tag === "string" && tag.length > 0);
|
|
517
|
+
if (validTags.length > 0) {
|
|
518
|
+
propagated.tags = validTags;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
481
521
|
|
|
482
|
-
|
|
483
|
-
const value
|
|
484
|
-
|
|
522
|
+
const metadata: Record<string, string> = {};
|
|
523
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
524
|
+
if (key.startsWith(OTEL_TRACE_METADATA_PREFIX) && typeof value === "string") {
|
|
525
|
+
metadata[key.slice(OTEL_TRACE_METADATA_PREFIX.length)] = value;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (Object.keys(metadata).length > 0) {
|
|
529
|
+
propagated.metadata = metadata;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return propagated;
|
|
485
533
|
}
|
|
486
534
|
|
|
487
535
|
function wrapObservation(
|
|
@@ -543,12 +591,19 @@ function wrapObservation(
|
|
|
543
591
|
return observation.end();
|
|
544
592
|
},
|
|
545
593
|
startObservation(childName: string, childBody?: Record<string, unknown>, options?: { asType?: string }) {
|
|
546
|
-
const
|
|
594
|
+
const inherited = readPropagatedAttributes(observation);
|
|
595
|
+
const fallbackSessionId = state.currentSessionId?.slice(0, 200);
|
|
596
|
+
if (!inherited.sessionId && fallbackSessionId) {
|
|
597
|
+
inherited.sessionId = fallbackSessionId;
|
|
598
|
+
}
|
|
547
599
|
const propagate = runtime?.propagateAttributes;
|
|
548
600
|
const createChild = () => observation.startObservation(childName, childBody, options);
|
|
549
|
-
const
|
|
550
|
-
?
|
|
551
|
-
: createChild
|
|
601
|
+
const createInheritingChild = propagate && Object.keys(inherited).length > 0
|
|
602
|
+
? () => propagate(inherited, createChild)
|
|
603
|
+
: createChild;
|
|
604
|
+
const child = runtime?.withRootContext
|
|
605
|
+
? runtime.withRootContext(createInheritingChild)
|
|
606
|
+
: createInheritingChild();
|
|
552
607
|
return wrapObservation(child, store, childName, childBody, options?.asType, id);
|
|
553
608
|
},
|
|
554
609
|
setTraceIO(traceBody?: { input?: unknown; output?: unknown }) {
|
|
@@ -777,7 +832,7 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
777
832
|
const [
|
|
778
833
|
{ BasicTracerProvider },
|
|
779
834
|
{ resources },
|
|
780
|
-
{ context },
|
|
835
|
+
{ context, ROOT_CONTEXT },
|
|
781
836
|
{ AsyncHooksContextManager },
|
|
782
837
|
{ LangfuseSpanProcessor },
|
|
783
838
|
tracing,
|
|
@@ -822,6 +877,7 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
822
877
|
return wrapObservation(observation, restFallback, name, body, options?.asType);
|
|
823
878
|
}) as unknown as LangfuseRuntime["startObservation"],
|
|
824
879
|
propagateAttributes: tracing.propagateAttributes as unknown as LangfuseRuntime["propagateAttributes"],
|
|
880
|
+
withRootContext: (fn) => context.with(ROOT_CONTEXT, fn),
|
|
825
881
|
scoreClient: new LangfuseClient({
|
|
826
882
|
publicKey: state.config.publicKey,
|
|
827
883
|
secretKey: state.config.secretKey,
|
package/src/types.ts
CHANGED
|
@@ -87,12 +87,20 @@ export interface LangfuseRuntime {
|
|
|
87
87
|
propagateAttributes: (
|
|
88
88
|
params: {
|
|
89
89
|
sessionId?: string;
|
|
90
|
+
userId?: string;
|
|
90
91
|
traceName?: string;
|
|
92
|
+
version?: string;
|
|
91
93
|
metadata?: Record<string, string>;
|
|
92
94
|
tags?: string[];
|
|
93
95
|
},
|
|
94
96
|
fn: () => LangfuseObservation,
|
|
95
97
|
) => LangfuseObservation;
|
|
98
|
+
/**
|
|
99
|
+
* Runs `fn` on the OTel root context, so the observations it creates take
|
|
100
|
+
* their propagated attributes only from what is passed explicitly and not
|
|
101
|
+
* from whatever the caller's ambient context happens to carry.
|
|
102
|
+
*/
|
|
103
|
+
withRootContext?: <T>(fn: () => T) => T;
|
|
96
104
|
scoreClient: LangfuseScoreClient;
|
|
97
105
|
spanProcessor?: { forceFlush?: () => Promise<void>; shutdown?: () => Promise<void> };
|
|
98
106
|
tracerProvider?: { forceFlush?: () => Promise<void>; shutdown?: () => Promise<void> };
|