langsmith
Version:
Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.
104 lines (103 loc) • 4.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.toBeRelativeCloseTo = toBeRelativeCloseTo;
exports.toBeAbsoluteCloseTo = toBeAbsoluteCloseTo;
exports.toBeSemanticCloseTo = toBeSemanticCloseTo;
// Levenshtein distance implementation
function levenshteinDistance(a, b) {
if (a.length === 0)
return b.length;
if (b.length === 0)
return a.length;
const matrix = Array(b.length + 1)
.fill(null)
.map(() => Array(a.length + 1).fill(null));
for (let i = 0; i <= a.length; i++)
matrix[0][i] = i;
for (let j = 0; j <= b.length; j++)
matrix[j][0] = j;
for (let j = 1; j <= b.length; j++) {
for (let i = 1; i <= a.length; i++) {
const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + substitutionCost);
}
}
return matrix[b.length][a.length];
}
async function toBeRelativeCloseTo(received, expected, options = {}) {
const { threshold = 0.1, algorithm = "levenshtein" } = options;
if (threshold < 0 || threshold > 1) {
throw new Error("Relative distance is normalized, and threshold must be between 0 and 1.");
}
let distance;
let maxLength;
switch (algorithm) {
case "levenshtein":
distance = levenshteinDistance(received, expected);
maxLength = Math.max(received.length, expected.length);
break;
default:
throw new Error(`Unsupported algorithm: ${algorithm}`);
}
// Calculate relative distance (normalized between 0 and 1)
const relativeDistance = maxLength === 0 ? 0 : distance / maxLength;
const pass = relativeDistance <= threshold;
return {
pass,
message: () => pass
? `Expected "${received}" not to be relatively close to "${expected}" (threshold: ${threshold}, actual distance: ${relativeDistance})`
: `Expected "${received}" to be relatively close to "${expected}" (threshold: ${threshold}, actual distance: ${relativeDistance})`,
};
}
async function toBeAbsoluteCloseTo(received, expected, options = {}) {
const { threshold = 3, algorithm = "levenshtein" } = options;
let distance;
switch (algorithm) {
case "levenshtein":
distance = levenshteinDistance(received, expected);
break;
default:
throw new Error(`Unsupported algorithm: ${algorithm}`);
}
const pass = distance <= threshold;
return {
pass,
message: () => pass
? `Expected "${received}" not to be absolutely close to "${expected}" (threshold: ${threshold}, actual distance: ${distance})`
: `Expected "${received}" to be absolutely close to "${expected}" (threshold: ${threshold}, actual distance: ${distance})`,
};
}
async function toBeSemanticCloseTo(received, expected, options) {
const { threshold = 0.2, embeddings, algorithm = "cosine" } = options;
// Get embeddings for both strings
const [receivedEmbedding, expectedEmbedding] = await Promise.all([
embeddings.embedQuery(received),
embeddings.embedQuery(expected),
]);
// Calculate similarity based on chosen algorithm
let similarity;
switch (algorithm) {
case "cosine": {
// Compute cosine similarity
const dotProduct = receivedEmbedding.reduce((sum, a, i) => sum + a * expectedEmbedding[i], 0);
const receivedMagnitude = Math.sqrt(receivedEmbedding.reduce((sum, a) => sum + a * a, 0));
const expectedMagnitude = Math.sqrt(expectedEmbedding.reduce((sum, a) => sum + a * a, 0));
similarity = dotProduct / (receivedMagnitude * expectedMagnitude);
break;
}
case "dot-product": {
// Compute dot product similarity
similarity = receivedEmbedding.reduce((sum, a, i) => sum + a * expectedEmbedding[i], 0);
break;
}
default:
throw new Error(`Unsupported algorithm: ${algorithm}`);
}
const pass = similarity >= 1 - threshold;
return {
pass,
message: () => pass
? `Expected "${received}" not to be semantically close to "${expected}" (threshold: ${threshold}, similarity: ${similarity})`
: `Expected "${received}" to be semantically close to "${expected}" (threshold: ${threshold}, similarity: ${similarity})`,
};
}