langsmith 0.5.22 → 0.5.24
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 +365 -19
- package/dist/client.d.ts +116 -1
- package/dist/client.js +369 -23
- package/dist/evaluation/_runner.cjs +4 -7
- package/dist/evaluation/_runner.js +2 -5
- package/dist/evaluation/evaluate_comparative.cjs +10 -10
- package/dist/evaluation/evaluate_comparative.js +1 -1
- package/dist/evaluation/evaluator.cjs +2 -2
- package/dist/evaluation/evaluator.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run_trees.cjs +8 -7
- package/dist/run_trees.d.ts +7 -0
- package/dist/run_trees.js +7 -6
- package/dist/schemas.d.ts +54 -0
- package/dist/singletons/otel.cjs +3 -2
- package/dist/singletons/otel.js +4 -3
- package/dist/traceable.cjs +1 -2
- package/dist/traceable.js +1 -2
- package/dist/utils/_uuid.cjs +2 -2
- package/dist/utils/_uuid.js +1 -1
- package/dist/utils/env.cjs +33 -0
- package/dist/utils/env.d.ts +9 -0
- package/dist/utils/env.js +32 -0
- package/dist/utils/error.cjs +7 -0
- package/dist/utils/error.d.ts +1 -0
- package/dist/utils/error.js +6 -0
- package/dist/utils/fast-safe-stringify/index.cjs +203 -0
- package/dist/utils/fast-safe-stringify/index.d.ts +46 -0
- package/dist/utils/fast-safe-stringify/index.js +202 -0
- package/dist/utils/jestlike/index.cjs +5 -5
- package/dist/utils/jestlike/index.js +1 -1
- package/dist/utils/jestlike/vendor/evaluatedBy.cjs +3 -3
- package/dist/utils/jestlike/vendor/evaluatedBy.js +1 -1
- package/dist/utils/prompts.cjs +7 -2
- package/dist/utils/prompts.d.ts +6 -1
- package/dist/utils/prompts.js +6 -1
- package/dist/utils/serialize_worker.cjs +389 -0
- package/dist/utils/serialize_worker.d.ts +67 -0
- package/dist/utils/serialize_worker.js +383 -0
- package/dist/utils/uuid/src/index.cjs +24 -0
- package/dist/utils/uuid/src/index.d.ts +10 -0
- package/dist/utils/uuid/src/index.js +9 -0
- package/dist/utils/uuid/src/max.cjs +3 -0
- package/dist/utils/uuid/src/max.d.ts +2 -0
- package/dist/utils/uuid/src/max.js +1 -0
- package/dist/utils/uuid/src/nil.cjs +3 -0
- package/dist/utils/uuid/src/nil.d.ts +2 -0
- package/dist/utils/uuid/src/nil.js +1 -0
- package/dist/utils/uuid/src/parse.cjs +23 -0
- package/dist/utils/uuid/src/parse.d.ts +3 -0
- package/dist/utils/uuid/src/parse.js +18 -0
- package/dist/utils/uuid/src/regex.cjs +3 -0
- package/dist/utils/uuid/src/regex.d.ts +2 -0
- package/dist/utils/uuid/src/regex.js +1 -0
- package/dist/utils/uuid/src/rng.cjs +10 -0
- package/dist/utils/uuid/src/rng.d.ts +1 -0
- package/dist/utils/uuid/src/rng.js +7 -0
- package/dist/utils/uuid/src/sha1.cjs +75 -0
- package/dist/utils/uuid/src/sha1.d.ts +2 -0
- package/dist/utils/uuid/src/sha1.js +73 -0
- package/dist/utils/uuid/src/stringify.cjs +55 -0
- package/dist/utils/uuid/src/stringify.d.ts +3 -0
- package/dist/utils/uuid/src/stringify.js +49 -0
- package/dist/utils/uuid/src/types.cjs +2 -0
- package/dist/utils/uuid/src/types.d.ts +22 -0
- package/dist/utils/uuid/src/types.js +1 -0
- package/dist/utils/uuid/src/v35.cjs +52 -0
- package/dist/utils/uuid/src/v35.d.ts +7 -0
- package/dist/utils/uuid/src/v35.js +44 -0
- package/dist/utils/uuid/src/v4.cjs +40 -0
- package/dist/utils/uuid/src/v4.d.ts +4 -0
- package/dist/utils/uuid/src/v4.js +35 -0
- package/dist/utils/uuid/src/v5.cjs +50 -0
- package/dist/utils/uuid/src/v5.d.ts +9 -0
- package/dist/utils/uuid/src/v5.js +9 -0
- package/dist/utils/uuid/src/v7.cjs +88 -0
- package/dist/utils/uuid/src/v7.d.ts +9 -0
- package/dist/utils/uuid/src/v7.js +82 -0
- package/dist/utils/uuid/src/validate.cjs +10 -0
- package/dist/utils/uuid/src/validate.d.ts +2 -0
- package/dist/utils/uuid/src/validate.js +5 -0
- package/dist/utils/uuid/src/version.cjs +13 -0
- package/dist/utils/uuid/src/version.d.ts +2 -0
- package/dist/utils/uuid/src/version.js +8 -0
- package/dist/utils/worker_threads.browser.cjs +16 -0
- package/dist/utils/worker_threads.browser.d.ts +14 -0
- package/dist/utils/worker_threads.browser.js +13 -0
- package/dist/utils/worker_threads.cjs +16 -0
- package/dist/utils/worker_threads.d.ts +13 -0
- package/dist/utils/worker_threads.js +13 -0
- package/dist/uuid.cjs +2 -2
- package/dist/uuid.js +1 -1
- package/package.json +7 -5
package/dist/client.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import * as uuid from "uuid";
|
|
1
|
+
import * as uuid from "./utils/uuid/src/index.js";
|
|
2
2
|
import { LangSmithToOTELTranslator, } from "./experimental/otel/translator.js";
|
|
3
3
|
import { getDefaultOTLPTracerComponents, getOTELTrace, getOTELContext, } from "./singletons/otel.js";
|
|
4
4
|
import { AsyncCaller } from "./utils/async_caller.js";
|
|
5
5
|
import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js";
|
|
6
|
-
import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment,
|
|
6
|
+
import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, getEnv, resolveTracingMode, } from "./utils/env.js";
|
|
7
7
|
import { __version__ } from "./index.js";
|
|
8
8
|
import { assertUuid } from "./utils/_uuid.js";
|
|
9
9
|
import { warnOnce } from "./utils/warn.js";
|
|
10
|
-
import {
|
|
11
|
-
import { raiseForStatus, isLangSmithNotFoundError } from "./utils/error.js";
|
|
10
|
+
import { parseHubIdentifier } from "./utils/prompts.js";
|
|
11
|
+
import { raiseForStatus, isLangSmithNotFoundError, isLangSmithConflictError, } from "./utils/error.js";
|
|
12
12
|
import { promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
13
13
|
import * as fsUtils from "./utils/fs.js";
|
|
14
14
|
import { _shouldStreamForGlobalFetchImplementation, _getFetchImplementation, } from "./singletons/fetch.js";
|
|
15
|
-
import { serialize as serializePayloadForTracing } from "./utils/fast-safe-stringify/index.js";
|
|
15
|
+
import { serialize as serializePayloadForTracing, estimateSerializedSize, } from "./utils/fast-safe-stringify/index.js";
|
|
16
|
+
import { getSharedSerializeWorker, hasLargeString, } from "./utils/serialize_worker.js";
|
|
16
17
|
/**
|
|
17
18
|
* Catches timestamps without a timezone suffix.
|
|
18
19
|
*/
|
|
@@ -152,7 +153,19 @@ export class AutoBatchQueue {
|
|
|
152
153
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
|
|
153
154
|
itemPromiseResolve = resolve;
|
|
154
155
|
});
|
|
155
|
-
|
|
156
|
+
// By default we compute the exact serialized size here by stringifying
|
|
157
|
+
// the payload. This is expensive: JSON.stringify on large payloads
|
|
158
|
+
// blocks the event loop on the user's hot path.
|
|
159
|
+
//
|
|
160
|
+
// Opting into LANGSMITH_PERF_OPTIMIZATION=true switches to a cheap
|
|
161
|
+
// structural estimate instead. The estimate is only used for soft
|
|
162
|
+
// memory accounting (queue size limit and downstream async caller
|
|
163
|
+
// memory tracking), never for anything correctness-critical -- the
|
|
164
|
+
// real serialization still happens later, off the hot path, when the
|
|
165
|
+
// batch is assembled for sending.
|
|
166
|
+
const size = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true"
|
|
167
|
+
? estimateSerializedSize(item.item).size
|
|
168
|
+
: serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
|
|
156
169
|
// Check if adding this item would exceed the size limit
|
|
157
170
|
// Allow the run if the queue is empty (to support large single traces)
|
|
158
171
|
if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
|
|
@@ -215,9 +228,67 @@ export class AutoBatchQueue {
|
|
|
215
228
|
}
|
|
216
229
|
}
|
|
217
230
|
export class Client {
|
|
231
|
+
get tracingMode() {
|
|
232
|
+
return this._tracingMode;
|
|
233
|
+
}
|
|
218
234
|
get _fetch() {
|
|
219
235
|
return this.fetchImplementation || _getFetchImplementation(this.debug);
|
|
220
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Serialize a payload for tracing, optionally offloading the work to a
|
|
239
|
+
* Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
|
|
240
|
+
* supports worker_threads.
|
|
241
|
+
*
|
|
242
|
+
* Falls back to synchronous serialization when:
|
|
243
|
+
* - the perf flag is off
|
|
244
|
+
* - manualFlushMode is enabled (serverless: worker boot cost > benefit)
|
|
245
|
+
* - worker_threads is unavailable (non-Node runtimes)
|
|
246
|
+
* - the payload contains values that can't be structured-cloned across
|
|
247
|
+
* threads (functions, non-cloneable class instances, streams, etc.)
|
|
248
|
+
* - the worker throws for any other reason
|
|
249
|
+
*
|
|
250
|
+
* In all fallback cases the returned bytes are identical to the sync path.
|
|
251
|
+
*/
|
|
252
|
+
_trackDrain(promise) {
|
|
253
|
+
this._pendingDrains.add(promise);
|
|
254
|
+
promise.finally(() => {
|
|
255
|
+
this._pendingDrains.delete(promise);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async _serializeBody(payload, errorContext) {
|
|
259
|
+
const perfOptIn = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true";
|
|
260
|
+
if (!perfOptIn || this.manualFlushMode) {
|
|
261
|
+
return serializePayloadForTracing(payload, errorContext);
|
|
262
|
+
}
|
|
263
|
+
// Shape-aware gate: worker offload pays for itself only when the
|
|
264
|
+
// payload is dominated by one or more large strings (V8 can refcount
|
|
265
|
+
// those across isolates instead of copying). For structure-heavy
|
|
266
|
+
// payloads -- many keys, deep nesting, lots of small strings -- the
|
|
267
|
+
// structuredClone walk plus thread-hop cost exceeds the JSON.stringify
|
|
268
|
+
// cost we would pay inline, so we fall through to sync serialize.
|
|
269
|
+
if (!hasLargeString(payload)) {
|
|
270
|
+
return serializePayloadForTracing(payload, errorContext);
|
|
271
|
+
}
|
|
272
|
+
if (this._serializeWorker === undefined) {
|
|
273
|
+
this._serializeWorker = getSharedSerializeWorker();
|
|
274
|
+
}
|
|
275
|
+
if (this._serializeWorker === null) {
|
|
276
|
+
return serializePayloadForTracing(payload, errorContext);
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const bytes = await this._serializeWorker.serialize(payload);
|
|
280
|
+
if (bytes === null) {
|
|
281
|
+
// Worker subsystem unavailable; cache the null to skip re-entry.
|
|
282
|
+
this._serializeWorker = null;
|
|
283
|
+
return serializePayloadForTracing(payload, errorContext);
|
|
284
|
+
}
|
|
285
|
+
return bytes;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// DataCloneError, worker crash, etc. Fall back silently.
|
|
289
|
+
return serializePayloadForTracing(payload, errorContext);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
221
292
|
constructor(config = {}) {
|
|
222
293
|
Object.defineProperty(this, "apiKey", {
|
|
223
294
|
enumerable: true,
|
|
@@ -382,12 +453,35 @@ export class Client {
|
|
|
382
453
|
writable: true,
|
|
383
454
|
value: false
|
|
384
455
|
});
|
|
456
|
+
Object.defineProperty(this, "_serializeWorker", {
|
|
457
|
+
enumerable: true,
|
|
458
|
+
configurable: true,
|
|
459
|
+
writable: true,
|
|
460
|
+
value: void 0
|
|
461
|
+
});
|
|
462
|
+
/**
|
|
463
|
+
* Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
|
|
464
|
+
* can wait on them even if the flush involves async work (worker-thread
|
|
465
|
+
* serialize) that hasn't yet registered with batchIngestCaller.queue.
|
|
466
|
+
*/
|
|
467
|
+
Object.defineProperty(this, "_pendingDrains", {
|
|
468
|
+
enumerable: true,
|
|
469
|
+
configurable: true,
|
|
470
|
+
writable: true,
|
|
471
|
+
value: new Set()
|
|
472
|
+
});
|
|
385
473
|
Object.defineProperty(this, "langSmithToOTELTranslator", {
|
|
386
474
|
enumerable: true,
|
|
387
475
|
configurable: true,
|
|
388
476
|
writable: true,
|
|
389
477
|
value: void 0
|
|
390
478
|
});
|
|
479
|
+
Object.defineProperty(this, "_tracingMode", {
|
|
480
|
+
enumerable: true,
|
|
481
|
+
configurable: true,
|
|
482
|
+
writable: true,
|
|
483
|
+
value: "langsmith"
|
|
484
|
+
});
|
|
391
485
|
Object.defineProperty(this, "fetchImplementation", {
|
|
392
486
|
enumerable: true,
|
|
393
487
|
configurable: true,
|
|
@@ -507,7 +601,8 @@ export class Client {
|
|
|
507
601
|
this.batchSizeLimit = config.batchSizeLimit;
|
|
508
602
|
this.fetchOptions = config.fetchOptions || {};
|
|
509
603
|
this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
|
|
510
|
-
|
|
604
|
+
this._tracingMode = resolveTracingMode(config.tracingMode);
|
|
605
|
+
if (this._tracingMode === "otel") {
|
|
511
606
|
this.langSmithToOTELTranslator = new LangSmithToOTELTranslator();
|
|
512
607
|
}
|
|
513
608
|
// Cache metadata env vars once during construction to avoid repeatedly scanning process.env
|
|
@@ -1046,18 +1141,18 @@ export class Client {
|
|
|
1046
1141
|
const sizeLimit = await this._getBatchSizeLimit();
|
|
1047
1142
|
if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
|
|
1048
1143
|
this.autoBatchQueue.items.length > sizeLimit) {
|
|
1049
|
-
|
|
1144
|
+
this._trackDrain(this.drainAutoBatchQueue({
|
|
1050
1145
|
batchSizeLimitBytes: sizeLimitBytes,
|
|
1051
1146
|
batchSizeLimit: sizeLimit,
|
|
1052
|
-
});
|
|
1147
|
+
}));
|
|
1053
1148
|
}
|
|
1054
1149
|
if (this.autoBatchQueue.items.length > 0) {
|
|
1055
1150
|
this.autoBatchTimeout = setTimeout(() => {
|
|
1056
1151
|
this.autoBatchTimeout = undefined;
|
|
1057
|
-
|
|
1152
|
+
this._trackDrain(this.drainAutoBatchQueue({
|
|
1058
1153
|
batchSizeLimitBytes: sizeLimitBytes,
|
|
1059
1154
|
batchSizeLimit: sizeLimit,
|
|
1060
|
-
});
|
|
1155
|
+
}));
|
|
1061
1156
|
}, this.autoBatchAggregationDelayMs);
|
|
1062
1157
|
}
|
|
1063
1158
|
return itemPromise;
|
|
@@ -1237,7 +1332,7 @@ export class Client {
|
|
|
1237
1332
|
.map((item) => item.id)
|
|
1238
1333
|
.concat(batchChunks.patch.map((item) => item.id))
|
|
1239
1334
|
.join(",");
|
|
1240
|
-
await this._postBatchIngestRuns(
|
|
1335
|
+
await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
|
|
1241
1336
|
}
|
|
1242
1337
|
}
|
|
1243
1338
|
async _postBatchIngestRuns(body, options) {
|
|
@@ -1338,7 +1433,7 @@ export class Client {
|
|
|
1338
1433
|
const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
|
|
1339
1434
|
const fields = { inputs, outputs, events, extra, error, serialized };
|
|
1340
1435
|
// encode the main run payload
|
|
1341
|
-
const stringifiedPayload =
|
|
1436
|
+
const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
|
|
1342
1437
|
accumulatedParts.push({
|
|
1343
1438
|
name: `${method}.${payload.id}`,
|
|
1344
1439
|
payload: new Blob([stringifiedPayload], {
|
|
@@ -1350,7 +1445,7 @@ export class Client {
|
|
|
1350
1445
|
if (value === undefined) {
|
|
1351
1446
|
continue;
|
|
1352
1447
|
}
|
|
1353
|
-
const stringifiedValue =
|
|
1448
|
+
const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
|
|
1354
1449
|
accumulatedParts.push({
|
|
1355
1450
|
name: `${method}.${payload.id}.${key}`,
|
|
1356
1451
|
payload: new Blob([stringifiedValue], {
|
|
@@ -3783,7 +3878,7 @@ export class Client {
|
|
|
3783
3878
|
return json.commits[0].commit_hash;
|
|
3784
3879
|
}
|
|
3785
3880
|
async _likeOrUnlikePrompt(promptIdentifier, like) {
|
|
3786
|
-
const [owner, promptName, _] =
|
|
3881
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
3787
3882
|
const body = JSON.stringify({ like: like });
|
|
3788
3883
|
const response = await this.caller.call(async () => {
|
|
3789
3884
|
const res = await this._fetch(`${this.apiUrl}/likes/${owner}/${promptName}`, {
|
|
@@ -3802,7 +3897,7 @@ export class Client {
|
|
|
3802
3897
|
return response.json();
|
|
3803
3898
|
}
|
|
3804
3899
|
async _getPromptUrl(promptIdentifier) {
|
|
3805
|
-
const [owner, promptName, commitHash] =
|
|
3900
|
+
const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
|
|
3806
3901
|
if (!(await this._currentTenantIsOwner(owner))) {
|
|
3807
3902
|
if (commitHash !== "latest") {
|
|
3808
3903
|
return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
|
|
@@ -3894,7 +3989,7 @@ export class Client {
|
|
|
3894
3989
|
* ```
|
|
3895
3990
|
*/
|
|
3896
3991
|
async *listCommits(promptIdentifier) {
|
|
3897
|
-
const [owner, promptName, _] =
|
|
3992
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
3898
3993
|
for await (const commits of this._getPaginated(`/commits/${owner}/${promptName}/`, new URLSearchParams(), (res) => res.commits)) {
|
|
3899
3994
|
yield* commits;
|
|
3900
3995
|
}
|
|
@@ -3957,7 +4052,7 @@ export class Client {
|
|
|
3957
4052
|
* ```
|
|
3958
4053
|
*/
|
|
3959
4054
|
async getPrompt(promptIdentifier) {
|
|
3960
|
-
const [owner, promptName, _] =
|
|
4055
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
3961
4056
|
const response = await this.caller.call(async () => {
|
|
3962
4057
|
const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${promptName}`, {
|
|
3963
4058
|
method: "GET",
|
|
@@ -4014,7 +4109,7 @@ export class Client {
|
|
|
4014
4109
|
You can add a handle by creating a public prompt at:\n
|
|
4015
4110
|
https://smith.langchain.com/prompts`);
|
|
4016
4111
|
}
|
|
4017
|
-
const [owner, promptName, _] =
|
|
4112
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
4018
4113
|
if (!(await this._currentTenantIsOwner(owner))) {
|
|
4019
4114
|
throw await this._ownerConflictError("create a prompt", owner);
|
|
4020
4115
|
}
|
|
@@ -4073,7 +4168,7 @@ export class Client {
|
|
|
4073
4168
|
if (!(await this.promptExists(promptIdentifier))) {
|
|
4074
4169
|
throw new Error("Prompt does not exist, you must create it first.");
|
|
4075
4170
|
}
|
|
4076
|
-
const [owner, promptName, _] =
|
|
4171
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
4077
4172
|
const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
|
|
4078
4173
|
? await this._getLatestCommitHash(`${owner}/${promptName}`)
|
|
4079
4174
|
: options?.parentCommitHash;
|
|
@@ -4271,7 +4366,7 @@ export class Client {
|
|
|
4271
4366
|
if (!(await this.promptExists(promptIdentifier))) {
|
|
4272
4367
|
throw new Error("Prompt does not exist, you must create it first.");
|
|
4273
4368
|
}
|
|
4274
|
-
const [owner, promptName] =
|
|
4369
|
+
const [owner, promptName] = parseHubIdentifier(promptIdentifier);
|
|
4275
4370
|
if (!(await this._currentTenantIsOwner(owner))) {
|
|
4276
4371
|
throw await this._ownerConflictError("update a prompt", owner);
|
|
4277
4372
|
}
|
|
@@ -4311,7 +4406,7 @@ export class Client {
|
|
|
4311
4406
|
if (!(await this.promptExists(promptIdentifier))) {
|
|
4312
4407
|
throw new Error("Prompt does not exist, you must create it first.");
|
|
4313
4408
|
}
|
|
4314
|
-
const [owner, promptName, _] =
|
|
4409
|
+
const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
|
|
4315
4410
|
if (!(await this._currentTenantIsOwner(owner))) {
|
|
4316
4411
|
throw await this._ownerConflictError("delete a prompt", owner);
|
|
4317
4412
|
}
|
|
@@ -4339,7 +4434,7 @@ export class Client {
|
|
|
4339
4434
|
* Fetch a prompt commit directly from the API (bypassing cache).
|
|
4340
4435
|
*/
|
|
4341
4436
|
async _fetchPromptFromApi(promptIdentifier, options) {
|
|
4342
|
-
const [owner, promptName, commitHash] =
|
|
4437
|
+
const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
|
|
4343
4438
|
const response = await this.caller.call(async () => {
|
|
4344
4439
|
const res = await this._fetch(`${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
|
|
4345
4440
|
method: "GET",
|
|
@@ -4419,6 +4514,251 @@ export class Client {
|
|
|
4419
4514
|
});
|
|
4420
4515
|
return url;
|
|
4421
4516
|
}
|
|
4517
|
+
/**
|
|
4518
|
+
* Check if an agent repo exists.
|
|
4519
|
+
*/
|
|
4520
|
+
async agentExists(identifier) {
|
|
4521
|
+
const [owner, name] = parseHubIdentifier(identifier);
|
|
4522
|
+
return this._repoExists(owner, name);
|
|
4523
|
+
}
|
|
4524
|
+
/**
|
|
4525
|
+
* Check if a skill repo exists.
|
|
4526
|
+
*/
|
|
4527
|
+
async skillExists(identifier) {
|
|
4528
|
+
const [owner, name] = parseHubIdentifier(identifier);
|
|
4529
|
+
return this._repoExists(owner, name);
|
|
4530
|
+
}
|
|
4531
|
+
/**
|
|
4532
|
+
* Pull an agent directory from Hub.
|
|
4533
|
+
* @param identifier The identifier (owner/name[:version]).
|
|
4534
|
+
* @param options.version Commit hash or tag; overrides identifier's version.
|
|
4535
|
+
*/
|
|
4536
|
+
async pullAgent(identifier, options) {
|
|
4537
|
+
return (await this._pullDirectory(identifier, "agent", options?.version));
|
|
4538
|
+
}
|
|
4539
|
+
/**
|
|
4540
|
+
* Pull a skill directory from Hub.
|
|
4541
|
+
*/
|
|
4542
|
+
async pullSkill(identifier, options) {
|
|
4543
|
+
return (await this._pullDirectory(identifier, "skill", options?.version));
|
|
4544
|
+
}
|
|
4545
|
+
/**
|
|
4546
|
+
* Push an agent to Hub. Creates the repo if missing, patches metadata if
|
|
4547
|
+
* provided, then commits the given files.
|
|
4548
|
+
* @returns The URL of the resulting commit.
|
|
4549
|
+
*/
|
|
4550
|
+
async pushAgent(identifier, options) {
|
|
4551
|
+
return this._pushDirectory(identifier, "agent", options);
|
|
4552
|
+
}
|
|
4553
|
+
/**
|
|
4554
|
+
* Push a skill to Hub.
|
|
4555
|
+
*/
|
|
4556
|
+
async pushSkill(identifier, options) {
|
|
4557
|
+
return this._pushDirectory(identifier, "skill", options);
|
|
4558
|
+
}
|
|
4559
|
+
/**
|
|
4560
|
+
* Delete an agent and all its owned child file repos.
|
|
4561
|
+
*/
|
|
4562
|
+
async deleteAgent(identifier) {
|
|
4563
|
+
return this._deleteDirectory(identifier);
|
|
4564
|
+
}
|
|
4565
|
+
/**
|
|
4566
|
+
* Delete a skill and all its owned child file repos.
|
|
4567
|
+
*/
|
|
4568
|
+
async deleteSkill(identifier) {
|
|
4569
|
+
return this._deleteDirectory(identifier);
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* List agent repos. Yields one at a time, auto-paginating.
|
|
4573
|
+
*/
|
|
4574
|
+
async *listAgents(options) {
|
|
4575
|
+
yield* this._listReposByType("agent", options);
|
|
4576
|
+
}
|
|
4577
|
+
/**
|
|
4578
|
+
* List skill repos. Yields one at a time, auto-paginating.
|
|
4579
|
+
*/
|
|
4580
|
+
async *listSkills(options) {
|
|
4581
|
+
yield* this._listReposByType("skill", options);
|
|
4582
|
+
}
|
|
4583
|
+
async *_listReposByType(repoType, options) {
|
|
4584
|
+
const params = new URLSearchParams();
|
|
4585
|
+
params.append("repo_type", repoType);
|
|
4586
|
+
params.append("is_archived", (!!options?.isArchived).toString());
|
|
4587
|
+
if (options?.isPublic !== undefined) {
|
|
4588
|
+
params.append("is_public", options.isPublic.toString());
|
|
4589
|
+
}
|
|
4590
|
+
if (options?.query) {
|
|
4591
|
+
params.append("query", options.query);
|
|
4592
|
+
}
|
|
4593
|
+
for await (const repos of this._getPaginated("/repos", params, (res) => res.repos)) {
|
|
4594
|
+
yield* repos;
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
async _pullDirectory(identifier, repoType, version) {
|
|
4598
|
+
const [owner, name, parsedVersion] = parseHubIdentifier(identifier);
|
|
4599
|
+
const resolvedVersion = version ?? (parsedVersion !== "latest" ? parsedVersion : undefined);
|
|
4600
|
+
const url = new URL(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`);
|
|
4601
|
+
url.searchParams.set("repo_type", repoType);
|
|
4602
|
+
if (resolvedVersion) {
|
|
4603
|
+
url.searchParams.set("commit", resolvedVersion);
|
|
4604
|
+
}
|
|
4605
|
+
const response = await this.caller.call(async () => {
|
|
4606
|
+
const res = await this._fetch(url.toString(), {
|
|
4607
|
+
method: "GET",
|
|
4608
|
+
headers: this._mergedHeaders,
|
|
4609
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4610
|
+
...this.fetchOptions,
|
|
4611
|
+
});
|
|
4612
|
+
await raiseForStatus(res, "pull directory");
|
|
4613
|
+
return res;
|
|
4614
|
+
});
|
|
4615
|
+
return (await response.json());
|
|
4616
|
+
}
|
|
4617
|
+
async _pushDirectory(identifier, repoType, options) {
|
|
4618
|
+
if (options.parentCommit !== undefined &&
|
|
4619
|
+
(options.parentCommit.length < 8 || options.parentCommit.length > 64)) {
|
|
4620
|
+
throw new Error("parent_commit must be 8-64 characters");
|
|
4621
|
+
}
|
|
4622
|
+
const [owner, name] = parseHubIdentifier(identifier);
|
|
4623
|
+
if (!(await this._currentTenantIsOwner(owner))) {
|
|
4624
|
+
throw await this._ownerConflictError(`push ${repoType}`, owner);
|
|
4625
|
+
}
|
|
4626
|
+
if (await this._repoExists(owner, name)) {
|
|
4627
|
+
if (options.description !== undefined ||
|
|
4628
|
+
options.readme !== undefined ||
|
|
4629
|
+
options.tags !== undefined ||
|
|
4630
|
+
options.isPublic !== undefined) {
|
|
4631
|
+
await this._updateRepoMetadata(owner, name, options);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
else {
|
|
4635
|
+
const REPO_HANDLE_PATTERN = /^[a-z][a-z0-9-_]*$/;
|
|
4636
|
+
if (!REPO_HANDLE_PATTERN.test(name)) {
|
|
4637
|
+
throw new Error(`Invalid repo_handle ${JSON.stringify(name)}: must match ${REPO_HANDLE_PATTERN}`);
|
|
4638
|
+
}
|
|
4639
|
+
await this._createRepo(name, repoType, options);
|
|
4640
|
+
}
|
|
4641
|
+
const body = { files: options.files };
|
|
4642
|
+
if (options.parentCommit) {
|
|
4643
|
+
body.parent_commit = options.parentCommit;
|
|
4644
|
+
}
|
|
4645
|
+
const response = await this.caller.call(async () => {
|
|
4646
|
+
const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories/commits`, {
|
|
4647
|
+
method: "POST",
|
|
4648
|
+
headers: {
|
|
4649
|
+
...this._mergedHeaders,
|
|
4650
|
+
"Content-Type": "application/json",
|
|
4651
|
+
},
|
|
4652
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4653
|
+
...this.fetchOptions,
|
|
4654
|
+
body: JSON.stringify(body),
|
|
4655
|
+
});
|
|
4656
|
+
await raiseForStatus(res, `push ${repoType}`);
|
|
4657
|
+
return res;
|
|
4658
|
+
});
|
|
4659
|
+
const data = (await response.json());
|
|
4660
|
+
const commitHash = data.commit.commit_hash;
|
|
4661
|
+
return `${this.getHostUrl()}/hub/${owner}/${name}:${commitHash.slice(0, 8)}`;
|
|
4662
|
+
}
|
|
4663
|
+
async _deleteDirectory(identifier) {
|
|
4664
|
+
const [owner, name] = parseHubIdentifier(identifier);
|
|
4665
|
+
if (!(await this._currentTenantIsOwner(owner))) {
|
|
4666
|
+
throw await this._ownerConflictError("delete", owner);
|
|
4667
|
+
}
|
|
4668
|
+
await this.caller.call(async () => {
|
|
4669
|
+
const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`, {
|
|
4670
|
+
method: "DELETE",
|
|
4671
|
+
headers: this._mergedHeaders,
|
|
4672
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4673
|
+
...this.fetchOptions,
|
|
4674
|
+
});
|
|
4675
|
+
await raiseForStatus(res, "delete directory");
|
|
4676
|
+
return res;
|
|
4677
|
+
});
|
|
4678
|
+
}
|
|
4679
|
+
async _repoExists(owner, name) {
|
|
4680
|
+
try {
|
|
4681
|
+
await this.caller.call(async () => {
|
|
4682
|
+
const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
|
|
4683
|
+
method: "GET",
|
|
4684
|
+
headers: this._mergedHeaders,
|
|
4685
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4686
|
+
...this.fetchOptions,
|
|
4687
|
+
});
|
|
4688
|
+
await raiseForStatus(res, "check repo exists");
|
|
4689
|
+
return res;
|
|
4690
|
+
});
|
|
4691
|
+
return true;
|
|
4692
|
+
}
|
|
4693
|
+
catch (e) {
|
|
4694
|
+
if (isLangSmithNotFoundError(e)) {
|
|
4695
|
+
return false;
|
|
4696
|
+
}
|
|
4697
|
+
throw e;
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
async _createRepo(name, repoType, options) {
|
|
4701
|
+
const body = {
|
|
4702
|
+
repo_handle: name,
|
|
4703
|
+
repo_type: repoType,
|
|
4704
|
+
is_public: !!options.isPublic,
|
|
4705
|
+
};
|
|
4706
|
+
if (options.description !== undefined)
|
|
4707
|
+
body.description = options.description;
|
|
4708
|
+
if (options.readme !== undefined)
|
|
4709
|
+
body.readme = options.readme;
|
|
4710
|
+
if (options.tags !== undefined)
|
|
4711
|
+
body.tags = options.tags;
|
|
4712
|
+
try {
|
|
4713
|
+
await this.caller.call(async () => {
|
|
4714
|
+
const res = await this._fetch(`${this.apiUrl}/repos/`, {
|
|
4715
|
+
method: "POST",
|
|
4716
|
+
headers: {
|
|
4717
|
+
...this._mergedHeaders,
|
|
4718
|
+
"Content-Type": "application/json",
|
|
4719
|
+
},
|
|
4720
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4721
|
+
...this.fetchOptions,
|
|
4722
|
+
body: JSON.stringify(body),
|
|
4723
|
+
});
|
|
4724
|
+
await raiseForStatus(res, `create ${repoType}`);
|
|
4725
|
+
return res;
|
|
4726
|
+
});
|
|
4727
|
+
}
|
|
4728
|
+
catch (e) {
|
|
4729
|
+
if (isLangSmithConflictError(e)) {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
throw e;
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
async _updateRepoMetadata(owner, name, options) {
|
|
4736
|
+
const body = {};
|
|
4737
|
+
if (options.description !== undefined)
|
|
4738
|
+
body.description = options.description;
|
|
4739
|
+
if (options.readme !== undefined)
|
|
4740
|
+
body.readme = options.readme;
|
|
4741
|
+
if (options.tags !== undefined)
|
|
4742
|
+
body.tags = options.tags;
|
|
4743
|
+
if (options.isPublic !== undefined)
|
|
4744
|
+
body.is_public = options.isPublic;
|
|
4745
|
+
if (Object.keys(body).length === 0)
|
|
4746
|
+
return;
|
|
4747
|
+
await this.caller.call(async () => {
|
|
4748
|
+
const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
|
|
4749
|
+
method: "PATCH",
|
|
4750
|
+
headers: {
|
|
4751
|
+
...this._mergedHeaders,
|
|
4752
|
+
"Content-Type": "application/json",
|
|
4753
|
+
},
|
|
4754
|
+
signal: AbortSignal.timeout(this.timeout_ms),
|
|
4755
|
+
...this.fetchOptions,
|
|
4756
|
+
body: JSON.stringify(body),
|
|
4757
|
+
});
|
|
4758
|
+
await raiseForStatus(res, "update repo metadata");
|
|
4759
|
+
return res;
|
|
4760
|
+
});
|
|
4761
|
+
}
|
|
4422
4762
|
/**
|
|
4423
4763
|
* Clone a public dataset to your own langsmith tenant.
|
|
4424
4764
|
* This operation is idempotent. If you already have a dataset with the given name,
|
|
@@ -4550,6 +4890,12 @@ export class Client {
|
|
|
4550
4890
|
* ```
|
|
4551
4891
|
*/
|
|
4552
4892
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
4893
|
+
// Wait for any in-flight drains (whose serialize work may still be
|
|
4894
|
+
// in-progress on a worker thread and not yet registered with the
|
|
4895
|
+
// batchIngestCaller queue) before checking queue idleness.
|
|
4896
|
+
while (this._pendingDrains.size > 0) {
|
|
4897
|
+
await Promise.all([...this._pendingDrains]);
|
|
4898
|
+
}
|
|
4553
4899
|
await Promise.all([
|
|
4554
4900
|
...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
|
|
4555
4901
|
this.batchIngestCaller.queue.onIdle(),
|
|
@@ -13,7 +13,7 @@ const env_js_1 = require("../utils/env.cjs");
|
|
|
13
13
|
const error_js_1 = require("../utils/error.cjs");
|
|
14
14
|
const _random_name_js_1 = require("./_random_name.cjs");
|
|
15
15
|
const evaluator_js_1 = require("./evaluator.cjs");
|
|
16
|
-
const
|
|
16
|
+
const index_js_2 = require("../utils/uuid/src/index.cjs");
|
|
17
17
|
const evaluate_comparative_js_1 = require("./evaluate_comparative.cjs");
|
|
18
18
|
const p_queue_js_1 = require("../utils/p-queue.cjs");
|
|
19
19
|
// Implementation signature
|
|
@@ -202,7 +202,7 @@ class _ExperimentManager {
|
|
|
202
202
|
this._experimentName = (0, _random_name_js_1.randomName)();
|
|
203
203
|
}
|
|
204
204
|
else if (typeof args.experiment === "string") {
|
|
205
|
-
this._experimentName = `${args.experiment}-${(0,
|
|
205
|
+
this._experimentName = `${args.experiment}-${(0, index_js_2.v4)().slice(0, 8)}`;
|
|
206
206
|
}
|
|
207
207
|
else {
|
|
208
208
|
if (!args.experiment.name) {
|
|
@@ -276,7 +276,7 @@ class _ExperimentManager {
|
|
|
276
276
|
catch (e) {
|
|
277
277
|
// Naming collision
|
|
278
278
|
if (e?.name === "LangSmithConflictError") {
|
|
279
|
-
const ent = (0,
|
|
279
|
+
const ent = (0, index_js_2.v4)().slice(0, 6);
|
|
280
280
|
this._experimentName = `${originalExperimentName}-${ent}`;
|
|
281
281
|
}
|
|
282
282
|
else {
|
|
@@ -726,10 +726,7 @@ async function _evaluate(target, fields) {
|
|
|
726
726
|
const standardFields = fields;
|
|
727
727
|
const [experiment_, newRuns] = await _resolveExperiment(fields.experiment ?? null, runs, client);
|
|
728
728
|
let manager = await new _ExperimentManager({
|
|
729
|
-
data:
|
|
730
|
-
examples: Array.isArray(standardFields.data)
|
|
731
|
-
? standardFields.data
|
|
732
|
-
: undefined,
|
|
729
|
+
data: standardFields.data,
|
|
733
730
|
client,
|
|
734
731
|
metadata: fields.metadata,
|
|
735
732
|
experiment: experiment_ ?? fields.experimentPrefix,
|
|
@@ -7,7 +7,7 @@ import { getLangSmithEnvVarsMetadata } from "../utils/env.js";
|
|
|
7
7
|
import { printErrorStackTrace } from "../utils/error.js";
|
|
8
8
|
import { randomName } from "./_random_name.js";
|
|
9
9
|
import { runEvaluator, } from "./evaluator.js";
|
|
10
|
-
import { v4 as uuidv4 } from "uuid";
|
|
10
|
+
import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
|
|
11
11
|
import { evaluateComparative, } from "./evaluate_comparative.js";
|
|
12
12
|
import { PQueue } from "../utils/p-queue.js";
|
|
13
13
|
// Implementation signature
|
|
@@ -719,10 +719,7 @@ async function _evaluate(target, fields) {
|
|
|
719
719
|
const standardFields = fields;
|
|
720
720
|
const [experiment_, newRuns] = await _resolveExperiment(fields.experiment ?? null, runs, client);
|
|
721
721
|
let manager = await new _ExperimentManager({
|
|
722
|
-
data:
|
|
723
|
-
examples: Array.isArray(standardFields.data)
|
|
724
|
-
? standardFields.data
|
|
725
|
-
: undefined,
|
|
722
|
+
data: standardFields.data,
|
|
726
723
|
client,
|
|
727
724
|
metadata: fields.metadata,
|
|
728
725
|
experiment: experiment_ ?? fields.experimentPrefix,
|
|
@@ -4,22 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.evaluateComparative = evaluateComparative;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const index_js_1 = require("../utils/uuid/src/index.cjs");
|
|
8
|
+
const index_js_2 = require("../index.cjs");
|
|
9
9
|
const shuffle_js_1 = require("../utils/shuffle.cjs");
|
|
10
10
|
const async_caller_js_1 = require("../utils/async_caller.cjs");
|
|
11
|
-
const
|
|
11
|
+
const index_js_3 = __importDefault(require("../utils/p-retry/index.cjs"));
|
|
12
12
|
const traceable_js_1 = require("../traceable.cjs");
|
|
13
13
|
function isExperimentResultsList(value) {
|
|
14
14
|
return value.some((x) => typeof x !== "string");
|
|
15
15
|
}
|
|
16
16
|
async function loadExperiment(client, experiment) {
|
|
17
17
|
const value = typeof experiment === "string" ? experiment : experiment.experimentName;
|
|
18
|
-
return client.readProject((0,
|
|
18
|
+
return client.readProject((0, index_js_1.validate)(value) ? { projectId: value } : { projectName: value });
|
|
19
19
|
}
|
|
20
20
|
async function loadTraces(client, experiment, options) {
|
|
21
21
|
const executionOrder = options.loadNested ? undefined : 1;
|
|
22
|
-
const runs = await client.listRuns((0,
|
|
22
|
+
const runs = await client.listRuns((0, index_js_1.validate)(experiment)
|
|
23
23
|
? { projectId: experiment, executionOrder }
|
|
24
24
|
: { projectName: experiment, executionOrder });
|
|
25
25
|
const treeMap = {};
|
|
@@ -56,7 +56,7 @@ async function evaluateComparative(experiments, options) {
|
|
|
56
56
|
if (options.maxConcurrency && options.maxConcurrency < 0) {
|
|
57
57
|
throw new Error("maxConcurrency must be a positive number.");
|
|
58
58
|
}
|
|
59
|
-
const client = options.client ?? new
|
|
59
|
+
const client = options.client ?? new index_js_2.Client();
|
|
60
60
|
const resolvedExperiments = await Promise.all(experiments);
|
|
61
61
|
const projects = await (() => {
|
|
62
62
|
if (!isExperimentResultsList(resolvedExperiments)) {
|
|
@@ -64,7 +64,7 @@ async function evaluateComparative(experiments, options) {
|
|
|
64
64
|
}
|
|
65
65
|
// if we know the number of runs beforehand, check if the
|
|
66
66
|
// number of runs in the project matches the expected number of runs
|
|
67
|
-
return Promise.all(resolvedExperiments.map((experiment) => (0,
|
|
67
|
+
return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_3.default)(async () => {
|
|
68
68
|
const project = await loadExperiment(client, experiment);
|
|
69
69
|
if (project.run_count !== experiment?.results.length) {
|
|
70
70
|
throw new Error("Experiment is missing runs. Retrying.");
|
|
@@ -83,16 +83,16 @@ async function evaluateComparative(experiments, options) {
|
|
|
83
83
|
console.warn("Detected multiple dataset versions used by experiments, which may lead to inaccurate results.");
|
|
84
84
|
}
|
|
85
85
|
const datasetVersion = projects.at(0)?.extra?.metadata?.dataset_version;
|
|
86
|
-
const id = (0,
|
|
86
|
+
const id = (0, index_js_1.v4)();
|
|
87
87
|
const experimentName = (() => {
|
|
88
88
|
if (!options.experimentPrefix) {
|
|
89
89
|
const names = projects
|
|
90
90
|
.map((p) => p.name)
|
|
91
91
|
.filter(Boolean)
|
|
92
92
|
.join(" vs. ");
|
|
93
|
-
return `${names}-${(0,
|
|
93
|
+
return `${names}-${(0, index_js_1.v4)().slice(0, 4)}`;
|
|
94
94
|
}
|
|
95
|
-
return `${options.experimentPrefix}-${(0,
|
|
95
|
+
return `${options.experimentPrefix}-${(0, index_js_1.v4)().slice(0, 4)}`;
|
|
96
96
|
})();
|
|
97
97
|
// TODO: add URL to the comparative experiment
|
|
98
98
|
console.log(`Starting pairwise evaluation of: ${experimentName}`);
|