Skip to content

Commit

Permalink
fix(js): Handle undefined values in object equality checks (#1460)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacoblee93 authored Jan 24, 2025
1 parent c0f8ee7 commit fe0cb1f
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 31 deletions.
2 changes: 1 addition & 1 deletion js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "langsmith",
"version": "0.3.2",
"version": "0.3.3",
"description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
"packageManager": "[email protected]",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ export { RunTree, type RunTreeConfig } from "./run_trees.js";
export { overrideFetchImplementation } from "./singletons/fetch.js";

// Update using yarn bump-version
export const __version__ = "0.3.2";
export const __version__ = "0.3.3";
11 changes: 11 additions & 0 deletions js/src/tests/jestlike/jest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";

import * as ls from "../../jest/index.js";
import { type SimpleEvaluator } from "../../jest/index.js";
import { objectHash } from "../../utils/jestlike/index.js";

const myEvaluator: SimpleEvaluator = (params) => {
const { referenceOutputs, outputs } = params;
Expand Down Expand Up @@ -253,3 +254,13 @@ ls.describe("Test Linkedin Post", () => {
}
);
});

test("object hash should work on undefined values", async () => {
expect(
objectHash({
foo: "bar",
baz: undefined,
qux: null,
})
).toEqual("88d67a35803b03a787d9fce25ebed027807c68ce0c3dee9f818fc58a43dd10af");
});
59 changes: 30 additions & 29 deletions js/src/utils/jestlike/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,41 +96,42 @@ export function logOutputs(output: Record<string, unknown>) {
context.setLoggedOutput(output);
}

export function objectHash(obj: KVMap, depth = 0): string {
// Prevent infinite recursion
if (depth > 50) {
throw new Error(
"Object is too deep to check equality for serialization. Please use a simpler example."
);
}

if (Array.isArray(obj)) {
const arrayHash = obj.map((item) => objectHash(item, depth + 1)).join(",");
return crypto.createHash("sha256").update(arrayHash).digest("hex");
}

if (obj && typeof obj === "object") {
const sortedHash = Object.keys(obj)
.sort()
.map((key) => `${key}:${objectHash(obj[key], depth + 1)}`)
.join(",");
return crypto.createHash("sha256").update(sortedHash).digest("hex");
}

return (
crypto
.createHash("sha256")
// Treat null and undefined as equal for serialization purposes
.update(JSON.stringify(obj ?? null))
.digest("hex")
);
}

export function generateWrapperFromJestlikeMethods(
methods: Record<string, any>,
testRunnerName: string
) {
const { expect, test, describe, beforeAll, afterAll } = methods;

const objectHash = (obj: KVMap, depth = 0): string => {
// Prevent infinite recursion
if (depth > 50) {
throw new Error(
"Object is too deep to check equality for serialization. Please use a simpler example."
);
}

if (Array.isArray(obj)) {
const arrayHash = obj
.map((item) => objectHash(item, depth + 1))
.join(",");
return crypto.createHash("sha256").update(arrayHash).digest("hex");
}

if (obj && typeof obj === "object") {
const sortedHash = Object.keys(obj)
.sort()
.map((key) => `${key}:${objectHash(obj[key], depth + 1)}`)
.join(",");
return crypto.createHash("sha256").update(sortedHash).digest("hex");
}

return crypto
.createHash("sha256")
.update(JSON.stringify(obj))
.digest("hex");
};

async function _createProject(
client: Client,
datasetId: string,
Expand Down

0 comments on commit fe0cb1f

Please sign in to comment.