@langchain/core
Version:
Core LangChain.js abstractions and schemas
202 lines (201 loc) • 8.87 kB
JavaScript
import { BaseMessage } from "../messages/base.js";
import { ToolMessage } from "../messages/tool.js";
import { AIMessage } from "../messages/ai.js";
import { HumanMessage } from "../messages/human.js";
import { SystemMessage } from "../messages/system.js";
import "../messages/index.js";
//#region src/testing/matchers.ts
function getMessageTypeName(msg) {
if (!BaseMessage.isInstance(msg)) return typeof msg;
return msg.constructor.name || msg.type;
}
function makeMessageTypeMatcher(typeName, isInstance) {
return function(received, expected) {
const { isNot, utils } = this;
if (!isInstance(received)) return {
pass: false,
message: () => `${utils.matcherHint(`toBe${typeName}`, void 0, void 0)}\n\nExpected: ${isNot ? "not " : ""}${typeName}\nReceived: ${getMessageTypeName(received)}`,
actual: getMessageTypeName(received),
expected: typeName
};
if (expected === void 0) return {
pass: true,
message: () => `${utils.matcherHint(`toBe${typeName}`, void 0, void 0)}\n\nExpected: not ${typeName}\nReceived: ${typeName}`
};
const msg = received;
if (typeof expected === "string") return {
pass: msg.content === expected,
message: () => `${utils.matcherHint(`toBe${typeName}`, void 0, void 0)}\n\nExpected: ${typeName} with content ${utils.printExpected(expected)}\nReceived: ${typeName} with content ${utils.printReceived(msg.content)}`,
actual: msg.content,
expected
};
return {
pass: Object.entries(expected).every(([key, value]) => this.equals(msg[key], value)),
message: () => {
const receivedFields = {};
for (const key of Object.keys(expected)) receivedFields[key] = msg[key];
return `${utils.matcherHint(`toBe${typeName}`, void 0, void 0)}\n\nExpected: ${typeName} matching ${utils.printExpected(expected)}\nReceived: ${typeName} with ${utils.printReceived(receivedFields)}`;
},
actual: (() => {
const receivedFields = {};
for (const key of Object.keys(expected)) receivedFields[key] = msg[key];
return receivedFields;
})(),
expected
};
};
}
const toBeHumanMessage = makeMessageTypeMatcher("HumanMessage", HumanMessage.isInstance);
const toBeAIMessage = makeMessageTypeMatcher("AIMessage", AIMessage.isInstance);
const toBeSystemMessage = makeMessageTypeMatcher("SystemMessage", SystemMessage.isInstance);
const toBeToolMessage = makeMessageTypeMatcher("ToolMessage", ToolMessage.isInstance);
function toHaveToolCalls(received, expected) {
const { isNot, utils } = this;
if (!AIMessage.isInstance(received)) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolCalls")}\n\nExpected: AIMessage\nReceived: ${getMessageTypeName(received)}`
};
const actual = received.tool_calls ?? [];
if (actual.length !== expected.length) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolCalls")}\n\nExpected ${isNot ? "not " : ""}${expected.length} tool call(s), received ${actual.length}`,
actual: actual.length,
expected: expected.length
};
const unmatched = expected.filter((exp) => !actual.some((tc) => Object.entries(exp).every(([key, value]) => this.equals(tc[key], value))));
if (unmatched.length > 0) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolCalls")}\n\nCould not find matching tool call(s) for:\n${utils.printExpected(unmatched)}\nReceived tool calls: ${utils.printReceived(actual.map((tc) => ({
name: tc.name,
id: tc.id,
args: tc.args
})))}`,
actual: actual.map((tc) => ({
name: tc.name,
id: tc.id,
args: tc.args
})),
expected
};
return {
pass: true,
message: () => `${utils.matcherHint("toHaveToolCalls")}\n\nExpected AIMessage not to have matching tool calls`
};
}
function toHaveToolCallCount(received, expected) {
const { isNot, utils } = this;
if (!AIMessage.isInstance(received)) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolCallCount")}\n\nExpected: AIMessage\nReceived: ${getMessageTypeName(received)}`
};
const actual = received.tool_calls?.length ?? 0;
return {
pass: actual === expected,
message: () => `${utils.matcherHint("toHaveToolCallCount")}\n\nExpected ${isNot ? "not " : ""}${expected} tool call(s)\nReceived: ${actual}`,
actual,
expected
};
}
function toContainToolCall(received, expected) {
const { isNot, utils } = this;
if (!AIMessage.isInstance(received)) return {
pass: false,
message: () => `${utils.matcherHint("toContainToolCall")}\n\nExpected: AIMessage\nReceived: ${getMessageTypeName(received)}`
};
const actual = received.tool_calls ?? [];
return {
pass: actual.some((tc) => Object.entries(expected).every(([key, value]) => this.equals(tc[key], value))),
message: () => `${utils.matcherHint("toContainToolCall")}\n\nExpected AIMessage ${isNot ? "not " : ""}to contain a tool call matching ${utils.printExpected(expected)}\nReceived tool calls: ${utils.printReceived(actual.map((tc) => ({
name: tc.name,
id: tc.id
})))}`,
actual: actual.map((tc) => ({
name: tc.name,
id: tc.id
})),
expected
};
}
function toHaveToolMessages(received, expected) {
const { isNot, utils } = this;
if (!Array.isArray(received)) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolMessages")}\n\nExpected an array of messages\nReceived: ${typeof received}`
};
const toolMessages = received.filter(ToolMessage.isInstance);
if (toolMessages.length !== expected.length) return {
pass: false,
message: () => `${utils.matcherHint("toHaveToolMessages")}\n\nExpected ${isNot ? "not " : ""}${expected.length} tool message(s), found ${toolMessages.length}`,
actual: toolMessages.length,
expected: expected.length
};
for (let i = 0; i < expected.length; i++) if (!Object.entries(expected[i]).every(([key, value]) => this.equals(toolMessages[i][key], value))) return {
pass: false,
message: () => {
const receivedFields = {};
for (const key of Object.keys(expected[i])) receivedFields[key] = toolMessages[i][key];
return `${utils.matcherHint("toHaveToolMessages")}\n\nTool message at index ${i} did not match:\nExpected: ${utils.printExpected(expected[i])}\nReceived: ${utils.printReceived(receivedFields)}`;
},
actual: toolMessages[i],
expected: expected[i]
};
return {
pass: true,
message: () => `${utils.matcherHint("toHaveToolMessages")}\n\nExpected messages not to contain matching tool messages`
};
}
function toHaveBeenInterrupted(received, expectedValue) {
const { isNot, utils } = this;
const interrupts = received?.__interrupt__;
if (!(Array.isArray(interrupts) && interrupts.length > 0)) return {
pass: false,
message: () => `${utils.matcherHint("toHaveBeenInterrupted")}\n\nExpected result ${isNot ? "not " : ""}to have been interrupted\nReceived __interrupt__: ${utils.printReceived(interrupts)}`
};
if (expectedValue === void 0) return {
pass: true,
message: () => `${utils.matcherHint("toHaveBeenInterrupted")}\n\nExpected result not to have been interrupted\nReceived ${interrupts.length} interrupt(s)`
};
const actualValue = interrupts[0]?.value;
return {
pass: this.equals(actualValue, expectedValue),
message: () => `${utils.matcherHint("toHaveBeenInterrupted")}\n\nExpected interrupt value: ${utils.printExpected(expectedValue)}\nReceived interrupt value: ${utils.printReceived(actualValue)}`,
actual: actualValue,
expected: expectedValue
};
}
function toHaveStructuredResponse(received, expected) {
const { isNot, utils } = this;
const structuredResponse = received?.structuredResponse;
if (!(structuredResponse !== void 0)) return {
pass: false,
message: () => `${utils.matcherHint("toHaveStructuredResponse")}\n\nExpected result ${isNot ? "not " : ""}to have a structured response\nReceived structuredResponse: undefined`
};
if (expected === void 0) return {
pass: true,
message: () => `${utils.matcherHint("toHaveStructuredResponse")}\n\nExpected result not to have a structured response`
};
return {
pass: Object.entries(expected).every(([key, value]) => this.equals(structuredResponse[key], value)),
message: () => `${utils.matcherHint("toHaveStructuredResponse")}\n\nExpected structured response: ${utils.printExpected(expected)}\nReceived structured response: ${utils.printReceived(structuredResponse)}`,
actual: structuredResponse,
expected
};
}
/**
* All matcher functions bundled for convenient use with `expect.extend()`.
*/
const langchainMatchers = {
toBeHumanMessage,
toBeAIMessage,
toBeSystemMessage,
toBeToolMessage,
toHaveToolCalls,
toHaveToolCallCount,
toContainToolCall,
toHaveToolMessages,
toHaveBeenInterrupted,
toHaveStructuredResponse
};
//#endregion
export { langchainMatchers, toBeAIMessage, toBeHumanMessage, toBeSystemMessage, toBeToolMessage, toContainToolCall, toHaveBeenInterrupted, toHaveStructuredResponse, toHaveToolCallCount, toHaveToolCalls, toHaveToolMessages };
//# sourceMappingURL=matchers.js.map