@introspection-sdk/introspection-node 0.20.0 → 0.21.1
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/README.md +28 -8
- package/dist/auth.d.ts +1 -1
- package/dist/client.d.ts +4 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -7
- package/dist/client.js.map +1 -1
- package/dist/dev-target.js +1 -1
- package/dist/index.d.ts +3 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/otel/index.d.ts +1 -14
- package/dist/otel/index.d.ts.map +1 -1
- package/dist/otel/index.js +10 -18
- package/dist/otel/index.js.map +1 -1
- package/dist/otel/init.d.ts +19 -21
- package/dist/otel/init.d.ts.map +1 -1
- package/dist/otel/init.js +62 -48
- package/dist/otel/init.js.map +1 -1
- package/dist/otel/integrations/base.d.ts +17 -37
- package/dist/otel/integrations/base.d.ts.map +1 -1
- package/dist/otel/integrations/base.js +6 -13
- package/dist/otel/integrations/base.js.map +1 -1
- package/dist/otel/integrations/index.d.ts +3 -4
- package/dist/otel/integrations/index.d.ts.map +1 -1
- package/dist/otel/integrations/index.js +6 -25
- package/dist/otel/integrations/index.js.map +1 -1
- package/dist/otel/integrations/pi.d.ts.map +1 -1
- package/dist/otel/integrations/pi.js +9 -1
- package/dist/otel/integrations/pi.js.map +1 -1
- package/dist/otel/logs.d.ts +41 -8
- package/dist/otel/logs.d.ts.map +1 -1
- package/dist/otel/logs.js +95 -66
- package/dist/otel/logs.js.map +1 -1
- package/dist/otel/pi.d.ts +18 -5
- package/dist/otel/pi.d.ts.map +1 -1
- package/dist/otel/pi.js +38 -9
- package/dist/otel/pi.js.map +1 -1
- package/dist/otel/register.d.ts +2 -2
- package/dist/otel/register.d.ts.map +1 -1
- package/dist/otel/register.js +7 -9
- package/dist/otel/register.js.map +1 -1
- package/dist/otel/setup.d.ts +2 -2
- package/dist/otel/setup.d.ts.map +1 -1
- package/dist/otel/setup.js +16 -6
- package/dist/otel/setup.js.map +1 -1
- package/dist/otel/span-processor.d.ts +18 -5
- package/dist/otel/span-processor.d.ts.map +1 -1
- package/dist/otel/span-processor.js +50 -65
- package/dist/otel/span-processor.js.map +1 -1
- package/dist/resources/experiments.d.ts +10 -5
- package/dist/resources/experiments.d.ts.map +1 -1
- package/dist/resources/experiments.js +9 -25
- package/dist/resources/experiments.js.map +1 -1
- package/dist/resources/recipes.d.ts +6 -6
- package/dist/resources/recipes.d.ts.map +1 -1
- package/dist/resources/recipes.js +5 -23
- package/dist/resources/recipes.js.map +1 -1
- package/dist/resources/runtimes.d.ts +2 -2
- package/dist/resources/runtimes.d.ts.map +1 -1
- package/dist/resources/runtimes.js +2 -2
- package/dist/resources/runtimes.js.map +1 -1
- package/dist/runner.d.ts +8 -4
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +26 -18
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +45 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +61 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +16 -58
- package/.turbo/turbo-build.log +0 -4
- package/dist/converters/claude.d.ts +0 -150
- package/dist/converters/claude.d.ts.map +0 -1
- package/dist/converters/claude.js +0 -211
- package/dist/converters/claude.js.map +0 -1
- package/dist/converters/gemini.d.ts +0 -90
- package/dist/converters/gemini.d.ts.map +0 -1
- package/dist/converters/gemini.js +0 -171
- package/dist/converters/gemini.js.map +0 -1
- package/dist/converters/index.d.ts +0 -9
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -9
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/openai.d.ts +0 -51
- package/dist/converters/openai.d.ts.map +0 -1
- package/dist/converters/openai.js +0 -269
- package/dist/converters/openai.js.map +0 -1
- package/dist/converters/openinference.d.ts +0 -107
- package/dist/converters/openinference.d.ts.map +0 -1
- package/dist/converters/openinference.js +0 -553
- package/dist/converters/openinference.js.map +0 -1
- package/dist/converters/vercel.d.ts +0 -21
- package/dist/converters/vercel.d.ts.map +0 -1
- package/dist/converters/vercel.js +0 -236
- package/dist/converters/vercel.js.map +0 -1
- package/dist/otel/anthropic.d.ts +0 -63
- package/dist/otel/anthropic.d.ts.map +0 -1
- package/dist/otel/anthropic.js +0 -475
- package/dist/otel/anthropic.js.map +0 -1
- package/dist/otel/claude-hooks.d.ts +0 -346
- package/dist/otel/claude-hooks.d.ts.map +0 -1
- package/dist/otel/claude-hooks.js +0 -647
- package/dist/otel/claude-hooks.js.map +0 -1
- package/dist/otel/claude-wrapper.d.ts +0 -93
- package/dist/otel/claude-wrapper.d.ts.map +0 -1
- package/dist/otel/claude-wrapper.js +0 -155
- package/dist/otel/claude-wrapper.js.map +0 -1
- package/dist/otel/gemini.d.ts +0 -56
- package/dist/otel/gemini.d.ts.map +0 -1
- package/dist/otel/gemini.js +0 -350
- package/dist/otel/gemini.js.map +0 -1
- package/dist/otel/integrations/anthropic.d.ts +0 -10
- package/dist/otel/integrations/anthropic.d.ts.map +0 -1
- package/dist/otel/integrations/anthropic.js +0 -26
- package/dist/otel/integrations/anthropic.js.map +0 -1
- package/dist/otel/integrations/claude-agent.d.ts +0 -12
- package/dist/otel/integrations/claude-agent.d.ts.map +0 -1
- package/dist/otel/integrations/claude-agent.js +0 -28
- package/dist/otel/integrations/claude-agent.js.map +0 -1
- package/dist/otel/integrations/gemini.d.ts +0 -11
- package/dist/otel/integrations/gemini.d.ts.map +0 -1
- package/dist/otel/integrations/gemini.js +0 -23
- package/dist/otel/integrations/gemini.js.map +0 -1
- package/dist/otel/integrations/langchain.d.ts +0 -15
- package/dist/otel/integrations/langchain.d.ts.map +0 -1
- package/dist/otel/integrations/langchain.js +0 -43
- package/dist/otel/integrations/langchain.js.map +0 -1
- package/dist/otel/integrations/mastra.d.ts +0 -14
- package/dist/otel/integrations/mastra.d.ts.map +0 -1
- package/dist/otel/integrations/mastra.js +0 -31
- package/dist/otel/integrations/mastra.js.map +0 -1
- package/dist/otel/integrations/openai-agents.d.ts +0 -11
- package/dist/otel/integrations/openai-agents.d.ts.map +0 -1
- package/dist/otel/integrations/openai-agents.js +0 -30
- package/dist/otel/integrations/openai-agents.js.map +0 -1
- package/dist/otel/integrations/vercel.d.ts +0 -14
- package/dist/otel/integrations/vercel.d.ts.map +0 -1
- package/dist/otel/integrations/vercel.js +0 -20
- package/dist/otel/integrations/vercel.js.map +0 -1
- package/dist/otel/langchain-handler.d.ts +0 -112
- package/dist/otel/langchain-handler.d.ts.map +0 -1
- package/dist/otel/langchain-handler.js +0 -561
- package/dist/otel/langchain-handler.js.map +0 -1
- package/dist/otel/mastra-exporter.d.ts +0 -87
- package/dist/otel/mastra-exporter.d.ts.map +0 -1
- package/dist/otel/mastra-exporter.js +0 -530
- package/dist/otel/mastra-exporter.js.map +0 -1
- package/dist/otel/openai.d.ts +0 -20
- package/dist/otel/openai.d.ts.map +0 -1
- package/dist/otel/openai.js +0 -55
- package/dist/otel/openai.js.map +0 -1
- package/dist/otel/tracing-processor.d.ts +0 -139
- package/dist/otel/tracing-processor.d.ts.map +0 -1
- package/dist/otel/tracing-processor.js +0 -434
- package/dist/otel/tracing-processor.js.map +0 -1
- package/eslint.config.mjs +0 -4
- package/src/auth.ts +0 -236
- package/src/client.ts +0 -163
- package/src/converters/claude.ts +0 -343
- package/src/converters/gemini.ts +0 -243
- package/src/converters/index.ts +0 -50
- package/src/converters/openai.ts +0 -300
- package/src/converters/openinference.ts +0 -665
- package/src/converters/vercel.ts +0 -302
- package/src/dev-target.ts +0 -79
- package/src/http.ts +0 -43
- package/src/index.ts +0 -277
- package/src/otel/anthropic.ts +0 -619
- package/src/otel/claude-hooks.ts +0 -1116
- package/src/otel/claude-wrapper.ts +0 -239
- package/src/otel/gemini.ts +0 -483
- package/src/otel/index.ts +0 -128
- package/src/otel/init.ts +0 -411
- package/src/otel/integrations/anthropic.ts +0 -36
- package/src/otel/integrations/base.ts +0 -158
- package/src/otel/integrations/claude-agent.ts +0 -36
- package/src/otel/integrations/gemini.ts +0 -30
- package/src/otel/integrations/index.ts +0 -152
- package/src/otel/integrations/langchain.ts +0 -60
- package/src/otel/integrations/mastra.ts +0 -48
- package/src/otel/integrations/openai-agents.ts +0 -47
- package/src/otel/integrations/pi.ts +0 -28
- package/src/otel/integrations/vercel.ts +0 -26
- package/src/otel/langchain-handler.ts +0 -804
- package/src/otel/logs.ts +0 -395
- package/src/otel/mastra-exporter.ts +0 -714
- package/src/otel/openai.ts +0 -93
- package/src/otel/pi.ts +0 -94
- package/src/otel/register.ts +0 -50
- package/src/otel/setup.ts +0 -215
- package/src/otel/span-processor.ts +0 -512
- package/src/otel/tracing-processor.ts +0 -637
- package/src/pagination.ts +0 -10
- package/src/proxy-from-env.d.ts +0 -7
- package/src/resources/experiments.ts +0 -176
- package/src/resources/recipes.ts +0 -74
- package/src/resources/runtimes.ts +0 -31
- package/src/runner.ts +0 -213
- package/src/types.ts +0 -13
- package/src/utils.ts +0 -95
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -8
|
@@ -1,804 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LangChain/LangGraph Callback Handler for Introspection.
|
|
3
|
-
*
|
|
4
|
-
* Hooks into LangChain's native callback system to capture LLM, tool, and
|
|
5
|
-
* chain interactions as gen_ai.* OTel spans for the Introspection backend.
|
|
6
|
-
*
|
|
7
|
-
* Similar to IntrospectionTracingProcessor for OpenAI Agents SDK and
|
|
8
|
-
* IntrospectionClaudeHooks for Claude Agent SDK.
|
|
9
|
-
*
|
|
10
|
-
* **Important:** This module requires `@langchain/core` at runtime.
|
|
11
|
-
* Import via the subpath: `@introspection-sdk/introspection-node/langchain`
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* import { IntrospectionCallbackHandler } from "@introspection-sdk/introspection-node/langchain";
|
|
16
|
-
* import { ChatOpenAI } from "@langchain/openai";
|
|
17
|
-
*
|
|
18
|
-
* const handler = new IntrospectionCallbackHandler({
|
|
19
|
-
* serviceName: "my-langchain-app",
|
|
20
|
-
* });
|
|
21
|
-
*
|
|
22
|
-
* const model = new ChatOpenAI({ modelName: "gpt-4o" });
|
|
23
|
-
* const response = await model.invoke("Hello!", { callbacks: [handler] });
|
|
24
|
-
*
|
|
25
|
-
* await handler.shutdown();
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
30
|
-
import type { Serialized } from "@langchain/core/load/serializable";
|
|
31
|
-
import type { LLMResult } from "@langchain/core/outputs";
|
|
32
|
-
import type { BaseMessage } from "@langchain/core/messages";
|
|
33
|
-
import type { ChainValues } from "@langchain/core/utils/types";
|
|
34
|
-
|
|
35
|
-
import {
|
|
36
|
-
context as otelContext,
|
|
37
|
-
propagation,
|
|
38
|
-
ROOT_CONTEXT,
|
|
39
|
-
SpanStatusCode,
|
|
40
|
-
trace as otelTrace,
|
|
41
|
-
type Span as OtelSpan,
|
|
42
|
-
} from "@opentelemetry/api";
|
|
43
|
-
import {
|
|
44
|
-
BasicTracerProvider,
|
|
45
|
-
BatchSpanProcessor,
|
|
46
|
-
SimpleSpanProcessor,
|
|
47
|
-
type SpanExporter,
|
|
48
|
-
type IdGenerator,
|
|
49
|
-
} from "@opentelemetry/sdk-trace-base";
|
|
50
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
51
|
-
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
52
|
-
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
53
|
-
import { randomUUID } from "crypto";
|
|
54
|
-
|
|
55
|
-
import { logger, withOtlpHttpsProxy } from "../utils.js";
|
|
56
|
-
import { VERSION } from "../version.js";
|
|
57
|
-
import type {
|
|
58
|
-
InputMessage,
|
|
59
|
-
OutputMessage,
|
|
60
|
-
MessagePart,
|
|
61
|
-
} from "@introspection-sdk/types";
|
|
62
|
-
import { providerCostAttributes } from "@introspection-sdk/types";
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Configuration
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
/** Advanced options for testing and customization. */
|
|
69
|
-
export interface LangChainHandlerAdvancedOptions {
|
|
70
|
-
/** Custom span exporter for testing. */
|
|
71
|
-
spanExporter?: SpanExporter;
|
|
72
|
-
/** Custom ID generator for testing. */
|
|
73
|
-
idGenerator?: IdGenerator;
|
|
74
|
-
/** Use SimpleSpanProcessor instead of BatchSpanProcessor. */
|
|
75
|
-
useSimpleSpanProcessor?: boolean;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Configuration for {@link IntrospectionCallbackHandler}. */
|
|
79
|
-
export interface IntrospectionCallbackHandlerOptions {
|
|
80
|
-
/** Authentication token (env: INTROSPECTION_TOKEN). */
|
|
81
|
-
token?: string;
|
|
82
|
-
/** Base URL for the API (env: INTROSPECTION_BASE_OTEL_URL). */
|
|
83
|
-
baseUrl?: string;
|
|
84
|
-
/** Service name for telemetry (env: INTROSPECTION_SERVICE_NAME). */
|
|
85
|
-
serviceName?: string;
|
|
86
|
-
/** Additional headers to include in requests. */
|
|
87
|
-
additionalHeaders?: Record<string, string>;
|
|
88
|
-
/** Advanced options for testing. */
|
|
89
|
-
advanced?: LangChainHandlerAdvancedOptions;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// Handler
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* LangChain callback handler that captures LLM, tool, and chain events
|
|
98
|
-
* as gen_ai.* OTel spans and exports them to the Introspection backend.
|
|
99
|
-
*
|
|
100
|
-
* Pass as a callback to any LangChain invoke call, or set globally via
|
|
101
|
-
* `setGlobalHandler()`.
|
|
102
|
-
*/
|
|
103
|
-
export class IntrospectionCallbackHandler extends BaseCallbackHandler {
|
|
104
|
-
name = "IntrospectionCallbackHandler";
|
|
105
|
-
|
|
106
|
-
private _tracerProvider: BasicTracerProvider;
|
|
107
|
-
private _tracer: ReturnType<BasicTracerProvider["getTracer"]>;
|
|
108
|
-
private _spans: Map<string, OtelSpan> = new Map();
|
|
109
|
-
private _conversationId: string;
|
|
110
|
-
// Track span names and parents so children can resolve gen_ai.agent.name
|
|
111
|
-
private _spanNames: Map<string, string> = new Map();
|
|
112
|
-
private _spanParents: Map<string, string> = new Map();
|
|
113
|
-
private _runRoots: Map<string, string> = new Map();
|
|
114
|
-
// LangChain wrapper names to skip when resolving agent names
|
|
115
|
-
private static _wrapperNames = new Set([
|
|
116
|
-
"RunnableSequence",
|
|
117
|
-
"RunnableParallel",
|
|
118
|
-
"RunnableMap",
|
|
119
|
-
"RunnableLambda",
|
|
120
|
-
"RunnableRetry",
|
|
121
|
-
"_ConfigurableModel",
|
|
122
|
-
"ChatOpenAI",
|
|
123
|
-
"ChatAnthropic",
|
|
124
|
-
"ChatGoogleGenerativeAI",
|
|
125
|
-
"ChatGroq",
|
|
126
|
-
]);
|
|
127
|
-
// Store LLM input messages per runId for use in handleLLMEnd
|
|
128
|
-
private _llmInputs: Map<string, InputMessage[]> = new Map();
|
|
129
|
-
|
|
130
|
-
constructor(options: IntrospectionCallbackHandlerOptions = {}) {
|
|
131
|
-
super();
|
|
132
|
-
|
|
133
|
-
const advanced = options.advanced;
|
|
134
|
-
const serviceName =
|
|
135
|
-
options.serviceName || process.env.INTROSPECTION_SERVICE_NAME;
|
|
136
|
-
const resource = serviceName
|
|
137
|
-
? resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName })
|
|
138
|
-
: undefined;
|
|
139
|
-
|
|
140
|
-
if (!advanced?.spanExporter) {
|
|
141
|
-
const token = options.token || process.env.INTROSPECTION_TOKEN;
|
|
142
|
-
if (!token) {
|
|
143
|
-
throw new Error("INTROSPECTION_TOKEN is required");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const baseUrl =
|
|
147
|
-
options.baseUrl ||
|
|
148
|
-
process.env.INTROSPECTION_BASE_OTEL_URL ||
|
|
149
|
-
"https://otel.introspection.dev";
|
|
150
|
-
|
|
151
|
-
const endpoint = baseUrl.endsWith("/v1/traces")
|
|
152
|
-
? baseUrl
|
|
153
|
-
: `${baseUrl.replace(/\/$/, "")}/v1/traces`;
|
|
154
|
-
|
|
155
|
-
const headers: Record<string, string> = {
|
|
156
|
-
"User-Agent": `introspection-sdk/${VERSION}`,
|
|
157
|
-
Authorization: `Bearer ${token}`,
|
|
158
|
-
...options.additionalHeaders,
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
logger.info(
|
|
162
|
-
`IntrospectionCallbackHandler initialized: endpoint=${endpoint}`,
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const spanExporter = new OTLPTraceExporter(
|
|
166
|
-
withOtlpHttpsProxy({ url: endpoint, headers }),
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const effectiveBatchSize =
|
|
170
|
-
token.startsWith("intro_dev") || token.startsWith("intro_staging")
|
|
171
|
-
? 1
|
|
172
|
-
: undefined;
|
|
173
|
-
const useSimple =
|
|
174
|
-
advanced?.useSimpleSpanProcessor || effectiveBatchSize === 1;
|
|
175
|
-
|
|
176
|
-
const spanProcessor = useSimple
|
|
177
|
-
? new SimpleSpanProcessor(spanExporter)
|
|
178
|
-
: new BatchSpanProcessor(spanExporter, {
|
|
179
|
-
scheduledDelayMillis: 1000,
|
|
180
|
-
...(effectiveBatchSize
|
|
181
|
-
? { maxExportBatchSize: effectiveBatchSize }
|
|
182
|
-
: {}),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
186
|
-
resource,
|
|
187
|
-
idGenerator: advanced?.idGenerator,
|
|
188
|
-
spanProcessors: [spanProcessor],
|
|
189
|
-
});
|
|
190
|
-
} else {
|
|
191
|
-
logger.info(
|
|
192
|
-
"IntrospectionCallbackHandler initialized in test mode with custom exporter",
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
const spanProcessor = advanced.useSimpleSpanProcessor
|
|
196
|
-
? new SimpleSpanProcessor(advanced.spanExporter)
|
|
197
|
-
: new BatchSpanProcessor(advanced.spanExporter, {
|
|
198
|
-
scheduledDelayMillis: 100,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
202
|
-
resource,
|
|
203
|
-
idGenerator: advanced.idGenerator,
|
|
204
|
-
spanProcessors: [spanProcessor],
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
this._tracer = this._tracerProvider.getTracer("langchain", VERSION);
|
|
209
|
-
this._conversationId = `intro_conv_${randomUUID().replace(/-/g, "")}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private _rootRunId(runId: string, parentRunId?: string): string {
|
|
213
|
-
if (!parentRunId) return runId;
|
|
214
|
-
return this._runRoots.get(parentRunId) || parentRunId;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Keep the top-level run span aligned with child conversation IDs. */
|
|
218
|
-
private _setRootConversationId(
|
|
219
|
-
conversationId: string,
|
|
220
|
-
runId: string,
|
|
221
|
-
parentRunId?: string,
|
|
222
|
-
): void {
|
|
223
|
-
const root = this._spans.get(this._rootRunId(runId, parentRunId));
|
|
224
|
-
root?.setAttribute("gen_ai.conversation.id", conversationId);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private _endRootIfComplete(runId: string): void {
|
|
228
|
-
this._runRoots.delete(runId);
|
|
229
|
-
this._spanNames.delete(runId);
|
|
230
|
-
this._spanParents.delete(runId);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** Create a span under a LangChain parent when one exists.
|
|
234
|
-
* Sets gen_ai.agent.name to the parent span's name for hierarchy. */
|
|
235
|
-
private _createChildSpan(
|
|
236
|
-
name: string,
|
|
237
|
-
runId: string,
|
|
238
|
-
parentRunId?: string,
|
|
239
|
-
): OtelSpan {
|
|
240
|
-
const parent = parentRunId ? this._spans.get(parentRunId) : undefined;
|
|
241
|
-
this._runRoots.set(runId, this._rootRunId(runId, parentRunId));
|
|
242
|
-
const ctx = parent
|
|
243
|
-
? otelTrace.setSpan(otelContext.active(), parent)
|
|
244
|
-
: ROOT_CONTEXT;
|
|
245
|
-
const span = this._tracer.startSpan(name, {}, ctx);
|
|
246
|
-
this._spanNames.set(runId, name);
|
|
247
|
-
if (parentRunId) {
|
|
248
|
-
this._spanParents.set(runId, parentRunId);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Baggage wins so per-call agent identity (e.g. via withAgent())
|
|
252
|
-
// overrides the parent-span-name walk used as a default.
|
|
253
|
-
const baggageAgentName = this._readBaggage("gen_ai.agent.name");
|
|
254
|
-
const agentName = baggageAgentName || this._findAgentName(parentRunId);
|
|
255
|
-
if (agentName) {
|
|
256
|
-
span.setAttribute("gen_ai.agent.name", agentName);
|
|
257
|
-
}
|
|
258
|
-
const baggageAgentId = this._readBaggage("gen_ai.agent.id");
|
|
259
|
-
if (baggageAgentId) {
|
|
260
|
-
span.setAttribute("gen_ai.agent.id", baggageAgentId);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return span;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// -------------------------------------------------------------------------
|
|
267
|
-
// Chat model callbacks (preferred over LLM callbacks for chat models)
|
|
268
|
-
// -------------------------------------------------------------------------
|
|
269
|
-
|
|
270
|
-
handleChatModelStart(
|
|
271
|
-
llm: Serialized,
|
|
272
|
-
messages: BaseMessage[][],
|
|
273
|
-
runId: string,
|
|
274
|
-
parentRunId?: string,
|
|
275
|
-
extraParams?: Record<string, unknown>,
|
|
276
|
-
_tags?: string[],
|
|
277
|
-
metadata?: Record<string, unknown>,
|
|
278
|
-
runName?: string,
|
|
279
|
-
): void {
|
|
280
|
-
const modelName = this._extractModelName(llm, extraParams);
|
|
281
|
-
const spanName = modelName ? `chat ${modelName}` : runName || "chat";
|
|
282
|
-
|
|
283
|
-
const otelSpan = this._createChildSpan(spanName, runId, parentRunId);
|
|
284
|
-
this._spans.set(runId, otelSpan);
|
|
285
|
-
|
|
286
|
-
const conversationId = this._getConversationId(metadata);
|
|
287
|
-
this._setRootConversationId(conversationId, runId, parentRunId);
|
|
288
|
-
|
|
289
|
-
otelSpan.setAttribute("gen_ai.operation.name", "chat");
|
|
290
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
291
|
-
otelSpan.setAttribute("openinference.span.kind", "LLM");
|
|
292
|
-
|
|
293
|
-
if (modelName) {
|
|
294
|
-
otelSpan.setAttribute("gen_ai.request.model", modelName);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const flatMessages = messages[0] || [];
|
|
298
|
-
const { inputMessages, systemInstructions } =
|
|
299
|
-
this._convertMessages(flatMessages);
|
|
300
|
-
|
|
301
|
-
if (inputMessages.length > 0) {
|
|
302
|
-
otelSpan.setAttribute(
|
|
303
|
-
"gen_ai.input.messages",
|
|
304
|
-
JSON.stringify(inputMessages),
|
|
305
|
-
);
|
|
306
|
-
this._llmInputs.set(runId, inputMessages);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (systemInstructions.length > 0) {
|
|
310
|
-
otelSpan.setAttribute(
|
|
311
|
-
"gen_ai.system_instructions",
|
|
312
|
-
JSON.stringify(
|
|
313
|
-
systemInstructions.map((s) => ({ type: "text", content: s })),
|
|
314
|
-
),
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const provider = this._extractProvider(llm);
|
|
319
|
-
if (provider) {
|
|
320
|
-
otelSpan.setAttribute("gen_ai.provider.name", provider);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const invocationParams = extraParams?.["invocation_params"] as
|
|
324
|
-
Record<string, unknown> | undefined;
|
|
325
|
-
|
|
326
|
-
const tools = invocationParams?.["tools"] as
|
|
327
|
-
Array<Record<string, unknown>> | undefined;
|
|
328
|
-
if (tools && tools.length > 0) {
|
|
329
|
-
otelSpan.setAttribute(
|
|
330
|
-
"gen_ai.tool.definitions",
|
|
331
|
-
JSON.stringify(
|
|
332
|
-
tools.map((t) => {
|
|
333
|
-
const fn = (t.function || t) as Record<string, unknown>;
|
|
334
|
-
return {
|
|
335
|
-
type: (t.type as string) || "function",
|
|
336
|
-
name: (fn.name || "") as string,
|
|
337
|
-
description: (fn.description || "") as string,
|
|
338
|
-
parameters: fn.parameters,
|
|
339
|
-
};
|
|
340
|
-
}),
|
|
341
|
-
),
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (invocationParams?.["temperature"] != null) {
|
|
346
|
-
otelSpan.setAttribute(
|
|
347
|
-
"gen_ai.request.temperature",
|
|
348
|
-
invocationParams["temperature"] as number,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
handleLLMStart(
|
|
354
|
-
llm: Serialized,
|
|
355
|
-
prompts: string[],
|
|
356
|
-
runId: string,
|
|
357
|
-
parentRunId?: string,
|
|
358
|
-
extraParams?: Record<string, unknown>,
|
|
359
|
-
_tags?: string[],
|
|
360
|
-
metadata?: Record<string, unknown>,
|
|
361
|
-
runName?: string,
|
|
362
|
-
): void {
|
|
363
|
-
const modelName = this._extractModelName(llm, extraParams);
|
|
364
|
-
const spanName = modelName ? `chat ${modelName}` : runName || "llm";
|
|
365
|
-
|
|
366
|
-
const otelSpan = this._createChildSpan(spanName, runId, parentRunId);
|
|
367
|
-
this._spans.set(runId, otelSpan);
|
|
368
|
-
|
|
369
|
-
const conversationId = this._getConversationId(metadata);
|
|
370
|
-
this._setRootConversationId(conversationId, runId, parentRunId);
|
|
371
|
-
|
|
372
|
-
otelSpan.setAttribute("gen_ai.operation.name", "chat");
|
|
373
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
374
|
-
otelSpan.setAttribute("openinference.span.kind", "LLM");
|
|
375
|
-
|
|
376
|
-
if (modelName) {
|
|
377
|
-
otelSpan.setAttribute("gen_ai.request.model", modelName);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (prompts.length > 0) {
|
|
381
|
-
const inputMessages: InputMessage[] = prompts.map((p) => ({
|
|
382
|
-
role: "user" as const,
|
|
383
|
-
parts: [{ type: "text" as const, content: p }],
|
|
384
|
-
}));
|
|
385
|
-
otelSpan.setAttribute(
|
|
386
|
-
"gen_ai.input.messages",
|
|
387
|
-
JSON.stringify(inputMessages),
|
|
388
|
-
);
|
|
389
|
-
this._llmInputs.set(runId, inputMessages);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
handleLLMEnd(output: LLMResult, runId: string): void {
|
|
394
|
-
const otelSpan = this._spans.get(runId);
|
|
395
|
-
if (!otelSpan) return;
|
|
396
|
-
|
|
397
|
-
this._spans.delete(runId);
|
|
398
|
-
this._spanNames.delete(runId);
|
|
399
|
-
this._llmInputs.delete(runId);
|
|
400
|
-
this._endRootIfComplete(runId);
|
|
401
|
-
|
|
402
|
-
const generations = output.generations?.[0];
|
|
403
|
-
if (generations && generations.length > 0) {
|
|
404
|
-
const parts: MessagePart[] = [];
|
|
405
|
-
for (const gen of generations) {
|
|
406
|
-
if (gen.text) {
|
|
407
|
-
parts.push({ type: "text", content: gen.text });
|
|
408
|
-
}
|
|
409
|
-
const msg = (gen as unknown as Record<string, unknown>).message as
|
|
410
|
-
Record<string, unknown> | undefined;
|
|
411
|
-
const kwargs = (msg?.["kwargs"] || msg) as
|
|
412
|
-
Record<string, unknown> | undefined;
|
|
413
|
-
const additionalKwargs = kwargs?.["additional_kwargs"] as
|
|
414
|
-
Record<string, unknown> | undefined;
|
|
415
|
-
const toolCalls = (additionalKwargs?.["tool_calls"] ||
|
|
416
|
-
kwargs?.["tool_calls"]) as Array<Record<string, unknown>> | undefined;
|
|
417
|
-
if (toolCalls) {
|
|
418
|
-
for (const tc of toolCalls) {
|
|
419
|
-
const fn = (tc.function || tc) as Record<string, unknown>;
|
|
420
|
-
parts.push({
|
|
421
|
-
type: "tool_call",
|
|
422
|
-
name: (fn.name || tc.name || "") as string,
|
|
423
|
-
id: (tc.id || "") as string,
|
|
424
|
-
arguments: (fn.arguments || tc.args || "") as string,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (parts.length > 0) {
|
|
431
|
-
const outputMessages: OutputMessage[] = [{ role: "assistant", parts }];
|
|
432
|
-
otelSpan.setAttribute(
|
|
433
|
-
"gen_ai.output.messages",
|
|
434
|
-
JSON.stringify(outputMessages),
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const usage = output.llmOutput as Record<string, unknown> | undefined;
|
|
440
|
-
const tokenUsage = (usage?.["tokenUsage"] ||
|
|
441
|
-
usage?.["token_usage"] ||
|
|
442
|
-
usage?.["usage"]) as Record<string, unknown> | undefined;
|
|
443
|
-
if (tokenUsage) {
|
|
444
|
-
const inputTokens =
|
|
445
|
-
tokenUsage["promptTokens"] ||
|
|
446
|
-
tokenUsage["prompt_tokens"] ||
|
|
447
|
-
tokenUsage["input_tokens"];
|
|
448
|
-
const outputTokens =
|
|
449
|
-
tokenUsage["completionTokens"] ||
|
|
450
|
-
tokenUsage["completion_tokens"] ||
|
|
451
|
-
tokenUsage["output_tokens"];
|
|
452
|
-
if (typeof inputTokens === "number") {
|
|
453
|
-
otelSpan.setAttribute("gen_ai.usage.input_tokens", inputTokens);
|
|
454
|
-
}
|
|
455
|
-
if (typeof outputTokens === "number") {
|
|
456
|
-
otelSpan.setAttribute("gen_ai.usage.output_tokens", outputTokens);
|
|
457
|
-
}
|
|
458
|
-
// Flat field (legacy format) or nested cache_creation object (Claude 4+).
|
|
459
|
-
const cacheCreationObj = tokenUsage["cache_creation"] as
|
|
460
|
-
Record<string, unknown> | undefined;
|
|
461
|
-
const cacheWriteTokens =
|
|
462
|
-
tokenUsage["cache_creation_input_tokens"] ||
|
|
463
|
-
tokenUsage["cacheCreationInputTokens"] ||
|
|
464
|
-
((cacheCreationObj?.["ephemeral_5m_input_tokens"] as number) || 0) +
|
|
465
|
-
((cacheCreationObj?.["ephemeral_1h_input_tokens"] as number) || 0) ||
|
|
466
|
-
undefined;
|
|
467
|
-
if (typeof cacheWriteTokens === "number") {
|
|
468
|
-
otelSpan.setAttribute(
|
|
469
|
-
"gen_ai.usage.cache_creation.input_tokens",
|
|
470
|
-
cacheWriteTokens,
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
const cacheReadTokens =
|
|
474
|
-
tokenUsage["cache_read_input_tokens"] ||
|
|
475
|
-
tokenUsage["cacheReadInputTokens"];
|
|
476
|
-
if (typeof cacheReadTokens === "number") {
|
|
477
|
-
otelSpan.setAttribute(
|
|
478
|
-
"gen_ai.usage.cache_read.input_tokens",
|
|
479
|
-
cacheReadTokens,
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
// Provider-reported cost (e.g. OpenRouter usage.cost) when present.
|
|
483
|
-
otelSpan.setAttributes(providerCostAttributes(tokenUsage));
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const model = usage?.["model"] || usage?.["model_name"];
|
|
487
|
-
if (typeof model === "string") {
|
|
488
|
-
otelSpan.setAttribute("gen_ai.response.model", model);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// gen_ai.response.id — required by the server for conversation tracking.
|
|
492
|
-
const responseId = usage?.["id"] || usage?.["system_fingerprint"];
|
|
493
|
-
otelSpan.setAttribute(
|
|
494
|
-
"gen_ai.response.id",
|
|
495
|
-
typeof responseId === "string" ? responseId : `langchain-${runId}`,
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
otelSpan.end();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
handleLLMError(err: Error, runId: string): void {
|
|
502
|
-
const otelSpan = this._spans.get(runId);
|
|
503
|
-
if (!otelSpan) return;
|
|
504
|
-
this._spans.delete(runId);
|
|
505
|
-
this._spanNames.delete(runId);
|
|
506
|
-
this._llmInputs.delete(runId);
|
|
507
|
-
this._endRootIfComplete(runId);
|
|
508
|
-
otelSpan.recordException(err);
|
|
509
|
-
otelSpan.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
510
|
-
otelSpan.setAttribute("exception.message", err.message);
|
|
511
|
-
otelSpan.end();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// -------------------------------------------------------------------------
|
|
515
|
-
// Chain callbacks
|
|
516
|
-
// -------------------------------------------------------------------------
|
|
517
|
-
|
|
518
|
-
handleChainStart(
|
|
519
|
-
chain: Serialized,
|
|
520
|
-
_inputs: ChainValues,
|
|
521
|
-
runId: string,
|
|
522
|
-
_runType?: string,
|
|
523
|
-
_tags?: string[],
|
|
524
|
-
metadata?: Record<string, unknown>,
|
|
525
|
-
runName?: string,
|
|
526
|
-
parentRunId?: string,
|
|
527
|
-
): void {
|
|
528
|
-
const name =
|
|
529
|
-
runName || chain?.name || chain?.id?.[chain.id.length - 1] || "chain";
|
|
530
|
-
|
|
531
|
-
const otelSpan = this._createChildSpan(name, runId, parentRunId);
|
|
532
|
-
this._spans.set(runId, otelSpan);
|
|
533
|
-
|
|
534
|
-
const conversationId = this._getConversationId(metadata);
|
|
535
|
-
this._setRootConversationId(conversationId, runId, parentRunId);
|
|
536
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
handleChainEnd(_outputs: ChainValues, runId: string): void {
|
|
540
|
-
const otelSpan = this._spans.get(runId);
|
|
541
|
-
if (!otelSpan) return;
|
|
542
|
-
this._spans.delete(runId);
|
|
543
|
-
this._endRootIfComplete(runId);
|
|
544
|
-
otelSpan.end();
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
handleChainError(err: Error, runId: string): void {
|
|
548
|
-
const otelSpan = this._spans.get(runId);
|
|
549
|
-
if (!otelSpan) return;
|
|
550
|
-
this._spans.delete(runId);
|
|
551
|
-
this._endRootIfComplete(runId);
|
|
552
|
-
otelSpan.recordException(err);
|
|
553
|
-
otelSpan.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
554
|
-
otelSpan.setAttribute("exception.message", err.message);
|
|
555
|
-
otelSpan.end();
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// -------------------------------------------------------------------------
|
|
559
|
-
// Tool callbacks
|
|
560
|
-
// -------------------------------------------------------------------------
|
|
561
|
-
|
|
562
|
-
handleToolStart(
|
|
563
|
-
tool: Serialized,
|
|
564
|
-
input: string,
|
|
565
|
-
runId: string,
|
|
566
|
-
parentRunId?: string,
|
|
567
|
-
_tags?: string[],
|
|
568
|
-
metadata?: Record<string, unknown>,
|
|
569
|
-
runName?: string,
|
|
570
|
-
): void {
|
|
571
|
-
const toolName =
|
|
572
|
-
runName || tool?.name || tool?.id?.[tool.id.length - 1] || "tool";
|
|
573
|
-
|
|
574
|
-
const otelSpan = this._createChildSpan(
|
|
575
|
-
`execute_tool ${toolName}`,
|
|
576
|
-
runId,
|
|
577
|
-
parentRunId,
|
|
578
|
-
);
|
|
579
|
-
this._spans.set(runId, otelSpan);
|
|
580
|
-
|
|
581
|
-
const conversationId = this._getConversationId(metadata);
|
|
582
|
-
this._setRootConversationId(conversationId, runId, parentRunId);
|
|
583
|
-
|
|
584
|
-
otelSpan.setAttribute("gen_ai.operation.name", "execute_tool");
|
|
585
|
-
otelSpan.setAttribute("gen_ai.tool.name", toolName);
|
|
586
|
-
otelSpan.setAttribute("gen_ai.tool.type", "function");
|
|
587
|
-
otelSpan.setAttribute("gen_ai.tool.call.id", runId);
|
|
588
|
-
otelSpan.setAttribute("openinference.span.kind", "TOOL");
|
|
589
|
-
otelSpan.setAttribute("gen_ai.tool.call.arguments", input);
|
|
590
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
handleToolEnd(output: unknown, runId: string): void {
|
|
594
|
-
const otelSpan = this._spans.get(runId);
|
|
595
|
-
if (!otelSpan) return;
|
|
596
|
-
this._spans.delete(runId);
|
|
597
|
-
this._endRootIfComplete(runId);
|
|
598
|
-
|
|
599
|
-
if (output != null) {
|
|
600
|
-
otelSpan.setAttribute(
|
|
601
|
-
"gen_ai.tool.call.result",
|
|
602
|
-
typeof output === "string" ? output : JSON.stringify(output),
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
otelSpan.end();
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
handleToolError(err: Error, runId: string): void {
|
|
610
|
-
const otelSpan = this._spans.get(runId);
|
|
611
|
-
if (!otelSpan) return;
|
|
612
|
-
this._spans.delete(runId);
|
|
613
|
-
this._endRootIfComplete(runId);
|
|
614
|
-
otelSpan.recordException(err);
|
|
615
|
-
otelSpan.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
616
|
-
otelSpan.setAttribute("exception.message", err.message);
|
|
617
|
-
otelSpan.end();
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// -------------------------------------------------------------------------
|
|
621
|
-
// Lifecycle
|
|
622
|
-
// -------------------------------------------------------------------------
|
|
623
|
-
|
|
624
|
-
async shutdown(): Promise<void> {
|
|
625
|
-
for (const span of this._spans.values()) span.end();
|
|
626
|
-
this._spans.clear();
|
|
627
|
-
this._spanNames.clear();
|
|
628
|
-
this._spanParents.clear();
|
|
629
|
-
this._runRoots.clear();
|
|
630
|
-
await this._tracerProvider.shutdown();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
async forceFlush(): Promise<void> {
|
|
634
|
-
await this._tracerProvider.forceFlush();
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// -------------------------------------------------------------------------
|
|
638
|
-
// Helpers
|
|
639
|
-
// -------------------------------------------------------------------------
|
|
640
|
-
|
|
641
|
-
private _extractModelName(
|
|
642
|
-
llm: Serialized,
|
|
643
|
-
extraParams?: Record<string, unknown>,
|
|
644
|
-
): string | undefined {
|
|
645
|
-
const invocationParams = extraParams?.["invocation_params"] as
|
|
646
|
-
Record<string, unknown> | undefined;
|
|
647
|
-
if (invocationParams) {
|
|
648
|
-
const model =
|
|
649
|
-
invocationParams["model"] ||
|
|
650
|
-
invocationParams["model_name"] ||
|
|
651
|
-
invocationParams["modelName"];
|
|
652
|
-
if (typeof model === "string") return model;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const kwargs = (llm as unknown as Record<string, unknown>)?.kwargs as
|
|
656
|
-
Record<string, unknown> | undefined;
|
|
657
|
-
if (kwargs) {
|
|
658
|
-
const model =
|
|
659
|
-
kwargs["model"] || kwargs["model_name"] || kwargs["modelName"];
|
|
660
|
-
if (typeof model === "string") return model;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
return undefined;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
private _extractProvider(llm: Serialized): string | undefined {
|
|
667
|
-
const id = llm?.id;
|
|
668
|
-
if (Array.isArray(id) && id.length > 0) {
|
|
669
|
-
return id[id.length - 1] as string;
|
|
670
|
-
}
|
|
671
|
-
return undefined;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/** Walk up the span tree to find the first non-wrapper span name. */
|
|
675
|
-
private _findAgentName(runId?: string): string | undefined {
|
|
676
|
-
let current = runId;
|
|
677
|
-
for (let i = 0; i < 20 && current; i++) {
|
|
678
|
-
const name = this._spanNames.get(current);
|
|
679
|
-
if (name && !IntrospectionCallbackHandler._wrapperNames.has(name)) {
|
|
680
|
-
return name;
|
|
681
|
-
}
|
|
682
|
-
current = this._spanParents.get(current);
|
|
683
|
-
}
|
|
684
|
-
return undefined;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
private _getConversationId(metadata?: Record<string, unknown>): string {
|
|
688
|
-
const metaConvId = metadata?.["gen_ai.conversation.id"];
|
|
689
|
-
if (metaConvId !== undefined && metaConvId !== null) {
|
|
690
|
-
return String(metaConvId);
|
|
691
|
-
}
|
|
692
|
-
const threadId = metadata?.["thread_id"];
|
|
693
|
-
if (threadId !== undefined && threadId !== null) {
|
|
694
|
-
return String(threadId);
|
|
695
|
-
}
|
|
696
|
-
const baggageConvId = this._readBaggage("gen_ai.conversation.id");
|
|
697
|
-
if (baggageConvId) return baggageConvId;
|
|
698
|
-
return this._conversationId;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* Read a single value from the active OTel baggage. Returns undefined when
|
|
703
|
-
* no propagator is installed or the key is absent.
|
|
704
|
-
*
|
|
705
|
-
* Used so callers can scope LangChain calls with
|
|
706
|
-
* `IntrospectionClient.withAgent()` / `.withConversation()` /
|
|
707
|
-
* `.withGenAi()` without threading metadata through every invoke().
|
|
708
|
-
*/
|
|
709
|
-
private _readBaggage(key: string): string | undefined {
|
|
710
|
-
const baggage = propagation.getBaggage(otelContext.active());
|
|
711
|
-
return baggage?.getEntry(key)?.value;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
private _convertMessages(messages: BaseMessage[]): {
|
|
715
|
-
inputMessages: InputMessage[];
|
|
716
|
-
systemInstructions: string[];
|
|
717
|
-
} {
|
|
718
|
-
const inputMessages: InputMessage[] = [];
|
|
719
|
-
const systemInstructions: string[] = [];
|
|
720
|
-
|
|
721
|
-
for (const msg of messages) {
|
|
722
|
-
const msgType =
|
|
723
|
-
(msg as unknown as { _getType?: () => string })._getType?.() ||
|
|
724
|
-
(msg as unknown as Record<string, unknown>).type ||
|
|
725
|
-
"unknown";
|
|
726
|
-
const role = this._mapRole(msgType as string);
|
|
727
|
-
const content = this._extractContent(msg.content);
|
|
728
|
-
|
|
729
|
-
if (role === "system") {
|
|
730
|
-
systemInstructions.push(
|
|
731
|
-
typeof msg.content === "string"
|
|
732
|
-
? msg.content
|
|
733
|
-
: JSON.stringify(msg.content),
|
|
734
|
-
);
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const parts: MessagePart[] = [];
|
|
739
|
-
|
|
740
|
-
if (role === "tool") {
|
|
741
|
-
const toolCallId =
|
|
742
|
-
((msg as unknown as Record<string, unknown>)
|
|
743
|
-
.tool_call_id as string) || "";
|
|
744
|
-
parts.push({
|
|
745
|
-
type: "tool_call_response",
|
|
746
|
-
id: toolCallId,
|
|
747
|
-
response: content,
|
|
748
|
-
});
|
|
749
|
-
} else if (content) {
|
|
750
|
-
parts.push({ type: "text", content });
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const toolCalls = (msg as unknown as Record<string, unknown>)
|
|
754
|
-
.tool_calls as Array<Record<string, unknown>> | undefined;
|
|
755
|
-
if (toolCalls) {
|
|
756
|
-
for (const tc of toolCalls) {
|
|
757
|
-
parts.push({
|
|
758
|
-
type: "tool_call",
|
|
759
|
-
name: (tc.name || "") as string,
|
|
760
|
-
id: (tc.id || "") as string,
|
|
761
|
-
arguments:
|
|
762
|
-
typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args),
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (parts.length > 0) {
|
|
768
|
-
inputMessages.push({ role, parts });
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
return { inputMessages, systemInstructions };
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
private _mapRole(msgType: string): "user" | "assistant" | "system" | "tool" {
|
|
776
|
-
switch (msgType) {
|
|
777
|
-
case "human":
|
|
778
|
-
return "user";
|
|
779
|
-
case "ai":
|
|
780
|
-
return "assistant";
|
|
781
|
-
case "system":
|
|
782
|
-
return "system";
|
|
783
|
-
case "tool":
|
|
784
|
-
return "tool";
|
|
785
|
-
default:
|
|
786
|
-
return "user";
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
private _extractContent(content: unknown): string {
|
|
791
|
-
if (typeof content === "string") return content;
|
|
792
|
-
if (Array.isArray(content)) {
|
|
793
|
-
return content
|
|
794
|
-
.map((part: unknown) => {
|
|
795
|
-
const p = part as Record<string, unknown>;
|
|
796
|
-
if (p.type === "text") return (p.text || "") as string;
|
|
797
|
-
return "";
|
|
798
|
-
})
|
|
799
|
-
.filter(Boolean)
|
|
800
|
-
.join("");
|
|
801
|
-
}
|
|
802
|
-
return String(content || "");
|
|
803
|
-
}
|
|
804
|
-
}
|