langsmith 0.5.5 → 0.5.7

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 CHANGED
@@ -47,6 +47,7 @@ const warn_js_1 = require("./utils/warn.cjs");
47
47
  const prompts_js_1 = require("./utils/prompts.cjs");
48
48
  const error_js_1 = require("./utils/error.cjs");
49
49
  const index_js_2 = require("./utils/prompt_cache/index.cjs");
50
+ const fsUtils = __importStar(require("./utils/fs.cjs"));
50
51
  const fetch_js_1 = require("./singletons/fetch.cjs");
51
52
  const index_js_3 = require("./utils/fast-safe-stringify/index.cjs");
52
53
  function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
@@ -435,6 +436,18 @@ class Client {
435
436
  writable: true,
436
437
  value: (0, env_js_1.getLangSmithEnvironmentVariable)("DISABLE_RUN_COMPRESSION") === "true"
437
438
  });
439
+ Object.defineProperty(this, "failedTracesDir", {
440
+ enumerable: true,
441
+ configurable: true,
442
+ writable: true,
443
+ value: void 0
444
+ });
445
+ Object.defineProperty(this, "failedTracesMaxBytes", {
446
+ enumerable: true,
447
+ configurable: true,
448
+ writable: true,
449
+ value: 100 * 1024 * 1024
450
+ });
438
451
  Object.defineProperty(this, "debug", {
439
452
  enumerable: true,
440
453
  configurable: true,
@@ -466,6 +479,16 @@ class Client {
466
479
  }
467
480
  this.debug = config.debug ?? this.debug;
468
481
  this.fetchImplementation = config.fetchImplementation;
482
+ // Failed trace dump configuration
483
+ this.failedTracesDir =
484
+ (0, env_js_1.getLangSmithEnvironmentVariable)("FAILED_TRACES_DIR") || undefined;
485
+ const failedTracesMb = (0, env_js_1.getLangSmithEnvironmentVariable)("FAILED_TRACES_MAX_MB");
486
+ if (failedTracesMb) {
487
+ const n = parseInt(failedTracesMb, 10);
488
+ if (Number.isFinite(n) && n > 0) {
489
+ this.failedTracesMaxBytes = n * 1024 * 1024;
490
+ }
491
+ }
469
492
  // Use maxIngestMemoryBytes for both queues
470
493
  const maxMemory = config.maxIngestMemoryBytes ?? exports.DEFAULT_MAX_SIZE_BYTES;
471
494
  this.batchIngestCaller = new async_caller_js_1.AsyncCaller({
@@ -806,6 +829,63 @@ class Client {
806
829
  }
807
830
  return Promise.all(promises);
808
831
  }
832
+ /**
833
+ * Persist a failed trace payload to a local fallback directory.
834
+ *
835
+ * Saves a self-contained JSON file containing the endpoint path, the HTTP
836
+ * headers required for replay, and the base64-encoded request body.
837
+ * Can be replayed later with a simple POST:
838
+ *
839
+ * POST /<endpoint>
840
+ * Content-Type: <value from saved headers>
841
+ * [Content-Encoding: <value from saved headers>]
842
+ * <decoded body>
843
+ */
844
+ static async _writeTraceToFallbackDir(directory, body, replayHeaders, endpoint, maxBytes) {
845
+ try {
846
+ const bodyBuffer = typeof body === "string"
847
+ ? Buffer.from(body, "utf8")
848
+ : Buffer.from(body);
849
+ const envelope = JSON.stringify({
850
+ version: 1,
851
+ endpoint,
852
+ headers: replayHeaders,
853
+ body_base64: bodyBuffer.toString("base64"),
854
+ });
855
+ const filename = `trace_${Date.now()}_${uuid.v4().slice(0, 8)}.json`;
856
+ const filepath = fsUtils.path.join(directory, filename);
857
+ if (!Client._fallbackDirsCreated.has(directory)) {
858
+ await fsUtils.mkdir(directory);
859
+ Client._fallbackDirsCreated.add(directory);
860
+ }
861
+ // Check budget before writing — drop new traces if over limit.
862
+ if (maxBytes !== undefined && maxBytes > 0) {
863
+ try {
864
+ const entries = await fsUtils.readdir(directory);
865
+ const traceFiles = entries.filter((f) => f.startsWith("trace_") && f.endsWith(".json"));
866
+ let total = 0;
867
+ for (const name of traceFiles) {
868
+ const { size } = await fsUtils.stat(fsUtils.path.join(directory, name));
869
+ total += size;
870
+ }
871
+ if (total >= maxBytes) {
872
+ console.warn(`Could not write trace to fallback dir ${directory} as it's ` +
873
+ `already over size limit (${total} bytes >= ${maxBytes} bytes). ` +
874
+ `Increase LANGSMITH_FAILED_TRACES_MAX_MB if possible.`);
875
+ return;
876
+ }
877
+ }
878
+ catch {
879
+ // budget check errors must never prevent writing
880
+ }
881
+ }
882
+ await fsUtils.writeFileAtomic(filepath, envelope);
883
+ console.warn(`LangSmith trace upload failed; data saved to ${filepath} for later replay.`);
884
+ }
885
+ catch (writeErr) {
886
+ console.error(`LangSmith tracing error: could not write trace to fallback dir ${directory}:`, writeErr);
887
+ }
888
+ }
809
889
  async _processBatch(batch, options) {
810
890
  if (!batch.length) {
811
891
  return;
@@ -1386,6 +1466,12 @@ class Client {
1386
1466
  throw e;
1387
1467
  }
1388
1468
  console.warn(`${e.message.trim()}\n\nContext: ${context}`);
1469
+ if (this.failedTracesDir) {
1470
+ const bodyBuffer = await this._createNodeFetchBody(parts, boundary).catch(() => null);
1471
+ if (bodyBuffer) {
1472
+ await Client._writeTraceToFallbackDir(this.failedTracesDir, bodyBuffer, { "Content-Type": `multipart/form-data; boundary=${boundary}` }, "runs/multipart", this.failedTracesMaxBytes);
1473
+ }
1474
+ }
1389
1475
  }
1390
1476
  }
1391
1477
  async updateRun(runId, run, options) {
@@ -1739,6 +1825,128 @@ class Client {
1739
1825
  }
1740
1826
  }
1741
1827
  }
1828
+ async *readThread(props) {
1829
+ const { threadId, projectId, projectName, isRoot = true, limit, filter: userFilter, order = "asc", } = props;
1830
+ if (!projectId && !projectName) {
1831
+ throw new Error("threadId requires projectId or projectName");
1832
+ }
1833
+ const threadFilter = `eq(thread_id, ${JSON.stringify(threadId)})`;
1834
+ const combinedFilter = userFilter
1835
+ ? `and(${threadFilter}, ${userFilter})`
1836
+ : threadFilter;
1837
+ yield* this.listRuns({
1838
+ projectId: projectId ?? undefined,
1839
+ projectName: projectName ?? undefined,
1840
+ isRoot,
1841
+ limit,
1842
+ filter: combinedFilter,
1843
+ order,
1844
+ });
1845
+ }
1846
+ async listThreads(props) {
1847
+ const { projectId, projectName, limit, offset = 0, filter, startTime, isRoot = true, } = props;
1848
+ if (!projectId && !projectName) {
1849
+ throw new Error("Either projectId or projectName must be provided");
1850
+ }
1851
+ if (projectId && projectName) {
1852
+ throw new Error("Provide exactly one of projectId or projectName");
1853
+ }
1854
+ const sessionId = projectId ?? (await this.readProject({ projectName: projectName })).id;
1855
+ const startTimeResolved = startTime ?? new Date(Date.now() - 1 * 24 * 60 * 60 * 1000);
1856
+ const runSelect = [
1857
+ "id",
1858
+ "name",
1859
+ "status",
1860
+ "start_time",
1861
+ "end_time",
1862
+ "thread_id",
1863
+ "trace_id",
1864
+ "run_type",
1865
+ "error",
1866
+ "tags",
1867
+ "session_id",
1868
+ "parent_run_id",
1869
+ "total_tokens",
1870
+ "total_cost",
1871
+ "dotted_order",
1872
+ "reference_example_id",
1873
+ "feedback_stats",
1874
+ "app_path",
1875
+ "completion_cost",
1876
+ "completion_tokens",
1877
+ "prompt_cost",
1878
+ "prompt_tokens",
1879
+ "first_token_time",
1880
+ ];
1881
+ const bodyQuery = {
1882
+ session: [sessionId],
1883
+ is_root: isRoot,
1884
+ limit: 100,
1885
+ order: "desc",
1886
+ select: runSelect,
1887
+ start_time: startTimeResolved.toISOString(),
1888
+ };
1889
+ if (filter != null) {
1890
+ bodyQuery.filter = filter;
1891
+ }
1892
+ const threadsMap = new Map();
1893
+ for await (const runs of this._getCursorPaginatedList("/runs/query", bodyQuery)) {
1894
+ for (const run of runs) {
1895
+ const tid = run.thread_id;
1896
+ if (tid) {
1897
+ const list = threadsMap.get(tid) ?? [];
1898
+ list.push(run);
1899
+ threadsMap.set(tid, list);
1900
+ }
1901
+ }
1902
+ }
1903
+ const result = [];
1904
+ for (const [threadId, runs] of threadsMap.entries()) {
1905
+ runs.sort((a, b) => {
1906
+ const aRun = a;
1907
+ const bRun = b;
1908
+ const aStart = aRun.start_time ?? "";
1909
+ const bStart = bRun.start_time ?? "";
1910
+ if (aStart !== bStart)
1911
+ return aStart.localeCompare(bStart);
1912
+ const aOrder = aRun.dotted_order ?? "";
1913
+ const bOrder = bRun.dotted_order ?? "";
1914
+ return aOrder.localeCompare(bOrder);
1915
+ });
1916
+ const startTimes = runs
1917
+ .map((r) => r.start_time)
1918
+ .filter(Boolean);
1919
+ const sortedTimes = [...startTimes].sort();
1920
+ const minStart = sortedTimes.length ? sortedTimes[0] : "";
1921
+ const maxStart = sortedTimes.length
1922
+ ? sortedTimes[sortedTimes.length - 1]
1923
+ : "";
1924
+ result.push({
1925
+ thread_id: threadId,
1926
+ runs,
1927
+ count: runs.length,
1928
+ filter: "",
1929
+ total_tokens: 0,
1930
+ total_cost: null,
1931
+ min_start_time: minStart,
1932
+ max_start_time: maxStart,
1933
+ latency_p50: 0,
1934
+ latency_p99: 0,
1935
+ feedback_stats: null,
1936
+ first_inputs: "",
1937
+ last_outputs: "",
1938
+ last_error: null,
1939
+ });
1940
+ }
1941
+ result.sort((a, b) => {
1942
+ const aMax = a.max_start_time ?? "";
1943
+ const bMax = b.max_start_time ?? "";
1944
+ return bMax.localeCompare(aMax);
1945
+ });
1946
+ const withOffset = offset > 0 ? result.slice(offset) : result;
1947
+ const withLimit = limit !== undefined ? withOffset.slice(0, limit) : withOffset;
1948
+ return withLimit;
1949
+ }
1742
1950
  async getRunStats({ id, trace, parentRun, runType, projectNames, projectIds, referenceExampleIds, startTime, endTime, error, query, filter, traceFilter, treeFilter, isRoot, dataSourceType, }) {
1743
1951
  let projectIds_ = projectIds || [];
1744
1952
  if (projectNames) {
@@ -3605,21 +3813,110 @@ class Client {
3605
3813
  }
3606
3814
  }
3607
3815
  }
3816
+ /**
3817
+ * Check if a prompt exists.
3818
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
3819
+ * - "promptName" (for private prompts, owner defaults to "-")
3820
+ * - "owner/promptName" (for prompts with explicit owner)
3821
+ * @returns A Promise that resolves to true if the prompt exists, false otherwise
3822
+ * @example
3823
+ * ```typescript
3824
+ * // Check if a prompt exists before creating a commit
3825
+ * if (await client.promptExists("my-prompt")) {
3826
+ * await client.createCommit("my-prompt", template);
3827
+ * } else {
3828
+ * await client.createPrompt("my-prompt");
3829
+ * }
3830
+ * ```
3831
+ */
3608
3832
  async promptExists(promptIdentifier) {
3609
3833
  const prompt = await this.getPrompt(promptIdentifier);
3610
3834
  return !!prompt;
3611
3835
  }
3836
+ /**
3837
+ * Like a prompt.
3838
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
3839
+ * - "promptName" (for private prompts, owner defaults to "-")
3840
+ * - "owner/promptName" (for prompts with explicit owner)
3841
+ * @returns A Promise that resolves to the like response containing the updated like count
3842
+ * @example
3843
+ * ```typescript
3844
+ * // Like a prompt
3845
+ * const response = await client.likePrompt("owner/useful-prompt");
3846
+ * console.log(`Prompt now has ${response.likes} likes`);
3847
+ * ```
3848
+ */
3612
3849
  async likePrompt(promptIdentifier) {
3613
3850
  return this._likeOrUnlikePrompt(promptIdentifier, true);
3614
3851
  }
3852
+ /**
3853
+ * Unlike a prompt (remove a previously added like).
3854
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
3855
+ * - "promptName" (for private prompts, owner defaults to "-")
3856
+ * - "owner/promptName" (for prompts with explicit owner)
3857
+ * @returns A Promise that resolves to the like response containing the updated like count
3858
+ * @example
3859
+ * ```typescript
3860
+ * // Unlike a prompt
3861
+ * const response = await client.unlikePrompt("owner/useful-prompt");
3862
+ * console.log(`Prompt now has ${response.likes} likes`);
3863
+ * ```
3864
+ */
3615
3865
  async unlikePrompt(promptIdentifier) {
3616
3866
  return this._likeOrUnlikePrompt(promptIdentifier, false);
3617
3867
  }
3618
- async *listCommits(promptOwnerAndName) {
3619
- for await (const commits of this._getPaginated(`/commits/${promptOwnerAndName}/`, new URLSearchParams(), (res) => res.commits)) {
3868
+ /**
3869
+ * List all commits for a prompt.
3870
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
3871
+ * - "promptName" (for private prompts, owner defaults to "-")
3872
+ * - "owner/promptName" (for prompts with explicit owner)
3873
+ * - "promptName:commitHash" (commit hash is ignored, all commits are returned)
3874
+ * @returns An async iterable iterator of PromptCommit objects
3875
+ * @example
3876
+ * ```typescript
3877
+ * // List commits for a private prompt
3878
+ * for await (const commit of client.listCommits("my-prompt")) {
3879
+ * console.log(commit);
3880
+ * }
3881
+ *
3882
+ * // List commits for a prompt with explicit owner
3883
+ * for await (const commit of client.listCommits("owner/my-prompt")) {
3884
+ * console.log(commit);
3885
+ * }
3886
+ * ```
3887
+ */
3888
+ async *listCommits(promptIdentifier) {
3889
+ const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
3890
+ for await (const commits of this._getPaginated(`/commits/${owner}/${promptName}/`, new URLSearchParams(), (res) => res.commits)) {
3620
3891
  yield* commits;
3621
3892
  }
3622
3893
  }
3894
+ /**
3895
+ * List prompts by filter.
3896
+ * @param options - Optional filters for listing prompts
3897
+ * @param options.isPublic - Filter by public/private prompts. If undefined, returns all prompts.
3898
+ * @param options.isArchived - Filter by archived status. Defaults to false (non-archived prompts only).
3899
+ * @param options.sortField - Field to sort by. Defaults to "updated_at".
3900
+ * @param options.query - Search query to filter prompts by name or description.
3901
+ * @returns An async iterable iterator of Prompt objects
3902
+ * @example
3903
+ * ```typescript
3904
+ * // List all prompts
3905
+ * for await (const prompt of client.listPrompts()) {
3906
+ * console.log(prompt);
3907
+ * }
3908
+ *
3909
+ * // List only public prompts
3910
+ * for await (const prompt of client.listPrompts({ isPublic: true })) {
3911
+ * console.log(prompt);
3912
+ * }
3913
+ *
3914
+ * // Search for prompts
3915
+ * for await (const prompt of client.listPrompts({ query: "translation" })) {
3916
+ * console.log(prompt);
3917
+ * }
3918
+ * ```
3919
+ */
3623
3920
  async *listPrompts(options) {
3624
3921
  const params = new URLSearchParams();
3625
3922
  params.append("sort_field", options?.sortField ?? "updated_at");
@@ -3635,6 +3932,22 @@ class Client {
3635
3932
  yield* prompts;
3636
3933
  }
3637
3934
  }
3935
+ /**
3936
+ * Get a prompt by its identifier.
3937
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
3938
+ * - "promptName" (for private prompts, owner defaults to "-")
3939
+ * - "owner/promptName" (for prompts with explicit owner)
3940
+ * - "promptName:commitHash" (commit hash is ignored, latest version is returned)
3941
+ * @returns A Promise that resolves to the Prompt object, or null if not found
3942
+ * @example
3943
+ * ```typescript
3944
+ * // Get a private prompt
3945
+ * const prompt = await client.getPrompt("my-prompt");
3946
+ *
3947
+ * // Get a public prompt
3948
+ * const publicPrompt = await client.getPrompt("owner/public-prompt");
3949
+ * ```
3950
+ */
3638
3951
  async getPrompt(promptIdentifier) {
3639
3952
  const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
3640
3953
  const response = await this.caller.call(async () => {
@@ -3658,6 +3971,33 @@ class Client {
3658
3971
  return null;
3659
3972
  }
3660
3973
  }
3974
+ /**
3975
+ * Create a new prompt.
3976
+ * @param promptIdentifier - The identifier for the new prompt. Can be in the format:
3977
+ * - "promptName" (creates a private prompt)
3978
+ * - "owner/promptName" (creates a prompt under a specific owner, must match your tenant)
3979
+ * @param options - Optional configuration for the prompt
3980
+ * @param options.description - A description of the prompt
3981
+ * @param options.readme - Markdown content for the prompt's README
3982
+ * @param options.tags - Array of tags to categorize the prompt
3983
+ * @param options.isPublic - Whether the prompt should be public. Requires a LangChain Hub handle.
3984
+ * @returns A Promise that resolves to the created Prompt object
3985
+ * @throws {Error} If creating a public prompt without a LangChain Hub handle, or if owner doesn't match current tenant
3986
+ * @example
3987
+ * ```typescript
3988
+ * // Create a private prompt
3989
+ * const prompt = await client.createPrompt("my-new-prompt", {
3990
+ * description: "A prompt for translations",
3991
+ * tags: ["translation", "language"]
3992
+ * });
3993
+ *
3994
+ * // Create a public prompt
3995
+ * const publicPrompt = await client.createPrompt("my-public-prompt", {
3996
+ * description: "A public translation prompt",
3997
+ * isPublic: true
3998
+ * });
3999
+ * ```
4000
+ */
3661
4001
  async createPrompt(promptIdentifier, options) {
3662
4002
  const settings = await this._getSettings();
3663
4003
  if (options?.isPublic && !settings.tenant_handle) {
@@ -3692,6 +4032,35 @@ class Client {
3692
4032
  const { repo } = await response.json();
3693
4033
  return repo;
3694
4034
  }
4035
+ /**
4036
+ * Create a new commit for an existing prompt.
4037
+ * @param promptIdentifier - The identifier of the prompt. Can be in the format:
4038
+ * - "promptName" (for private prompts, owner defaults to "-")
4039
+ * - "owner/promptName" (for prompts with explicit owner)
4040
+ * @param object - The prompt object/manifest to commit (e.g., ChatPromptTemplate, messages array, etc.)
4041
+ * @param options - Optional configuration for the commit
4042
+ * @param options.parentCommitHash - The parent commit hash. Defaults to "latest" (the most recent commit).
4043
+ * @returns A Promise that resolves to the URL of the newly created commit
4044
+ * @throws {Error} If the prompt does not exist
4045
+ * @example
4046
+ * ```typescript
4047
+ * import { ChatPromptTemplate } from "@langchain/core/prompts";
4048
+ *
4049
+ * // Create a commit with a new version of the prompt
4050
+ * const template = ChatPromptTemplate.fromMessages([
4051
+ * ["system", "You are a helpful assistant."],
4052
+ * ["human", "{input}"]
4053
+ * ]);
4054
+ *
4055
+ * const commitUrl = await client.createCommit("my-prompt", template);
4056
+ * console.log(`Commit created: ${commitUrl}`);
4057
+ *
4058
+ * // Create a commit based on a specific parent commit
4059
+ * const commitUrl2 = await client.createCommit("my-prompt", template, {
4060
+ * parentCommitHash: "abc123def456"
4061
+ * });
4062
+ * ```
4063
+ */
3695
4064
  async createCommit(promptIdentifier, object, options) {
3696
4065
  if (!(await this.promptExists(promptIdentifier))) {
3697
4066
  throw new Error("Prompt does not exist, you must create it first.");
@@ -4176,6 +4545,12 @@ class Client {
4176
4545
  }
4177
4546
  }
4178
4547
  exports.Client = Client;
4548
+ Object.defineProperty(Client, "_fallbackDirsCreated", {
4549
+ enumerable: true,
4550
+ configurable: true,
4551
+ writable: true,
4552
+ value: new Set()
4553
+ });
4179
4554
  function isExampleCreate(input) {
4180
4555
  return "dataset_id" in input || "dataset_name" in input;
4181
4556
  }