langsmith 0.9.0 → 0.10.0
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/_openapi_client/client.cjs +1 -1
- package/dist/_openapi_client/client.d.ts +2 -2
- package/dist/_openapi_client/client.js +1 -1
- package/dist/_openapi_client/internal/detect-platform.cjs +1 -1
- package/dist/_openapi_client/internal/detect-platform.js +1 -1
- package/dist/_openapi_client/resources/annotation-queues/annotation-queues.d.ts +1 -0
- package/dist/_openapi_client/resources/index.d.ts +1 -1
- package/dist/_openapi_client/resources/issues.cjs +1 -1
- package/dist/_openapi_client/resources/issues.d.ts +18 -1
- package/dist/_openapi_client/resources/issues.js +1 -1
- package/dist/_openapi_client/resources/online-evaluators.d.ts +2 -2
- package/dist/_openapi_client/resources/sandboxes/boxes.cjs +20 -3
- package/dist/_openapi_client/resources/sandboxes/boxes.d.ts +40 -9
- package/dist/_openapi_client/resources/sandboxes/boxes.js +20 -3
- package/dist/_openapi_client/resources/sandboxes/sandboxes.d.ts +46 -3
- package/dist/_openapi_client/resources/sandboxes/snapshots.cjs +6 -2
- package/dist/_openapi_client/resources/sandboxes/snapshots.d.ts +16 -6
- package/dist/_openapi_client/resources/sandboxes/snapshots.js +6 -2
- package/dist/client.cjs +56 -83
- package/dist/client.d.ts +16 -14
- package/dist/client.js +56 -83
- package/dist/env.cjs +14 -1
- package/dist/env.d.ts +9 -0
- package/dist/env.js +12 -0
- package/dist/evaluation/evaluate_comparative.cjs +2 -0
- package/dist/evaluation/evaluate_comparative.js +2 -0
- package/dist/experimental/vercel/telemetry.cjs +19 -6
- package/dist/experimental/vercel/telemetry.js +17 -4
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/singletons/constants.cjs +7 -1
- package/dist/singletons/constants.d.ts +3 -0
- package/dist/singletons/constants.js +5 -0
- package/dist/traceable.cjs +46 -15
- package/dist/traceable.js +48 -17
- package/dist/utils/_uuid.cjs +11 -19
- package/dist/utils/_uuid.d.ts +7 -7
- package/dist/utils/_uuid.js +11 -19
- package/dist/utils/redaction.cjs +38 -0
- package/dist/utils/redaction.d.ts +10 -0
- package/dist/utils/redaction.js +33 -0
- package/dist/utils/sampling.cjs +34 -0
- package/dist/utils/sampling.d.ts +10 -0
- package/dist/utils/sampling.js +30 -0
- package/dist/wrappers/anthropic.cjs +21 -24
- package/dist/wrappers/anthropic.js +21 -24
- package/package.json +1 -1
package/dist/traceable.cjs
CHANGED
|
@@ -91,12 +91,26 @@ const runInputsToMap = (rawInputs) => {
|
|
|
91
91
|
const handleRunInputs = (
|
|
92
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
93
|
inputs, processInputs) => {
|
|
94
|
+
// Fail closed: trace the run, but with a marker instead of raw inputs.
|
|
95
|
+
const dropped = (e) => {
|
|
96
|
+
if ((0, env_js_1.allowUnprocessedPayloads)()) {
|
|
97
|
+
console.warn("Error occurred during processInputs. Tracing unprocessed inputs " +
|
|
98
|
+
"because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
|
|
99
|
+
return inputs;
|
|
100
|
+
}
|
|
101
|
+
console.warn("Error occurred during processInputs. Dropping inputs:", e);
|
|
102
|
+
return {
|
|
103
|
+
[constants_js_1._PROCESSING_FAILED_KEY]: (0, constants_js_1._processingFailed)("inputs", "processInputs"),
|
|
104
|
+
};
|
|
105
|
+
};
|
|
94
106
|
try {
|
|
95
|
-
|
|
107
|
+
const processed = processInputs(inputs);
|
|
108
|
+
return (0, asserts_js_1.isThenable)(processed)
|
|
109
|
+
? Promise.resolve(processed).catch(dropped)
|
|
110
|
+
: processed;
|
|
96
111
|
}
|
|
97
112
|
catch (e) {
|
|
98
|
-
|
|
99
|
-
return inputs;
|
|
113
|
+
return dropped(e);
|
|
100
114
|
}
|
|
101
115
|
};
|
|
102
116
|
const _extractUsage = (runData) => {
|
|
@@ -156,12 +170,36 @@ async function handleRunOutputs(params) {
|
|
|
156
170
|
else {
|
|
157
171
|
outputs = { outputs: rawOutputs };
|
|
158
172
|
}
|
|
173
|
+
// `outputs` is overwritten by the redactor call below; keep the raw value.
|
|
174
|
+
const unprocessed = outputs;
|
|
159
175
|
const childRunEndPromises = !skipChildPromiseDelay &&
|
|
160
176
|
(0, run_trees_js_1.isRunTree)(runTree) &&
|
|
161
177
|
constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY in runTree &&
|
|
162
178
|
Array.isArray(runTree[constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY])
|
|
163
179
|
? Promise.all(runTree[constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY] ?? [])
|
|
164
180
|
: Promise.resolve();
|
|
181
|
+
// Fail closed, as for inputs: a marker rather than the raw value.
|
|
182
|
+
const droppedOutputs = (e) => {
|
|
183
|
+
if ((0, env_js_1.allowUnprocessedPayloads)()) {
|
|
184
|
+
console.error("Error occurred during processOutputs. Tracing unprocessed outputs " +
|
|
185
|
+
"because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
|
|
186
|
+
return unprocessed;
|
|
187
|
+
}
|
|
188
|
+
console.error("Error occurred during processOutputs. Dropping outputs:", e);
|
|
189
|
+
return {
|
|
190
|
+
[constants_js_1._PROCESSING_FAILED_KEY]: (0, constants_js_1._processingFailed)("outputs", "processOutputs"),
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
const endOutputsDropped = async (e) => {
|
|
194
|
+
const finalOutputs = droppedOutputs(e);
|
|
195
|
+
try {
|
|
196
|
+
await childRunEndPromises;
|
|
197
|
+
await runTree?.end(finalOutputs);
|
|
198
|
+
}
|
|
199
|
+
catch (endErr) {
|
|
200
|
+
console.error("Error occurred during runTree?.end.", endErr);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
165
203
|
try {
|
|
166
204
|
outputs = processOutputsFn(outputs);
|
|
167
205
|
// TODO: Investigate making this behavior for all returned promises
|
|
@@ -173,16 +211,7 @@ async function handleRunOutputs(params) {
|
|
|
173
211
|
await childRunEndPromises;
|
|
174
212
|
await runTree?.end(processedOutputs);
|
|
175
213
|
})
|
|
176
|
-
.catch(
|
|
177
|
-
console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
|
|
178
|
-
try {
|
|
179
|
-
await childRunEndPromises;
|
|
180
|
-
await runTree?.end(outputs);
|
|
181
|
-
}
|
|
182
|
-
catch (e) {
|
|
183
|
-
console.error("Error occurred during runTree?.end.", e);
|
|
184
|
-
}
|
|
185
|
-
})
|
|
214
|
+
.catch(endOutputsDropped)
|
|
186
215
|
.finally(async () => {
|
|
187
216
|
try {
|
|
188
217
|
await handleEnd({
|
|
@@ -200,7 +229,7 @@ async function handleRunOutputs(params) {
|
|
|
200
229
|
}
|
|
201
230
|
}
|
|
202
231
|
catch (e) {
|
|
203
|
-
|
|
232
|
+
outputs = droppedOutputs(e);
|
|
204
233
|
}
|
|
205
234
|
_populateUsageMetadataAndOutputs(outputs, runTree);
|
|
206
235
|
void childRunEndPromises
|
|
@@ -238,7 +267,9 @@ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs,
|
|
|
238
267
|
const [attached, args] = handleRunAttachments(inputs, extractAttachments);
|
|
239
268
|
runTree.attachments = attached;
|
|
240
269
|
const processedInputs = handleRunInputs(args, processInputs);
|
|
241
|
-
|
|
270
|
+
// Off the value, not the fn shape: non-async redactors return promises too.
|
|
271
|
+
// Client.prepareRunCreateOrUpdateInputs also flattens it; don't rely on that.
|
|
272
|
+
if ((0, asserts_js_1.isThenable)(processedInputs)) {
|
|
242
273
|
runTree._awaitInputsOnPost = true;
|
|
243
274
|
}
|
|
244
275
|
runTree.inputs = processedInputs;
|
package/dist/traceable.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { RunTree, isRunTree, isRunnableConfigLike, } from "./run_trees.js";
|
|
3
|
-
import { isEnvTracingEnabled } from "./env.js";
|
|
3
|
+
import { allowUnprocessedPayloads, isEnvTracingEnabled } from "./env.js";
|
|
4
4
|
import { ROOT, AsyncLocalStorageProviderSingleton, getCurrentRunTree, } from "./singletons/traceable.js";
|
|
5
|
-
import { _LC_CHILD_RUN_END_PROMISES_KEY, _LC_CONTEXT_VARIABLES_KEY, } from "./singletons/constants.js";
|
|
5
|
+
import { _LC_CHILD_RUN_END_PROMISES_KEY, _LC_CONTEXT_VARIABLES_KEY, _PROCESSING_FAILED_KEY, _processingFailed, } from "./singletons/constants.js";
|
|
6
6
|
import { isKVMap, isReadableStream, isAsyncIterable, isIteratorLike, isThenable, isGenerator, isPromiseMethod, } from "./utils/asserts.js";
|
|
7
7
|
import { __version__ } from "./index.js";
|
|
8
8
|
import { getOTELTrace, getOTELContext } from "./singletons/otel.js";
|
|
@@ -87,12 +87,26 @@ const runInputsToMap = (rawInputs) => {
|
|
|
87
87
|
const handleRunInputs = (
|
|
88
88
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
89
|
inputs, processInputs) => {
|
|
90
|
+
// Fail closed: trace the run, but with a marker instead of raw inputs.
|
|
91
|
+
const dropped = (e) => {
|
|
92
|
+
if (allowUnprocessedPayloads()) {
|
|
93
|
+
console.warn("Error occurred during processInputs. Tracing unprocessed inputs " +
|
|
94
|
+
"because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
|
|
95
|
+
return inputs;
|
|
96
|
+
}
|
|
97
|
+
console.warn("Error occurred during processInputs. Dropping inputs:", e);
|
|
98
|
+
return {
|
|
99
|
+
[_PROCESSING_FAILED_KEY]: _processingFailed("inputs", "processInputs"),
|
|
100
|
+
};
|
|
101
|
+
};
|
|
90
102
|
try {
|
|
91
|
-
|
|
103
|
+
const processed = processInputs(inputs);
|
|
104
|
+
return isThenable(processed)
|
|
105
|
+
? Promise.resolve(processed).catch(dropped)
|
|
106
|
+
: processed;
|
|
92
107
|
}
|
|
93
108
|
catch (e) {
|
|
94
|
-
|
|
95
|
-
return inputs;
|
|
109
|
+
return dropped(e);
|
|
96
110
|
}
|
|
97
111
|
};
|
|
98
112
|
const _extractUsage = (runData) => {
|
|
@@ -152,12 +166,36 @@ async function handleRunOutputs(params) {
|
|
|
152
166
|
else {
|
|
153
167
|
outputs = { outputs: rawOutputs };
|
|
154
168
|
}
|
|
169
|
+
// `outputs` is overwritten by the redactor call below; keep the raw value.
|
|
170
|
+
const unprocessed = outputs;
|
|
155
171
|
const childRunEndPromises = !skipChildPromiseDelay &&
|
|
156
172
|
isRunTree(runTree) &&
|
|
157
173
|
_LC_CHILD_RUN_END_PROMISES_KEY in runTree &&
|
|
158
174
|
Array.isArray(runTree[_LC_CHILD_RUN_END_PROMISES_KEY])
|
|
159
175
|
? Promise.all(runTree[_LC_CHILD_RUN_END_PROMISES_KEY] ?? [])
|
|
160
176
|
: Promise.resolve();
|
|
177
|
+
// Fail closed, as for inputs: a marker rather than the raw value.
|
|
178
|
+
const droppedOutputs = (e) => {
|
|
179
|
+
if (allowUnprocessedPayloads()) {
|
|
180
|
+
console.error("Error occurred during processOutputs. Tracing unprocessed outputs " +
|
|
181
|
+
"because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
|
|
182
|
+
return unprocessed;
|
|
183
|
+
}
|
|
184
|
+
console.error("Error occurred during processOutputs. Dropping outputs:", e);
|
|
185
|
+
return {
|
|
186
|
+
[_PROCESSING_FAILED_KEY]: _processingFailed("outputs", "processOutputs"),
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const endOutputsDropped = async (e) => {
|
|
190
|
+
const finalOutputs = droppedOutputs(e);
|
|
191
|
+
try {
|
|
192
|
+
await childRunEndPromises;
|
|
193
|
+
await runTree?.end(finalOutputs);
|
|
194
|
+
}
|
|
195
|
+
catch (endErr) {
|
|
196
|
+
console.error("Error occurred during runTree?.end.", endErr);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
161
199
|
try {
|
|
162
200
|
outputs = processOutputsFn(outputs);
|
|
163
201
|
// TODO: Investigate making this behavior for all returned promises
|
|
@@ -169,16 +207,7 @@ async function handleRunOutputs(params) {
|
|
|
169
207
|
await childRunEndPromises;
|
|
170
208
|
await runTree?.end(processedOutputs);
|
|
171
209
|
})
|
|
172
|
-
.catch(
|
|
173
|
-
console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
|
|
174
|
-
try {
|
|
175
|
-
await childRunEndPromises;
|
|
176
|
-
await runTree?.end(outputs);
|
|
177
|
-
}
|
|
178
|
-
catch (e) {
|
|
179
|
-
console.error("Error occurred during runTree?.end.", e);
|
|
180
|
-
}
|
|
181
|
-
})
|
|
210
|
+
.catch(endOutputsDropped)
|
|
182
211
|
.finally(async () => {
|
|
183
212
|
try {
|
|
184
213
|
await handleEnd({
|
|
@@ -196,7 +225,7 @@ async function handleRunOutputs(params) {
|
|
|
196
225
|
}
|
|
197
226
|
}
|
|
198
227
|
catch (e) {
|
|
199
|
-
|
|
228
|
+
outputs = droppedOutputs(e);
|
|
200
229
|
}
|
|
201
230
|
_populateUsageMetadataAndOutputs(outputs, runTree);
|
|
202
231
|
void childRunEndPromises
|
|
@@ -234,7 +263,9 @@ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs,
|
|
|
234
263
|
const [attached, args] = handleRunAttachments(inputs, extractAttachments);
|
|
235
264
|
runTree.attachments = attached;
|
|
236
265
|
const processedInputs = handleRunInputs(args, processInputs);
|
|
237
|
-
|
|
266
|
+
// Off the value, not the fn shape: non-async redactors return promises too.
|
|
267
|
+
// Client.prepareRunCreateOrUpdateInputs also flattens it; don't rely on that.
|
|
268
|
+
if (isThenable(processedInputs)) {
|
|
238
269
|
runTree._awaitInputsOnPost = true;
|
|
239
270
|
}
|
|
240
271
|
runTree.inputs = processedInputs;
|
package/dist/utils/_uuid.cjs
CHANGED
|
@@ -113,14 +113,14 @@ function _fastHash128(str) {
|
|
|
113
113
|
/**
|
|
114
114
|
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
115
115
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* - Is valid UUID v7 format
|
|
116
|
+
* A pure function of `originalId` and `key`: it reads no ambient state, so the
|
|
117
|
+
* same pair always yields the same UUID, in any process, at any time. This is
|
|
118
|
+
* used for creating replica ids that are reproducible across distributed
|
|
119
|
+
* systems, including across SDK implementations.
|
|
121
120
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
|
|
122
|
+
* start time. Any other original has none to keep, and the field is
|
|
123
|
+
* hash-derived: stable, but not chronological.
|
|
124
124
|
*
|
|
125
125
|
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
126
126
|
* @param key - A string key used for deterministic derivation (e.g., project name)
|
|
@@ -147,8 +147,6 @@ function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
|
147
147
|
// [8] 2 bits: variant (10) + 6 bits rand_b
|
|
148
148
|
// [9-15] 56 bits: rand_b (continued)
|
|
149
149
|
const b = new Uint8Array(16);
|
|
150
|
-
// Check if original is UUID v7 - if so, preserve its timestamp
|
|
151
|
-
// If not, use current time to ensure the derived UUID has a valid timestamp
|
|
152
150
|
const version = getUuidVersion(originalId);
|
|
153
151
|
if (version === 7) {
|
|
154
152
|
// Preserve timestamp from original UUID7 (bytes 0-5)
|
|
@@ -156,16 +154,10 @@ function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
|
156
154
|
b.set(originalBytes.slice(0, 6), 0);
|
|
157
155
|
}
|
|
158
156
|
else {
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
b[0] = (msecs / 0x10000000000) & 0xff;
|
|
164
|
-
b[1] = (msecs / 0x100000000) & 0xff;
|
|
165
|
-
b[2] = (msecs / 0x1000000) & 0xff;
|
|
166
|
-
b[3] = (msecs / 0x10000) & 0xff;
|
|
167
|
-
b[4] = (msecs / 0x100) & 0xff;
|
|
168
|
-
b[5] = msecs & 0xff;
|
|
157
|
+
// No timestamp to preserve. Use the six hash bytes left unused below: a
|
|
158
|
+
// clock read here would make this impure, and the same id is derived
|
|
159
|
+
// from several runs, all of which must agree.
|
|
160
|
+
b.set(h.slice(10, 16), 0);
|
|
169
161
|
}
|
|
170
162
|
// Set version 7 (0111) in high nibble + 4 bits from hash
|
|
171
163
|
b[6] = 0x70 | (h[0] & 0x0f);
|
package/dist/utils/_uuid.d.ts
CHANGED
|
@@ -22,14 +22,14 @@ export declare function warnIfNotUuidV7(uuidStr: string, _idType: string): void;
|
|
|
22
22
|
/**
|
|
23
23
|
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - Is valid UUID v7 format
|
|
25
|
+
* A pure function of `originalId` and `key`: it reads no ambient state, so the
|
|
26
|
+
* same pair always yields the same UUID, in any process, at any time. This is
|
|
27
|
+
* used for creating replica ids that are reproducible across distributed
|
|
28
|
+
* systems, including across SDK implementations.
|
|
30
29
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
|
|
31
|
+
* start time. Any other original has none to keep, and the field is
|
|
32
|
+
* hash-derived: stable, but not chronological.
|
|
33
33
|
*
|
|
34
34
|
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
35
35
|
* @param key - A string key used for deterministic derivation (e.g., project name)
|
package/dist/utils/_uuid.js
CHANGED
|
@@ -106,14 +106,14 @@ function _fastHash128(str) {
|
|
|
106
106
|
/**
|
|
107
107
|
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
108
108
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* - Is valid UUID v7 format
|
|
109
|
+
* A pure function of `originalId` and `key`: it reads no ambient state, so the
|
|
110
|
+
* same pair always yields the same UUID, in any process, at any time. This is
|
|
111
|
+
* used for creating replica ids that are reproducible across distributed
|
|
112
|
+
* systems, including across SDK implementations.
|
|
114
113
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
|
|
115
|
+
* start time. Any other original has none to keep, and the field is
|
|
116
|
+
* hash-derived: stable, but not chronological.
|
|
117
117
|
*
|
|
118
118
|
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
119
119
|
* @param key - A string key used for deterministic derivation (e.g., project name)
|
|
@@ -140,8 +140,6 @@ export function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
|
140
140
|
// [8] 2 bits: variant (10) + 6 bits rand_b
|
|
141
141
|
// [9-15] 56 bits: rand_b (continued)
|
|
142
142
|
const b = new Uint8Array(16);
|
|
143
|
-
// Check if original is UUID v7 - if so, preserve its timestamp
|
|
144
|
-
// If not, use current time to ensure the derived UUID has a valid timestamp
|
|
145
143
|
const version = getUuidVersion(originalId);
|
|
146
144
|
if (version === 7) {
|
|
147
145
|
// Preserve timestamp from original UUID7 (bytes 0-5)
|
|
@@ -149,16 +147,10 @@ export function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
|
149
147
|
b.set(originalBytes.slice(0, 6), 0);
|
|
150
148
|
}
|
|
151
149
|
else {
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
b[0] = (msecs / 0x10000000000) & 0xff;
|
|
157
|
-
b[1] = (msecs / 0x100000000) & 0xff;
|
|
158
|
-
b[2] = (msecs / 0x1000000) & 0xff;
|
|
159
|
-
b[3] = (msecs / 0x10000) & 0xff;
|
|
160
|
-
b[4] = (msecs / 0x100) & 0xff;
|
|
161
|
-
b[5] = msecs & 0xff;
|
|
150
|
+
// No timestamp to preserve. Use the six hash bytes left unused below: a
|
|
151
|
+
// clock read here would make this impure, and the same id is derived
|
|
152
|
+
// from several runs, all of which must agree.
|
|
153
|
+
b.set(h.slice(10, 16), 0);
|
|
162
154
|
}
|
|
163
155
|
// Set version 7 (0111) in high nibble + 4 bits from hash
|
|
164
156
|
b[6] = 0x70 | (h[0] & 0x0f);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mask = mask;
|
|
4
|
+
exports.redactOutside = redactOutside;
|
|
5
|
+
exports.overParams = overParams;
|
|
6
|
+
const index_js_1 = require("../anonymizer/index.cjs");
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
/** Mask a value, keeping object key names so a trace shows what was set. */
|
|
11
|
+
function mask(value) {
|
|
12
|
+
if (isPlainObject(value)) {
|
|
13
|
+
return Object.fromEntries(Object.keys(value).map((key) => [key, index_js_1.SECRET_PLACEHOLDER]));
|
|
14
|
+
}
|
|
15
|
+
return index_js_1.SECRET_PLACEHOLDER;
|
|
16
|
+
}
|
|
17
|
+
/** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
|
|
18
|
+
function redactOutside(entry, safeKeys) {
|
|
19
|
+
if (!isPlainObject(entry))
|
|
20
|
+
return entry;
|
|
21
|
+
return Object.fromEntries(Object.entries(entry).map(([key, value]) => [
|
|
22
|
+
key,
|
|
23
|
+
safeKeys.has(key) ? value : index_js_1.SECRET_PLACEHOLDER,
|
|
24
|
+
]));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Apply `redact` to every object argument, since `traceable` shapes a
|
|
28
|
+
* multi-argument call as `{ args: [params, requestOptions] }`.
|
|
29
|
+
*/
|
|
30
|
+
function overParams(inputs, redact) {
|
|
31
|
+
if (Array.isArray(inputs?.args)) {
|
|
32
|
+
return {
|
|
33
|
+
...inputs,
|
|
34
|
+
args: inputs.args.map((arg) => isPlainObject(arg) ? redact(arg) : arg),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return redact(inputs);
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { KVMap } from "../schemas.js";
|
|
2
|
+
/** Mask a value, keeping object key names so a trace shows what was set. */
|
|
3
|
+
export declare function mask(value: unknown): unknown;
|
|
4
|
+
/** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
|
|
5
|
+
export declare function redactOutside(entry: unknown, safeKeys: ReadonlySet<string>): unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Apply `redact` to every object argument, since `traceable` shapes a
|
|
8
|
+
* multi-argument call as `{ args: [params, requestOptions] }`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function overParams(inputs: KVMap, redact: (params: KVMap) => KVMap): KVMap;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SECRET_PLACEHOLDER } from "../anonymizer/index.js";
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
/** Mask a value, keeping object key names so a trace shows what was set. */
|
|
6
|
+
export function mask(value) {
|
|
7
|
+
if (isPlainObject(value)) {
|
|
8
|
+
return Object.fromEntries(Object.keys(value).map((key) => [key, SECRET_PLACEHOLDER]));
|
|
9
|
+
}
|
|
10
|
+
return SECRET_PLACEHOLDER;
|
|
11
|
+
}
|
|
12
|
+
/** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
|
|
13
|
+
export function redactOutside(entry, safeKeys) {
|
|
14
|
+
if (!isPlainObject(entry))
|
|
15
|
+
return entry;
|
|
16
|
+
return Object.fromEntries(Object.entries(entry).map(([key, value]) => [
|
|
17
|
+
key,
|
|
18
|
+
safeKeys.has(key) ? value : SECRET_PLACEHOLDER,
|
|
19
|
+
]));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Apply `redact` to every object argument, since `traceable` shapes a
|
|
23
|
+
* multi-argument call as `{ args: [params, requestOptions] }`.
|
|
24
|
+
*/
|
|
25
|
+
export function overParams(inputs, redact) {
|
|
26
|
+
if (Array.isArray(inputs?.args)) {
|
|
27
|
+
return {
|
|
28
|
+
...inputs,
|
|
29
|
+
args: inputs.args.map((arg) => isPlainObject(arg) ? redact(arg) : arg),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return redact(inputs);
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic trace sampling, kept consistent across LangSmith SDKs.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isSampledById = void 0;
|
|
7
|
+
const xxhash_js_1 = require("./xxhash/xxhash.cjs");
|
|
8
|
+
const SAMPLING_HASH_MODULUS = 1000000n;
|
|
9
|
+
// Reused across calls: constructing a TextEncoder per hash costs more than the
|
|
10
|
+
// hash itself.
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
/**
|
|
13
|
+
* Decide whether `identifier` is sampled in at `samplingRate`.
|
|
14
|
+
*
|
|
15
|
+
* The decision is a pure function of the identifier, so every process and
|
|
16
|
+
* every SDK agrees on it, and a run's create and patch never disagree.
|
|
17
|
+
*/
|
|
18
|
+
const isSampledById = (identifier, samplingRate) => {
|
|
19
|
+
if (samplingRate === undefined || samplingRate >= 1) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (samplingRate <= 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (identifier == null) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// XXH3-128 over the UTF-8 bytes, matching the Python SDK's `xxhash.xxh3_128`.
|
|
29
|
+
// The identifier is sampled in when its fraction of the modulus falls below
|
|
30
|
+
// the rate, which is also expressed in [0, 1).
|
|
31
|
+
const bucket = (0, xxhash_js_1.XXH3_128)(encoder.encode(identifier.toLowerCase())) % SAMPLING_HASH_MODULUS;
|
|
32
|
+
return Number(bucket) / Number(SAMPLING_HASH_MODULUS) < samplingRate;
|
|
33
|
+
};
|
|
34
|
+
exports.isSampledById = isSampledById;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic trace sampling, kept consistent across LangSmith SDKs.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Decide whether `identifier` is sampled in at `samplingRate`.
|
|
6
|
+
*
|
|
7
|
+
* The decision is a pure function of the identifier, so every process and
|
|
8
|
+
* every SDK agrees on it, and a run's create and patch never disagree.
|
|
9
|
+
*/
|
|
10
|
+
export declare const isSampledById: (identifier: string | null | undefined, samplingRate: number | undefined) => boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic trace sampling, kept consistent across LangSmith SDKs.
|
|
3
|
+
*/
|
|
4
|
+
import { XXH3_128 } from "./xxhash/xxhash.js";
|
|
5
|
+
const SAMPLING_HASH_MODULUS = 1000000n;
|
|
6
|
+
// Reused across calls: constructing a TextEncoder per hash costs more than the
|
|
7
|
+
// hash itself.
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
/**
|
|
10
|
+
* Decide whether `identifier` is sampled in at `samplingRate`.
|
|
11
|
+
*
|
|
12
|
+
* The decision is a pure function of the identifier, so every process and
|
|
13
|
+
* every SDK agrees on it, and a run's create and patch never disagree.
|
|
14
|
+
*/
|
|
15
|
+
export const isSampledById = (identifier, samplingRate) => {
|
|
16
|
+
if (samplingRate === undefined || samplingRate >= 1) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (samplingRate <= 0) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (identifier == null) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// XXH3-128 over the UTF-8 bytes, matching the Python SDK's `xxhash.xxh3_128`.
|
|
26
|
+
// The identifier is sampled in when its fraction of the modulus falls below
|
|
27
|
+
// the rate, which is also expressed in [0, 1).
|
|
28
|
+
const bucket = XXH3_128(encoder.encode(identifier.toLowerCase())) % SAMPLING_HASH_MODULUS;
|
|
29
|
+
return Number(bucket) / Number(SAMPLING_HASH_MODULUS) < samplingRate;
|
|
30
|
+
};
|
|
@@ -5,7 +5,7 @@ exports.createUsageMetadata = createUsageMetadata;
|
|
|
5
5
|
const anthropic_managed_js_1 = require("./utils/anthropic_managed.cjs");
|
|
6
6
|
const traceable_js_1 = require("../traceable.cjs");
|
|
7
7
|
const usage_js_1 = require("../utils/usage.cjs");
|
|
8
|
-
const
|
|
8
|
+
const redaction_js_1 = require("../utils/redaction.cjs");
|
|
9
9
|
const TRACED_INVOCATION_KEYS = ["top_k", "top_p", "stream", "thinking"];
|
|
10
10
|
/** Keys of an MCP server definition that are safe to trace. */
|
|
11
11
|
const MCP_SERVER_SAFE_KEYS = new Set([
|
|
@@ -14,27 +14,20 @@ const MCP_SERVER_SAFE_KEYS = new Set([
|
|
|
14
14
|
"url",
|
|
15
15
|
"tool_configuration",
|
|
16
16
|
]);
|
|
17
|
-
/**
|
|
18
|
-
* Mask credentials in `mcp_servers` before tracing. Anything outside
|
|
19
|
-
* {@link MCP_SERVER_SAFE_KEYS} keeps its name but gets
|
|
20
|
-
* {@link SECRET_PLACEHOLDER} as its value, so the trace shows the field was set
|
|
21
|
-
* without exposing it, and fields the API adds later are masked by default.
|
|
22
|
-
*
|
|
23
|
-
* Returns new objects; the caller's are also sent to Anthropic.
|
|
24
|
-
*/
|
|
17
|
+
/** Mask `mcp_servers` credentials outside {@link MCP_SERVER_SAFE_KEYS}. */
|
|
25
18
|
function redactMcpServers(servers) {
|
|
26
19
|
if (!Array.isArray(servers))
|
|
27
20
|
return servers;
|
|
28
|
-
return servers.map((server) =>
|
|
29
|
-
if (typeof server !== "object" || server == null || Array.isArray(server)) {
|
|
30
|
-
return server;
|
|
31
|
-
}
|
|
32
|
-
return Object.fromEntries(Object.entries(server).map(([key, value]) => [
|
|
33
|
-
key,
|
|
34
|
-
MCP_SERVER_SAFE_KEYS.has(key) ? value : index_js_1.SECRET_PLACEHOLDER,
|
|
35
|
-
]));
|
|
36
|
-
});
|
|
21
|
+
return servers.map((server) => (0, redaction_js_1.redactOutside)(server, MCP_SERVER_SAFE_KEYS));
|
|
37
22
|
}
|
|
23
|
+
/** Request-transport keys that are credentials by construction. */
|
|
24
|
+
const TRANSPORT_SECRET_KEYS = new Set([
|
|
25
|
+
"headers",
|
|
26
|
+
"query",
|
|
27
|
+
"extra_headers",
|
|
28
|
+
"extra_body",
|
|
29
|
+
"extra_query",
|
|
30
|
+
]);
|
|
38
31
|
/**
|
|
39
32
|
* Create usage metadata from Anthropic's token usage format.
|
|
40
33
|
*/
|
|
@@ -246,17 +239,18 @@ const wrapAnthropic = (anthropic, options) => {
|
|
|
246
239
|
...options,
|
|
247
240
|
metadata: restMetadata,
|
|
248
241
|
};
|
|
249
|
-
/**
|
|
250
|
-
|
|
251
|
-
* This provides parity with the Python SDK behavior and enables system prompts
|
|
252
|
-
* to be viewed and edited in the LangSmith playground.
|
|
253
|
-
*/
|
|
254
|
-
function processTracedInputs(params) {
|
|
242
|
+
/** Mask credentials and surface `system` as a message the playground can edit. */
|
|
243
|
+
function redactAnthropicParams(params) {
|
|
255
244
|
// Copy first: `params` also goes to the API and must keep its real values.
|
|
256
245
|
const processed = { ...params };
|
|
257
246
|
if (processed.mcp_servers != null) {
|
|
258
247
|
processed.mcp_servers = redactMcpServers(processed.mcp_servers);
|
|
259
248
|
}
|
|
249
|
+
for (const key of TRANSPORT_SECRET_KEYS) {
|
|
250
|
+
if (processed[key] != null) {
|
|
251
|
+
processed[key] = (0, redaction_js_1.mask)(processed[key]);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
260
254
|
if (!params.system) {
|
|
261
255
|
return processed;
|
|
262
256
|
}
|
|
@@ -275,6 +269,9 @@ const wrapAnthropic = (anthropic, options) => {
|
|
|
275
269
|
delete processed.system;
|
|
276
270
|
return processed;
|
|
277
271
|
}
|
|
272
|
+
function processTracedInputs(inputs) {
|
|
273
|
+
return (0, redaction_js_1.overParams)(inputs, redactAnthropicParams);
|
|
274
|
+
}
|
|
278
275
|
// Common configuration for messages.create
|
|
279
276
|
const messagesCreateConfig = {
|
|
280
277
|
name: "ChatAnthropic",
|