langsmith
Version:
Client library to connect to the LangSmith Observability and Evaluation Platform.
487 lines (486 loc) • 18.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.estimateSerializedSize = estimateSerializedSize;
exports.serialize = serialize;
/* eslint-disable */
// @ts-nocheck
const env_js_1 = require("../../utils/env.cjs");
var LIMIT_REPLACE_NODE = "[...]";
var CIRCULAR_REPLACE_NODE = { result: "[Circular]" };
var arr = [];
var replacerStack = [];
const encoder = new TextEncoder();
function defaultOptions() {
return {
depthLimit: Number.MAX_SAFE_INTEGER,
edgesLimit: Number.MAX_SAFE_INTEGER,
};
}
function encodeString(str) {
return encoder.encode(str);
}
// Shared function to handle well-known types
function serializeWellKnownTypes(val) {
if (val && typeof val === "object" && val !== null) {
if (val instanceof Map) {
return Object.fromEntries(val);
}
else if (val instanceof Set) {
return Array.from(val);
}
else if (val instanceof Date) {
return val.toISOString();
}
else if (val instanceof RegExp) {
return val.toString();
}
else if (val instanceof Error) {
return {
name: val.name,
message: val.message,
};
}
}
else if (typeof val === "bigint") {
return val.toString();
}
return val;
}
// Default replacer function to handle well-known types
function createDefaultReplacer(userReplacer) {
return function (key, val) {
// Apply user replacer first if provided
if (userReplacer) {
const userResult = userReplacer.call(this, key, val);
// If user replacer returned undefined, fall back to our serialization
if (userResult !== undefined) {
return userResult;
}
}
// Fall back to our well-known type handling
return serializeWellKnownTypes(val);
};
}
function estimateSerializedSize(value) {
try {
// Ancestor set for cycle detection. An object is only treated as
// circular if it appears on the current recursion path, not merely
// if it has been seen before elsewhere in the graph.
const ancestors = new Set();
let maxStringLen = 0;
// In Node / Bun, Buffer.byteLength is a fast native way to get UTF-8
// byte length without allocating an encoded copy. In other runtimes
// we fall back to code-unit length (a small under-estimate for
// non-ASCII text, which is acceptable for soft limits).
const byteLen = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function"
? (s) => Buffer.byteLength(s, "utf8")
: (s) => s.length;
function estimateString(s) {
// +2 for the surrounding quotes. Escape expansion is not counted.
const n = byteLen(s);
if (n > maxStringLen)
maxStringLen = n;
return n + 2;
}
// Size of a byte sequence when rendered as a JSON array of decimal
// numbers: "[b0,b1,b2,...]". Each byte averages ~3.5 chars (value 0-9
// => 1 char, 10-99 => 2 chars, 100-255 => 3 chars; weighted mean over
// a uniform distribution is ~2.81, plus one comma per element except
// the last). Round up to 4 bytes/element for a small safety margin.
function estimateByteArrayJson(byteLength) {
if (byteLength === 0)
return 2; // "[]"
return 2 + byteLength * 4;
}
// Returns true for values that JSON.stringify drops when they appear
// as an object property (as opposed to an array element, where they
// become "null").
function isDropped(v) {
return (v === undefined || typeof v === "function" || typeof v === "symbol");
}
// In arrays, undefined / function / symbol become "null" (4 bytes).
function estimateInArray(v) {
if (v === undefined || typeof v === "function" || typeof v === "symbol") {
return 4;
}
return estimate(v);
}
function estimate(val) {
if (val === null)
return 4; // "null"
if (val === undefined)
return 0; // top-level or property context; array handled separately
const t = typeof val;
if (t === "boolean")
return 5; // "true" / "false" upper bound
if (t === "number") {
if (!Number.isFinite(val))
return 4; // "null"
// Convert to string to get exact length. This is cheap for numbers
// (V8 caches small-number strings) and makes the estimate far
// tighter for common cases like integer arrays.
return val.toString().length;
}
if (t === "bigint") {
// Our replacer renders BigInt via .toString(), then JSON.stringify
// quotes it.
return val.toString().length + 2;
}
if (t === "string")
return estimateString(val);
if (t === "function" || t === "symbol")
return 0;
// Objects from here on.
const obj = val;
// Well-known types handled by our replacer.
if (obj instanceof Date)
return 26; // "2024-01-01T00:00:00.000Z"
if (obj instanceof RegExp)
return byteLen(obj.toString()) + 2;
if (obj instanceof Error) {
const name = obj.name ?? "";
const message = obj.message ?? "";
// {"name":"...","message":"..."}
return 22 + byteLen(name) + byteLen(message);
}
// Binary data types. These commonly appear in LLM payloads (images,
// audio) and their JSON representations vary widely.
if (typeof Buffer !== "undefined" && obj instanceof Buffer) {
// { "type": "Buffer", "data": [0, 1, 2, ...] }
return 28 + estimateByteArrayJson(obj.byteLength);
}
if (ArrayBuffer.isView(obj)) {
if (obj instanceof DataView) {
// DataView has no enumerable own properties; serializes as "{}".
return 2;
}
// Typed arrays serialize as {"0":v0,"1":v1,...} (keyed objects),
// which is much larger than a plain array would be. Per element
// cost: digits for index + digits for value + ":" + "," + quotes.
const len = obj.length ?? 0;
const isFloat = obj instanceof Float32Array || obj instanceof Float64Array;
// Index digits grow with len; worst-case per element:
// "NNN":V, where NNN = log10(len) digits and V depends on type.
// Loose but safe bounds: integer views ~12 chars/element, float
// views ~30 chars/element (Float32 ToString can be up to ~17 chars).
const perElement = isFloat ? 30 : 12;
return 2 + len * perElement;
}
if (obj instanceof ArrayBuffer) {
// Plain ArrayBuffer has no enumerable properties; serializes as "{}".
return 2;
}
if (ancestors.has(obj)) {
// Cycle: our decirc fallback replaces with { result: "[Circular]" }.
return 24;
}
// Custom toJSON (Decimal.js, Moment, Luxon, Mongoose docs, etc.).
// This runs after explicit built-in / binary cases above so known
// types (for example Buffer) use their dedicated fast-path sizing
// logic instead of duck-typing through toJSON().
if (typeof obj.toJSON === "function") {
let projected;
try {
projected = obj.toJSON("");
}
catch {
// If toJSON throws, JSON.stringify would also throw and our
// serializer would emit "[Unserializable]" (~16 bytes).
return 16;
}
ancestors.add(obj);
const size = estimate(projected);
ancestors.delete(obj);
return size;
}
ancestors.add(obj);
let size;
if (Array.isArray(obj)) {
size = 2; // []
const len = obj.length;
for (let i = 0; i < len; i++) {
size += estimateInArray(obj[i]);
if (i < len - 1)
size += 1; // comma
}
}
else if (obj instanceof Map) {
// Rendered as { k: v, ... } via Object.fromEntries.
size = 2;
let emitted = 0;
for (const [k, v] of obj) {
if (isDropped(v))
continue;
if (emitted > 0)
size += 1; // comma
const keyStr = typeof k === "string" ? k : String(k);
size += byteLen(keyStr) + 3; // "key":
size += estimate(v);
emitted++;
}
}
else if (obj instanceof Set) {
// Rendered as [v, ...] via Array.from.
size = 2;
let emitted = 0;
for (const v of obj) {
if (emitted > 0)
size += 1; // comma
size += estimateInArray(v);
emitted++;
}
}
else {
size = 2; // {}
let emitted = 0;
// Object.keys only returns own enumerable string keys, matching
// JSON.stringify.
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const v = obj[key];
if (isDropped(v))
continue;
if (emitted > 0)
size += 1; // comma
size += byteLen(key) + 3; // "key":
size += estimate(v);
emitted++;
}
}
ancestors.delete(obj);
return size;
}
const size = estimate(value);
return { size, maxStringLen };
}
catch {
// If the estimator itself hits an unexpected edge case, fall back to the
// exact serialized size. This preserves correctness of queue-size
// accounting at the cost of a slower hot path for that one payload.
// We cannot cheaply recover maxStringLen here, so report 0: the worker
// gate will then fall back to sync serialization, which is safe.
return { size: serialize(value).length, maxStringLen: 0 };
}
}
// Regular stringify
function serialize(obj, errorContext, replacer, spacer, options) {
try {
const str = JSON.stringify(obj, createDefaultReplacer(replacer), spacer);
return encodeString(str);
}
catch (e) {
// Fall back to more complex stringify if circular reference
if (!e.message?.includes("Converting circular structure to JSON")) {
console.warn(`[WARNING]: LangSmith received unserializable value.${errorContext ? `\nContext: ${errorContext}` : ""}`);
return encodeString("[Unserializable]");
}
(0, env_js_1.getLangSmithEnvironmentVariable)("SUPPRESS_CIRCULAR_JSON_WARNINGS") !==
"true" &&
console.warn(`[WARNING]: LangSmith received circular JSON. This will decrease tracer performance. ${errorContext ? `\nContext: ${errorContext}` : ""}`);
if (typeof options === "undefined") {
options = defaultOptions();
}
decirc(obj, "", 0, [], undefined, 0, options);
let res;
try {
if (replacerStack.length === 0) {
res = JSON.stringify(obj, replacer, spacer);
}
else {
res = JSON.stringify(obj, replaceGetterValues(replacer), spacer);
}
}
catch (_) {
return encodeString("[unable to serialize, circular reference is too complex to analyze]");
}
finally {
while (arr.length !== 0) {
const part = arr.pop();
if (part.length === 4) {
Object.defineProperty(part[0], part[1], part[3]);
}
else {
part[0][part[1]] = part[2];
}
}
}
return encodeString(res);
}
}
function setReplace(replace, val, k, parent) {
var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k);
if (propertyDescriptor.get !== undefined) {
if (propertyDescriptor.configurable) {
Object.defineProperty(parent, k, { value: replace });
arr.push([parent, k, val, propertyDescriptor]);
}
else {
replacerStack.push([val, k, replace]);
}
}
else {
parent[k] = replace;
arr.push([parent, k, val]);
}
}
function decirc(val, k, edgeIndex, stack, parent, depth, options) {
depth += 1;
var i;
if (typeof val === "object" && val !== null) {
for (i = 0; i < stack.length; i++) {
if (stack[i] === val) {
setReplace(CIRCULAR_REPLACE_NODE, val, k, parent);
return;
}
}
if (typeof options.depthLimit !== "undefined" &&
depth > options.depthLimit) {
setReplace(LIMIT_REPLACE_NODE, val, k, parent);
return;
}
if (typeof options.edgesLimit !== "undefined" &&
edgeIndex + 1 > options.edgesLimit) {
setReplace(LIMIT_REPLACE_NODE, val, k, parent);
return;
}
stack.push(val);
// Optimize for Arrays. Big arrays could kill the performance otherwise!
if (Array.isArray(val)) {
for (i = 0; i < val.length; i++) {
decirc(val[i], i, i, stack, val, depth, options);
}
}
else {
// Handle well-known types before Object.keys iteration
val = serializeWellKnownTypes(val);
var keys = Object.keys(val);
for (i = 0; i < keys.length; i++) {
var key = keys[i];
decirc(val[key], key, i, stack, val, depth, options);
}
}
stack.pop();
}
}
// Stable-stringify
function compareFunction(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
function deterministicStringify(obj, replacer, spacer, options) {
if (typeof options === "undefined") {
options = defaultOptions();
}
var tmp = deterministicDecirc(obj, "", 0, [], undefined, 0, options) || obj;
var res;
try {
if (replacerStack.length === 0) {
res = JSON.stringify(tmp, replacer, spacer);
}
else {
res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer);
}
}
catch (_) {
return JSON.stringify("[unable to serialize, circular reference is too complex to analyze]");
}
finally {
// Ensure that we restore the object as it was.
while (arr.length !== 0) {
var part = arr.pop();
if (part.length === 4) {
Object.defineProperty(part[0], part[1], part[3]);
}
else {
part[0][part[1]] = part[2];
}
}
}
return res;
}
function deterministicDecirc(val, k, edgeIndex, stack, parent, depth, options) {
depth += 1;
var i;
if (typeof val === "object" && val !== null) {
for (i = 0; i < stack.length; i++) {
if (stack[i] === val) {
setReplace(CIRCULAR_REPLACE_NODE, val, k, parent);
return;
}
}
try {
if (typeof val.toJSON === "function") {
return;
}
}
catch (_) {
return;
}
if (typeof options.depthLimit !== "undefined" &&
depth > options.depthLimit) {
setReplace(LIMIT_REPLACE_NODE, val, k, parent);
return;
}
if (typeof options.edgesLimit !== "undefined" &&
edgeIndex + 1 > options.edgesLimit) {
setReplace(LIMIT_REPLACE_NODE, val, k, parent);
return;
}
stack.push(val);
// Optimize for Arrays. Big arrays could kill the performance otherwise!
if (Array.isArray(val)) {
for (i = 0; i < val.length; i++) {
deterministicDecirc(val[i], i, i, stack, val, depth, options);
}
}
else {
// Handle well-known types before Object.keys iteration
val = serializeWellKnownTypes(val);
// Create a temporary object in the required way
var tmp = {};
var keys = Object.keys(val).sort(compareFunction);
for (i = 0; i < keys.length; i++) {
var key = keys[i];
deterministicDecirc(val[key], key, i, stack, val, depth, options);
tmp[key] = val[key];
}
if (typeof parent !== "undefined") {
arr.push([parent, k, val]);
parent[k] = tmp;
}
else {
return tmp;
}
}
stack.pop();
}
}
// wraps replacer function to handle values we couldn't replace
// and mark them as replaced value
function replaceGetterValues(replacer) {
replacer =
typeof replacer !== "undefined"
? replacer
: function (k, v) {
return v;
};
return function (key, val) {
if (replacerStack.length > 0) {
for (var i = 0; i < replacerStack.length; i++) {
var part = replacerStack[i];
if (part[1] === key && part[0] === val) {
val = part[2];
replacerStack.splice(i, 1);
break;
}
}
}
return replacer.call(this, key, val);
};
}