langsmith 0.3.26 → 0.3.28-rc.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/client.cjs +2 -2
- package/dist/client.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/error.cjs +10 -1
- package/dist/utils/error.d.ts +1 -0
- package/dist/utils/error.js +10 -1
- package/dist/vercel.cjs +133 -45
- package/dist/vercel.d.ts +7 -1
- package/dist/vercel.js +133 -45
- package/dist/wrappers/openai.cjs +1 -1
- package/dist/wrappers/openai.d.ts +1 -1
- package/dist/wrappers/openai.js +1 -1
- package/package.json +1 -1
package/dist/client.cjs
CHANGED
|
@@ -662,7 +662,7 @@ class Client {
|
|
|
662
662
|
return itemPromise;
|
|
663
663
|
}
|
|
664
664
|
async _getServerInfo() {
|
|
665
|
-
const response = await (0, fetch_js_1._getFetchImplementation)(this.debug)
|
|
665
|
+
const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/info`, {
|
|
666
666
|
method: "GET",
|
|
667
667
|
headers: { Accept: "application/json" },
|
|
668
668
|
signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT),
|
|
@@ -685,7 +685,7 @@ class Client {
|
|
|
685
685
|
this._serverInfo = await this._getServerInfo();
|
|
686
686
|
}
|
|
687
687
|
catch (e) {
|
|
688
|
-
console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to batch operations and default limits.`);
|
|
688
|
+
console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations with status code ${e.status}. Falling back to batch operations and default limits.`);
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
691
|
return this._serverInfo ?? {};
|
package/dist/client.js
CHANGED
|
@@ -624,7 +624,7 @@ export class Client {
|
|
|
624
624
|
return itemPromise;
|
|
625
625
|
}
|
|
626
626
|
async _getServerInfo() {
|
|
627
|
-
const response = await _getFetchImplementation(this.debug)
|
|
627
|
+
const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/info`, {
|
|
628
628
|
method: "GET",
|
|
629
629
|
headers: { Accept: "application/json" },
|
|
630
630
|
signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT),
|
|
@@ -647,7 +647,7 @@ export class Client {
|
|
|
647
647
|
this._serverInfo = await this._getServerInfo();
|
|
648
648
|
}
|
|
649
649
|
catch (e) {
|
|
650
|
-
console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to batch operations and default limits.`);
|
|
650
|
+
console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations with status code ${e.status}. Falling back to batch operations and default limits.`);
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
653
|
return this._serverInfo ?? {};
|
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () {
|
|
|
8
8
|
var fetch_js_1 = require("./singletons/fetch.cjs");
|
|
9
9
|
Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true, get: function () { return fetch_js_1.overrideFetchImplementation; } });
|
|
10
10
|
// Update using yarn bump-version
|
|
11
|
-
exports.__version__ = "0.3.
|
|
11
|
+
exports.__version__ = "0.3.28-rc.0";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { Client, type ClientConfig, type LangSmithTracingClientInterface, } from
|
|
|
2
2
|
export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, } from "./schemas.js";
|
|
3
3
|
export { RunTree, type RunTreeConfig } from "./run_trees.js";
|
|
4
4
|
export { overrideFetchImplementation } from "./singletons/fetch.js";
|
|
5
|
-
export declare const __version__ = "0.3.
|
|
5
|
+
export declare const __version__ = "0.3.28-rc.0";
|
package/dist/index.js
CHANGED
package/dist/utils/error.cjs
CHANGED
|
@@ -58,7 +58,14 @@ function printErrorStackTrace(e) {
|
|
|
58
58
|
class LangSmithConflictError extends Error {
|
|
59
59
|
constructor(message) {
|
|
60
60
|
super(message);
|
|
61
|
+
Object.defineProperty(this, "status", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: void 0
|
|
66
|
+
});
|
|
61
67
|
this.name = "LangSmithConflictError";
|
|
68
|
+
this.status = 409;
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
exports.LangSmithConflictError = LangSmithConflictError;
|
|
@@ -85,5 +92,7 @@ async function raiseForStatus(response, context, consume) {
|
|
|
85
92
|
if (response.status === 409) {
|
|
86
93
|
throw new LangSmithConflictError(fullMessage);
|
|
87
94
|
}
|
|
88
|
-
|
|
95
|
+
const err = new Error(fullMessage);
|
|
96
|
+
err.status = response.status;
|
|
97
|
+
throw err;
|
|
89
98
|
}
|
package/dist/utils/error.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare function printErrorStackTrace(e: unknown): void;
|
|
|
31
31
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
|
|
32
32
|
*/
|
|
33
33
|
export declare class LangSmithConflictError extends Error {
|
|
34
|
+
status: number;
|
|
34
35
|
constructor(message: string);
|
|
35
36
|
}
|
|
36
37
|
/**
|
package/dist/utils/error.js
CHANGED
|
@@ -53,7 +53,14 @@ export function printErrorStackTrace(e) {
|
|
|
53
53
|
export class LangSmithConflictError extends Error {
|
|
54
54
|
constructor(message) {
|
|
55
55
|
super(message);
|
|
56
|
+
Object.defineProperty(this, "status", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: void 0
|
|
61
|
+
});
|
|
56
62
|
this.name = "LangSmithConflictError";
|
|
63
|
+
this.status = 409;
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
/**
|
|
@@ -79,5 +86,7 @@ export async function raiseForStatus(response, context, consume) {
|
|
|
79
86
|
if (response.status === 409) {
|
|
80
87
|
throw new LangSmithConflictError(fullMessage);
|
|
81
88
|
}
|
|
82
|
-
|
|
89
|
+
const err = new Error(fullMessage);
|
|
90
|
+
err.status = response.status;
|
|
91
|
+
throw err;
|
|
83
92
|
}
|
package/dist/vercel.cjs
CHANGED
|
@@ -182,9 +182,21 @@ function convertToTimestamp([seconds, nanoseconds]) {
|
|
|
182
182
|
return Number(String(seconds) + ms);
|
|
183
183
|
}
|
|
184
184
|
function sortByHr(a, b) {
|
|
185
|
-
if (a[0] !== b[0])
|
|
186
|
-
return Math.sign(a[0] - b[0]);
|
|
187
|
-
|
|
185
|
+
if (a.startTime[0] !== b.startTime[0]) {
|
|
186
|
+
return Math.sign(a.startTime[0] - b.startTime[0]);
|
|
187
|
+
}
|
|
188
|
+
else if (a.startTime[1] !== b.startTime[1]) {
|
|
189
|
+
return Math.sign(a.startTime[1] - b.startTime[1]);
|
|
190
|
+
}
|
|
191
|
+
else if (getParentSpanId(a) === b.spanContext().spanId) {
|
|
192
|
+
return -1;
|
|
193
|
+
}
|
|
194
|
+
else if (getParentSpanId(b) === a.spanContext().spanId) {
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
188
200
|
}
|
|
189
201
|
const ROOT = "$";
|
|
190
202
|
const RUN_ID_NAMESPACE = "5c718b20-9078-11ef-9a3d-325096b39f47";
|
|
@@ -269,6 +281,18 @@ class AISDKExporter {
|
|
|
269
281
|
writable: true,
|
|
270
282
|
value: {}
|
|
271
283
|
});
|
|
284
|
+
Object.defineProperty(this, "seenSpanInfo", {
|
|
285
|
+
enumerable: true,
|
|
286
|
+
configurable: true,
|
|
287
|
+
writable: true,
|
|
288
|
+
value: {}
|
|
289
|
+
});
|
|
290
|
+
Object.defineProperty(this, "pendingSpans", {
|
|
291
|
+
enumerable: true,
|
|
292
|
+
configurable: true,
|
|
293
|
+
writable: true,
|
|
294
|
+
value: []
|
|
295
|
+
});
|
|
272
296
|
Object.defineProperty(this, "debug", {
|
|
273
297
|
enumerable: true,
|
|
274
298
|
configurable: true,
|
|
@@ -608,104 +632,165 @@ class AISDKExporter {
|
|
|
608
632
|
return false;
|
|
609
633
|
}
|
|
610
634
|
}
|
|
611
|
-
|
|
635
|
+
_export(spans, resultCallback) {
|
|
612
636
|
this.logDebug("exporting spans", spans);
|
|
613
637
|
const typedSpans = spans
|
|
638
|
+
.concat(this.pendingSpans)
|
|
614
639
|
.slice()
|
|
615
|
-
|
|
640
|
+
// Parent spans should go before child spans in the final order,
|
|
641
|
+
// but may have the same exact start time as their children.
|
|
642
|
+
// They will end earlier, so break ties by end time.
|
|
643
|
+
// TODO: Figure out why this happens.
|
|
644
|
+
.sort((a, b) => sortByHr(a, b));
|
|
645
|
+
// This is really important, as OTEL seems to do weird things with threads.
|
|
646
|
+
// Don't use a while loop.
|
|
647
|
+
this.pendingSpans = [];
|
|
648
|
+
const skippedSpans = [];
|
|
649
|
+
const potentialChildRootRunIds = [];
|
|
616
650
|
for (const span of typedSpans) {
|
|
617
651
|
const { traceId, spanId } = span.spanContext();
|
|
618
|
-
const
|
|
652
|
+
const runId = (0, uuid_1.v5)(spanId, RUN_ID_NAMESPACE);
|
|
653
|
+
let parentId = getParentSpanId(span);
|
|
654
|
+
let parentRunId = parentId
|
|
655
|
+
? (0, uuid_1.v5)(parentId, RUN_ID_NAMESPACE)
|
|
656
|
+
: undefined;
|
|
657
|
+
let parentSpanInfo = parentRunId
|
|
658
|
+
? this.seenSpanInfo[parentRunId]
|
|
659
|
+
: undefined;
|
|
660
|
+
// Unrelated, untraced spans should behave as passthroughs from LangSmith's perspective.
|
|
661
|
+
while (parentSpanInfo != null &&
|
|
662
|
+
this.getRunCreate(parentSpanInfo.span) == null) {
|
|
663
|
+
parentId = getParentSpanId(parentSpanInfo.span);
|
|
664
|
+
if (parentId == null) {
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
parentRunId = parentId ? (0, uuid_1.v5)(parentId, RUN_ID_NAMESPACE) : undefined;
|
|
668
|
+
parentSpanInfo = parentRunId
|
|
669
|
+
? this.seenSpanInfo[parentRunId]
|
|
670
|
+
: undefined;
|
|
671
|
+
}
|
|
672
|
+
// Export may be called in any order, so we need to queue any spans with missing parents
|
|
673
|
+
// for retry later in order to determine whether their parents are tool calls
|
|
674
|
+
// and should not be reparented below.
|
|
675
|
+
if (parentRunId !== undefined && parentSpanInfo === undefined) {
|
|
676
|
+
skippedSpans.push(span);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
619
679
|
this.traceByMap[traceId] ??= {
|
|
620
680
|
childMap: {},
|
|
621
681
|
nodeMap: {},
|
|
622
682
|
relativeExecutionOrder: {},
|
|
623
683
|
};
|
|
624
|
-
const runId = (0, uuid_1.v5)(spanId, RUN_ID_NAMESPACE);
|
|
625
|
-
const parentRunId = parentId
|
|
626
|
-
? (0, uuid_1.v5)(parentId, RUN_ID_NAMESPACE)
|
|
627
|
-
: undefined;
|
|
628
684
|
const traceMap = this.traceByMap[traceId];
|
|
629
685
|
const run = this.getRunCreate(span);
|
|
630
686
|
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] ??= -1;
|
|
631
687
|
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] += 1;
|
|
632
|
-
const parentSpan = parentId
|
|
633
|
-
? typedSpans.find((i) => i.spanContext().spanId === parentId)
|
|
634
|
-
: undefined;
|
|
635
688
|
traceMap.nodeMap[runId] ??= {
|
|
636
689
|
id: runId,
|
|
637
690
|
startTime: span.startTime,
|
|
638
691
|
run,
|
|
639
692
|
sent: false,
|
|
640
|
-
interop: this.parseInteropFromMetadata(span,
|
|
693
|
+
interop: this.parseInteropFromMetadata(span, parentSpanInfo?.span),
|
|
641
694
|
executionOrder: traceMap.relativeExecutionOrder[parentRunId ?? ROOT],
|
|
642
695
|
};
|
|
696
|
+
if (this.seenSpanInfo[runId] == null) {
|
|
697
|
+
this.seenSpanInfo[runId] = {
|
|
698
|
+
span,
|
|
699
|
+
dotOrder: joinDotOrder(parentSpanInfo?.dotOrder, getDotOrder(traceMap.nodeMap[runId])),
|
|
700
|
+
sent: false,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
643
703
|
if (this.debug)
|
|
644
704
|
console.log(`[${span.name}] ${runId}`, run);
|
|
645
705
|
traceMap.childMap[parentRunId ?? ROOT] ??= [];
|
|
646
706
|
traceMap.childMap[parentRunId ?? ROOT].push(traceMap.nodeMap[runId]);
|
|
707
|
+
if (parentRunId != null) {
|
|
708
|
+
potentialChildRootRunIds.push(parentRunId);
|
|
709
|
+
}
|
|
647
710
|
}
|
|
648
711
|
const sampled = [];
|
|
649
712
|
const actions = [];
|
|
650
713
|
for (const traceId of Object.keys(this.traceByMap)) {
|
|
651
714
|
const traceMap = this.traceByMap[traceId];
|
|
652
|
-
const queue = traceMap.childMap
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
715
|
+
const queue = Object.keys(traceMap.childMap)
|
|
716
|
+
.map((runId) => {
|
|
717
|
+
if (runId === ROOT || potentialChildRootRunIds.includes(runId)) {
|
|
718
|
+
return traceMap.childMap[runId];
|
|
719
|
+
}
|
|
720
|
+
return [];
|
|
721
|
+
})
|
|
722
|
+
.flat();
|
|
656
723
|
const seen = new Set();
|
|
657
724
|
while (queue.length) {
|
|
658
725
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
659
726
|
const task = queue.shift();
|
|
660
|
-
if (seen.has(task.
|
|
727
|
+
if (seen.has(task.id))
|
|
661
728
|
continue;
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
729
|
+
let taskDotOrder = this.seenSpanInfo[task.id].dotOrder;
|
|
730
|
+
if (!task.sent) {
|
|
731
|
+
if (task.run != null) {
|
|
732
|
+
if (task.interop?.type === "user") {
|
|
665
733
|
actions.push({
|
|
666
734
|
type: "rename",
|
|
667
|
-
sourceRunId: task.
|
|
668
|
-
targetRunId: task.
|
|
735
|
+
sourceRunId: task.id,
|
|
736
|
+
targetRunId: task.interop.userRunId,
|
|
669
737
|
});
|
|
670
738
|
}
|
|
671
|
-
if (task.
|
|
739
|
+
if (task.interop?.type === "traceable") {
|
|
672
740
|
actions.push({
|
|
673
741
|
type: "reparent",
|
|
674
|
-
runId: task.
|
|
675
|
-
parentDotOrder: task.
|
|
742
|
+
runId: task.id,
|
|
743
|
+
parentDotOrder: task.interop.parentRunTree.dotted_order,
|
|
676
744
|
});
|
|
677
745
|
}
|
|
678
|
-
let dotOrder = task.dotOrder;
|
|
679
746
|
for (const action of actions) {
|
|
680
747
|
if (action.type === "delete") {
|
|
681
|
-
|
|
748
|
+
taskDotOrder = removeDotOrder(taskDotOrder, action.runId);
|
|
682
749
|
}
|
|
683
750
|
if (action.type === "reparent") {
|
|
684
|
-
|
|
751
|
+
taskDotOrder = reparentDotOrder(taskDotOrder, action.runId, action.parentDotOrder);
|
|
685
752
|
}
|
|
686
753
|
if (action.type === "rename") {
|
|
687
|
-
|
|
754
|
+
taskDotOrder = taskDotOrder.replace(action.sourceRunId, action.targetRunId);
|
|
688
755
|
}
|
|
689
756
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
757
|
+
this.seenSpanInfo[task.id].dotOrder = taskDotOrder;
|
|
758
|
+
if (!this.seenSpanInfo[task.id].sent) {
|
|
759
|
+
sampled.push({
|
|
760
|
+
...task.run,
|
|
761
|
+
...getMutableRunCreate(taskDotOrder),
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
this.seenSpanInfo[task.id].sent = true;
|
|
694
765
|
}
|
|
695
766
|
else {
|
|
696
|
-
actions.push({ type: "delete", runId: task.
|
|
767
|
+
actions.push({ type: "delete", runId: task.id });
|
|
697
768
|
}
|
|
698
|
-
task.
|
|
769
|
+
task.sent = true;
|
|
699
770
|
}
|
|
700
|
-
const children = traceMap.childMap[task.
|
|
701
|
-
queue.push(...children
|
|
702
|
-
item,
|
|
703
|
-
dotOrder: joinDotOrder(task.dotOrder, getDotOrder(item)),
|
|
704
|
-
})));
|
|
771
|
+
const children = traceMap.childMap[task.id] ?? [];
|
|
772
|
+
queue.push(...children);
|
|
705
773
|
}
|
|
706
774
|
}
|
|
707
775
|
this.logDebug(`sampled runs to be sent to LangSmith`, sampled);
|
|
708
|
-
Promise.all(sampled.map((run) => this.client.createRun(run)))
|
|
776
|
+
Promise.all(sampled.map((run) => this.client.createRun(run)))
|
|
777
|
+
.then(() => {
|
|
778
|
+
// OTEL seems to do weird things with threads, so we need to queue any spans
|
|
779
|
+
// with missing parents for retry at the end after async operations.
|
|
780
|
+
this.pendingSpans = skippedSpans;
|
|
781
|
+
})
|
|
782
|
+
.then(() => resultCallback({ code: 0 }), (error) => resultCallback({ code: 1, error }));
|
|
783
|
+
}
|
|
784
|
+
export(spans, resultCallback) {
|
|
785
|
+
this._export(spans, (result) => {
|
|
786
|
+
if (result.code === 0) {
|
|
787
|
+
// Empty export to try flushing pending spans to rule out any trace order shenanigans
|
|
788
|
+
this._export([], resultCallback);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
resultCallback(result);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
709
794
|
}
|
|
710
795
|
async shutdown() {
|
|
711
796
|
// find nodes which are incomplete
|
|
@@ -714,10 +799,13 @@ class AISDKExporter {
|
|
|
714
799
|
if (incompleteNodes.length > 0) {
|
|
715
800
|
console.warn("Some incomplete nodes were found before shutdown and not sent to LangSmith.");
|
|
716
801
|
}
|
|
717
|
-
await this.
|
|
802
|
+
await this.forceFlush();
|
|
718
803
|
}
|
|
719
804
|
async forceFlush() {
|
|
720
|
-
await
|
|
805
|
+
await new Promise((resolve) => {
|
|
806
|
+
this.export([], resolve);
|
|
807
|
+
});
|
|
808
|
+
await this.client.awaitPendingTraceBatches();
|
|
721
809
|
}
|
|
722
810
|
logDebug(...args) {
|
|
723
811
|
if (!this.debug)
|
package/dist/vercel.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export interface TelemetrySettings extends AITelemetrySettings {
|
|
|
50
50
|
export declare class AISDKExporter {
|
|
51
51
|
private client;
|
|
52
52
|
private traceByMap;
|
|
53
|
+
private seenSpanInfo;
|
|
54
|
+
private pendingSpans;
|
|
53
55
|
private debug;
|
|
54
56
|
constructor(args?: {
|
|
55
57
|
client?: Client;
|
|
@@ -65,11 +67,15 @@ export declare class AISDKExporter {
|
|
|
65
67
|
functionId?: string;
|
|
66
68
|
tracer?: import("@opentelemetry/api").Tracer;
|
|
67
69
|
};
|
|
70
|
+
_export(spans: unknown[], resultCallback: (result: {
|
|
71
|
+
code: 0 | 1;
|
|
72
|
+
error?: Error;
|
|
73
|
+
}) => void): void;
|
|
68
74
|
export(spans: unknown[], resultCallback: (result: {
|
|
69
75
|
code: 0 | 1;
|
|
70
76
|
error?: Error;
|
|
71
77
|
}) => void): void;
|
|
72
78
|
shutdown(): Promise<void>;
|
|
73
|
-
forceFlush
|
|
79
|
+
forceFlush(): Promise<void>;
|
|
74
80
|
protected logDebug(...args: Parameters<typeof console.debug>): void;
|
|
75
81
|
}
|
package/dist/vercel.js
CHANGED
|
@@ -179,9 +179,21 @@ function convertToTimestamp([seconds, nanoseconds]) {
|
|
|
179
179
|
return Number(String(seconds) + ms);
|
|
180
180
|
}
|
|
181
181
|
function sortByHr(a, b) {
|
|
182
|
-
if (a[0] !== b[0])
|
|
183
|
-
return Math.sign(a[0] - b[0]);
|
|
184
|
-
|
|
182
|
+
if (a.startTime[0] !== b.startTime[0]) {
|
|
183
|
+
return Math.sign(a.startTime[0] - b.startTime[0]);
|
|
184
|
+
}
|
|
185
|
+
else if (a.startTime[1] !== b.startTime[1]) {
|
|
186
|
+
return Math.sign(a.startTime[1] - b.startTime[1]);
|
|
187
|
+
}
|
|
188
|
+
else if (getParentSpanId(a) === b.spanContext().spanId) {
|
|
189
|
+
return -1;
|
|
190
|
+
}
|
|
191
|
+
else if (getParentSpanId(b) === a.spanContext().spanId) {
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
185
197
|
}
|
|
186
198
|
const ROOT = "$";
|
|
187
199
|
const RUN_ID_NAMESPACE = "5c718b20-9078-11ef-9a3d-325096b39f47";
|
|
@@ -266,6 +278,18 @@ export class AISDKExporter {
|
|
|
266
278
|
writable: true,
|
|
267
279
|
value: {}
|
|
268
280
|
});
|
|
281
|
+
Object.defineProperty(this, "seenSpanInfo", {
|
|
282
|
+
enumerable: true,
|
|
283
|
+
configurable: true,
|
|
284
|
+
writable: true,
|
|
285
|
+
value: {}
|
|
286
|
+
});
|
|
287
|
+
Object.defineProperty(this, "pendingSpans", {
|
|
288
|
+
enumerable: true,
|
|
289
|
+
configurable: true,
|
|
290
|
+
writable: true,
|
|
291
|
+
value: []
|
|
292
|
+
});
|
|
269
293
|
Object.defineProperty(this, "debug", {
|
|
270
294
|
enumerable: true,
|
|
271
295
|
configurable: true,
|
|
@@ -605,104 +629,165 @@ export class AISDKExporter {
|
|
|
605
629
|
return false;
|
|
606
630
|
}
|
|
607
631
|
}
|
|
608
|
-
|
|
632
|
+
_export(spans, resultCallback) {
|
|
609
633
|
this.logDebug("exporting spans", spans);
|
|
610
634
|
const typedSpans = spans
|
|
635
|
+
.concat(this.pendingSpans)
|
|
611
636
|
.slice()
|
|
612
|
-
|
|
637
|
+
// Parent spans should go before child spans in the final order,
|
|
638
|
+
// but may have the same exact start time as their children.
|
|
639
|
+
// They will end earlier, so break ties by end time.
|
|
640
|
+
// TODO: Figure out why this happens.
|
|
641
|
+
.sort((a, b) => sortByHr(a, b));
|
|
642
|
+
// This is really important, as OTEL seems to do weird things with threads.
|
|
643
|
+
// Don't use a while loop.
|
|
644
|
+
this.pendingSpans = [];
|
|
645
|
+
const skippedSpans = [];
|
|
646
|
+
const potentialChildRootRunIds = [];
|
|
613
647
|
for (const span of typedSpans) {
|
|
614
648
|
const { traceId, spanId } = span.spanContext();
|
|
615
|
-
const
|
|
649
|
+
const runId = uuid5(spanId, RUN_ID_NAMESPACE);
|
|
650
|
+
let parentId = getParentSpanId(span);
|
|
651
|
+
let parentRunId = parentId
|
|
652
|
+
? uuid5(parentId, RUN_ID_NAMESPACE)
|
|
653
|
+
: undefined;
|
|
654
|
+
let parentSpanInfo = parentRunId
|
|
655
|
+
? this.seenSpanInfo[parentRunId]
|
|
656
|
+
: undefined;
|
|
657
|
+
// Unrelated, untraced spans should behave as passthroughs from LangSmith's perspective.
|
|
658
|
+
while (parentSpanInfo != null &&
|
|
659
|
+
this.getRunCreate(parentSpanInfo.span) == null) {
|
|
660
|
+
parentId = getParentSpanId(parentSpanInfo.span);
|
|
661
|
+
if (parentId == null) {
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
parentRunId = parentId ? uuid5(parentId, RUN_ID_NAMESPACE) : undefined;
|
|
665
|
+
parentSpanInfo = parentRunId
|
|
666
|
+
? this.seenSpanInfo[parentRunId]
|
|
667
|
+
: undefined;
|
|
668
|
+
}
|
|
669
|
+
// Export may be called in any order, so we need to queue any spans with missing parents
|
|
670
|
+
// for retry later in order to determine whether their parents are tool calls
|
|
671
|
+
// and should not be reparented below.
|
|
672
|
+
if (parentRunId !== undefined && parentSpanInfo === undefined) {
|
|
673
|
+
skippedSpans.push(span);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
616
676
|
this.traceByMap[traceId] ??= {
|
|
617
677
|
childMap: {},
|
|
618
678
|
nodeMap: {},
|
|
619
679
|
relativeExecutionOrder: {},
|
|
620
680
|
};
|
|
621
|
-
const runId = uuid5(spanId, RUN_ID_NAMESPACE);
|
|
622
|
-
const parentRunId = parentId
|
|
623
|
-
? uuid5(parentId, RUN_ID_NAMESPACE)
|
|
624
|
-
: undefined;
|
|
625
681
|
const traceMap = this.traceByMap[traceId];
|
|
626
682
|
const run = this.getRunCreate(span);
|
|
627
683
|
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] ??= -1;
|
|
628
684
|
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] += 1;
|
|
629
|
-
const parentSpan = parentId
|
|
630
|
-
? typedSpans.find((i) => i.spanContext().spanId === parentId)
|
|
631
|
-
: undefined;
|
|
632
685
|
traceMap.nodeMap[runId] ??= {
|
|
633
686
|
id: runId,
|
|
634
687
|
startTime: span.startTime,
|
|
635
688
|
run,
|
|
636
689
|
sent: false,
|
|
637
|
-
interop: this.parseInteropFromMetadata(span,
|
|
690
|
+
interop: this.parseInteropFromMetadata(span, parentSpanInfo?.span),
|
|
638
691
|
executionOrder: traceMap.relativeExecutionOrder[parentRunId ?? ROOT],
|
|
639
692
|
};
|
|
693
|
+
if (this.seenSpanInfo[runId] == null) {
|
|
694
|
+
this.seenSpanInfo[runId] = {
|
|
695
|
+
span,
|
|
696
|
+
dotOrder: joinDotOrder(parentSpanInfo?.dotOrder, getDotOrder(traceMap.nodeMap[runId])),
|
|
697
|
+
sent: false,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
640
700
|
if (this.debug)
|
|
641
701
|
console.log(`[${span.name}] ${runId}`, run);
|
|
642
702
|
traceMap.childMap[parentRunId ?? ROOT] ??= [];
|
|
643
703
|
traceMap.childMap[parentRunId ?? ROOT].push(traceMap.nodeMap[runId]);
|
|
704
|
+
if (parentRunId != null) {
|
|
705
|
+
potentialChildRootRunIds.push(parentRunId);
|
|
706
|
+
}
|
|
644
707
|
}
|
|
645
708
|
const sampled = [];
|
|
646
709
|
const actions = [];
|
|
647
710
|
for (const traceId of Object.keys(this.traceByMap)) {
|
|
648
711
|
const traceMap = this.traceByMap[traceId];
|
|
649
|
-
const queue = traceMap.childMap
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
712
|
+
const queue = Object.keys(traceMap.childMap)
|
|
713
|
+
.map((runId) => {
|
|
714
|
+
if (runId === ROOT || potentialChildRootRunIds.includes(runId)) {
|
|
715
|
+
return traceMap.childMap[runId];
|
|
716
|
+
}
|
|
717
|
+
return [];
|
|
718
|
+
})
|
|
719
|
+
.flat();
|
|
653
720
|
const seen = new Set();
|
|
654
721
|
while (queue.length) {
|
|
655
722
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
656
723
|
const task = queue.shift();
|
|
657
|
-
if (seen.has(task.
|
|
724
|
+
if (seen.has(task.id))
|
|
658
725
|
continue;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
726
|
+
let taskDotOrder = this.seenSpanInfo[task.id].dotOrder;
|
|
727
|
+
if (!task.sent) {
|
|
728
|
+
if (task.run != null) {
|
|
729
|
+
if (task.interop?.type === "user") {
|
|
662
730
|
actions.push({
|
|
663
731
|
type: "rename",
|
|
664
|
-
sourceRunId: task.
|
|
665
|
-
targetRunId: task.
|
|
732
|
+
sourceRunId: task.id,
|
|
733
|
+
targetRunId: task.interop.userRunId,
|
|
666
734
|
});
|
|
667
735
|
}
|
|
668
|
-
if (task.
|
|
736
|
+
if (task.interop?.type === "traceable") {
|
|
669
737
|
actions.push({
|
|
670
738
|
type: "reparent",
|
|
671
|
-
runId: task.
|
|
672
|
-
parentDotOrder: task.
|
|
739
|
+
runId: task.id,
|
|
740
|
+
parentDotOrder: task.interop.parentRunTree.dotted_order,
|
|
673
741
|
});
|
|
674
742
|
}
|
|
675
|
-
let dotOrder = task.dotOrder;
|
|
676
743
|
for (const action of actions) {
|
|
677
744
|
if (action.type === "delete") {
|
|
678
|
-
|
|
745
|
+
taskDotOrder = removeDotOrder(taskDotOrder, action.runId);
|
|
679
746
|
}
|
|
680
747
|
if (action.type === "reparent") {
|
|
681
|
-
|
|
748
|
+
taskDotOrder = reparentDotOrder(taskDotOrder, action.runId, action.parentDotOrder);
|
|
682
749
|
}
|
|
683
750
|
if (action.type === "rename") {
|
|
684
|
-
|
|
751
|
+
taskDotOrder = taskDotOrder.replace(action.sourceRunId, action.targetRunId);
|
|
685
752
|
}
|
|
686
753
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
754
|
+
this.seenSpanInfo[task.id].dotOrder = taskDotOrder;
|
|
755
|
+
if (!this.seenSpanInfo[task.id].sent) {
|
|
756
|
+
sampled.push({
|
|
757
|
+
...task.run,
|
|
758
|
+
...getMutableRunCreate(taskDotOrder),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
this.seenSpanInfo[task.id].sent = true;
|
|
691
762
|
}
|
|
692
763
|
else {
|
|
693
|
-
actions.push({ type: "delete", runId: task.
|
|
764
|
+
actions.push({ type: "delete", runId: task.id });
|
|
694
765
|
}
|
|
695
|
-
task.
|
|
766
|
+
task.sent = true;
|
|
696
767
|
}
|
|
697
|
-
const children = traceMap.childMap[task.
|
|
698
|
-
queue.push(...children
|
|
699
|
-
item,
|
|
700
|
-
dotOrder: joinDotOrder(task.dotOrder, getDotOrder(item)),
|
|
701
|
-
})));
|
|
768
|
+
const children = traceMap.childMap[task.id] ?? [];
|
|
769
|
+
queue.push(...children);
|
|
702
770
|
}
|
|
703
771
|
}
|
|
704
772
|
this.logDebug(`sampled runs to be sent to LangSmith`, sampled);
|
|
705
|
-
Promise.all(sampled.map((run) => this.client.createRun(run)))
|
|
773
|
+
Promise.all(sampled.map((run) => this.client.createRun(run)))
|
|
774
|
+
.then(() => {
|
|
775
|
+
// OTEL seems to do weird things with threads, so we need to queue any spans
|
|
776
|
+
// with missing parents for retry at the end after async operations.
|
|
777
|
+
this.pendingSpans = skippedSpans;
|
|
778
|
+
})
|
|
779
|
+
.then(() => resultCallback({ code: 0 }), (error) => resultCallback({ code: 1, error }));
|
|
780
|
+
}
|
|
781
|
+
export(spans, resultCallback) {
|
|
782
|
+
this._export(spans, (result) => {
|
|
783
|
+
if (result.code === 0) {
|
|
784
|
+
// Empty export to try flushing pending spans to rule out any trace order shenanigans
|
|
785
|
+
this._export([], resultCallback);
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
resultCallback(result);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
706
791
|
}
|
|
707
792
|
async shutdown() {
|
|
708
793
|
// find nodes which are incomplete
|
|
@@ -711,10 +796,13 @@ export class AISDKExporter {
|
|
|
711
796
|
if (incompleteNodes.length > 0) {
|
|
712
797
|
console.warn("Some incomplete nodes were found before shutdown and not sent to LangSmith.");
|
|
713
798
|
}
|
|
714
|
-
await this.
|
|
799
|
+
await this.forceFlush();
|
|
715
800
|
}
|
|
716
801
|
async forceFlush() {
|
|
717
|
-
await
|
|
802
|
+
await new Promise((resolve) => {
|
|
803
|
+
this.export([], resolve);
|
|
804
|
+
});
|
|
805
|
+
await this.client.awaitPendingTraceBatches();
|
|
718
806
|
}
|
|
719
807
|
logDebug(...args) {
|
|
720
808
|
if (!this.debug)
|
package/dist/wrappers/openai.cjs
CHANGED
|
@@ -166,7 +166,7 @@ function processChatCompletion(outputs) {
|
|
|
166
166
|
* const patchedStream = await patchedClient.chat.completions.create(
|
|
167
167
|
* {
|
|
168
168
|
* messages: [{ role: "user", content: `Say 'foo'` }],
|
|
169
|
-
* model: "gpt-
|
|
169
|
+
* model: "gpt-4.1-mini",
|
|
170
170
|
* stream: true,
|
|
171
171
|
* },
|
|
172
172
|
* {
|
|
@@ -61,7 +61,7 @@ type PatchedOpenAIClient<T extends OpenAIType> = T & {
|
|
|
61
61
|
* const patchedStream = await patchedClient.chat.completions.create(
|
|
62
62
|
* {
|
|
63
63
|
* messages: [{ role: "user", content: `Say 'foo'` }],
|
|
64
|
-
* model: "gpt-
|
|
64
|
+
* model: "gpt-4.1-mini",
|
|
65
65
|
* stream: true,
|
|
66
66
|
* },
|
|
67
67
|
* {
|
package/dist/wrappers/openai.js
CHANGED
|
@@ -163,7 +163,7 @@ function processChatCompletion(outputs) {
|
|
|
163
163
|
* const patchedStream = await patchedClient.chat.completions.create(
|
|
164
164
|
* {
|
|
165
165
|
* messages: [{ role: "user", content: `Say 'foo'` }],
|
|
166
|
-
* model: "gpt-
|
|
166
|
+
* model: "gpt-4.1-mini",
|
|
167
167
|
* stream: true,
|
|
168
168
|
* },
|
|
169
169
|
* {
|