matchers.cjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.toBeRelativeCloseTo = toBeRelativeCloseTo;
  4. exports.toBeAbsoluteCloseTo = toBeAbsoluteCloseTo;
  5. exports.toBeSemanticCloseTo = toBeSemanticCloseTo;
  6. // Levenshtein distance implementation
  7. function levenshteinDistance(a, b) {
  8. if (a.length === 0)
  9. return b.length;
  10. if (b.length === 0)
  11. return a.length;
  12. const matrix = Array(b.length + 1)
  13. .fill(null)
  14. .map(() => Array(a.length + 1).fill(null));
  15. for (let i = 0; i <= a.length; i++)
  16. matrix[0][i] = i;
  17. for (let j = 0; j <= b.length; j++)
  18. matrix[j][0] = j;
  19. for (let j = 1; j <= b.length; j++) {
  20. for (let i = 1; i <= a.length; i++) {
  21. const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
  22. matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + substitutionCost);
  23. }
  24. }
  25. return matrix[b.length][a.length];
  26. }
  27. async function toBeRelativeCloseTo(received, expected, options = {}) {
  28. const { threshold = 0.1, algorithm = "levenshtein" } = options;
  29. if (threshold < 0 || threshold > 1) {
  30. throw new Error("Relative distance is normalized, and threshold must be between 0 and 1.");
  31. }
  32. let distance;
  33. let maxLength;
  34. switch (algorithm) {
  35. case "levenshtein":
  36. distance = levenshteinDistance(received, expected);
  37. maxLength = Math.max(received.length, expected.length);
  38. break;
  39. default:
  40. throw new Error(`Unsupported algorithm: ${algorithm}`);
  41. }
  42. // Calculate relative distance (normalized between 0 and 1)
  43. const relativeDistance = maxLength === 0 ? 0 : distance / maxLength;
  44. const pass = relativeDistance <= threshold;
  45. return {
  46. pass,
  47. message: () => pass
  48. ? `Expected "${received}" not to be relatively close to "${expected}" (threshold: ${threshold}, actual distance: ${relativeDistance})`
  49. : `Expected "${received}" to be relatively close to "${expected}" (threshold: ${threshold}, actual distance: ${relativeDistance})`,
  50. };
  51. }
  52. async function toBeAbsoluteCloseTo(received, expected, options = {}) {
  53. const { threshold = 3, algorithm = "levenshtein" } = options;
  54. let distance;
  55. switch (algorithm) {
  56. case "levenshtein":
  57. distance = levenshteinDistance(received, expected);
  58. break;
  59. default:
  60. throw new Error(`Unsupported algorithm: ${algorithm}`);
  61. }
  62. const pass = distance <= threshold;
  63. return {
  64. pass,
  65. message: () => pass
  66. ? `Expected "${received}" not to be absolutely close to "${expected}" (threshold: ${threshold}, actual distance: ${distance})`
  67. : `Expected "${received}" to be absolutely close to "${expected}" (threshold: ${threshold}, actual distance: ${distance})`,
  68. };
  69. }
  70. async function toBeSemanticCloseTo(received, expected, options) {
  71. const { threshold = 0.2, embeddings, algorithm = "cosine" } = options;
  72. // Get embeddings for both strings
  73. const [receivedEmbedding, expectedEmbedding] = await Promise.all([
  74. embeddings.embedQuery(received),
  75. embeddings.embedQuery(expected),
  76. ]);
  77. // Calculate similarity based on chosen algorithm
  78. let similarity;
  79. switch (algorithm) {
  80. case "cosine": {
  81. // Compute cosine similarity
  82. const dotProduct = receivedEmbedding.reduce((sum, a, i) => sum + a * expectedEmbedding[i], 0);
  83. const receivedMagnitude = Math.sqrt(receivedEmbedding.reduce((sum, a) => sum + a * a, 0));
  84. const expectedMagnitude = Math.sqrt(expectedEmbedding.reduce((sum, a) => sum + a * a, 0));
  85. similarity = dotProduct / (receivedMagnitude * expectedMagnitude);
  86. break;
  87. }
  88. case "dot-product": {
  89. // Compute dot product similarity
  90. similarity = receivedEmbedding.reduce((sum, a, i) => sum + a * expectedEmbedding[i], 0);
  91. break;
  92. }
  93. default:
  94. throw new Error(`Unsupported algorithm: ${algorithm}`);
  95. }
  96. const pass = similarity >= 1 - threshold;
  97. return {
  98. pass,
  99. message: () => pass
  100. ? `Expected "${received}" not to be semantically close to "${expected}" (threshold: ${threshold}, similarity: ${similarity})`
  101. : `Expected "${received}" to be semantically close to "${expected}" (threshold: ${threshold}, similarity: ${similarity})`,
  102. };
  103. }