langsmith 0.10.5 → 0.10.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.
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnvAddressError = void 0;
4
+ exports.normalizeAddress = normalizeAddress;
5
+ exports.wireKeys = wireKeys;
6
+ exports.envNames = envNames;
7
+ exports.toPayload = toPayload;
8
+ exports.toWire = toWire;
9
+ exports.fromWire = fromWire;
10
+ exports.addressFromEnv = addressFromEnv;
11
+ exports.seed = seed;
12
+ exports.sameAddress = sameAddress;
13
+ // Wire key per field; env var is `LANGSMITH_<WIRE KEY>`.
14
+ const FIELDS = {
15
+ agentId: "agent_id",
16
+ agentEnvironment: "agent_environment",
17
+ };
18
+ // The server's agent id rule: a DNS label, so a hostname can carry the id.
19
+ const AGENT_ID_PATTERN = /^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/;
20
+ // Not `utils/env`: it imports the package index, which imports this module.
21
+ function getEnv(name) {
22
+ try {
23
+ // eslint-disable-next-line no-process-env
24
+ return typeof process !== "undefined" ? process.env?.[name] : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ /** The `LANGSMITH_AGENT_*` / project env vars name half an address, or both. */
31
+ class EnvAddressError extends Error {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = "EnvAddressError";
35
+ }
36
+ }
37
+ exports.EnvAddressError = EnvAddressError;
38
+ /**
39
+ * @internal Validate `value` and return a frozen copy of its fields.
40
+ * @throws If it isn't an object with exactly the address fields, or a value is invalid.
41
+ */
42
+ function normalizeAddress(value) {
43
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
44
+ throw new Error("`address` must be an object: { agentId, agentEnvironment }.");
45
+ }
46
+ const fields = value;
47
+ const unknown = Object.keys(fields).filter((key) => !(key in FIELDS));
48
+ if (unknown.length > 0) {
49
+ throw new Error(`Unknown address fields: ${unknown.join(", ")}.`);
50
+ }
51
+ for (const name of Object.keys(FIELDS)) {
52
+ const field = fields[name];
53
+ if (typeof field !== "string" || field === "") {
54
+ throw new Error(`Address ${name} must be a non-empty string.`);
55
+ }
56
+ }
57
+ const { agentId, agentEnvironment } = fields;
58
+ if (!AGENT_ID_PATTERN.test(agentId)) {
59
+ throw new Error("Address agentId must be 1 to 63 lowercase ASCII letters, digits, or " +
60
+ "hyphens, start with a letter, and end with a letter or digit, got " +
61
+ `${JSON.stringify(agentId)}. Use an id such as "support-agent".`);
62
+ }
63
+ return Object.freeze({ agentId, agentEnvironment });
64
+ }
65
+ /** @internal */
66
+ function wireKeys() {
67
+ return Object.values(FIELDS);
68
+ }
69
+ /** @internal */
70
+ function envNames() {
71
+ return wireKeys().map((key) => `LANGSMITH_${key.toUpperCase()}`);
72
+ }
73
+ /**
74
+ * @internal The `address` object of a run / feedback payload. `kind` and
75
+ * `environment` are uppercase and case-sensitive on the wire.
76
+ */
77
+ function toPayload(address) {
78
+ return {
79
+ address: {
80
+ kind: "AGENT",
81
+ id: address.agentId,
82
+ environment: address.agentEnvironment.toUpperCase(),
83
+ },
84
+ };
85
+ }
86
+ /** @internal Header / replica fields. */
87
+ function toWire(address) {
88
+ return {
89
+ [FIELDS.agentId]: address.agentId,
90
+ [FIELDS.agentEnvironment]: address.agentEnvironment,
91
+ };
92
+ }
93
+ /** @internal `undefined` if no field is set; throws if only some are. */
94
+ function fromWire(values) {
95
+ const fields = Object.fromEntries(Object.entries(FIELDS).map(([name, key]) => [
96
+ name,
97
+ values[key] ?? undefined,
98
+ ]));
99
+ const missing = Object.entries(FIELDS)
100
+ .filter(([name]) => fields[name] === undefined)
101
+ .map(([, key]) => key);
102
+ if (missing.length === Object.keys(FIELDS).length) {
103
+ return undefined;
104
+ }
105
+ if (missing.length > 0) {
106
+ throw new Error(`An address needs ${missing.join(" and ")} as well.`);
107
+ }
108
+ return normalizeAddress(fields);
109
+ }
110
+ /**
111
+ * @internal
112
+ * @throws {EnvAddressError} If only some `LANGSMITH_AGENT_*` vars are set, or one is invalid.
113
+ */
114
+ function addressFromEnv() {
115
+ const values = Object.fromEntries(wireKeys().map((key, i) => [key, getEnv(envNames()[i]) || undefined]));
116
+ try {
117
+ return fromWire(values);
118
+ }
119
+ catch (e) {
120
+ throw new EnvAddressError(`The LANGSMITH_AGENT_* env vars can't address a run: ${e.message}`);
121
+ }
122
+ }
123
+ /** @internal Replica id derivation seed. */
124
+ function seed(address) {
125
+ return ["agent", ...Object.values(toWire(address))].join("/");
126
+ }
127
+ /** @internal */
128
+ function sameAddress(a, b) {
129
+ if (!a || !b) {
130
+ return a === b;
131
+ }
132
+ return a.agentId === b.agentId && a.agentEnvironment === b.agentEnvironment;
133
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * (beta) An address that runs are sent to instead of a project.
3
+ * Enabled per workspace; a workspace without it rejects the runs.
4
+ */
5
+ export interface Address {
6
+ /** 1 to 63 lowercase ASCII letters, digits or hyphens; starts with a letter. */
7
+ agentId: string;
8
+ agentEnvironment: string;
9
+ }
10
+ /** The `LANGSMITH_AGENT_*` / project env vars name half an address, or both. */
11
+ export declare class EnvAddressError extends Error {
12
+ constructor(message: string);
13
+ }
@@ -0,0 +1,120 @@
1
+ // Wire key per field; env var is `LANGSMITH_<WIRE KEY>`.
2
+ const FIELDS = {
3
+ agentId: "agent_id",
4
+ agentEnvironment: "agent_environment",
5
+ };
6
+ // The server's agent id rule: a DNS label, so a hostname can carry the id.
7
+ const AGENT_ID_PATTERN = /^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/;
8
+ // Not `utils/env`: it imports the package index, which imports this module.
9
+ function getEnv(name) {
10
+ try {
11
+ // eslint-disable-next-line no-process-env
12
+ return typeof process !== "undefined" ? process.env?.[name] : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ /** The `LANGSMITH_AGENT_*` / project env vars name half an address, or both. */
19
+ export class EnvAddressError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "EnvAddressError";
23
+ }
24
+ }
25
+ /**
26
+ * @internal Validate `value` and return a frozen copy of its fields.
27
+ * @throws If it isn't an object with exactly the address fields, or a value is invalid.
28
+ */
29
+ export function normalizeAddress(value) {
30
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
31
+ throw new Error("`address` must be an object: { agentId, agentEnvironment }.");
32
+ }
33
+ const fields = value;
34
+ const unknown = Object.keys(fields).filter((key) => !(key in FIELDS));
35
+ if (unknown.length > 0) {
36
+ throw new Error(`Unknown address fields: ${unknown.join(", ")}.`);
37
+ }
38
+ for (const name of Object.keys(FIELDS)) {
39
+ const field = fields[name];
40
+ if (typeof field !== "string" || field === "") {
41
+ throw new Error(`Address ${name} must be a non-empty string.`);
42
+ }
43
+ }
44
+ const { agentId, agentEnvironment } = fields;
45
+ if (!AGENT_ID_PATTERN.test(agentId)) {
46
+ throw new Error("Address agentId must be 1 to 63 lowercase ASCII letters, digits, or " +
47
+ "hyphens, start with a letter, and end with a letter or digit, got " +
48
+ `${JSON.stringify(agentId)}. Use an id such as "support-agent".`);
49
+ }
50
+ return Object.freeze({ agentId, agentEnvironment });
51
+ }
52
+ /** @internal */
53
+ export function wireKeys() {
54
+ return Object.values(FIELDS);
55
+ }
56
+ /** @internal */
57
+ export function envNames() {
58
+ return wireKeys().map((key) => `LANGSMITH_${key.toUpperCase()}`);
59
+ }
60
+ /**
61
+ * @internal The `address` object of a run / feedback payload. `kind` and
62
+ * `environment` are uppercase and case-sensitive on the wire.
63
+ */
64
+ export function toPayload(address) {
65
+ return {
66
+ address: {
67
+ kind: "AGENT",
68
+ id: address.agentId,
69
+ environment: address.agentEnvironment.toUpperCase(),
70
+ },
71
+ };
72
+ }
73
+ /** @internal Header / replica fields. */
74
+ export function toWire(address) {
75
+ return {
76
+ [FIELDS.agentId]: address.agentId,
77
+ [FIELDS.agentEnvironment]: address.agentEnvironment,
78
+ };
79
+ }
80
+ /** @internal `undefined` if no field is set; throws if only some are. */
81
+ export function fromWire(values) {
82
+ const fields = Object.fromEntries(Object.entries(FIELDS).map(([name, key]) => [
83
+ name,
84
+ values[key] ?? undefined,
85
+ ]));
86
+ const missing = Object.entries(FIELDS)
87
+ .filter(([name]) => fields[name] === undefined)
88
+ .map(([, key]) => key);
89
+ if (missing.length === Object.keys(FIELDS).length) {
90
+ return undefined;
91
+ }
92
+ if (missing.length > 0) {
93
+ throw new Error(`An address needs ${missing.join(" and ")} as well.`);
94
+ }
95
+ return normalizeAddress(fields);
96
+ }
97
+ /**
98
+ * @internal
99
+ * @throws {EnvAddressError} If only some `LANGSMITH_AGENT_*` vars are set, or one is invalid.
100
+ */
101
+ export function addressFromEnv() {
102
+ const values = Object.fromEntries(wireKeys().map((key, i) => [key, getEnv(envNames()[i]) || undefined]));
103
+ try {
104
+ return fromWire(values);
105
+ }
106
+ catch (e) {
107
+ throw new EnvAddressError(`The LANGSMITH_AGENT_* env vars can't address a run: ${e.message}`);
108
+ }
109
+ }
110
+ /** @internal Replica id derivation seed. */
111
+ export function seed(address) {
112
+ return ["agent", ...Object.values(toWire(address))].join("/");
113
+ }
114
+ /** @internal */
115
+ export function sameAddress(a, b) {
116
+ if (!a || !b) {
117
+ return a === b;
118
+ }
119
+ return a.agentId === b.agentId && a.agentEnvironment === b.agentEnvironment;
120
+ }
package/dist/client.cjs CHANGED
@@ -47,6 +47,8 @@ const index_js_2 = require("./_openapi_client/index.cjs");
47
47
  const _uuid_js_1 = require("./utils/_uuid.cjs");
