UNPKG

@langchain/core

Version:
202 lines (201 loc) 8.87 kB
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