langsmith 0.8.5 → 0.8.7
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/experimental/vercel/telemetry.cjs +158 -29
- package/dist/experimental/vercel/telemetry.js +158 -29
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/command_handle.cjs +1 -1
- package/dist/sandbox/command_handle.js +2 -2
- package/dist/sandbox/errors.cjs +16 -1
- package/dist/sandbox/errors.d.ts +11 -0
- package/dist/sandbox/errors.js +14 -0
- package/dist/sandbox/sandbox.cjs +55 -33
- package/dist/sandbox/sandbox.d.ts +2 -1
- package/dist/sandbox/sandbox.js +57 -35
- package/dist/sandbox/ws_execute.cjs +12 -5
- package/dist/sandbox/ws_execute.d.ts +2 -0
- package/dist/sandbox/ws_execute.js +11 -5
- package/dist/utils/jestlike/globals.cjs +2 -3
- package/dist/utils/jestlike/globals.d.ts +1 -1
- package/dist/utils/jestlike/globals.js +1 -2
- package/dist/utils/jestlike/index.cjs +11 -5
- package/dist/utils/jestlike/index.js +12 -6
- package/package.json +9 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LangSmithTelemetry = LangSmithTelemetry;
|
|
4
4
|
const run_trees_js_1 = require("../../run_trees.cjs");
|
|
5
|
-
const traceable_js_1 = require("../../
|
|
5
|
+
const traceable_js_1 = require("../../traceable.cjs");
|
|
6
6
|
const env_js_1 = require("../../env.cjs");
|
|
7
7
|
const utils_js_1 = require("./utils.cjs");
|
|
8
8
|
const utils_js_2 = require("./utils.cjs");
|
|
@@ -58,6 +58,12 @@ function _getLsAgentType(parentRunTree) {
|
|
|
58
58
|
}
|
|
59
59
|
return "root";
|
|
60
60
|
}
|
|
61
|
+
function _hasNonzeroUsageMetadata(runTree) {
|
|
62
|
+
const usageMetadata = runTree.extra?.metadata?.usage_metadata;
|
|
63
|
+
if (!(0, types_js_1.isRecord)(usageMetadata))
|
|
64
|
+
return false;
|
|
65
|
+
return ["input_tokens", "output_tokens", "total_tokens"].some((key) => typeof usageMetadata[key] === "number" && usageMetadata[key] > 0);
|
|
66
|
+
}
|
|
61
67
|
/**
|
|
62
68
|
* Creates a LangSmith `Telemetry` for the Vercel AI SDK.
|
|
63
69
|
*
|
|
@@ -82,6 +88,31 @@ function _getLsAgentType(parentRunTree) {
|
|
|
82
88
|
*/
|
|
83
89
|
function LangSmithTelemetry(config) {
|
|
84
90
|
const { name: customName, runType = "chain", metadata: customMetadata, tags: customTags, client, projectName, processInputs, processOutputs, processChildLLMRunInputs, processChildLLMRunOutputs, traceResponseMetadata, traceRawHttp, tracingEnabled, extra: customExtra, } = config ?? {};
|
|
91
|
+
function formatToolResultMessage(toolCallId, toolName, toolOutput) {
|
|
92
|
+
const rawOutput = (0, types_js_1.isRecord)(toolOutput)
|
|
93
|
+
? toolOutput.type === "tool-result"
|
|
94
|
+
? toolOutput.output
|
|
95
|
+
: "error" in toolOutput
|
|
96
|
+
? toolOutput.error
|
|
97
|
+
: toolOutput
|
|
98
|
+
: toolOutput;
|
|
99
|
+
const artifact = rawOutput instanceof Error ? rawOutput.message : rawOutput;
|
|
100
|
+
const content = typeof artifact === "string"
|
|
101
|
+
? artifact
|
|
102
|
+
: (JSON.stringify(artifact) ?? String(artifact));
|
|
103
|
+
return {
|
|
104
|
+
role: "tool",
|
|
105
|
+
content,
|
|
106
|
+
tool_call_id: toolCallId,
|
|
107
|
+
name: toolName,
|
|
108
|
+
artifact,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function appendToolResultMessage(harnessState, toolCallId, toolName, toolOutput) {
|
|
112
|
+
const message = formatToolResultMessage(toolCallId, toolName, toolOutput);
|
|
113
|
+
harnessState.completedStepMessages.push(message);
|
|
114
|
+
return message;
|
|
115
|
+
}
|
|
85
116
|
function getOpenStepOrRoot(state) {
|
|
86
117
|
let openStep;
|
|
87
118
|
state.stepRunTrees.forEach((stepRt) => {
|
|
@@ -91,6 +122,16 @@ function LangSmithTelemetry(config) {
|
|
|
91
122
|
});
|
|
92
123
|
return openStep ?? state.rootRunTree;
|
|
93
124
|
}
|
|
125
|
+
async function flushPiDeferredStepRun(state) {
|
|
126
|
+
const workaround = state.experimental_harness?.piAggregateUsageWorkaround;
|
|
127
|
+
if (workaround == null)
|
|
128
|
+
return;
|
|
129
|
+
const stepRunTree = workaround.deferredStepRunTree;
|
|
130
|
+
if (stepRunTree == null)
|
|
131
|
+
return;
|
|
132
|
+
workaround.deferredStepRunTree = undefined;
|
|
133
|
+
await stepRunTree.patchRun({ excludeInputs: true });
|
|
134
|
+
}
|
|
94
135
|
async function finalizeOpenToolRuns(state, opts) {
|
|
95
136
|
const entries = Array.from(state.toolRunTrees.entries());
|
|
96
137
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -161,7 +202,6 @@ function LangSmithTelemetry(config) {
|
|
|
161
202
|
ai_sdk_method: event.operationId,
|
|
162
203
|
ls_agent_type: _getLsAgentType(parentRunTree),
|
|
163
204
|
ls_model_name: event.modelId,
|
|
164
|
-
ls_provider: event.provider,
|
|
165
205
|
ls_integration: "vercel-ai-sdk-telemetry",
|
|
166
206
|
},
|
|
167
207
|
},
|
|
@@ -181,17 +221,29 @@ function LangSmithTelemetry(config) {
|
|
|
181
221
|
rootRunTree,
|
|
182
222
|
stepRunTrees: new Map(),
|
|
183
223
|
toolRunTrees: new Map(),
|
|
224
|
+
experimental_harness: event.operationId === "ai.harness"
|
|
225
|
+
? {
|
|
226
|
+
lastStepContent: [],
|
|
227
|
+
completedStepMessages: [],
|
|
228
|
+
toolResults: new Map(),
|
|
229
|
+
piAggregateUsageWorkaround: event.provider === "pi" ? {} : undefined,
|
|
230
|
+
}
|
|
231
|
+
: undefined,
|
|
184
232
|
});
|
|
185
233
|
};
|
|
186
234
|
const onStepStart = async (event) => {
|
|
187
235
|
const state = invocationsByCallId.get(event.callId);
|
|
188
236
|
if (!state)
|
|
189
237
|
return;
|
|
238
|
+
await flushPiDeferredStepRun(state);
|
|
190
239
|
const stepNumber = event.stepNumber ?? 0;
|
|
191
240
|
let inputs = {};
|
|
192
241
|
if (event.recordInputs !== false) {
|
|
193
242
|
if ("messages" in event && event.messages != null) {
|
|
194
|
-
inputs.messages =
|
|
243
|
+
inputs.messages = [
|
|
244
|
+
..._formatMessages(event.messages),
|
|
245
|
+
...(state.experimental_harness?.completedStepMessages ?? []),
|
|
246
|
+
];
|
|
195
247
|
}
|
|
196
248
|
if ("runtimeContext" in event && event.runtimeContext != null) {
|
|
197
249
|
inputs.runtimeContext = event.runtimeContext;
|
|
@@ -212,9 +264,18 @@ function LangSmithTelemetry(config) {
|
|
|
212
264
|
name: event.provider,
|
|
213
265
|
run_type: "llm",
|
|
214
266
|
inputs,
|
|
215
|
-
extra: {
|
|
267
|
+
extra: {
|
|
268
|
+
metadata: {
|
|
269
|
+
step_number: stepNumber,
|
|
270
|
+
ls_model_name: event.modelId,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
216
273
|
});
|
|
217
274
|
state.stepRunTrees.set(stepNumber, stepRunTree);
|
|
275
|
+
const piUsageWorkaround = state.experimental_harness?.piAggregateUsageWorkaround;
|
|
276
|
+
if (piUsageWorkaround != null) {
|
|
277
|
+
piUsageWorkaround.latestStepRunTree = stepRunTree;
|
|
278
|
+
}
|
|
218
279
|
await stepRunTree.postRun();
|
|
219
280
|
};
|
|
220
281
|
const onLanguageModelCallStart = async (event) => {
|
|
@@ -254,22 +315,25 @@ function LangSmithTelemetry(config) {
|
|
|
254
315
|
const state = invocationsByCallId.get(event.callId);
|
|
255
316
|
if (!state)
|
|
256
317
|
return;
|
|
257
|
-
const parentRunTree =
|
|
318
|
+
const parentRunTree = state.rootRunTree;
|
|
258
319
|
let inputs = {};
|
|
259
320
|
if (event.recordInputs !== false) {
|
|
260
|
-
|
|
261
|
-
|
|
321
|
+
let toolCallInput = event.toolCall.input;
|
|
322
|
+
if (typeof toolCallInput === "string") {
|
|
323
|
+
try {
|
|
324
|
+
toolCallInput = JSON.parse(toolCallInput);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// The tool argument itself may be a plain string rather than JSON.
|
|
328
|
+
}
|
|
262
329
|
}
|
|
263
|
-
|
|
264
|
-
inputs = {
|
|
330
|
+
if ((0, types_js_1.isRecord)(toolCallInput)) {
|
|
331
|
+
inputs = { ...toolCallInput };
|
|
265
332
|
}
|
|
266
|
-
if (
|
|
267
|
-
inputs
|
|
333
|
+
else if (toolCallInput !== undefined) {
|
|
334
|
+
inputs = { input: toolCallInput };
|
|
268
335
|
}
|
|
269
336
|
}
|
|
270
|
-
else {
|
|
271
|
-
inputs = {};
|
|
272
|
-
}
|
|
273
337
|
const toolRunTree = parentRunTree.createChild({
|
|
274
338
|
name: event.toolCall.toolName,
|
|
275
339
|
run_type: "tool",
|
|
@@ -291,14 +355,20 @@ function LangSmithTelemetry(config) {
|
|
|
291
355
|
const toolRunTree = state.toolRunTrees.get(event.toolCall.toolCallId);
|
|
292
356
|
if (!toolRunTree)
|
|
293
357
|
return;
|
|
294
|
-
state.
|
|
358
|
+
const harnessState = state.experimental_harness;
|
|
359
|
+
if (harnessState != null) {
|
|
360
|
+
harnessState.toolResults.set(event.toolCall.toolCallId, {
|
|
361
|
+
toolName: event.toolCall.toolName,
|
|
362
|
+
output: event.toolOutput,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
295
365
|
let outputs;
|
|
296
366
|
let error;
|
|
297
367
|
if (event.recordOutputs !== false) {
|
|
298
368
|
if (event.toolOutput.type === "tool-result") {
|
|
299
369
|
outputs = { output: event.toolOutput.output };
|
|
300
370
|
}
|
|
301
|
-
else if (event.toolOutput
|
|
371
|
+
else if ((0, types_js_1.isRecord)(event.toolOutput) && "error" in event.toolOutput) {
|
|
302
372
|
const err = event.toolOutput.error;
|
|
303
373
|
error = err instanceof Error ? err.message : String(err);
|
|
304
374
|
}
|
|
@@ -308,6 +378,7 @@ function LangSmithTelemetry(config) {
|
|
|
308
378
|
}
|
|
309
379
|
await toolRunTree.end(outputs, error, Math.floor(toolRunTree.start_time + event.toolExecutionMs));
|
|
310
380
|
await toolRunTree.patchRun({ excludeInputs: true });
|
|
381
|
+
state.toolRunTrees.delete(event.toolCall.toolCallId);
|
|
311
382
|
};
|
|
312
383
|
const onStepFinish = async (event) => {
|
|
313
384
|
const state = invocationsByCallId.get(event.callId);
|
|
@@ -317,8 +388,12 @@ function LangSmithTelemetry(config) {
|
|
|
317
388
|
const stepRunTree = state.stepRunTrees.get(stepNumber);
|
|
318
389
|
if (!stepRunTree)
|
|
319
390
|
return;
|
|
391
|
+
const harnessState = state.experimental_harness;
|
|
320
392
|
let outputs = {};
|
|
321
393
|
if (event.recordOutputs !== false) {
|
|
394
|
+
if (harnessState != null && Array.isArray(event.content)) {
|
|
395
|
+
harnessState.lastStepContent = [...event.content];
|
|
396
|
+
}
|
|
322
397
|
outputs = _formatStepOutput(event, traceRawHttp);
|
|
323
398
|
if (processChildLLMRunOutputs) {
|
|
324
399
|
try {
|
|
@@ -329,11 +404,38 @@ function LangSmithTelemetry(config) {
|
|
|
329
404
|
}
|
|
330
405
|
}
|
|
331
406
|
}
|
|
407
|
+
if (harnessState != null && Array.isArray(event.content)) {
|
|
408
|
+
harnessState.completedStepMessages.push((0, utils_js_1.convertMessageToTracedFormat)({
|
|
409
|
+
role: "assistant",
|
|
410
|
+
content: event.content,
|
|
411
|
+
}));
|
|
412
|
+
for (const part of event.content) {
|
|
413
|
+
if (!(0, types_js_1.isRecord)(part) ||
|
|
414
|
+
part.type !== "tool-call" ||
|
|
415
|
+
typeof part.toolCallId !== "string") {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const toolResult = harnessState.toolResults.get(part.toolCallId);
|
|
419
|
+
if (toolResult) {
|
|
420
|
+
appendToolResultMessage(harnessState, part.toolCallId, toolResult.toolName, toolResult.output);
|
|
421
|
+
harnessState.toolResults.delete(part.toolCallId);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
332
425
|
// Set usage metadata
|
|
333
426
|
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
334
427
|
(0, utils_js_2.setUsageMetadataOnRunTree)(event, stepRunTree);
|
|
335
428
|
await stepRunTree.end(outputs);
|
|
336
|
-
|
|
429
|
+
const piUsageWorkaround = harnessState?.piAggregateUsageWorkaround;
|
|
430
|
+
if (piUsageWorkaround != null && !_hasNonzeroUsageMetadata(stepRunTree)) {
|
|
431
|
+
// Pi reports zero usage for inferred step boundaries and sends the real
|
|
432
|
+
// aggregate at turn end. Defer the final PATCH so that aggregate can be
|
|
433
|
+
// attached to the last LLM run without issuing a duplicate update.
|
|
434
|
+
piUsageWorkaround.deferredStepRunTree = stepRunTree;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
await stepRunTree.patchRun({ excludeInputs: true });
|
|
438
|
+
}
|
|
337
439
|
state.stepRunTrees.delete(stepNumber);
|
|
338
440
|
};
|
|
339
441
|
const onEnd = async (event) => {
|
|
@@ -343,6 +445,8 @@ function LangSmithTelemetry(config) {
|
|
|
343
445
|
if (!state)
|
|
344
446
|
return;
|
|
345
447
|
const { rootRunTree } = state;
|
|
448
|
+
const harnessState = state.experimental_harness;
|
|
449
|
+
const piUsageWorkaround = harnessState?.piAggregateUsageWorkaround;
|
|
346
450
|
await finalizeOpenToolRuns(state, { note: "closed on finish" });
|
|
347
451
|
// Ensure any remaining step runs are closed
|
|
348
452
|
const remainingSteps = Array.from(state.stepRunTrees.entries());
|
|
@@ -350,7 +454,12 @@ function LangSmithTelemetry(config) {
|
|
|
350
454
|
const [stepNumber, stepRt] = remainingSteps[i];
|
|
351
455
|
if (stepRt.end_time == null) {
|
|
352
456
|
await stepRt.end({ note: "closed on finish" });
|
|
353
|
-
|
|
457
|
+
if (piUsageWorkaround != null && !_hasNonzeroUsageMetadata(stepRt)) {
|
|
458
|
+
piUsageWorkaround.deferredStepRunTree = stepRt;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
await stepRt.patchRun({ excludeInputs: true });
|
|
462
|
+
}
|
|
354
463
|
}
|
|
355
464
|
state.stepRunTrees.delete(stepNumber);
|
|
356
465
|
}
|
|
@@ -360,9 +469,18 @@ function LangSmithTelemetry(config) {
|
|
|
360
469
|
if ("text" in event && event.text != null) {
|
|
361
470
|
outputs.content = event.text;
|
|
362
471
|
}
|
|
363
|
-
else if ("content" in event &&
|
|
472
|
+
else if ("content" in event &&
|
|
473
|
+
event.content != null &&
|
|
474
|
+
(!Array.isArray(event.content) || event.content.length > 0)) {
|
|
364
475
|
outputs.content = event.content;
|
|
365
476
|
}
|
|
477
|
+
else if (harnessState != null &&
|
|
478
|
+
harnessState.lastStepContent.length > 0) {
|
|
479
|
+
// HarnessAgent currently reports an empty root `content` array even
|
|
480
|
+
// though each completed step contains assistant output. Only the final
|
|
481
|
+
// step belongs on the root; prior steps have their own child runs.
|
|
482
|
+
outputs.content = harnessState.lastStepContent;
|
|
483
|
+
}
|
|
366
484
|
if (outputs.content != null) {
|
|
367
485
|
outputs.role = "assistant";
|
|
368
486
|
}
|
|
@@ -394,18 +512,28 @@ function LangSmithTelemetry(config) {
|
|
|
394
512
|
}
|
|
395
513
|
}
|
|
396
514
|
}
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
515
|
+
// Pi reports zero usage on inferred step boundaries and provides the actual
|
|
516
|
+
// aggregate only when the turn ends. Keep that usage on an LLM run rather
|
|
517
|
+
// than incorrectly elevating it to the parent chain. The latest step is the
|
|
518
|
+
// only accurate place available until Harness Pi exposes per-step usage.
|
|
519
|
+
const latestStep = piUsageWorkaround?.latestStepRunTree;
|
|
520
|
+
if (latestStep?.run_type === "llm" &&
|
|
521
|
+
!_hasNonzeroUsageMetadata(latestStep)) {
|
|
522
|
+
if ("totalUsage" in event && event.totalUsage != null) {
|
|
523
|
+
(0, utils_js_2.setUsageMetadataOnRunTree)(
|
|
524
|
+
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
525
|
+
{ usage: event.totalUsage, providerMetadata: event.providerMetadata }, latestStep);
|
|
526
|
+
}
|
|
527
|
+
else if ("usage" in event && event.usage != null) {
|
|
528
|
+
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
529
|
+
(0, utils_js_2.setUsageMetadataOnRunTree)(event, latestStep);
|
|
530
|
+
}
|
|
406
531
|
}
|
|
407
532
|
await rootRunTree.end(outputs);
|
|
408
|
-
await
|
|
533
|
+
await Promise.all([
|
|
534
|
+
flushPiDeferredStepRun(state),
|
|
535
|
+
rootRunTree.patchRun({ excludeInputs: true }),
|
|
536
|
+
]);
|
|
409
537
|
invocationsByCallId.delete(event.callId);
|
|
410
538
|
};
|
|
411
539
|
const onError = async (payload) => {
|
|
@@ -436,6 +564,7 @@ function LangSmithTelemetry(config) {
|
|
|
436
564
|
}
|
|
437
565
|
state.stepRunTrees.delete(stepNumber);
|
|
438
566
|
}
|
|
567
|
+
await flushPiDeferredStepRun(state);
|
|
439
568
|
await rootRunTree.end(undefined, errorMsg);
|
|
440
569
|
await rootRunTree.patchRun({ excludeInputs: true });
|
|
441
570
|
invocationsByCallId.delete(callId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRunTree, RunTree } from "../../run_trees.js";
|
|
2
|
-
import { getCurrentRunTree, withRunTree } from "../../
|
|
2
|
+
import { getCurrentRunTree, withRunTree } from "../../traceable.js";
|
|
3
3
|
import { isEnvTracingEnabled } from "../../env.js";
|
|
4
4
|
import { convertMessageToTracedFormat } from "./utils.js";
|
|
5
5
|
import { setUsageMetadataOnRunTree } from "./utils.js";
|
|
@@ -55,6 +55,12 @@ function _getLsAgentType(parentRunTree) {
|
|
|
55
55
|
}
|
|
56
56
|
return "root";
|
|
57
57
|
}
|
|
58
|
+
function _hasNonzeroUsageMetadata(runTree) {
|
|
59
|
+
const usageMetadata = runTree.extra?.metadata?.usage_metadata;
|
|
60
|
+
if (!isRecord(usageMetadata))
|
|
61
|
+
return false;
|
|
62
|
+
return ["input_tokens", "output_tokens", "total_tokens"].some((key) => typeof usageMetadata[key] === "number" && usageMetadata[key] > 0);
|
|
63
|
+
}
|
|
58
64
|
/**
|
|
59
65
|
* Creates a LangSmith `Telemetry` for the Vercel AI SDK.
|
|
60
66
|
*
|
|
@@ -79,6 +85,31 @@ function _getLsAgentType(parentRunTree) {
|
|
|
79
85
|
*/
|
|
80
86
|
export function LangSmithTelemetry(config) {
|
|
81
87
|
const { name: customName, runType = "chain", metadata: customMetadata, tags: customTags, client, projectName, processInputs, processOutputs, processChildLLMRunInputs, processChildLLMRunOutputs, traceResponseMetadata, traceRawHttp, tracingEnabled, extra: customExtra, } = config ?? {};
|
|
88
|
+
function formatToolResultMessage(toolCallId, toolName, toolOutput) {
|
|
89
|
+
const rawOutput = isRecord(toolOutput)
|
|
90
|
+
? toolOutput.type === "tool-result"
|
|
91
|
+
? toolOutput.output
|
|
92
|
+
: "error" in toolOutput
|
|
93
|
+
? toolOutput.error
|
|
94
|
+
: toolOutput
|
|
95
|
+
: toolOutput;
|
|
96
|
+
const artifact = rawOutput instanceof Error ? rawOutput.message : rawOutput;
|
|
97
|
+
const content = typeof artifact === "string"
|
|
98
|
+
? artifact
|
|
99
|
+
: (JSON.stringify(artifact) ?? String(artifact));
|
|
100
|
+
return {
|
|
101
|
+
role: "tool",
|
|
102
|
+
content,
|
|
103
|
+
tool_call_id: toolCallId,
|
|
104
|
+
name: toolName,
|
|
105
|
+
artifact,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function appendToolResultMessage(harnessState, toolCallId, toolName, toolOutput) {
|
|
109
|
+
const message = formatToolResultMessage(toolCallId, toolName, toolOutput);
|
|
110
|
+
harnessState.completedStepMessages.push(message);
|
|
111
|
+
return message;
|
|
112
|
+
}
|
|
82
113
|
function getOpenStepOrRoot(state) {
|
|
83
114
|
let openStep;
|
|
84
115
|
state.stepRunTrees.forEach((stepRt) => {
|
|
@@ -88,6 +119,16 @@ export function LangSmithTelemetry(config) {
|
|
|
88
119
|
});
|
|
89
120
|
return openStep ?? state.rootRunTree;
|
|
90
121
|
}
|
|
122
|
+
async function flushPiDeferredStepRun(state) {
|
|
123
|
+
const workaround = state.experimental_harness?.piAggregateUsageWorkaround;
|
|
124
|
+
if (workaround == null)
|
|
125
|
+
return;
|
|
126
|
+
const stepRunTree = workaround.deferredStepRunTree;
|
|
127
|
+
if (stepRunTree == null)
|
|
128
|
+
return;
|
|
129
|
+
workaround.deferredStepRunTree = undefined;
|
|
130
|
+
await stepRunTree.patchRun({ excludeInputs: true });
|
|
131
|
+
}
|
|
91
132
|
async function finalizeOpenToolRuns(state, opts) {
|
|
92
133
|
const entries = Array.from(state.toolRunTrees.entries());
|
|
93
134
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -158,7 +199,6 @@ export function LangSmithTelemetry(config) {
|
|
|
158
199
|
ai_sdk_method: event.operationId,
|
|
159
200
|
ls_agent_type: _getLsAgentType(parentRunTree),
|
|
160
201
|
ls_model_name: event.modelId,
|
|
161
|
-
ls_provider: event.provider,
|
|
162
202
|
ls_integration: "vercel-ai-sdk-telemetry",
|
|
163
203
|
},
|
|
164
204
|
},
|
|
@@ -178,17 +218,29 @@ export function LangSmithTelemetry(config) {
|
|
|
178
218
|
rootRunTree,
|
|
179
219
|
stepRunTrees: new Map(),
|
|
180
220
|
toolRunTrees: new Map(),
|
|
221
|
+
experimental_harness: event.operationId === "ai.harness"
|
|
222
|
+
? {
|
|
223
|
+
lastStepContent: [],
|
|
224
|
+
completedStepMessages: [],
|
|
225
|
+
toolResults: new Map(),
|
|
226
|
+
piAggregateUsageWorkaround: event.provider === "pi" ? {} : undefined,
|
|
227
|
+
}
|
|
228
|
+
: undefined,
|
|
181
229
|
});
|
|
182
230
|
};
|
|
183
231
|
const onStepStart = async (event) => {
|
|
184
232
|
const state = invocationsByCallId.get(event.callId);
|
|
185
233
|
if (!state)
|
|
186
234
|
return;
|
|
235
|
+
await flushPiDeferredStepRun(state);
|
|
187
236
|
const stepNumber = event.stepNumber ?? 0;
|
|
188
237
|
let inputs = {};
|
|
189
238
|
if (event.recordInputs !== false) {
|
|
190
239
|
if ("messages" in event && event.messages != null) {
|
|
191
|
-
inputs.messages =
|
|
240
|
+
inputs.messages = [
|
|
241
|
+
..._formatMessages(event.messages),
|
|
242
|
+
...(state.experimental_harness?.completedStepMessages ?? []),
|
|
243
|
+
];
|
|
192
244
|
}
|
|
193
245
|
if ("runtimeContext" in event && event.runtimeContext != null) {
|
|
194
246
|
inputs.runtimeContext = event.runtimeContext;
|
|
@@ -209,9 +261,18 @@ export function LangSmithTelemetry(config) {
|
|
|
209
261
|
name: event.provider,
|
|
210
262
|
run_type: "llm",
|
|
211
263
|
inputs,
|
|
212
|
-
extra: {
|
|
264
|
+
extra: {
|
|
265
|
+
metadata: {
|
|
266
|
+
step_number: stepNumber,
|
|
267
|
+
ls_model_name: event.modelId,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
213
270
|
});
|
|
214
271
|
state.stepRunTrees.set(stepNumber, stepRunTree);
|
|
272
|
+
const piUsageWorkaround = state.experimental_harness?.piAggregateUsageWorkaround;
|
|
273
|
+
if (piUsageWorkaround != null) {
|
|
274
|
+
piUsageWorkaround.latestStepRunTree = stepRunTree;
|
|
275
|
+
}
|
|
215
276
|
await stepRunTree.postRun();
|
|
216
277
|
};
|
|
217
278
|
const onLanguageModelCallStart = async (event) => {
|
|
@@ -251,22 +312,25 @@ export function LangSmithTelemetry(config) {
|
|
|
251
312
|
const state = invocationsByCallId.get(event.callId);
|
|
252
313
|
if (!state)
|
|
253
314
|
return;
|
|
254
|
-
const parentRunTree =
|
|
315
|
+
const parentRunTree = state.rootRunTree;
|
|
255
316
|
let inputs = {};
|
|
256
317
|
if (event.recordInputs !== false) {
|
|
257
|
-
|
|
258
|
-
|
|
318
|
+
let toolCallInput = event.toolCall.input;
|
|
319
|
+
if (typeof toolCallInput === "string") {
|
|
320
|
+
try {
|
|
321
|
+
toolCallInput = JSON.parse(toolCallInput);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// The tool argument itself may be a plain string rather than JSON.
|
|
325
|
+
}
|
|
259
326
|
}
|
|
260
|
-
|
|
261
|
-
inputs = {
|
|
327
|
+
if (isRecord(toolCallInput)) {
|
|
328
|
+
inputs = { ...toolCallInput };
|
|
262
329
|
}
|
|
263
|
-
if (
|
|
264
|
-
inputs
|
|
330
|
+
else if (toolCallInput !== undefined) {
|
|
331
|
+
inputs = { input: toolCallInput };
|
|
265
332
|
}
|
|
266
333
|
}
|
|
267
|
-
else {
|
|
268
|
-
inputs = {};
|
|
269
|
-
}
|
|
270
334
|
const toolRunTree = parentRunTree.createChild({
|
|
271
335
|
name: event.toolCall.toolName,
|
|
272
336
|
run_type: "tool",
|
|
@@ -288,14 +352,20 @@ export function LangSmithTelemetry(config) {
|
|
|
288
352
|
const toolRunTree = state.toolRunTrees.get(event.toolCall.toolCallId);
|
|
289
353
|
if (!toolRunTree)
|
|
290
354
|
return;
|
|
291
|
-
state.
|
|
355
|
+
const harnessState = state.experimental_harness;
|
|
356
|
+
if (harnessState != null) {
|
|
357
|
+
harnessState.toolResults.set(event.toolCall.toolCallId, {
|
|
358
|
+
toolName: event.toolCall.toolName,
|
|
359
|
+
output: event.toolOutput,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
292
362
|
let outputs;
|
|
293
363
|
let error;
|
|
294
364
|
if (event.recordOutputs !== false) {
|
|
295
365
|
if (event.toolOutput.type === "tool-result") {
|
|
296
366
|
outputs = { output: event.toolOutput.output };
|
|
297
367
|
}
|
|
298
|
-
else if (event.toolOutput
|
|
368
|
+
else if (isRecord(event.toolOutput) && "error" in event.toolOutput) {
|
|
299
369
|
const err = event.toolOutput.error;
|
|
300
370
|
error = err instanceof Error ? err.message : String(err);
|
|
301
371
|
}
|
|
@@ -305,6 +375,7 @@ export function LangSmithTelemetry(config) {
|
|
|
305
375
|
}
|
|
306
376
|
await toolRunTree.end(outputs, error, Math.floor(toolRunTree.start_time + event.toolExecutionMs));
|
|
307
377
|
await toolRunTree.patchRun({ excludeInputs: true });
|
|
378
|
+
state.toolRunTrees.delete(event.toolCall.toolCallId);
|
|
308
379
|
};
|
|
309
380
|
const onStepFinish = async (event) => {
|
|
310
381
|
const state = invocationsByCallId.get(event.callId);
|
|
@@ -314,8 +385,12 @@ export function LangSmithTelemetry(config) {
|
|
|
314
385
|
const stepRunTree = state.stepRunTrees.get(stepNumber);
|
|
315
386
|
if (!stepRunTree)
|
|
316
387
|
return;
|
|
388
|
+
const harnessState = state.experimental_harness;
|
|
317
389
|
let outputs = {};
|
|
318
390
|
if (event.recordOutputs !== false) {
|
|
391
|
+
if (harnessState != null && Array.isArray(event.content)) {
|
|
392
|
+
harnessState.lastStepContent = [...event.content];
|
|
393
|
+
}
|
|
319
394
|
outputs = _formatStepOutput(event, traceRawHttp);
|
|
320
395
|
if (processChildLLMRunOutputs) {
|
|
321
396
|
try {
|
|
@@ -326,11 +401,38 @@ export function LangSmithTelemetry(config) {
|
|
|
326
401
|
}
|
|
327
402
|
}
|
|
328
403
|
}
|
|
404
|
+
if (harnessState != null && Array.isArray(event.content)) {
|
|
405
|
+
harnessState.completedStepMessages.push(convertMessageToTracedFormat({
|
|
406
|
+
role: "assistant",
|
|
407
|
+
content: event.content,
|
|
408
|
+
}));
|
|
409
|
+
for (const part of event.content) {
|
|
410
|
+
if (!isRecord(part) ||
|
|
411
|
+
part.type !== "tool-call" ||
|
|
412
|
+
typeof part.toolCallId !== "string") {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const toolResult = harnessState.toolResults.get(part.toolCallId);
|
|
416
|
+
if (toolResult) {
|
|
417
|
+
appendToolResultMessage(harnessState, part.toolCallId, toolResult.toolName, toolResult.output);
|
|
418
|
+
harnessState.toolResults.delete(part.toolCallId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
329
422
|
// Set usage metadata
|
|
330
423
|
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
331
424
|
setUsageMetadataOnRunTree(event, stepRunTree);
|
|
332
425
|
await stepRunTree.end(outputs);
|
|
333
|
-
|
|
426
|
+
const piUsageWorkaround = harnessState?.piAggregateUsageWorkaround;
|
|
427
|
+
if (piUsageWorkaround != null && !_hasNonzeroUsageMetadata(stepRunTree)) {
|
|
428
|
+
// Pi reports zero usage for inferred step boundaries and sends the real
|
|
429
|
+
// aggregate at turn end. Defer the final PATCH so that aggregate can be
|
|
430
|
+
// attached to the last LLM run without issuing a duplicate update.
|
|
431
|
+
piUsageWorkaround.deferredStepRunTree = stepRunTree;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
await stepRunTree.patchRun({ excludeInputs: true });
|
|
435
|
+
}
|
|
334
436
|
state.stepRunTrees.delete(stepNumber);
|
|
335
437
|
};
|
|
336
438
|
const onEnd = async (event) => {
|
|
@@ -340,6 +442,8 @@ export function LangSmithTelemetry(config) {
|
|
|
340
442
|
if (!state)
|
|
341
443
|
return;
|
|
342
444
|
const { rootRunTree } = state;
|
|
445
|
+
const harnessState = state.experimental_harness;
|
|
446
|
+
const piUsageWorkaround = harnessState?.piAggregateUsageWorkaround;
|
|
343
447
|
await finalizeOpenToolRuns(state, { note: "closed on finish" });
|
|
344
448
|
// Ensure any remaining step runs are closed
|
|
345
449
|
const remainingSteps = Array.from(state.stepRunTrees.entries());
|
|
@@ -347,7 +451,12 @@ export function LangSmithTelemetry(config) {
|
|
|
347
451
|
const [stepNumber, stepRt] = remainingSteps[i];
|
|
348
452
|
if (stepRt.end_time == null) {
|
|
349
453
|
await stepRt.end({ note: "closed on finish" });
|
|
350
|
-
|
|
454
|
+
if (piUsageWorkaround != null && !_hasNonzeroUsageMetadata(stepRt)) {
|
|
455
|
+
piUsageWorkaround.deferredStepRunTree = stepRt;
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
await stepRt.patchRun({ excludeInputs: true });
|
|
459
|
+
}
|
|
351
460
|
}
|
|
352
461
|
state.stepRunTrees.delete(stepNumber);
|
|
353
462
|
}
|
|
@@ -357,9 +466,18 @@ export function LangSmithTelemetry(config) {
|
|
|
357
466
|
if ("text" in event && event.text != null) {
|
|
358
467
|
outputs.content = event.text;
|
|
359
468
|
}
|
|
360
|
-
else if ("content" in event &&
|
|
469
|
+
else if ("content" in event &&
|
|
470
|
+
event.content != null &&
|
|
471
|
+
(!Array.isArray(event.content) || event.content.length > 0)) {
|
|
361
472
|
outputs.content = event.content;
|
|
362
473
|
}
|
|
474
|
+
else if (harnessState != null &&
|
|
475
|
+
harnessState.lastStepContent.length > 0) {
|
|
476
|
+
// HarnessAgent currently reports an empty root `content` array even
|
|
477
|
+
// though each completed step contains assistant output. Only the final
|
|
478
|
+
// step belongs on the root; prior steps have their own child runs.
|
|
479
|
+
outputs.content = harnessState.lastStepContent;
|
|
480
|
+
}
|
|
363
481
|
if (outputs.content != null) {
|
|
364
482
|
outputs.role = "assistant";
|
|
365
483
|
}
|
|
@@ -391,18 +509,28 @@ export function LangSmithTelemetry(config) {
|
|
|
391
509
|
}
|
|
392
510
|
}
|
|
393
511
|
}
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
512
|
+
// Pi reports zero usage on inferred step boundaries and provides the actual
|
|
513
|
+
// aggregate only when the turn ends. Keep that usage on an LLM run rather
|
|
514
|
+
// than incorrectly elevating it to the parent chain. The latest step is the
|
|
515
|
+
// only accurate place available until Harness Pi exposes per-step usage.
|
|
516
|
+
const latestStep = piUsageWorkaround?.latestStepRunTree;
|
|
517
|
+
if (latestStep?.run_type === "llm" &&
|
|
518
|
+
!_hasNonzeroUsageMetadata(latestStep)) {
|
|
519
|
+
if ("totalUsage" in event && event.totalUsage != null) {
|
|
520
|
+
setUsageMetadataOnRunTree(
|
|
521
|
+
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
522
|
+
{ usage: event.totalUsage, providerMetadata: event.providerMetadata }, latestStep);
|
|
523
|
+
}
|
|
524
|
+
else if ("usage" in event && event.usage != null) {
|
|
525
|
+
// @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
|
|
526
|
+
setUsageMetadataOnRunTree(event, latestStep);
|
|
527
|
+
}
|
|
403
528
|
}
|
|
404
529
|
await rootRunTree.end(outputs);
|
|
405
|
-
await
|
|
530
|
+
await Promise.all([
|
|
531
|
+
flushPiDeferredStepRun(state),
|
|
532
|
+
rootRunTree.patchRun({ excludeInputs: true }),
|
|
533
|
+
]);
|
|
406
534
|
invocationsByCallId.delete(event.callId);
|
|
407
535
|
};
|
|
408
536
|
const onError = async (payload) => {
|
|
@@ -433,6 +561,7 @@ export function LangSmithTelemetry(config) {
|
|
|
433
561
|
}
|
|
434
562
|
state.stepRunTrees.delete(stepNumber);
|
|
435
563
|
}
|
|
564
|
+
await flushPiDeferredStepRun(state);
|
|
436
565
|
await rootRunTree.end(undefined, errorMsg);
|
|
437
566
|
await rootRunTree.patchRun({ excludeInputs: true });
|
|
438
567
|
invocationsByCallId.delete(callId);
|