langsmith 0.8.5 → 0.8.6
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 +44 -20
- package/dist/sandbox/sandbox.js +45 -21
- 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);
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,6 @@ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, g
|
|
|
35
35
|
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return error_js_1.RateLimitError; } });
|
|
36
36
|
Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return error_js_1.InternalServerError; } });
|
|
37
37
|
// Update using pnpm bump-version
|
|
38
|
-
exports.__version__ = "0.8.
|
|
38
|
+
exports.__version__ = "0.8.6";
|
|
39
39
|
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
40
40
|
exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.j
|
|
|
7
7
|
export { isTracingEnabled } from "./utils/guard.js";
|
|
8
8
|
export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
9
9
|
export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
|
|
10
|
-
export declare const __version__ = "0.8.
|
|
10
|
+
export declare const __version__ = "0.8.6";
|
|
11
11
|
export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,6 @@ export { isTracingEnabled } from "./utils/guard.js";
|
|
|
7
7
|
export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
8
8
|
export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
|
|
9
9
|
// Update using pnpm bump-version
|
|
10
|
-
export const __version__ = "0.8.
|
|
10
|
+
export const __version__ = "0.8.6";
|
|
11
11
|
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
12
12
|
export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
|
@@ -147,7 +147,7 @@ class CommandHandle {
|
|
|
147
147
|
return;
|
|
148
148
|
const firstResult = await this._stream.next();
|
|
149
149
|
if (firstResult.done) {
|
|
150
|
-
throw new errors_js_1.
|
|
150
|
+
throw new errors_js_1.LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
|
|
151
151
|
}
|
|
152
152
|
const firstMsg = firstResult.value;
|
|
153
153
|
if (firstMsg.type !== "started") {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Port of Python's AsyncCommandHandle to TypeScript.
|
|
6
6
|
*/
|
|
7
|
-
import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, } from "./errors.js";
|
|
7
|
+
import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
|
|
8
8
|
/**
|
|
9
9
|
* Async handle to a running command with streaming output and auto-reconnect.
|
|
10
10
|
*
|
|
@@ -144,7 +144,7 @@ export class CommandHandle {
|
|
|
144
144
|
return;
|
|
145
145
|
const firstResult = await this._stream.next();
|
|
146
146
|
if (firstResult.done) {
|
|
147
|
-
throw new
|
|
147
|
+
throw new LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
|
|
148
148
|
}
|
|
149
149
|
const firstMsg = firstResult.value;
|
|
150
150
|
if (firstMsg.type !== "started") {
|
package/dist/sandbox/errors.cjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* properties for specific handling when needed.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.LangSmithSandboxServerReloadError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
|
|
10
|
+
exports.LangSmithSandboxServerReloadError = exports.LangSmithStreamEndedBeforeStartedError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
|
|
11
11
|
/**
|
|
12
12
|
* Base exception for sandbox client errors.
|
|
13
13
|
*/
|
|
@@ -357,6 +357,21 @@ class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
exports.LangSmithCommandTimeoutError = LangSmithCommandTimeoutError;
|
|
360
|
+
/**
|
|
361
|
+
* Internal: a command WebSocket closed before the guest sent its 'started'
|
|
362
|
+
* frame (the proxied tunnel was torn down gracefully mid-handshake).
|
|
363
|
+
*
|
|
364
|
+
* Marks the idempotently-retryable early close, as distinct from a
|
|
365
|
+
* command-level failure. Subclasses the operation error so callers that catch
|
|
366
|
+
* the public type are unaffected.
|
|
367
|
+
*/
|
|
368
|
+
class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
|
|
369
|
+
constructor(message) {
|
|
370
|
+
super(message, "command");
|
|
371
|
+
this.name = "LangSmithStreamEndedBeforeStartedError";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
exports.LangSmithStreamEndedBeforeStartedError = LangSmithStreamEndedBeforeStartedError;
|
|
360
375
|
/**
|
|
361
376
|
* Raised when the sandbox server is reloading (close code 1001).
|
|
362
377
|
*
|
package/dist/sandbox/errors.d.ts
CHANGED
|
@@ -154,6 +154,17 @@ export declare class LangSmithCommandTimeoutError extends LangSmithSandboxOperat
|
|
|
154
154
|
timeout?: number;
|
|
155
155
|
constructor(message: string, timeout?: number);
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Internal: a command WebSocket closed before the guest sent its 'started'
|
|
159
|
+
* frame (the proxied tunnel was torn down gracefully mid-handshake).
|
|
160
|
+
*
|
|
161
|
+
* Marks the idempotently-retryable early close, as distinct from a
|
|
162
|
+
* command-level failure. Subclasses the operation error so callers that catch
|
|
163
|
+
* the public type are unaffected.
|
|
164
|
+
*/
|
|
165
|
+
export declare class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
|
|
166
|
+
constructor(message: string);
|
|
167
|
+
}
|
|
157
168
|
/**
|
|
158
169
|
* Raised when the sandbox server is reloading (close code 1001).
|
|
159
170
|
*
|
package/dist/sandbox/errors.js
CHANGED
|
@@ -337,6 +337,20 @@ export class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError
|
|
|
337
337
|
this.timeout = timeout;
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Internal: a command WebSocket closed before the guest sent its 'started'
|
|
342
|
+
* frame (the proxied tunnel was torn down gracefully mid-handshake).
|
|
343
|
+
*
|
|
344
|
+
* Marks the idempotently-retryable early close, as distinct from a
|
|
345
|
+
* command-level failure. Subclasses the operation error so callers that catch
|
|
346
|
+
* the public type are unaffected.
|
|
347
|
+
*/
|
|
348
|
+
export class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
|
|
349
|
+
constructor(message) {
|
|
350
|
+
super(message, "command");
|
|
351
|
+
this.name = "LangSmithStreamEndedBeforeStartedError";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
340
354
|
/**
|
|
341
355
|
* Raised when the sandbox server is reloading (close code 1001).
|
|
342
356
|
*
|
package/dist/sandbox/sandbox.cjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Sandbox = void 0;
|
|
7
|
+
const uuid_js_1 = require("../uuid.cjs");
|
|
7
8
|
const errors_js_1 = require("./errors.cjs");
|
|
8
9
|
const helpers_js_1 = require("./helpers.cjs");
|
|
9
10
|
const command_handle_js_1 = require("./command_handle.cjs");
|
|
@@ -229,26 +230,49 @@ class Sandbox {
|
|
|
229
230
|
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
|
|
230
231
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
231
232
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
233
|
+
// A client-supplied command_id makes execute idempotent: the daemon does
|
|
234
|
+
// get-or-create keyed on it, so if the tunnel closes before "started" we
|
|
235
|
+
// can re-issue the same id and reattach to the existing command instead of
|
|
236
|
+
// spawning a second one.
|
|
237
|
+
const execCommandId = commandId ?? (0, uuid_js_1.uuid7)();
|
|
238
|
+
let attempt = 0;
|
|
239
|
+
// eslint-disable-next-line no-constant-condition
|
|
240
|
+
while (true) {
|
|
241
|
+
const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
|
|
242
|
+
timeout,
|
|
243
|
+
env,
|
|
244
|
+
cwd,
|
|
245
|
+
shell,
|
|
246
|
+
commandId: execCommandId,
|
|
247
|
+
idleTimeout,
|
|
248
|
+
killOnDisconnect,
|
|
249
|
+
ttlSeconds,
|
|
250
|
+
pty,
|
|
251
|
+
...(Object.keys(clientHeaders).length > 0
|
|
252
|
+
? { headers: clientHeaders }
|
|
253
|
+
: {}),
|
|
254
|
+
});
|
|
255
|
+
const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
|
|
256
|
+
onStdout,
|
|
257
|
+
onStderr,
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
await handle._ensureStarted();
|
|
261
|
+
return handle;
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
// Idempotent re-issue (same command_id) after an early close.
|
|
265
|
+
if (!(e instanceof errors_js_1.LangSmithStreamEndedBeforeStartedError)) {
|
|
266
|
+
throw e;
|
|
267
|
+
}
|
|
268
|
+
attempt++;
|
|
269
|
+
if (attempt > command_handle_js_1.CommandHandle.MAX_AUTO_RECONNECTS) {
|
|
270
|
+
throw e;
|
|
271
|
+
}
|
|
272
|
+
const delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
|
|
273
|
+
await new Promise((r) => setTimeout(r, delay * 1000));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
252
276
|
}
|
|
253
277
|
/**
|
|
254
278
|
* Execute a command via HTTP POST (blocking).
|
package/dist/sandbox/sandbox.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sandbox class for interacting with a specific sandbox instance.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { uuid7 } from "../uuid.js";
|
|
5
|
+
import { LangSmithDataplaneNotConfiguredError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
|
|
5
6
|
import { handleSandboxHttpError } from "./helpers.js";
|
|
6
7
|
import { CommandHandle } from "./command_handle.js";
|
|
7
8
|
import { reconnectWsStream, runWsStream } from "./ws_execute.js";
|
|
@@ -226,26 +227,49 @@ export class Sandbox {
|
|
|
226
227
|
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
|
|
227
228
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
228
229
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
230
|
+
// A client-supplied command_id makes execute idempotent: the daemon does
|
|
231
|
+
// get-or-create keyed on it, so if the tunnel closes before "started" we
|
|
232
|
+
// can re-issue the same id and reattach to the existing command instead of
|
|
233
|
+
// spawning a second one.
|
|
234
|
+
const execCommandId = commandId ?? uuid7();
|
|
235
|
+
let attempt = 0;
|
|
236
|
+
// eslint-disable-next-line no-constant-condition
|
|
237
|
+
while (true) {
|
|
238
|
+
const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
|
|
239
|
+
timeout,
|
|
240
|
+
env,
|
|
241
|
+
cwd,
|
|
242
|
+
shell,
|
|
243
|
+
commandId: execCommandId,
|
|
244
|
+
idleTimeout,
|
|
245
|
+
killOnDisconnect,
|
|
246
|
+
ttlSeconds,
|
|
247
|
+
pty,
|
|
248
|
+
...(Object.keys(clientHeaders).length > 0
|
|
249
|
+
? { headers: clientHeaders }
|
|
250
|
+
: {}),
|
|
251
|
+
});
|
|
252
|
+
const handle = new CommandHandle(stream, control, this, {
|
|
253
|
+
onStdout,
|
|
254
|
+
onStderr,
|
|
255
|
+
});
|
|
256
|
+
try {
|
|
257
|
+
await handle._ensureStarted();
|
|
258
|
+
return handle;
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
// Idempotent re-issue (same command_id) after an early close.
|
|
262
|
+
if (!(e instanceof LangSmithStreamEndedBeforeStartedError)) {
|
|
263
|
+
throw e;
|
|
264
|
+
}
|
|
265
|
+
attempt++;
|
|
266
|
+
if (attempt > CommandHandle.MAX_AUTO_RECONNECTS) {
|
|
267
|
+
throw e;
|
|
268
|
+
}
|
|
269
|
+
const delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
|
|
270
|
+
await new Promise((r) => setTimeout(r, delay * 1000));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
249
273
|
}
|
|
250
274
|
/**
|
|
251
275
|
* Execute a command via HTTP POST (blocking).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.evaluatorLogFeedbackPromises = exports.testWrapperAsyncLocalStorageInstance = exports.DEFAULT_TEST_CLIENT = void 0;
|
|
4
4
|
exports.trackingEnabled = trackingEnabled;
|
|
5
5
|
exports._logTestFeedback = _logTestFeedback;
|
|
6
6
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
@@ -19,7 +19,6 @@ function trackingEnabled(context) {
|
|
|
19
19
|
return (0, env_js_2.isEnvTracingEnabled)();
|
|
20
20
|
}
|
|
21
21
|
exports.evaluatorLogFeedbackPromises = new Set();
|
|
22
|
-
exports.syncExamplePromises = new Map();
|
|
23
22
|
function _logTestFeedback(params) {
|
|
24
23
|
const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
|
|
25
24
|
if (trackingEnabled(context)) {
|
|
@@ -33,7 +32,7 @@ function _logTestFeedback(params) {
|
|
|
33
32
|
if (context.project == null) {
|
|
34
33
|
throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
|
|
35
34
|
}
|
|
36
|
-
await
|
|
35
|
+
await context.syncExamplePromises?.get(exampleId);
|
|
37
36
|
await client?.logEvaluationFeedback({
|
|
38
37
|
evaluatorResponse: feedback,
|
|
39
38
|
run: runTree,
|
|
@@ -20,11 +20,11 @@ export type TestWrapperAsyncLocalStorageData = {
|
|
|
20
20
|
suiteName: string;
|
|
21
21
|
testRootRunTree?: RunTree;
|
|
22
22
|
setupPromise?: Promise<void>;
|
|
23
|
+
syncExamplePromises?: Map<string, Promise<Example>>;
|
|
23
24
|
};
|
|
24
25
|
export declare const testWrapperAsyncLocalStorageInstance: AsyncLocalStorage<TestWrapperAsyncLocalStorageData>;
|
|
25
26
|
export declare function trackingEnabled(context: TestWrapperAsyncLocalStorageData): boolean;
|
|
26
27
|
export declare const evaluatorLogFeedbackPromises: Set<unknown>;
|
|
27
|
-
export declare const syncExamplePromises: Map<any, any>;
|
|
28
28
|
export declare function _logTestFeedback(params: {
|
|
29
29
|
exampleId?: string;
|
|
30
30
|
feedback: SimpleEvaluationResult;
|
|
@@ -14,7 +14,6 @@ export function trackingEnabled(context) {
|
|
|
14
14
|
return isEnvTracingEnabled();
|
|
15
15
|
}
|
|
16
16
|
export const evaluatorLogFeedbackPromises = new Set();
|
|
17
|
-
export const syncExamplePromises = new Map();
|
|
18
17
|
export function _logTestFeedback(params) {
|
|
19
18
|
const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
|
|
20
19
|
if (trackingEnabled(context)) {
|
|
@@ -28,7 +27,7 @@ export function _logTestFeedback(params) {
|
|
|
28
27
|
if (context.project == null) {
|
|
29
28
|
throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
|
|
30
29
|
}
|
|
31
|
-
await syncExamplePromises
|
|
30
|
+
await context.syncExamplePromises?.get(exampleId);
|
|
32
31
|
await client?.logEvaluationFeedback({
|
|
33
32
|
evaluatorResponse: feedback,
|
|
34
33
|
run: runTree,
|
|
@@ -300,6 +300,7 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
300
300
|
},
|
|
301
301
|
enableTestTracking: experimentConfig?.enableTestTracking,
|
|
302
302
|
setupPromise,
|
|
303
|
+
syncExamplePromises: new Map(),
|
|
303
304
|
};
|
|
304
305
|
beforeAll(async () => {
|
|
305
306
|
const storageValue = await runDatasetSetup(context);
|
|
@@ -307,15 +308,14 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
307
308
|
setupPromiseResolver();
|
|
308
309
|
});
|
|
309
310
|
afterAll(async () => {
|
|
311
|
+
const examples = await Promise.all(context.syncExamplePromises.values());
|
|
310
312
|
await Promise.all([
|
|
311
313
|
client.awaitPendingTraceBatches(),
|
|
312
|
-
...globals_js_1.syncExamplePromises.values(),
|
|
313
314
|
...globals_js_1.evaluatorLogFeedbackPromises.values(),
|
|
314
315
|
]);
|
|
315
316
|
if (!(0, globals_js_1.trackingEnabled)(context)) {
|
|
316
317
|
return;
|
|
317
318
|
}
|
|
318
|
-
const examples = [...globals_js_1.syncExamplePromises.values()];
|
|
319
319
|
if (examples.length === 0) {
|
|
320
320
|
return;
|
|
321
321
|
}
|
|
@@ -364,6 +364,7 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
364
364
|
await client.updateProject(datasetInfo.project.id, {
|
|
365
365
|
metadata: {
|
|
366
366
|
...suiteMetadata,
|
|
367
|
+
dataset_version: finalModifiedAt,
|
|
367
368
|
commit,
|
|
368
369
|
branch,
|
|
369
370
|
dirty,
|
|
@@ -546,8 +547,11 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
546
547
|
// Currently run end time has to be after example modified time
|
|
547
548
|
// for examples to render properly, so we must modify the example
|
|
548
549
|
// first before running the test.
|
|
549
|
-
if (
|
|
550
|
-
|
|
550
|
+
if (context.syncExamplePromises === undefined) {
|
|
551
|
+
throw new Error("Internal error: syncExamplePromises not initialized. This is a bug in the LangSmith SDK — please open an issue at https://github.com/langchain-ai/langsmith-sdk/issues.");
|
|
552
|
+
}
|
|
553
|
+
if (!context.syncExamplePromises.has(exampleId)) {
|
|
554
|
+
const syncPromise = syncExample({
|
|
551
555
|
client,
|
|
552
556
|
exampleId,
|
|
553
557
|
datasetId: dataset.id,
|
|
@@ -556,8 +560,10 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
556
560
|
metadata,
|
|
557
561
|
split,
|
|
558
562
|
createdAt,
|
|
559
|
-
})
|
|
563
|
+
});
|
|
564
|
+
context.syncExamplePromises.set(exampleId, syncPromise);
|
|
560
565
|
}
|
|
566
|
+
await context.syncExamplePromises.get(exampleId);
|
|
561
567
|
const traceableOptions = {
|
|
562
568
|
reference_example_id: exampleId,
|
|
563
569
|
project_name: project.name,
|
|
@@ -9,7 +9,7 @@ import { execSync } from "child_process";
|
|
|
9
9
|
import { getCurrentRunTree, traceable } from "../../traceable.js";
|
|
10
10
|
import { randomName } from "../../evaluation/_random_name.js";
|
|
11
11
|
import { toBeRelativeCloseTo, toBeAbsoluteCloseTo, toBeSemanticCloseTo, } from "./matchers.js";
|
|
12
|
-
import { evaluatorLogFeedbackPromises, testWrapperAsyncLocalStorageInstance, _logTestFeedback,
|
|
12
|
+
import { evaluatorLogFeedbackPromises, testWrapperAsyncLocalStorageInstance, _logTestFeedback, trackingEnabled, DEFAULT_TEST_CLIENT, } from "./globals.js";
|
|
13
13
|
import { wrapExpect } from "./vendor/chain.js";
|
|
14
14
|
import { getEnvironmentVariable, isJsDom } from "../env.js";
|
|
15
15
|
import { STRIP_ANSI_REGEX, TEST_ID_DELIMITER, UUID5_NAMESPACE, } from "./constants.js";
|
|
@@ -253,6 +253,7 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
253
253
|
},
|
|
254
254
|
enableTestTracking: experimentConfig?.enableTestTracking,
|
|
255
255
|
setupPromise,
|
|
256
|
+
syncExamplePromises: new Map(),
|
|
256
257
|
};
|
|
257
258
|
beforeAll(async () => {
|
|
258
259
|
const storageValue = await runDatasetSetup(context);
|
|
@@ -260,15 +261,14 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
260
261
|
setupPromiseResolver();
|
|
261
262
|
});
|
|
262
263
|
afterAll(async () => {
|
|
264
|
+
const examples = await Promise.all(context.syncExamplePromises.values());
|
|
263
265
|
await Promise.all([
|
|
264
266
|
client.awaitPendingTraceBatches(),
|
|
265
|
-
...syncExamplePromises.values(),
|
|
266
267
|
...evaluatorLogFeedbackPromises.values(),
|
|
267
268
|
]);
|
|
268
269
|
if (!trackingEnabled(context)) {
|
|
269
270
|
return;
|
|
270
271
|
}
|
|
271
|
-
const examples = [...syncExamplePromises.values()];
|
|
272
272
|
if (examples.length === 0) {
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
@@ -317,6 +317,7 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
317
317
|
await client.updateProject(datasetInfo.project.id, {
|
|
318
318
|
metadata: {
|
|
319
319
|
...suiteMetadata,
|
|
320
|
+
dataset_version: finalModifiedAt,
|
|
320
321
|
commit,
|
|
321
322
|
branch,
|
|
322
323
|
dirty,
|
|
@@ -499,8 +500,11 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
499
500
|
// Currently run end time has to be after example modified time
|
|
500
501
|
// for examples to render properly, so we must modify the example
|
|
501
502
|
// first before running the test.
|
|
502
|
-
if (syncExamplePromises
|
|
503
|
-
syncExamplePromises.
|
|
503
|
+
if (context.syncExamplePromises === undefined) {
|
|
504
|
+
throw new Error("Internal error: syncExamplePromises not initialized. This is a bug in the LangSmith SDK — please open an issue at https://github.com/langchain-ai/langsmith-sdk/issues.");
|
|
505
|
+
}
|
|
506
|
+
if (!context.syncExamplePromises.has(exampleId)) {
|
|
507
|
+
const syncPromise = syncExample({
|
|
504
508
|
client,
|
|
505
509
|
exampleId,
|
|
506
510
|
datasetId: dataset.id,
|
|
@@ -509,8 +513,10 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
|
|
|
509
513
|
metadata,
|
|
510
514
|
split,
|
|
511
515
|
createdAt,
|
|
512
|
-
})
|
|
516
|
+
});
|
|
517
|
+
context.syncExamplePromises.set(exampleId, syncPromise);
|
|
513
518
|
}
|
|
519
|
+
await context.syncExamplePromises.get(exampleId);
|
|
514
520
|
const traceableOptions = {
|
|
515
521
|
reference_example_id: exampleId,
|
|
516
522
|
project_name: project.name,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langsmith",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
|
|
5
5
|
"packageManager": "pnpm@10.33.0",
|
|
6
6
|
"files": [
|
|
@@ -156,12 +156,18 @@
|
|
|
156
156
|
"p-queue": "6.6.2"
|
|
157
157
|
},
|
|
158
158
|
"devDependencies": {
|
|
159
|
+
"@ai-sdk/anthropic": "4.0.18",
|
|
160
|
+
"@ai-sdk/harness": "1.0.41",
|
|
161
|
+
"@ai-sdk/harness-claude-code": "1.0.42",
|
|
162
|
+
"@ai-sdk/harness-pi": "1.0.41",
|
|
159
163
|
"@ai-sdk/openai": "4.0.4",
|
|
160
164
|
"@ai-sdk/provider": "4.0.1",
|
|
161
|
-
"@ai-sdk/
|
|
165
|
+
"@ai-sdk/sandbox-just-bash": "1.0.40",
|
|
166
|
+
"@ai-sdk/sandbox-vercel": "1.0.41",
|
|
162
167
|
"@anthropic-ai/claude-agent-sdk": "^0.3.150",
|
|
163
168
|
"@anthropic-ai/sdk": "^0.98.0",
|
|
164
169
|
"@babel/preset-env": "^8.0.2",
|
|
170
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
165
171
|
"@faker-js/faker": "^8.4.1",
|
|
166
172
|
"@google/genai": "^2.0.1",
|
|
167
173
|
"@jest/globals": "^30.4.1",
|
|
@@ -184,7 +190,7 @@
|
|
|
184
190
|
"@types/ws": "^8.18.1",
|
|
185
191
|
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
|
186
192
|
"@typescript-eslint/parser": "^8.62.1",
|
|
187
|
-
"ai": "7.0.
|
|
193
|
+
"ai": "7.0.36",
|
|
188
194
|
"babel-jest": "^30.2.0",
|
|
189
195
|
"cross-env": "^10.1.0",
|
|
190
196
|
"dotenv": "^17.3.1",
|