From 41e6e12f1786b7f7bc165e56dc3e6f0b65319e5f Mon Sep 17 00:00:00 2001 From: Guglielmo Faglioni Date: Wed, 4 Mar 2026 23:11:14 +0100 Subject: [PATCH 1/3] feat(openai): expose observation and trace IDs from observeOpenAI After an observeOpenAI-wrapped call completes, the observation ID and trace ID are attached to the response object as non-enumerable properties: const response = await openai.chat.completions.create({ ... }); response.langfuseObservationId // the generation's observation ID response.langfuseTraceId // the parent trace ID A WithLangfuseIds utility type is exported for TypeScript consumers. This enables attaching scores to specific generations via the Langfuse API (e.g. user feedback) without having to replace observeOpenAI with manual startActiveObservation calls. Closes langfuse/langfuse#12389 Co-Authored-By: Claude Opus 4.6 --- packages/openai/src/index.ts | 1 + packages/openai/src/langfuseIds.ts | 41 ++++++++++++++++++++++++++++++ packages/openai/src/traceMethod.ts | 8 +++++- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/openai/src/langfuseIds.ts diff --git a/packages/openai/src/index.ts b/packages/openai/src/index.ts index 5b69e093c..1b67b1b6d 100644 --- a/packages/openai/src/index.ts +++ b/packages/openai/src/index.ts @@ -1,2 +1,3 @@ export { observeOpenAI } from "./observeOpenAI.js"; export * from "./types.js"; +export type { WithLangfuseIds } from "./langfuseIds.js"; diff --git a/packages/openai/src/langfuseIds.ts b/packages/openai/src/langfuseIds.ts new file mode 100644 index 000000000..d34173641 --- /dev/null +++ b/packages/openai/src/langfuseIds.ts @@ -0,0 +1,41 @@ +import type { LangfuseGeneration } from "@langfuse/tracing"; + +/** + * Extends a response object with Langfuse observation and trace IDs. + * + * @public + */ +export type WithLangfuseIds = T & { + /** The Langfuse observation ID for this generation */ + langfuseObservationId: string; + /** The Langfuse trace ID for this generation */ + langfuseTraceId: string; +}; + +/** + * Attaches Langfuse observation and trace IDs to a response object as non-enumerable properties. + * + * The properties are non-enumerable so they don't appear in JSON.stringify output + * or for-in loops, but are directly accessible on the response object. + * + * @internal + */ +export function attachLangfuseIds( + result: T, + generation: LangfuseGeneration, +): asserts result is T & WithLangfuseIds { + if (result && typeof result === "object") { + Object.defineProperty(result, "langfuseObservationId", { + value: generation.id, + enumerable: false, + writable: false, + configurable: false, + }); + Object.defineProperty(result, "langfuseTraceId", { + value: generation.traceId, + enumerable: false, + writable: false, + configurable: false, + }); + } +} diff --git a/packages/openai/src/traceMethod.ts b/packages/openai/src/traceMethod.ts index bafe35ca1..49e4ea7fb 100644 --- a/packages/openai/src/traceMethod.ts +++ b/packages/openai/src/traceMethod.ts @@ -5,6 +5,7 @@ import { } from "@langfuse/tracing"; import type OpenAI from "openai"; +import { attachLangfuseIds } from "./langfuseIds.js"; import { getToolCallOutput, parseChunk, @@ -129,6 +130,8 @@ const wrapMethod = ( }) .end(); + attachLangfuseIds(result, generation); + return result; }) .catch((err) => { @@ -258,5 +261,8 @@ function wrapAsyncIterable( .end(); } - return tracedOutputGenerator() as R; + const generator = tracedOutputGenerator(); + attachLangfuseIds(generator, generation); + + return generator as R; } From 8d0241230955edd7df58003bbf5dffc0c73d9e4e Mon Sep 17 00:00:00 2001 From: Guglielmo Faglioni Date: Wed, 4 Mar 2026 23:23:44 +0100 Subject: [PATCH 2/3] fix: guard Object.defineProperty against frozen/sealed objects Co-Authored-By: Claude Opus 4.6 --- packages/openai/src/langfuseIds.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/openai/src/langfuseIds.ts b/packages/openai/src/langfuseIds.ts index d34173641..60ca022fd 100644 --- a/packages/openai/src/langfuseIds.ts +++ b/packages/openai/src/langfuseIds.ts @@ -25,17 +25,23 @@ export function attachLangfuseIds( generation: LangfuseGeneration, ): asserts result is T & WithLangfuseIds { if (result && typeof result === "object") { - Object.defineProperty(result, "langfuseObservationId", { - value: generation.id, - enumerable: false, - writable: false, - configurable: false, - }); - Object.defineProperty(result, "langfuseTraceId", { - value: generation.traceId, - enumerable: false, - writable: false, - configurable: false, - }); + try { + Object.defineProperty(result, "langfuseObservationId", { + value: generation.id, + enumerable: false, + writable: false, + configurable: false, + }); + Object.defineProperty(result, "langfuseTraceId", { + value: generation.traceId, + enumerable: false, + writable: false, + configurable: false, + }); + } catch { + // In practice this never happens: OpenAI SDK responses are plain objects + // from JSON parsing (never frozen/sealed), and this function is only called + // once per response. Guard kept for defensive robustness. + } } } From e850d65a3b5cdc7f81bf8935801eb0cca5a6d687 Mon Sep 17 00:00:00 2001 From: Guglielmo Faglioni Date: Thu, 5 Mar 2026 01:43:23 +0100 Subject: [PATCH 3/3] feat(openai): augment OpenAI response types with Langfuse IDs Add module augmentation for ChatCompletion and Response interfaces so consumers can access langfuseObservationId/langfuseTraceId without type casts when using observeOpenAI. Co-Authored-By: Claude Opus 4.6 --- packages/openai/src/index.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/openai/src/index.ts b/packages/openai/src/index.ts index 1b67b1b6d..42304f5c8 100644 --- a/packages/openai/src/index.ts +++ b/packages/openai/src/index.ts @@ -1,3 +1,31 @@ export { observeOpenAI } from "./observeOpenAI.js"; export * from "./types.js"; export type { WithLangfuseIds } from "./langfuseIds.js"; + +/** + * Module augmentation for OpenAI SDK response types. + * + * When `observeOpenAI` wraps a client, all response objects are augmented + * with Langfuse observation and trace IDs at runtime. These declarations + * make TypeScript aware of those properties so consumers don't need casts. + * + * The properties are marked optional because the base OpenAI types are also + * used without `observeOpenAI`. + */ +declare module "openai/resources/chat/completions/completions" { + interface ChatCompletion { + /** Langfuse observation ID — present when the client is wrapped with `observeOpenAI` */ + langfuseObservationId?: string; + /** Langfuse trace ID — present when the client is wrapped with `observeOpenAI` */ + langfuseTraceId?: string; + } +} + +declare module "openai/resources/responses/responses" { + interface Response { + /** Langfuse observation ID — present when the client is wrapped with `observeOpenAI` */ + langfuseObservationId?: string; + /** Langfuse trace ID — present when the client is wrapped with `observeOpenAI` */ + langfuseTraceId?: string; + } +}