48
48
  const sampling_js_1 = require("./utils/sampling.cjs");
49
49
  const warn_js_1 = require("./utils/warn.cjs");
50
+ const address_js_1 = require("./address.cjs");
51
+ const agent_addressing_js_1 = require("./utils/agent_addressing.cjs");
50
52
  const v2_migration_js_1 = require("./utils/v2_migration.cjs");
51
53
  const prompts_js_1 = require("./utils/prompts.cjs");
52
54
  const error_js_1 = require("./utils/error.cjs");
@@ -82,6 +84,26 @@ function _normalizeRunTimestamps(run) {
82
84
  end_time: _ensureUTCTimestamp(run.end_time),
83
85
  };
84
86
  }
87
+ /** Index creates by id (last wins); warns when one id goes to two projects. */
88
+ function indexRunCreatesById(creates) {
89
+ const byId = {};
90
+ for (const run of creates) {
91
+ if (!run.id) {
92
+ continue;
93
+ }
94
+ const previous = byId[run.id];
95
+ if (previous !== undefined && previous.session_name !== run.session_name) {
96
+ console.warn(`LangSmith run ${run.id} was queued for two projects with the same id ` +
97
+ `(${JSON.stringify(previous.session_name)} and ` +
98
+ `${JSON.stringify(run.session_name)}); only one of the projects will ` +
99
+ "keep the run. This usually means two write replicas both keep the " +
100
+ "original run ids, for example a replica marked `primary` plus one " +
101
+ "for the run's own project.");
102
+ }
103
+ byId[run.id] = run;
104
+ }
105
+ return byId;
106
+ }
85
107
  function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo, tracingSampleRate) {
86
108
  if (omitTracedRuntimeInfo) {
87
109
  return run;
@@ -921,6 +943,7 @@ class Client {
921
943
  }
922
944
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
923
945
  this.cachedLSEnvVarsForMetadata = (0, env_js_1.getLangSmithEnvVarsMetadata)();
946
+ (0, agent_addressing_js_1.warnOnEnv)();
924
947
  // Initialize prompt cache
925
948
  // Handle backwards compatibility for deprecated `cache` parameter
926
949
  if (config.cache !== undefined && config.disablePromptCache) {
@@ -1288,8 +1311,9 @@ class Client {
1288
1311
  return event;
1289
1312
  });
1290
1313
  }
1291
- async prepareRunCreateOrUpdateInputs(run) {
1314
+ async prepareRunCreateOrUpdateInputs(run, options) {
1292
1315
  const runParams = { ...run };
1316
+ (0, agent_addressing_js_1.applyToPayload)(runParams, { update: options?.update ?? false });
1293
1317
  if (runParams.inputs !== undefined) {
1294
1318
  runParams.inputs = await this.processInputs(runParams.inputs);
1295
1319
  }
@@ -1771,8 +1795,24 @@ class Client {
1771
1795
  ...this._mergedHeaders,
1772
1796
  "Content-Type": "application/json",
1773
1797
  };
1798
+ const address = (0, agent_addressing_js_1.checkAddress)(run.address);
1799
+ // `RunTree.postRun` passes a built run body, carrying the wire `session_name`.
1800
+ const bodySessionName = run.session_name;
1801
+ (0, agent_addressing_js_1.rejectConflicting)(run.project_name ?? bodySessionName, address);
1774
1802
  const session_name = run.project_name;
1775
1803
  delete run.project_name;
1804
+ if (!session_name && bodySessionName == null && !address) {
1805
+ try {
1806
+ run.address = (0, agent_addressing_js_1.resolveFromEnv)()[1];
1807
+ }
1808
+ catch (e) {
1809
+ if (!(e instanceof address_js_1.EnvAddressError)) {
1810
+ throw e;
1811
+ }
1812
+ (0, agent_addressing_js_1.logUntraced)(e);
1813
+ return;
1814
+ }
1815
+ }
1776
1816
  const runCreate = await this.prepareRunCreateOrUpdateInputs({
1777
1817
  session_name,
1778
1818
  ...run,
@@ -1821,15 +1861,10 @@ class Client {
1821
1861
  return;
1822
1862
  }
1823
1863
  let preparedCreateParams = await Promise.all(runCreates?.map((create) => this.prepareRunCreateOrUpdateInputs(create)) ?? []);
1824
- let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update)) ?? []);
1864
+ let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update, { update: true })) ?? []);
1865
+ // Index even without updates: the collision warning must see creates-only batches.
1866
+ const createById = indexRunCreatesById(preparedCreateParams);
1825
1867
  if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
