UNPKG

langsmith

Version:

Client library to connect to the LangSmith Observability and Evaluation Platform.

1,253 lines 217 kB
"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