@langchain/core
Version:
Core LangChain.js abstractions and schemas
141 lines (140 loc) • 6 kB
JavaScript
//#region src/utils/testing/stream.ts
function isChatModelStream(received) {
if (received == null || typeof received !== "object") return false;
const stream = received;
return typeof stream.text !== "undefined" && typeof stream.toolCalls !== "undefined" && typeof stream.reasoning !== "undefined" && typeof stream.usage !== "undefined" && typeof stream.output !== "undefined" && typeof stream[Symbol.asyncIterator] === "function";
}
function matchesPartialObject(actual, expected, equals) {
if (actual == null) return false;
return Object.entries(expected).every(([key, value]) => equals(actual[key], value));
}
function matchesStreamUsage(actual, expected, equals) {
if (actual == null) return false;
return matchesPartialObject(actual, expected, equals);
}
function getOutputText(message) {
return message.content.find((block) => block.type === "text")?.text;
}
function matchesStreamOutput(message, expected, equals) {
if (expected.id !== void 0 && message.id !== expected.id) return false;
if (expected.text !== void 0 && getOutputText(message) !== expected.text) return false;
if (expected.toolCalls !== void 0) {
const calls = message.tool_calls ?? [];
if (calls.length !== expected.toolCalls.length) return false;
for (let i = 0; i < expected.toolCalls.length; i++) {
const call = calls[i];
const exp = expected.toolCalls[i];
if (call?.name !== exp.name || !equals(call.args, exp.args)) return false;
}
}
if (expected.usage !== void 0 && !matchesStreamUsage(message.usage_metadata, expected.usage, equals)) return false;
if (expected.responseMetadata !== void 0 && !matchesPartialObject(message.response_metadata, expected.responseMetadata, equals)) return false;
return true;
}
function invalidStreamResult(received, matcherName, utils) {
return {
pass: false,
message: () => `${utils.matcherHint(matcherName)}\n\nExpected: ChatModelStream (return value of model.streamEvents("Hello"))\nReceived: ${utils.printReceived(received)}`,
actual: received,
expected: "ChatModelStream"
};
}
function applyNot(pass, isNot) {
return isNot ? !pass : pass;
}
async function toHaveStreamText(received, expected) {
const { isNot, utils } = this;
const matcherName = "toHaveStreamText";
if (!isChatModelStream(received)) return invalidStreamResult(received, matcherName, utils);
const actual = await received.text;
return {
pass: applyNot(actual === expected, isNot),
message: () => `${utils.matcherHint(matcherName, void 0, void 0, { isNot })}\n\nExpected stream text: ${isNot ? "not " : ""}${utils.printExpected(expected)}\nReceived stream text: ${utils.printReceived(actual)}`,
actual,
expected
};
}
async function toHaveStreamReasoning(received, expected) {
const { isNot, utils } = this;
const matcherName = "toHaveStreamReasoning";
if (!isChatModelStream(received)) return invalidStreamResult(received, matcherName, utils);
const actual = await received.reasoning;
return {
pass: applyNot(actual === expected, isNot),
message: () => `${utils.matcherHint(matcherName, void 0, void 0, { isNot })}\n\nExpected stream reasoning: ${isNot ? "not " : ""}${utils.printExpected(expected)}\nReceived stream reasoning: ${utils.printReceived(actual)}`,
actual,
expected
};
}
async function toHaveStreamToolCalls(received, expected) {
const { isNot, utils } = this;
const matcherName = "toHaveStreamToolCalls";
if (!isChatModelStream(received)) return invalidStreamResult(received, matcherName, utils);
const actual = await received.toolCalls;
let pass = actual.length === expected.length && expected.every((exp, i) => {
const call = actual[i];
return call?.name === exp.name && this.equals(call.args, exp.args);
});
pass = applyNot(pass, isNot);
return {
pass,
message: () => `${utils.matcherHint(matcherName, void 0, void 0, { isNot })}\n\nExpected stream tool calls: ${utils.printExpected(expected)}\nReceived stream tool calls: ${utils.printReceived(actual.map((tc) => ({
name: tc.name,
args: tc.args
})))}`,
actual: actual.map((tc) => ({
name: tc.name,
args: tc.args
})),
expected
};
}
async function toHaveStreamUsage(received, expected) {
const { isNot, utils } = this;
const matcherName = "toHaveStreamUsage";
if (!isChatModelStream(received)) return invalidStreamResult(received, matcherName, utils);
const actual = await received.usage;
return {
pass: applyNot(matchesStreamUsage(actual, expected, this.equals), isNot),
message: () => `${utils.matcherHint(matcherName, void 0, void 0, { isNot })}\n\nExpected stream usage: ${utils.printExpected(expected)}\nReceived stream usage: ${utils.printReceived(actual)}`,
actual,
expected
};
}
async function toHaveStreamOutput(received, expected) {
const { isNot, utils } = this;
const matcherName = "toHaveStreamOutput";
if (!isChatModelStream(received)) return invalidStreamResult(received, matcherName, utils);
const message = await received.output;
return {
pass: applyNot(matchesStreamOutput(message, expected, this.equals), isNot),
message: () => `${utils.matcherHint(matcherName, void 0, void 0, { isNot })}\n\nExpected stream output: ${utils.printExpected(expected)}\nReceived stream output: ${utils.printReceived({
id: message.id,
text: getOutputText(message),
tool_calls: message.tool_calls?.map((tc) => ({
name: tc.name,
args: tc.args
})),
usage_metadata: message.usage_metadata,
response_metadata: message.response_metadata
})}`,
actual: message,
expected
};
}
/** Stream matchers for `expect.extend()`. */
const streamMatchers = {
toHaveStreamText,
toHaveStreamReasoning,
toHaveStreamToolCalls,
toHaveStreamUsage,
toHaveStreamOutput
};
//#endregion
exports.streamMatchers = streamMatchers;
exports.toHaveStreamOutput = toHaveStreamOutput;
exports.toHaveStreamReasoning = toHaveStreamReasoning;
exports.toHaveStreamText = toHaveStreamText;
exports.toHaveStreamToolCalls = toHaveStreamToolCalls;
exports.toHaveStreamUsage = toHaveStreamUsage;
//# sourceMappingURL=stream.cjs.map