@softprobe/langchain 0.1.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/dist/CallbackHandler.d.ts +54 -0
- package/dist/CallbackHandler.js +325 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
2
|
+
import type { AgentAction, AgentFinish } from "@langchain/core/agents";
|
|
3
|
+
import type { DocumentInterface } from "@langchain/core/documents";
|
|
4
|
+
import type { Serialized } from "@langchain/core/load/serializable";
|
|
5
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
6
|
+
import type { LLMResult } from "@langchain/core/outputs";
|
|
7
|
+
import type { ChainValues } from "@langchain/core/utils/types";
|
|
8
|
+
import { SoftprobeClient, type JsonValue } from "@softprobe/tracing";
|
|
9
|
+
export type CallbackHandlerParams = {
|
|
10
|
+
softprobeClient: SoftprobeClient;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
userId?: string;
|
|
13
|
+
tags?: string[];
|
|
14
|
+
metadata?: Record<string, JsonValue>;
|
|
15
|
+
version?: string;
|
|
16
|
+
parentSpanContext?: {
|
|
17
|
+
traceId: string;
|
|
18
|
+
spanId: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare class CallbackHandler extends BaseCallbackHandler {
|
|
22
|
+
name: string;
|
|
23
|
+
private readonly client;
|
|
24
|
+
private readonly sessionId?;
|
|
25
|
+
private readonly userId?;
|
|
26
|
+
private readonly tags;
|
|
27
|
+
private readonly metadata?;
|
|
28
|
+
private readonly version?;
|
|
29
|
+
private readonly parentSpanContext?;
|
|
30
|
+
private readonly runs;
|
|
31
|
+
private readonly completionStart;
|
|
32
|
+
private readonly toolMeta;
|
|
33
|
+
lastTraceId: string | null;
|
|
34
|
+
constructor(params: CallbackHandlerParams);
|
|
35
|
+
private parent;
|
|
36
|
+
private start;
|
|
37
|
+
private end;
|
|
38
|
+
handleLLMNewToken(_token: string, _idx: unknown, runId: string): Promise<void>;
|
|
39
|
+
handleChainStart(chain: Serialized, inputs: ChainValues, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, _runType?: string, name?: string): Promise<void>;
|
|
40
|
+
handleChainEnd(outputs: ChainValues, runId: string): Promise<void>;
|
|
41
|
+
handleChainError(err: Error, runId: string): Promise<void>;
|
|
42
|
+
handleAgentAction(action: AgentAction, runId: string, parentRunId?: string): Promise<void>;
|
|
43
|
+
handleAgentEnd(action: AgentFinish, runId: string): Promise<void>;
|
|
44
|
+
handleChatModelStart(llm: Serialized, messages: BaseMessage[][], runId: string, parentRunId?: string, extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, name?: string): Promise<void>;
|
|
45
|
+
handleLLMStart(llm: Serialized, prompts: string[], runId: string, parentRunId?: string, extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, name?: string): Promise<void>;
|
|
46
|
+
handleLLMEnd(output: LLMResult, runId: string): Promise<void>;
|
|
47
|
+
handleLLMError(err: Error, runId: string): Promise<void>;
|
|
48
|
+
handleToolStart(tool: Serialized, input: string, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, name?: string): Promise<void>;
|
|
49
|
+
handleToolEnd(output: unknown, runId: string): Promise<void>;
|
|
50
|
+
handleToolError(err: Error, runId: string): Promise<void>;
|
|
51
|
+
handleRetrieverStart(retriever: Serialized, query: string, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, name?: string): Promise<void>;
|
|
52
|
+
handleRetrieverEnd(documents: DocumentInterface[], runId: string): Promise<void>;
|
|
53
|
+
handleRetrieverError(err: Error, runId: string): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
2
|
+
import { recordToolCalls, recordToolDefinitions, toolResultEventPayload, toolSpanAttributes, normalizeToolCalls, } from "@softprobe/tracing";
|
|
3
|
+
function asJsonValue(value) {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
function messageToDict(message) {
|
|
7
|
+
const roleMap = {
|
|
8
|
+
human: "user",
|
|
9
|
+
ai: "assistant",
|
|
10
|
+
system: "system",
|
|
11
|
+
tool: "tool",
|
|
12
|
+
function: "function",
|
|
13
|
+
};
|
|
14
|
+
const raw = message.getType?.() ?? message._getType?.() ?? "unknown";
|
|
15
|
+
const payload = {
|
|
16
|
+
role: roleMap[raw] ?? raw,
|
|
17
|
+
content: asJsonValue(message.content),
|
|
18
|
+
};
|
|
19
|
+
const toolCalls = message.tool_calls;
|
|
20
|
+
if (toolCalls) {
|
|
21
|
+
const normalized = normalizeToolCalls(toolCalls);
|
|
22
|
+
payload.tool_calls = asJsonValue(normalized.length ? normalized : toolCalls);
|
|
23
|
+
}
|
|
24
|
+
const toolCallId = message.tool_call_id;
|
|
25
|
+
if (toolCallId)
|
|
26
|
+
payload.tool_call_id = toolCallId;
|
|
27
|
+
const name = message.name;
|
|
28
|
+
if (name)
|
|
29
|
+
payload.name = name;
|
|
30
|
+
return payload;
|
|
31
|
+
}
|
|
32
|
+
export class CallbackHandler extends BaseCallbackHandler {
|
|
33
|
+
name = "SoftprobeCallbackHandler";
|
|
34
|
+
client;
|
|
35
|
+
sessionId;
|
|
36
|
+
userId;
|
|
37
|
+
tags;
|
|
38
|
+
metadata;
|
|
39
|
+
version;
|
|
40
|
+
parentSpanContext;
|
|
41
|
+
runs = new Map();
|
|
42
|
+
completionStart = new Map();
|
|
43
|
+
toolMeta = new Map();
|
|
44
|
+
lastTraceId = null;
|
|
45
|
+
constructor(params) {
|
|
46
|
+
super();
|
|
47
|
+
this.client = params.softprobeClient;
|
|
48
|
+
this.sessionId = params.sessionId;
|
|
49
|
+
this.userId = params.userId;
|
|
50
|
+
this.tags = params.tags ?? [];
|
|
51
|
+
this.metadata = params.metadata;
|
|
52
|
+
this.version = params.version;
|
|
53
|
+
this.parentSpanContext = params.parentSpanContext;
|
|
54
|
+
}
|
|
55
|
+
parent(parentRunId) {
|
|
56
|
+
if (!parentRunId)
|
|
57
|
+
return undefined;
|
|
58
|
+
return this.runs.get(parentRunId);
|
|
59
|
+
}
|
|
60
|
+
start(options) {
|
|
61
|
+
const parent = this.parent(options.parentRunId);
|
|
62
|
+
const mergedTags = [...new Set([...(options.tags ?? []), ...this.tags])];
|
|
63
|
+
const mergedMeta = {
|
|
64
|
+
...(this.metadata ?? {}),
|
|
65
|
+
...(options.metadata ?? {}),
|
|
66
|
+
};
|
|
67
|
+
const common = {
|
|
68
|
+
name: options.name,
|
|
69
|
+
sessionId: this.sessionId,
|
|
70
|
+
userId: this.userId,
|
|
71
|
+
tags: mergedTags.length ? mergedTags : undefined,
|
|
72
|
+
metadata: Object.keys(mergedMeta).length ? mergedMeta : undefined,
|
|
73
|
+
version: this.version,
|
|
74
|
+
input: options.input,
|
|
75
|
+
attributes: options.attributes,
|
|
76
|
+
parent,
|
|
77
|
+
parentSpanContext: !parent && this.parentSpanContext ? this.parentSpanContext : undefined,
|
|
78
|
+
};
|
|
79
|
+
let obs;
|
|
80
|
+
if (options.asType === "generation") {
|
|
81
|
+
obs = this.client.startGeneration({
|
|
82
|
+
...common,
|
|
83
|
+
model: options.model,
|
|
84
|
+
operationName: options.operationName,
|
|
85
|
+
modelParameters: {
|
|
86
|
+
temperature: options.temperature,
|
|
87
|
+
maxTokens: options.maxTokens,
|
|
88
|
+
},
|
|
89
|
+
promptEvent: options.promptEvent,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
obs = this.client.startObservation({
|
|
94
|
+
...common,
|
|
95
|
+
asType: options.asType,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
this.runs.set(options.runId, obs);
|
|
99
|
+
if (!parent)
|
|
100
|
+
this.lastTraceId = obs.traceId;
|
|
101
|
+
return obs;
|
|
102
|
+
}
|
|
103
|
+
end(runId, update = {}) {
|
|
104
|
+
const obs = this.runs.get(runId);
|
|
105
|
+
this.runs.delete(runId);
|
|
106
|
+
this.completionStart.delete(runId);
|
|
107
|
+
this.toolMeta.delete(runId);
|
|
108
|
+
if (!obs)
|
|
109
|
+
return;
|
|
110
|
+
obs.end(update);
|
|
111
|
+
}
|
|
112
|
+
async handleLLMNewToken(_token, _idx, runId) {
|
|
113
|
+
if (!this.completionStart.has(runId) && this.runs.has(runId)) {
|
|
114
|
+
const iso = new Date().toISOString();
|
|
115
|
+
this.completionStart.set(runId, iso);
|
|
116
|
+
const obs = this.runs.get(runId);
|
|
117
|
+
if (obs && "update" in obs) {
|
|
118
|
+
obs.update({ completionStartTime: iso });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, name) {
|
|
123
|
+
const runName = name ?? chain.id?.at(-1)?.toString() ?? chain.name ?? "Langchain Run";
|
|
124
|
+
const asType = /agent/i.test(runName) ? "agent" : "chain";
|
|
125
|
+
this.start({
|
|
126
|
+
runId,
|
|
127
|
+
parentRunId,
|
|
128
|
+
name: runName,
|
|
129
|
+
asType,
|
|
130
|
+
input: inputs,
|
|
131
|
+
metadata,
|
|
132
|
+
tags,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async handleChainEnd(outputs, runId) {
|
|
136
|
+
this.end(runId, { output: outputs });
|
|
137
|
+
}
|
|
138
|
+
async handleChainError(err, runId) {
|
|
139
|
+
this.end(runId, { statusMessage: String(err) });
|
|
140
|
+
}
|
|
141
|
+
async handleAgentAction(action, runId, parentRunId) {
|
|
142
|
+
// LangChain reuses the agent runId here; do not overwrite the agent span.
|
|
143
|
+
if (this.runs.has(runId))
|
|
144
|
+
return;
|
|
145
|
+
this.start({
|
|
146
|
+
runId,
|
|
147
|
+
parentRunId,
|
|
148
|
+
name: action.tool,
|
|
149
|
+
asType: "tool",
|
|
150
|
+
input: action.toolInput,
|
|
151
|
+
attributes: toolSpanAttributes({
|
|
152
|
+
toolName: action.tool,
|
|
153
|
+
kind: "function",
|
|
154
|
+
status: "ok",
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async handleAgentEnd(action, runId) {
|
|
159
|
+
this.end(runId, { output: action.returnValues });
|
|
160
|
+
}
|
|
161
|
+
async handleChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, name) {
|
|
162
|
+
const flat = messages.flat().map(messageToDict);
|
|
163
|
+
const invocation = (extraParams?.invocation_params ?? {});
|
|
164
|
+
const model = (typeof invocation.model === "string" && invocation.model) ||
|
|
165
|
+
(typeof metadata?.ls_model_name === "string" && metadata.ls_model_name) ||
|
|
166
|
+
llm.id?.at(-1)?.toString();
|
|
167
|
+
const obs = this.start({
|
|
168
|
+
runId,
|
|
169
|
+
parentRunId,
|
|
170
|
+
name: name ?? llm.id?.at(-1)?.toString() ?? "ChatModel",
|
|
171
|
+
asType: "generation",
|
|
172
|
+
input: { messages: flat },
|
|
173
|
+
metadata,
|
|
174
|
+
tags,
|
|
175
|
+
model: model || undefined,
|
|
176
|
+
operationName: "chat",
|
|
177
|
+
temperature: typeof invocation.temperature === "number"
|
|
178
|
+
? invocation.temperature
|
|
179
|
+
: undefined,
|
|
180
|
+
maxTokens: typeof invocation.max_tokens === "number"
|
|
181
|
+
? invocation.max_tokens
|
|
182
|
+
: undefined,
|
|
183
|
+
promptEvent: flat,
|
|
184
|
+
});
|
|
185
|
+
if (invocation.tools !== undefined) {
|
|
186
|
+
recordToolDefinitions(obs, invocation.tools);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async handleLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata, name) {
|
|
190
|
+
const invocation = (extraParams?.invocation_params ?? {});
|
|
191
|
+
const model = (typeof invocation.model === "string" && invocation.model) ||
|
|
192
|
+
(typeof metadata?.ls_model_name === "string" && metadata.ls_model_name) ||
|
|
193
|
+
undefined;
|
|
194
|
+
const obs = this.start({
|
|
195
|
+
runId,
|
|
196
|
+
parentRunId,
|
|
197
|
+
name: name ?? llm.id?.at(-1)?.toString() ?? "LLM",
|
|
198
|
+
asType: "generation",
|
|
199
|
+
input: { prompts },
|
|
200
|
+
metadata,
|
|
201
|
+
tags,
|
|
202
|
+
model,
|
|
203
|
+
operationName: "completion",
|
|
204
|
+
promptEvent: prompts,
|
|
205
|
+
});
|
|
206
|
+
if (invocation.tools !== undefined) {
|
|
207
|
+
recordToolDefinitions(obs, invocation.tools);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async handleLLMEnd(output, runId) {
|
|
211
|
+
const toolCalls = [];
|
|
212
|
+
const finishReasons = [];
|
|
213
|
+
const generations = output.generations.flat().map((gen) => {
|
|
214
|
+
const message = gen.message;
|
|
215
|
+
if (message) {
|
|
216
|
+
const payload = messageToDict(message);
|
|
217
|
+
if (payload &&
|
|
218
|
+
typeof payload === "object" &&
|
|
219
|
+
!Array.isArray(payload) &&
|
|
220
|
+
Array.isArray(payload.tool_calls) &&
|
|
221
|
+
payload.tool_calls.length) {
|
|
222
|
+
toolCalls.push(...payload.tool_calls);
|
|
223
|
+
finishReasons.push("tool_calls");
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
finishReasons.push("stop");
|
|
227
|
+
}
|
|
228
|
+
return payload;
|
|
229
|
+
}
|
|
230
|
+
finishReasons.push("stop");
|
|
231
|
+
return { content: gen.text };
|
|
232
|
+
});
|
|
233
|
+
const tokenUsage = output.llmOutput?.tokenUsage ??
|
|
234
|
+
output.llmOutput?.token_usage;
|
|
235
|
+
const obs = this.runs.get(runId);
|
|
236
|
+
if (obs && toolCalls.length) {
|
|
237
|
+
recordToolCalls(obs, toolCalls);
|
|
238
|
+
}
|
|
239
|
+
this.end(runId, {
|
|
240
|
+
output: { generations },
|
|
241
|
+
completionEvent: generations,
|
|
242
|
+
usage: tokenUsage
|
|
243
|
+
? {
|
|
244
|
+
inputTokens: tokenUsage.promptTokens ?? tokenUsage.prompt_tokens,
|
|
245
|
+
outputTokens: tokenUsage.completionTokens ?? tokenUsage.completion_tokens,
|
|
246
|
+
totalTokens: tokenUsage.totalTokens ?? tokenUsage.total_tokens,
|
|
247
|
+
}
|
|
248
|
+
: undefined,
|
|
249
|
+
finishReasons: finishReasons.length ? finishReasons : ["stop"],
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async handleLLMError(err, runId) {
|
|
253
|
+
this.end(runId, { statusMessage: String(err) });
|
|
254
|
+
}
|
|
255
|
+
async handleToolStart(tool, input, runId, parentRunId, tags, metadata, name) {
|
|
256
|
+
const toolName = name ?? tool.name ?? tool.id?.at(-1)?.toString() ?? "tool";
|
|
257
|
+
// Only correlate with a provider-supplied id (matches generation
|
|
258
|
+
// sp.tool.call_ids); never fabricate one from the LangChain runId.
|
|
259
|
+
const toolCallId = (typeof metadata?.tool_call_id === "string" && metadata.tool_call_id) ||
|
|
260
|
+
(typeof metadata?.toolCallId === "string" && metadata.toolCallId) ||
|
|
261
|
+
undefined;
|
|
262
|
+
this.toolMeta.set(runId, { name: toolName, toolCallId });
|
|
263
|
+
this.start({
|
|
264
|
+
runId,
|
|
265
|
+
parentRunId,
|
|
266
|
+
name: toolName,
|
|
267
|
+
asType: "tool",
|
|
268
|
+
input,
|
|
269
|
+
metadata,
|
|
270
|
+
tags,
|
|
271
|
+
attributes: toolSpanAttributes({
|
|
272
|
+
toolName,
|
|
273
|
+
toolCallId,
|
|
274
|
+
kind: "function",
|
|
275
|
+
status: "ok",
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async handleToolEnd(output, runId) {
|
|
280
|
+
const obs = this.runs.get(runId);
|
|
281
|
+
const meta = this.toolMeta.get(runId);
|
|
282
|
+
const content = asJsonValue(output);
|
|
283
|
+
if (obs) {
|
|
284
|
+
obs.addContentEvent("gen_ai.tool.message", toolResultEventPayload({
|
|
285
|
+
name: meta?.name ?? "tool",
|
|
286
|
+
content,
|
|
287
|
+
toolCallId: meta?.toolCallId,
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
this.end(runId, {
|
|
291
|
+
output: content,
|
|
292
|
+
attributes: toolSpanAttributes({ status: "ok" }),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
async handleToolError(err, runId) {
|
|
296
|
+
this.end(runId, {
|
|
297
|
+
statusMessage: String(err),
|
|
298
|
+
attributes: toolSpanAttributes({ status: "error" }),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
|
|
302
|
+
this.start({
|
|
303
|
+
runId,
|
|
304
|
+
parentRunId,
|
|
305
|
+
name: name ?? retriever.name ?? "retriever",
|
|
306
|
+
asType: "retriever",
|
|
307
|
+
input: { query },
|
|
308
|
+
metadata,
|
|
309
|
+
tags,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async handleRetrieverEnd(documents, runId) {
|
|
313
|
+
this.end(runId, {
|
|
314
|
+
output: {
|
|
315
|
+
documents: documents.map((doc) => ({
|
|
316
|
+
pageContent: doc.pageContent,
|
|
317
|
+
metadata: doc.metadata,
|
|
318
|
+
})),
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async handleRetrieverError(err, runId) {
|
|
323
|
+
this.end(runId, { statusMessage: String(err) });
|
|
324
|
+
}
|
|
325
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CallbackHandler, type CallbackHandlerParams, } from "./CallbackHandler.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CallbackHandler, } from "./CallbackHandler.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@softprobe/langchain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Softprobe LangChain callback handler",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@softprobe/tracing": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@langchain/core": ">=0.3.0",
|
|
32
|
+
"@opentelemetry/api": "^1.9.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@langchain/core": "^0.3.40",
|
|
36
|
+
"@opentelemetry/api": "^1.9.0",
|
|
37
|
+
"@opentelemetry/sdk-trace-base": "^1.30.1",
|
|
38
|
+
"@types/node": "^22.13.10",
|
|
39
|
+
"typescript": "^5.8.2",
|
|
40
|
+
"vitest": "^3.0.8"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/softprobe/softprobe-js.git",
|
|
48
|
+
"directory": "packages/langchain"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT"
|
|
51
|
+
}
|