1826
- const createById = preparedCreateParams.reduce((params, run) => {
1827
- if (!run.id) {
1828
- return params;
1829
- }
1830
- params[run.id] = run;
1831
- return params;
1832
- }, {});
1833
1868
  const standaloneUpdates = [];
1834
1869
  for (const updateParam of preparedUpdateParams) {
1835
1870
  if (updateParam.id !== undefined && createById[updateParam.id]) {
@@ -1920,7 +1955,7 @@ class Client {
1920
1955
  }
1921
1956
  let preparedUpdateParams = [];
1922
1957
  for (const update of runUpdates ?? []) {
1923
- preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update));
1958
+ preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update, { update: true }));
1924
1959
  }
1925
1960
  // require trace_id and dotted_order
1926
1961
  const invalidRunCreate = preparedCreateParams.find((runCreate) => {
@@ -1936,14 +1971,8 @@ class Client {
1936
1971
  throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when updating a run`);
1937
1972
  }
1938
1973
  // combine post and patch dicts where possible
1974
+ const createById = indexRunCreatesById(preparedCreateParams);
1939
1975
  if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
1940
- const createById = preparedCreateParams.reduce((params, run) => {
1941
- if (!run.id) {
1942
- return params;
1943
- }
1944
- params[run.id] = run;
1945
- return params;
1946
- }, {});
1947
1976
  const standaloneUpdates = [];
1948
1977
  for (const updateParam of preparedUpdateParams) {
1949
1978
  if (updateParam.id !== undefined && createById[updateParam.id]) {
@@ -2193,6 +2222,8 @@ class Client {
2193
2222
  if (run.events) {
2194
2223
  run.events = this._filterNewTokenEvents(run.events);
2195
2224
  }
2225
+ // On `run` too: the direct path below serializes it.
2226
+ (0, agent_addressing_js_1.applyToPayload)(run, { update: true });
2196
2227
  // TODO: Untangle types
2197
2228
  const data = { ...run, id: runId };
2198
2229
  if (!this._filterForSampling([data]).length) {
@@ -2284,6 +2315,9 @@ class Client {
2284
2315
  "Use client.runs.getURL() instead. " +
2285
2316
  "See https://docs.langchain.com/langsmith/smithdb-sdk-migration-runs#runs-get-url for the migration guide.", { type: "DeprecationWarning", code: "LANGSMITH_DEPRECATED_GET_RUN_URL" });
2286
2317
  if (run !== undefined) {
2318
+ if (!run.session_id && run.address) {
2319
+ throw new Error("Addressed runs have no URL until read back.");
2320
+ }
2287
2321
  let sessionId;
2288
2322
  if (run.session_id) {
2289
2323
  sessionId = run.session_id;
@@ -3613,7 +3647,7 @@ class Client {
3613
3647
  if (attachment_urls) {
3614
3648
  example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
3615
3649
  acc[key.slice("attachment.".length)] = {
3616
- presigned_url: value.presigned_url,
3650
+ presigned_url: new URL(value.presigned_url, this.apiUrl).href,
3617
3651
  mime_type: value.mime_type,
3618
3652
  };
3619
3653
  return acc;
@@ -3681,7 +3715,7 @@ class Client {
3681
3715
  if (attachment_urls) {
3682
3716
  example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
3683
3717
  acc[key.slice("attachment.".length)] = {
3684
- presigned_url: value.presigned_url,
3718
+ presigned_url: new URL(value.presigned_url, this.apiUrl).href,
3685
3719
  mime_type: value.mime_type || undefined,
3686
3720
  };
3687
3721
  return acc;
@@ -3906,16 +3940,18 @@ class Client {
3906
3940
  });
3907
3941
  }
3908
3942
  async createFeedback(runIdOrParams, keyArg, optionsArg) {
3909
- const { runId = null, key, score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, traceId, comparativeExperimentId, sessionId, startTime, extendTraceRetention, } = typeof runIdOrParams === "object" && runIdOrParams !== null
3943
+ const { runId = null, key, score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, traceId, comparativeExperimentId, sessionId, startTime, extendTraceRetention, address: rawAddress, } = typeof runIdOrParams === "object" && runIdOrParams !== null
3910
3944
  ? runIdOrParams
3911
3945
  : { runId: runIdOrParams, key: keyArg, ...optionsArg };
3946
+ const address = (0, agent_addressing_js_1.checkAddress)(rawAddress);
3947
+ (0, agent_addressing_js_1.rejectConflicting)(sessionId ?? projectId, address);
3912
3948
  if (!runId && !projectId) {
3913
3949
  throw new Error("One of runId or projectId must be provided");
3914
3950
  }
3915
3951
  if (runId && projectId) {
3916
3952
  throw new Error("Only one of runId or projectId can be provided");
3917
3953
  }
3918
- if (runId && sessionId === undefined) {
3954
+ if (runId && sessionId === undefined && !address) {
3919
3955
  await this._checkFeedbackSessionId();
3920
3956
  }
3921
3957
  const feedback_source = {
@@ -3951,7 +3987,7 @@ class Client {
3951
3987
  if (samplingId != null && !this._shouldSample(samplingId)) {
3952
3988
  return feedback;
3953
3989
  }
3954
- const body = JSON.stringify(feedback);
3990
+ const body = JSON.stringify(address ? { ...feedback, ...(0, address_js_1.toPayload)(address) } : feedback);
3955
3991
  const url = `${this.apiUrl}/feedback`;
3956
3992
  await this.caller.call(async () => {
3957
3993
  const res = await this._fetch(url, {
package/dist/client.d.ts CHANGED
@@ -11,6 +11,7 @@ import { AnnotationQueues } from "./_openapi_client/resources/annotation-queues/
11
11
  import { Threads } from "./_openapi_client/resources/threads.js";
12
12
  import { Traces } from "./_openapi_client/resources/traces.js";
13
13
  import { Public } from "./_openapi_client/resources/public/public.js";
14
+ import { type Address } from "./address.js";
14
15
  import { PromptCache } from "./utils/prompt_cache/index.js";
15
16
  import { ProfileAuth } from "./utils/profiles.js";
16
17
  export interface ClientConfig {
@@ -326,6 +327,8 @@ interface CreateRunParams {
326
327
  child_runs?: RunCreate[];
327
328
  parent_run_id?: string;
328
329
  project_name?: string;
330
+ /** (beta) Send the run to this address instead of a project. */
331
+ address?: Address;
329
332
  revision_id?: string;
330
333
  trace_id?: string;
331
334
  dotted_order?: string;
@@ -378,11 +381,20 @@ export type CreateFeedbackParams = CreateFeedbackOptions & ({
378
381
  /** The session (project) ID of the run. */
379
382
  sessionId: string;
380
383
  projectId?: never;
384
+ address?: never;
385
+ } | {
386
+ /** The run to provide feedback on. */
387
+ runId: string;
388
+ /** (beta) The address the run was sent to, e.g. `runTree.address`. */
389
+ address: Address;
390
+ sessionId?: never;
391
+ projectId?: never;
381
392
  } | {
382
393
  runId?: null;
383
394
  /** The project (or experiment) to provide feedback on. */
384
395
  projectId: string;
385
396
  sessionId?: never;
397
+ address?: never;
386
398
  });
387
399
  export type CreateExampleOptions = {
388
400
  /** The ID of the dataset to create the example in. */
package/dist/client.js CHANGED
@@ -9,6 +9,8 @@ import { Langsmith as OpenAPILangsmith } from "./_openapi_client/index.js";
9
9
  import { assertUuid } from "./utils/_uuid.js";
10
10
  import { isSampledById } from "./utils/sampling.js";
11
11
  import { warnOnce } from "./utils/warn.js";
12
+ import { EnvAddressError, toPayload } from "./address.js";
13
+ import { applyToPayload, checkAddress, logUntraced, rejectConflicting, resolveFromEnv, warnOnEnv, } from "./utils/agent_addressing.js";
12
14
  import { getQueryBackend, QueryBackend } from "./utils/v2_migration.js";
13
15
  import { parseHubIdentifier } from "./utils/prompts.js";
14
16
  import { raiseForStatus, isLangSmithNotFoundError, isLangSmithConflictError, } from "./utils/error.js";
@@ -44,6 +46,26 @@ function _normalizeRunTimestamps(run) {
44
46
  end_time: _ensureUTCTimestamp(run.end_time),
45
47
  };
46
48
  }
49
+ /** Index creates by id (last wins); warns when one id goes to two projects. */
50
+ function indexRunCreatesById(creates) {
51
+ const byId = {};
52
+ for (const run of creates) {
53
+ if (!run.id) {
54
+ continue;
55
+ }
56
+ const previous = byId[run.id];
57
+ if (previous !== undefined && previous.session_name !== run.session_name) {
58
+ console.warn(`LangSmith run ${run.id} was queued for two projects with the same id ` +
59
+ `(${JSON.stringify(previous.session_name)} and ` +
60
+ `${JSON.stringify(run.session_name)}); only one of the projects will ` +
61
+ "keep the run. This usually means two write replicas both keep the " +
62
+ "original run ids, for example a replica marked `primary` plus one " +
63
+ "for the run's own project.");
64
+ }
65
+ byId[run.id] = run;
66
+ }
67
+ return byId;
68
+ }
47
69
  export function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo, tracingSampleRate) {
48
70
  if (omitTracedRuntimeInfo) {
49
71
  return run;
@@ -882,6 +904,7 @@ export class Client {
882
904
  }
883
905
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
884
906
  this.cachedLSEnvVarsForMetadata = getLangSmithEnvVarsMetadata();
907
+ warnOnEnv();
885
908
  // Initialize prompt cache
886
909
  // Handle backwards compatibility for deprecated `cache` parameter
887
910
  if (config.cache !== undefined && config.disablePromptCache) {
@@ -1249,8 +1272,9 @@ export class Client {
1249
1272
  return event;
1250
1273
  });
1251
1274
  }
1252
- async prepareRunCreateOrUpdateInputs(run) {
1275
+ async prepareRunCreateOrUpdateInputs(run, options) {
1253
1276
  const runParams = { ...run };
1277
+ applyToPayload(runParams, { update: options?.update ?? false });
1254
1278
  if (runParams.inputs !== undefined) {
1255
1279
  runParams.inputs = await this.processInputs(runParams.inputs);
1256
1280
  }
@@ -1732,8 +1756,24 @@ export class Client {
1732
1756
  ...this._mergedHeaders,
1733
1757
  "Content-Type": "application/json",
1734
1758
  };
1759
+ const address = checkAddress(run.address);
1760
+ // `RunTree.postRun` passes a built run body, carrying the wire `session_name`.
1761
+ const bodySessionName = run.session_name;
1762
+ rejectConflicting(run.project_name ?? bodySessionName, address);
1735
1763
  const session_name = run.project_name;
1736
1764
  delete run.project_name;
1765
+ if (!session_name && bodySessionName == null && !address) {
1766
+ try {
1767
+ run.address = resolveFromEnv()[1];
1768
+ }
1769
+ catch (e) {
1770
+ if (!(e instanceof EnvAddressError)) {
1771
+ throw e;
1772
+ }
1773
+ logUntraced(e);
1774
+ return;
1775
+ }
1776
+ }
1737
1777
  const runCreate = await this.prepareRunCreateOrUpdateInputs({
1738
1778
  session_name,
1739
1779
  ...run,
@@ -1782,15 +1822,10 @@ export class Client {
1782
1822
  return;
1783
1823
  }
1784
1824
  let preparedCreateParams = await Promise.all(runCreates?.map((create) => this.prepareRunCreateOrUpdateInputs(create)) ?? []);
1785
- let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update)) ?? []);
1825
+ let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update, { update: true })) ?? []);
1826
+ // Index even without updates: the collision warning must see creates-only batches.
1827
+ const createById = indexRunCreatesById(preparedCreateParams);
1786
1828
  if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
1787
- const createById = preparedCreateParams.reduce((params, run) => {
1788
- if (!run.id) {
1789
- return params;
1790
- }
1791
- params[run.id] = run;
1792
- return params;
1793
- }, {});
1794
1829
  const standaloneUpdates = [];
1795
1830
  for (const updateParam of preparedUpdateParams) {
1796
1831
  if (updateParam.id !== undefined && createById[updateParam.id]) {
@@ -1881,7 +1916,7 @@ export class Client {
1881
1916
  }
1882
1917
  let preparedUpdateParams = [];
1883
1918
  for (const update of runUpdates ?? []) {
1884
- preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update));
1919
+ preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update, { update: true }));
1885
1920
  }
1886
1921
  // require trace_id and dotted_order
1887
1922
  const invalidRunCreate = preparedCreateParams.find((runCreate) => {
@@ -1897,14 +1932,8 @@ export class Client {
1897
1932
  throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when updating a run`);
1898
1933
  }
1899
1934
  // combine post and patch dicts where possible
1935
+ const createById = indexRunCreatesById(preparedCreateParams);
1900
1936
  if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
1901
- const createById = preparedCreateParams.reduce((params, run) => {
1902
- if (!run.id) {
1903
- return params;
1904
- }
1905
- params[run.id] = run;
1906
- return params;
1907
- }, {});
1908
1937
  const standaloneUpdates = [];
1909
1938
  for (const updateParam of preparedUpdateParams) {
1910
1939
  if (updateParam.id !== undefined && createById[updateParam.id]) {
@@ -2154,6 +2183,8 @@ export class Client {
2154
2183
  if (run.events) {
2155
2184
  run.events = this._filterNewTokenEvents(run.events);
2156
2185
  }
2186
+ // On `run` too: the direct path below serializes it.
2187
+ applyToPayload(run, { update: true });
2157
2188
  // TODO: Untangle types
2158
2189
  const data = { ...run, id: runId };
2159
2190
  if (!this._filterForSampling([data]).length) {
@@ -2245,6 +2276,9 @@ export class Client {
2245
2276
  "Use client.runs.getURL() instead. " +
2246
2277
  "See https://docs.langchain.com/langsmith/smithdb-sdk-migration-runs#runs-get-url for the migration guide.", { type: "DeprecationWarning", code: "LANGSMITH_DEPRECATED_GET_RUN_URL" });
2247
2278
  if (run !== undefined) {
2279
+ if (!run.session_id && run.address) {
2280
+ throw new Error("Addressed runs have no URL until read back.");
2281
+ }
2248
2282
  let sessionId;
2249
2283
  if (run.session_id) {
2250
2284
  sessionId = run.session_id;
@@ -3574,7 +3608,7 @@ export class Client {
3574
3608
  if (attachment_urls) {
3575
3609
  example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
3576
3610
  acc[key.slice("attachment.".length)] = {
3577
- presigned_url: value.presigned_url,
3611
+ presigned_url: new URL(value.presigned_url, this.apiUrl).href,
3578
3612
  mime_type: value.mime_type,
3579
3613
  };
3580
3614
  return acc;
@@ -3642,7 +3676,7 @@ export class Client {
3642
3676
  if (attachment_urls) {
3643
3677
  example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
3644
3678
  acc[key.slice("attachment.".length)] = {
3645
- presigned_url: value.presigned_url,
3679
+ presigned_url: new URL(value.presigned_url, this.apiUrl).href,
3646
3680
  mime_type: value.mime_type || undefined,
3647
3681
  };
3648
3682
  return acc;
@@ -3867,16 +3901,18 @@ export class Client {
3867
3901
  });
3868
3902
  }
3869
3903
  async createFeedback(runIdOrParams, keyArg, optionsArg) {
3870
- const { runId = null, key, score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, traceId, comparativeExperimentId, sessionId, startTime, extendTraceRetention, } = typeof runIdOrParams === "object" && runIdOrParams !== null
3904
+ const { runId = null, key, score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, traceId, comparativeExperimentId, sessionId, startTime, extendTraceRetention, address: rawAddress, } = typeof runIdOrParams === "object" && runIdOrParams !== null
3871
3905
  ? runIdOrParams
3872
3906
  : { runId: runIdOrParams, key: keyArg, ...optionsArg };
3907
+ const address = checkAddress(rawAddress);
3908
+ rejectConflicting(sessionId ?? projectId, address);
3873
3909
  if (!runId && !projectId) {
3874
3910
  throw new Error("One of runId or projectId must be provided");
3875
3911
  }
3876
3912
  if (runId && projectId) {
3877
3913
  throw new Error("Only one of runId or projectId can be provided");
3878
3914
  }
3879
- if (runId && sessionId === undefined) {
3915
+ if (runId && sessionId === undefined && !address) {
3880
3916
  await this._checkFeedbackSessionId();
3881
3917
  }
3882
3918
  const feedback_source = {
@@ -3912,7 +3948,7 @@ export class Client {
3912
3948
  if (samplingId != null && !this._shouldSample(samplingId)) {
3913
3949
  return feedback;
3914
3950
  }
3915
- const body = JSON.stringify(feedback);
3951
+ const body = JSON.stringify(address ? { ...feedback, ...toPayload(address) } : feedback);
3916
3952
  const url = `${this.apiUrl}/feedback`;
3917
3953
  await this.caller.call(async () => {
3918
3954
  const res = await this._fetch(url, {