langsmith
Version:
Client library to connect to the LangSmith Observability and Evaluation Platform.
1,253 lines • 217 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = exports.AutoBatchQueue = exports.DEFAULT_MAX_SIZE_BYTES = exports.DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES = void 0;
exports.mergeRuntimeEnvIntoRun = mergeRuntimeEnvIntoRun;
const uuid = __importStar(require("./utils/uuid/src/index.cjs"));
const translator_js_1 = require("./experimental/otel/translator.cjs");
const otel_js_1 = require("./singletons/otel.cjs");
const async_caller_js_1 = require("./utils/async_caller.cjs");
const messages_js_1 = require("./utils/messages.cjs");
const env_js_1 = require("./utils/env.cjs");
const index_js_1 = require("./index.cjs");
const _uuid_js_1 = require("./utils/_uuid.cjs");
const warn_js_1 = require("./utils/warn.cjs");
const prompts_js_1 = require("./utils/prompts.cjs");
const error_js_1 = require("./utils/error.cjs");
const index_js_2 = require("./utils/prompt_cache/index.cjs");
const fsUtils = __importStar(require("./utils/fs.cjs"));
const fetch_js_1 = require("./singletons/fetch.cjs");
const profiles_js_1 = require("./utils/profiles.cjs");
const index_js_3 = require("./utils/fast-safe-stringify/index.cjs");
const serialize_worker_js_1 = require("./utils/serialize_worker.cjs");
function assertPullPublicPromptAllowed(promptIdentifier, dangerouslyPullPublicPrompt) {
const [owner] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
if (owner !== "-" && !dangerouslyPullPublicPrompt) {
throw new Error("Pulling a public prompt by owner/name is disabled by default because prompts may contain untrusted serialized LangChain objects. If you trust this prompt, set `dangerouslyPullPublicPrompt: true` to acknowledge the risk.");
}
}
/**
* Catches timestamps without a timezone suffix.
*/
function _ensureUTCTimestamp(ts) {
if (typeof ts === "string" &&
ts.length > 0 &&
!ts.includes("Z") &&
!ts.includes("+") &&
!ts.includes("-", 10)) {
return ts + "Z";
}
return ts;
}
function _normalizeRunTimestamps(run) {
return {
...run,
start_time: _ensureUTCTimestamp(run.start_time),
end_time: _ensureUTCTimestamp(run.end_time),
};
}
function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
if (omitTracedRuntimeInfo) {
return run;
}
const runtimeEnv = (0, env_js_1.getRuntimeEnvironment)();
const envVars = cachedEnvVars ?? (0, env_js_1.getLangSmithEnvVarsMetadata)();
const extra = run.extra ?? {};
const metadata = extra.metadata;
run.extra = {
...extra,
runtime: {
...runtimeEnv,
...extra?.runtime,
},
metadata: {
...envVars,
...(envVars.revision_id || ("revision_id" in run && run.revision_id)
? {
revision_id: ("revision_id" in run ? run.revision_id : undefined) ??
envVars.revision_id,
}
: {}),
...metadata,
},
};
return run;
}
const getTracingSamplingRate = (configRate) => {
const samplingRateStr = configRate?.toString() ??
(0, env_js_1.getLangSmithEnvironmentVariable)("TRACING_SAMPLING_RATE");
if (samplingRateStr === undefined) {
return undefined;
}
const samplingRate = parseFloat(samplingRateStr);
if (samplingRate < 0 || samplingRate > 1) {
throw new Error(`LANGSMITH_TRACING_SAMPLING_RATE must be between 0 and 1 if set. Got: ${samplingRate}`);
}
return samplingRate;
};
// utility functions
const isLocalhost = (url) => {
const strippedUrl = url.replace("http://", "").replace("https://", "");
const hostname = strippedUrl.split("/")[0].split(":")[0];
return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
};
async function toArray(iterable) {
const result = [];
for await (const item of iterable) {
result.push(item);
}
return result;
}
function trimQuotes(str) {
if (str === undefined) {
return undefined;
}
return str
.trim()
.replace(/^"(.*)"$/, "$1")
.replace(/^'(.*)'$/, "$1");
}
const handle429 = async (response) => {
if (response?.status === 429) {
const retryAfter = parseInt(response.headers.get("retry-after") ?? "10", 10) * 1000;
if (retryAfter > 0) {
await new Promise((resolve) => setTimeout(resolve, retryAfter));
// Return directly after calling this check
return true;
}
}
// Fall back to existing status checks
return false;
};
function _formatFeedbackScore(score) {
if (typeof score === "number") {
// Truncate at 4 decimal places
return Number(score.toFixed(4));
}
return score;
}
exports.DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES = 24 * 1024 * 1024;
/** Default maximum memory (1GB) for queue size limits. */
exports.DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024; // 1GB
const SERVER_INFO_REQUEST_TIMEOUT_MS = 10000;
/** Maximum number of operations to batch in a single request. */
const DEFAULT_BATCH_SIZE_LIMIT = 100;
class AutoBatchQueue {
constructor(maxSizeBytes) {
Object.defineProperty(this, "items", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "sizeBytes", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "maxSizeBytes", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.maxSizeBytes = maxSizeBytes ?? exports.DEFAULT_MAX_SIZE_BYTES;
}
peek() {
return this.items[0];
}
push(item) {
let itemPromiseResolve;
const itemPromise = new Promise((resolve) => {
// Setting itemPromiseResolve is synchronous with promise creation:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
itemPromiseResolve = resolve;
});
// Use a cheap structural estimate for soft memory accounting (queue size
// limit and downstream async caller memory tracking). The exact
// serialization still happens later, off the hot path, when the batch is
// assembled for sending.
const size = (0, index_js_3.estimateSerializedSize)(item.item).size;
// Check if adding this item would exceed the size limit
// Allow the run if the queue is empty (to support large single traces)
if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
console.warn(`AutoBatchQueue size limit (${this.maxSizeBytes} bytes) exceeded. Dropping run with id: ${item.item.id}. ` +
`Current queue size: ${this.sizeBytes} bytes, attempted addition: ${size} bytes.`);
// Resolve immediately to avoid blocking caller
itemPromiseResolve();
return itemPromise;
}
this.items.push({
action: item.action,
payload: item.item,
otelContext: item.otelContext,
apiKey: item.apiKey,
apiUrl: item.apiUrl,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
itemPromiseResolve: itemPromiseResolve,
itemPromise,
size,
});
this.sizeBytes += size;
return itemPromise;
}
pop({ upToSizeBytes, upToSize, }) {
if (upToSizeBytes < 1) {
throw new Error("Number of bytes to pop off may not be less than 1.");
}
const popped = [];
let poppedSizeBytes = 0;
// Pop items until we reach or exceed the size limit
while (poppedSizeBytes + (this.peek()?.size ?? 0) < upToSizeBytes &&
this.items.length > 0 &&
popped.length < upToSize) {
const item = this.items.shift();
if (item) {
popped.push(item);
poppedSizeBytes += item.size;
this.sizeBytes -= item.size;
}
}
// If there is an item on the queue we were unable to pop,
// just return it as a single batch.
if (popped.length === 0 && this.items.length > 0) {
const item = this.items.shift();
popped.push(item);
poppedSizeBytes += item.size;
this.sizeBytes -= item.size;
}
return [
popped.map((it) => ({
action: it.action,
item: it.payload,
otelContext: it.otelContext,
apiKey: it.apiKey,
apiUrl: it.apiUrl,
size: it.size,
})),
() => popped.forEach((it) => it.itemPromiseResolve()),
];
}
}
exports.AutoBatchQueue = AutoBatchQueue;
class Client {
get tracingMode() {
return this._tracingMode;
}
get _fetch() {
const fetchImplementation = this.fetchImplementation || (0, fetch_js_1._getFetchImplementation)(this.debug);
return (async (input, init) => {
let authHeader;
const profileManagedAuthorization = this.getProfileManagedAuthorizationHeader(init);
if (this.apiKey !== undefined) {
authHeader = { name: "x-api-key", value: `${this.apiKey}` };
}
else if (!this.hasExplicitAuthHeader(init, profileManagedAuthorization)) {
authHeader = await this.profileAuth?.getAuthHeader(fetchImplementation, init?.signal);
}
return fetchImplementation(input, this.applyCurrentAuthHeaders(init, authHeader, profileManagedAuthorization));
});
}
getProfileManagedAuthorizationHeader(init) {
if (!init?.headers || !this.profileAuth) {
return undefined;
}
const authorization = new Headers(init.headers).get("Authorization");
if (!(0, profiles_js_1.hasValue)(authorization)) {
return undefined;
}
return this.profileAuth.isProfileAuthorizationHeader(authorization ?? "")
? (authorization ?? undefined)
: undefined;
}
isProfileManagedAuthorizationHeader(value, profileManagedAuthorization) {
return (value === profileManagedAuthorization ||
this.profileAuth?.isProfileAuthorizationHeader(value) === true);
}
hasExplicitAuthHeader(init, profileManagedAuthorization) {
if (!init?.headers) {
return false;
}
const headers = new Headers(init.headers);
if ((0, profiles_js_1.hasValue)(headers.get("x-api-key"))) {
return true;
}
const authorization = headers.get("Authorization");
if (!(0, profiles_js_1.hasValue)(authorization)) {
return false;
}
return !this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization);
}
applyCurrentAuthHeaders(init, authHeader, profileManagedAuthorization) {
if (!authHeader) {
return init;
}
const applyAuth = (headers) => {
if (this.apiKey !== undefined && authHeader.name === "x-api-key") {
headers.delete("Authorization");
if (!headers.has("x-api-key")) {
headers.set("x-api-key", authHeader.value);
}
return headers;
}
if (authHeader.name === "Authorization") {
if ((0, profiles_js_1.hasValue)(headers.get("x-api-key"))) {
return headers;
}
const authorization = headers.get("Authorization");
if ((0, profiles_js_1.hasValue)(authorization) &&
!this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization)) {
return headers;
}
headers.set("Authorization", authHeader.value);
return headers;
}
const authorization = headers.get("Authorization");
if ((0, profiles_js_1.hasValue)(authorization) &&
!this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization)) {
return headers;
}
if ((0, profiles_js_1.hasValue)(authorization)) {
headers.delete("Authorization");
}
if (!headers.has("x-api-key")) {
headers.set("x-api-key", authHeader.value);
}
return headers;
};
if (!init) {
return {
headers: { [authHeader.name]: authHeader.value },
};
}
if (init.headers instanceof Headers) {
return { ...init, headers: applyAuth(new Headers(init.headers)) };
}
if (Array.isArray(init.headers)) {
return { ...init, headers: applyAuth(new Headers(init.headers)) };
}
const headers = {
...(init.headers ?? {}),
};
const getHeaderKey = (name) => Object.keys(headers).find((key) => key.toLowerCase() === name);
const getHeader = (name) => {
const key = getHeaderKey(name);
return key ? headers[key] : undefined;
};
const hasApiKey = (0, profiles_js_1.hasValue)(getHeader("x-api-key"));
const authorization = getHeader("authorization");
const hasExplicitAuthorization = (0, profiles_js_1.hasValue)(authorization) &&
!this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization);
if (this.apiKey !== undefined && authHeader.name === "x-api-key") {
const authorizationKey = getHeaderKey("authorization");
if (authorizationKey) {
delete headers[authorizationKey];
}
if (!hasApiKey) {
headers["x-api-key"] = authHeader.value;
}
return { ...init, headers };
}
if (authHeader.name === "Authorization") {
if (!hasApiKey && !hasExplicitAuthorization) {
const authorizationKey = getHeaderKey("authorization");
if (authorizationKey && authorizationKey !== "Authorization") {
delete headers[authorizationKey];
}
headers.Authorization = authHeader.value;
}
return { ...init, headers };
}
if (!hasExplicitAuthorization) {
const authorizationKey = getHeaderKey("authorization");
if (authorizationKey) {
delete headers[authorizationKey];
}
if (!hasApiKey) {
headers["x-api-key"] = authHeader.value;
}
}
return { ...init, headers };
}
/**
* Serialize a payload for tracing, optionally offloading the work to a
* Node worker thread when the runtime supports worker_threads.
*
* Falls back to synchronous serialization when:
* - manualFlushMode is enabled (serverless: worker boot cost > benefit)
* - worker_threads is unavailable (non-Node runtimes)
* - the payload contains values that can't be structured-cloned across
* threads (functions, non-cloneable class instances, streams, etc.)
* - the worker throws for any other reason
*
* In all fallback cases the returned bytes are identical to the sync path.
*/
_trackDrain(promise) {
this._pendingDrains.add(promise);
promise.finally(() => {
this._pendingDrains.delete(promise);
});
}
async _serializeBody(payload, errorContext) {
if (this.manualFlushMode) {
return (0, index_js_3.serialize)(payload, errorContext);
}
// Shape-aware gate: worker offload pays for itself only when the
// payload is dominated by one or more large strings (V8 can refcount
// those across isolates instead of copying). For structure-heavy
// payloads -- many keys, deep nesting, lots of small strings -- the
// structuredClone walk plus thread-hop cost exceeds the JSON.stringify
// cost we would pay inline, so we fall through to sync serialize.
if (!(0, serialize_worker_js_1.hasLargeString)(payload)) {
return (0, index_js_3.serialize)(payload, errorContext);
}
if (this._serializeWorker === undefined) {
this._serializeWorker = (0, serialize_worker_js_1.getSharedSerializeWorker)();
}
if (this._serializeWorker === null) {
return (0, index_js_3.serialize)(payload, errorContext);
}
try {
const bytes = await this._serializeWorker.serialize(payload);
if (bytes === null) {
// Worker subsystem unavailable; cache the null to skip re-entry.
this._serializeWorker = null;
return (0, index_js_3.serialize)(payload, errorContext);
}
return bytes;
}
catch {
// DataCloneError, worker crash, etc. Fall back silently.
return (0, index_js_3.serialize)(payload, errorContext);
}
}
constructor(config = {}) {
Object.defineProperty(this, "apiKey", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "apiUrl", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "webUrl", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "workspaceId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "caller", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "batchIngestCaller", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "timeout_ms", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_tenantId", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "hideInputs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "hideOutputs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "hideMetadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "omitTracedRuntimeInfo", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tracingSampleRate", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "filteredPostUuids", {
enumerable: true,
configurable: true,
writable: true,
value: new Set()
});
Object.defineProperty(this, "autoBatchTracing", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "autoBatchQueue", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "autoBatchTimeout", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "autoBatchAggregationDelayMs", {
enumerable: true,
configurable: true,
writable: true,
value: 250
});
Object.defineProperty(this, "batchSizeBytesLimit", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "batchSizeLimit", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "fetchOptions", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "settings", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "blockOnRootRunFinalization", {
enumerable: true,
configurable: true,
writable: true,
value: (0, env_js_1.getEnvironmentVariable)("LANGSMITH_TRACING_BACKGROUND") === "false"
});
Object.defineProperty(this, "traceBatchConcurrency", {
enumerable: true,
configurable: true,
writable: true,
value: 5
});
Object.defineProperty(this, "_serverInfo", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Object.defineProperty(this, "_getServerInfoPromise", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "manualFlushMode", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_serializeWorker", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
* can wait on them even if the flush involves async work (worker-thread
* serialize) that hasn't yet registered with batchIngestCaller.queue.
*/
Object.defineProperty(this, "_pendingDrains", {
enumerable: true,
configurable: true,
writable: true,
value: new Set()
});
Object.defineProperty(this, "langSmithToOTELTranslator", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_tracingMode", {
enumerable: true,
configurable: true,
writable: true,
value: "langsmith"
});
Object.defineProperty(this, "fetchImplementation", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "cachedLSEnvVarsForMetadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_promptCache", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "profileAuth", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "multipartStreamingDisabled", {
enumerable: true,
configurable: true,
writable: true,
value: (0, env_js_1.getLangSmithEnvironmentVariable)("DISABLE_MULTIPART_STREAMING") === "true"
});
Object.defineProperty(this, "_multipartDisabled", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_runCompressionDisabled", {
enumerable: true,
configurable: true,
writable: true,
value: (0, env_js_1.getLangSmithEnvironmentVariable)("DISABLE_RUN_COMPRESSION") === "true"
});
Object.defineProperty(this, "failedTracesDir", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "failedTracesMaxBytes", {
enumerable: true,
configurable: true,
writable: true,
value: 100 * 1024 * 1024
});
Object.defineProperty(this, "_customHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "debug", {
enumerable: true,
configurable: true,
writable: true,
value: (0, env_js_1.getEnvironmentVariable)("LANGSMITH_DEBUG") === "true"
});
const defaultConfig = Client.getDefaultClientConfig();
this.tracingSampleRate = getTracingSamplingRate(config.tracingSamplingRate);
this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? "";
if (this.apiUrl.endsWith("/")) {
this.apiUrl = this.apiUrl.slice(0, -1);
}
const configuredApiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey);
this.apiKey = (0, profiles_js_1.hasValue)(configuredApiKey) ? configuredApiKey : undefined;
this.profileAuth =
this.apiKey !== undefined ? undefined : defaultConfig.profileAuth;
this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
if (this.webUrl?.endsWith("/")) {
this.webUrl = this.webUrl.slice(0, -1);
}
this.workspaceId = trimQuotes(config.workspaceId ?? defaultConfig.workspaceId);
this.timeout_ms = config.timeout_ms ?? 90_000;
this.caller = new async_caller_js_1.AsyncCaller({
...(config.callerOptions ?? {}),
maxRetries: 4,
debug: config.debug ?? this.debug,
});
this.traceBatchConcurrency =
config.traceBatchConcurrency ?? this.traceBatchConcurrency;
if (this.traceBatchConcurrency < 1) {
throw new Error("Trace batch concurrency must be positive.");
}
this.debug = config.debug ?? this.debug;
this.fetchImplementation = config.fetchImplementation;
// Failed trace dump configuration
this.failedTracesDir =
(0, env_js_1.getLangSmithEnvironmentVariable)("FAILED_TRACES_DIR") || undefined;
const failedTracesMb = (0, env_js_1.getLangSmithEnvironmentVariable)("FAILED_TRACES_MAX_MB");
if (failedTracesMb) {
const n = parseInt(failedTracesMb, 10);
if (Number.isFinite(n) && n > 0) {
this.failedTracesMaxBytes = n * 1024 * 1024;
}
}
// Use maxIngestMemoryBytes for both queues
const maxMemory = config.maxIngestMemoryBytes ?? exports.DEFAULT_MAX_SIZE_BYTES;
this.batchIngestCaller = new async_caller_js_1.AsyncCaller({
maxRetries: 4,
maxConcurrency: this.traceBatchConcurrency,
maxQueueSizeBytes: maxMemory,
...(config.callerOptions ?? {}),
onFailedResponseHook: handle429,
debug: config.debug ?? this.debug,
});
this.hideInputs =
config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
this.hideOutputs =
config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
this.hideMetadata = config.hideMetadata ?? defaultConfig.hideMetadata;
this.omitTracedRuntimeInfo = config.omitTracedRuntimeInfo ?? false;
this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
this.autoBatchQueue = new AutoBatchQueue(maxMemory);
this.blockOnRootRunFinalization =
config.blockOnRootRunFinalization ?? this.blockOnRootRunFinalization;
this.batchSizeBytesLimit = config.batchSizeBytesLimit;
this.batchSizeLimit = config.batchSizeLimit;
this.fetchOptions = config.fetchOptions || {};
this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
this._tracingMode = (0, env_js_1.resolveTracingMode)(config.tracingMode);
if (this._tracingMode === "otel") {
this.langSmithToOTELTranslator = new translator_js_1.LangSmithToOTELTranslator();
}
// Cache metadata env vars once during construction to avoid repeatedly scanning process.env
this.cachedLSEnvVarsForMetadata = (0, env_js_1.getLangSmithEnvVarsMetadata)();
// Initialize prompt cache
// Handle backwards compatibility for deprecated `cache` parameter
if (config.cache !== undefined && config.disablePromptCache) {
(0, warn_js_1.warnOnce)("Both 'cache' and 'disablePromptCache' were provided. " +
"The 'cache' parameter is deprecated and will be removed in a future version. " +
"Using 'cache' parameter value.");
}
if (config.cache !== undefined) {
(0, warn_js_1.warnOnce)("The 'cache' parameter is deprecated and will be removed in a future version. " +
"Use 'configureGlobalPromptCache()' to configure the global cache, or " +
"'disablePromptCache: true' to disable caching for this client.");
// Handle old cache parameter
if (config.cache === false) {
this._promptCache = undefined;
}
else if (config.cache === true) {
this._promptCache = index_js_2.promptCacheSingleton;
}
else {
// Custom PromptCache instance provided
this._promptCache = config.cache;
}
}
else if (!config.disablePromptCache) {
// Use the global singleton instance
this._promptCache = index_js_2.promptCacheSingleton;
}
// Initialize custom headers
this._customHeaders = config.headers ?? {};
}
static getDefaultClientConfig() {
const profileConfig = (0, profiles_js_1.loadProfileClientConfig)();
const envApiKey = (0, env_js_1.getLangSmithEnvironmentVariable)("API_KEY");
const envApiUrl = (0, env_js_1.getLangSmithEnvironmentVariable)("ENDPOINT");
const envWorkspaceId = (0, env_js_1.getLangSmithEnvironmentVariable)("WORKSPACE_ID");
const envAuthSet = (0, profiles_js_1.hasValue)(envApiKey);
const apiUrl = envApiUrl ?? profileConfig.apiUrl ?? profiles_js_1.DEFAULT_API_URL;
const workspaceId = envWorkspaceId ?? profileConfig.workspaceId;
const hideInputs = (0, env_js_1.getLangSmithEnvironmentVariable)("HIDE_INPUTS") === "true";
const hideOutputs = (0, env_js_1.getLangSmithEnvironmentVariable)("HIDE_OUTPUTS") === "true";
const hideMetadata = (0, env_js_1.getLangSmithEnvironmentVariable)("HIDE_METADATA") === "true";
return {
apiUrl: apiUrl,
apiKey: envApiKey,
webUrl: undefined,
hideInputs: hideInputs,
hideOutputs: hideOutputs,
hideMetadata: hideMetadata,
workspaceId: workspaceId,
oauthAccessToken: !envAuthSet
? profileConfig.oauthAccessToken
: undefined,
oauthRefreshToken: !envAuthSet
? profileConfig.oauthRefreshToken
: undefined,
profileAuth: !envAuthSet ? profileConfig.profileAuth : undefined,
};
}
getHostUrl() {
if (this.webUrl) {
return this.webUrl;
}
else if (isLocalhost(this.apiUrl)) {
this.webUrl = "http://localhost:3000";
return this.webUrl;
}
else if (this.apiUrl.endsWith("/api/v1")) {
this.webUrl = this.apiUrl.replace("/api/v1", "");
return this.webUrl;
}
else if (this.apiUrl.includes("/api") &&
!this.apiUrl.split(".", 1)[0].endsWith("api")) {
this.webUrl = this.apiUrl.replace("/api", "");
return this.webUrl;
}
else if (this.apiUrl.split(".", 1)[0].includes("dev")) {
this.webUrl = "https://dev.smith.langchain.com";
return this.webUrl;
}
else if (this.apiUrl.split(".", 1)[0].includes("eu")) {
this.webUrl = "https://eu.smith.langchain.com";
return this.webUrl;
}
else if (this.apiUrl.split(".", 1)[0].includes("aws")) {
this.webUrl = "https://aws.smith.langchain.com";
return this.webUrl;
}
else if (this.apiUrl.split(".", 1)[0].includes("apac")) {
this.webUrl = "https://apac.smith.langchain.com";
return this.webUrl;
}
else if (this.apiUrl.split(".", 1)[0].includes("beta")) {
this.webUrl = "https://beta.smith.langchain.com";
return this.webUrl;
}
else {
this.webUrl = "https://smith.langchain.com";
return this.webUrl;
}
}
get _mergedHeaders() {
// Start with custom headers so they don't override required headers
const headers = {
"User-Agent": `langsmith-js/${index_js_1.__version__}`,
...this._customHeaders,
};
// Required headers that should not be overridden
if (this.apiKey !== undefined) {
headers["x-api-key"] = `${this.apiKey}`;
}
else {
const profileAuthHeader = this.profileAuth?.currentAuthHeader();
if (profileAuthHeader) {
headers[profileAuthHeader.name] = profileAuthHeader.value;
}
}
if (this.workspaceId) {
headers["x-tenant-id"] = this.workspaceId;
}
return headers;
}
/**
* Get or set custom headers for the client.
* Custom headers are merged with default headers (User-Agent, x-api-key, x-tenant-id).
* Custom headers will not override the default required headers.
*/
get headers() {
return this._customHeaders;
}
set headers(value) {
this._customHeaders = value ?? {};
}
_getPlatformEndpointPath(path) {
// Check if apiUrl already ends with /v1 or /v1/ to avoid double /v1/v1/ paths
const needsV1Prefix = this.apiUrl.slice(-3) !== "/v1" && this.apiUrl.slice(-4) !== "/v1/";
return needsV1Prefix ? `/v1/platform/${path}` : `/platform/${path}`;
}
async processInputs(inputs) {
if (this.hideInputs === false) {
return inputs;
}
if (this.hideInputs === true) {
return {};
}
if (typeof this.hideInputs === "function") {
return this.hideInputs(inputs);
}
return inputs;
}
async processOutputs(outputs) {
if (this.hideOutputs === false) {
return outputs;
}
if (this.hideOutputs === true) {
return {};
}
if (typeof this.hideOutputs === "function") {
return this.hideOutputs(outputs);
}
return outputs;
}
async processMetadata(metadata) {
if (this.hideMetadata === false) {
return metadata;
}
if (this.hideMetadata === true) {
return {};
}
if (typeof this.hideMetadata === "function") {
return this.hideMetadata(metadata);
}
return metadata;
}
/**
* Filter content from new_token events to prevent streaming LLM output
* from being uploaded via events.
*/
_filterNewTokenEvents(events) {
if (!events || events.length === 0) {
return events;
}
return events.map((event) => {
if (event.name === "new_token") {
// Remove the kwargs containing the token data
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { kwargs: _, ...rest } = event;
return rest;
}
return event;
});
}
async prepareRunCreateOrUpdateInputs(run) {
const runParams = { ...run };
if (runParams.inputs !== undefined) {
runParams.inputs = await this.processInputs(runParams.inputs);
}
if (runParams.outputs !== undefined) {
runParams.outputs = await this.processOutputs(runParams.outputs);
}
if (runParams.extra != null && "metadata" in runParams.extra) {
runParams.extra = {
...runParams.extra,
metadata: await this.processMetadata(runParams.extra.metadata),
};
}
if (runParams.events !== undefined) {
runParams.events = this._filterNewTokenEvents(runParams.events);
}
return runParams;
}
async _getResponse(path, queryParams) {
const paramsString = queryParams?.toString() ?? "";
const url = `${this.apiUrl}${path}?${paramsString}`;
const response = await this.caller.call(async () => {
const res = await this._fetch(url, {
method: "GET",
headers: this._mergedHeaders,
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
});
await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
return res;
});
return response;
}
async _get(path, queryParams) {
const response = await this._getResponse(path, queryParams);
return response.json();
}
async *_getPaginated(path, queryParams = new URLSearchParams(), transform) {
let offset = Number(queryParams.get("offset")) || 0;
const limit = Number(queryParams.get("limit")) || 100;
while (true) {
queryParams.set("offset", String(offset));
queryParams.set("limit", String(limit));
const url = `${this.apiUrl}${path}?${queryParams}`;
const response = await this.caller.call(async () => {
const res = await this._fetch(url, {
method: "GET",
headers: this._mergedHeaders,
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
});
await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
return res;
});
const items = transform
? transform(await response.json())
: await response.json();
if (items.length === 0) {
break;
}
yield items;
if (items.length < limit) {
break;
}
offset += items.length;
}
}
async *_getCursorPaginatedList(path, body = null, requestMethod = "POST", dataKey = "runs") {
const bodyParams = body ? { ...body } : {};
while (true) {
const body = JSON.stringify(bodyParams);
const response = await this.caller.call(async () => {
const res = await this._fetch(`${this.apiUrl}${path}`, {
method: requestMethod,
headers: {
...this._mergedHeaders,
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
body,
});
await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
return res;
});
const responseBody = await response.json();
if (!responseBody) {
break;
}
if (!responseBody[dataKey]) {
break;
}
yield responseBody[dataKey];
const cursors = responseBody.cursors;
if (!cursors) {
break;
}
if (!cursors.next) {
break;
}
bodyParams.cursor = cursors.next;
}
}
// Allows mocking for tests
_shouldSample() {
if (this.tracingSampleRate === undefined) {
return true;
}
return Math.random() < this.tracingSampleRate;
}
_filterForSampling(runs, patch = false) {
if (this.tracingSampleRate === undefined) {
return runs;
}
if (patch) {
const sampled = [];
for (const run of runs) {
if (!this.filteredPostUuids.has(run.trace_id)) {
sampled.push(run);
}
else if (run.id === run.trace_id) {
this.filteredPostUuids.delete(run.trace_id);
}
}
return sampled;
}
else {
// For new runs, sample at trace level to maintain consistency
const sampled = [];
for (const run of runs) {
const traceId = run.trace_id ?? run.id;
// If we've already made a decision about this trace, follow it
if (this.filteredPostUuids.has(traceId)) {
continue;
}
// For new traces, apply sampling
if (run.id === traceId) {
if (this._shouldSample()) {
sampled.push(run);
}
else {
this.filteredPostUuids.add(traceId);
}
}
else {
// Child runs follow their trace's sampling decision
sampled.push(run);
}
}
return sampled;
}
}
async _getBatchSizeLimitBytes() {
const serverInfo = await this._ensureServerInfo();
return (this.batchSizeBytesLimit ??
serverInfo?.batch_ingest_config?.size_limit_bytes ??
exports.DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES);
}
/**
* Get the maximum number of operations to batch in a single request.
*/
async _getBatchSizeLimit() {
const serverInfo = await this._ensureServerInfo();
return (this.batchSizeLimit ??
serverInfo?.batch_ingest_config?.size_limit ??
DEFAULT_BATCH_SIZE_LIMIT);
}
async _getDatasetExamplesMultiPartSupport() {
const serverInfo = await this._ensureServerInfo();
return (serverInfo.instance_flags?.dataset_examples_multipart_enabled ?? false);
}
drainAutoBatchQueue({ batchSizeLimitBytes, batchSizeLimit, }) {
const promises = [];
while (this.autoBatchQueue.items.length > 0) {
const [batch, done] = this.autoBatchQueue.pop({
upToSizeBytes: batchSizeLimitBytes,
upToSize: batchSizeLimit,
});
if (!batch.length) {
done();
break;
}
const batchesByDestination = batch.reduce((acc, item) => {
const apiUrl = item.apiUrl ?? this.apiUrl;
const apiKey = item.apiKey ?? this.apiKey;
const isDefault = item.apiKey === this.apiKey && item.apiUrl === this.apiUrl;
const batchKey = isDefault ? "default" : `${apiUrl}|${apiKey}`;
if (!acc[batchKey]) {
acc[batchKey] = [];
}
acc[batchKey].push(item);
return acc;
}, {});
const batchPromises = [];
for (const [batchKey, batch] of Object.entries(batchesByDestination)) {
const batchPromise = this._processBatch(batch, {
apiUrl: batchKey === "default" ? undefined : batchKey.split("|")[0],
apiKey: batchKey === "default" ? undefined : batchKey.split("|")[1],
});
batchPromises.push(batchPromise);
}
// Wait for all batches to complete, then call the overall done callback
const allBatchesPromise = Promise.all(batchPromises).finally(done);
promises.push(allBatchesPromise);
}
return Promise.all(promises);
}
/**
* Persist a failed trace payload to a local fallback directory.
*
* Saves a self-contained JSON file containing the endpoint path, the HTTP
* headers required for replay, and the base64-encoded request body.
* Can be replayed later with a simple POST:
*
* POST /<endpoint>
* Content-Type: <value from saved headers>
* [Content-Encoding: <value from saved headers>]
* <decoded body>
*/
static async _writeTraceToFallbackDir(directory, body, replayHeaders, endpoint, maxBytes) {
try {
const bodyBuffer = typeof body === "string"
? Buffer.from(body, "utf8")
: Buffer.from(body);
const envelope = JSON.stringify({
version: 1,
endpoint,
headers: replayHeaders,
body_base64: bodyBuffer.toString("base64"),
});
const filename = `trace_${Date.now()}_${uuid.v4().slice(0, 8)}.json`;
const filepath = fsUtils.path.join(directory, filename);
if (!Client._fallbackDirsCreated.has(directory)) {
await fsUtils.mkdir(directory);
Client._fallbackDirsCreated.add(directory);
}
// Check budget before writing — drop new traces if over limit.
if (maxBytes !== undefined && maxBytes > 0) {
try {
const entries = await fsUtils.readdir(directory);
const traceFiles = entries.filter((f) => f.startsWith("trace_") && f.endsWith(".json"));
let total = 0;
for (const name of traceFiles) {
const { size } = await fsUtils.stat(fsUtils.path.join(directory, name));
total += size;
}
if (total >= maxBytes) {
console.warn(`Could not write trace to fallback dir ${directory} as it's ` +
`already over size limit (${total} bytes >= ${maxBytes} bytes). ` +
`Increase LANGSMITH_FAILED_TRACES_MAX_MB if possible.`);
return;
}
}
catch {
// budget check errors must never prevent writing
}
}
await fsUtils.writeFileAtomic(filepath, envelope);
console.warn(`LangSmith trace upload failed; data saved to ${filepath} for later replay.`);
}
catch (writeErr) {
console.error(`LangSmith tracing error: could not write trace to fallback dir ${directory}:`, writeErr);
}
}
async _processBatch(batch, options) {
if (!batch.length) {
return;
}
// Calculate total batch size for queue tracking
const batchSizeBytes = batch.reduce((sum, item) => sum + (item.size ?? 0), 0);
try {
if (this.langSmithToOTELTranslator !== undefined) {
this._sendBatchToOTELTranslator(batch);
}
else {
const ingestParams = {
runCreates: batch
.filter((i