@langchain/core
Version:
Core LangChain.js abstractions and schemas
363 lines (362 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isBaseMessageChunk = exports.isBaseMessage = exports._isMessageFieldWithRole = exports.BaseMessageChunk = exports._mergeObj = exports._mergeLists = exports._mergeDicts = exports.isOpenAIToolCallArray = exports.BaseMessage = exports._mergeStatus = exports.mergeContent = void 0;
const serializable_js_1 = require("../load/serializable.cjs");
function mergeContent(firstContent, secondContent) {
// If first content is a string
if (typeof firstContent === "string") {
if (firstContent === "") {
return secondContent;
}
if (typeof secondContent === "string") {
return firstContent + secondContent;
}
else {
return [{ type: "text", text: firstContent }, ...secondContent];
}
// If both are arrays
}
else if (Array.isArray(secondContent)) {
return (_mergeLists(firstContent, secondContent) ?? [
...firstContent,
...secondContent,
]);
}
else {
if (secondContent === "") {
return firstContent;
}
// Otherwise, add the second content as a new element of the list
return [...firstContent, { type: "text", text: secondContent }];
}
}
exports.mergeContent = mergeContent;
/**
* 'Merge' two statuses. If either value passed is 'error', it will return 'error'. Else
* it will return 'success'.
*
* @param {"success" | "error" | undefined} left The existing value to 'merge' with the new value.
* @param {"success" | "error" | undefined} right The new value to 'merge' with the existing value
* @returns {"success" | "error"} The 'merged' value.
*/
function _mergeStatus(left, right) {
if (left === "error" || right === "error") {
return "error";
}
return "success";
}
exports._mergeStatus = _mergeStatus;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function stringifyWithDepthLimit(obj, depthLimit) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function helper(obj, currentDepth) {
if (typeof obj !== "object" || obj === null || obj === undefined) {
return obj;
}
if (currentDepth >= depthLimit) {
if (Array.isArray(obj)) {
return "[Array]";
}
return "[Object]";
}
if (Array.isArray(obj)) {
return obj.map((item) => helper(item, currentDepth + 1));
}
const result = {};
for (const key of Object.keys(obj)) {
result[key] = helper(obj[key], currentDepth + 1);
}
return result;
}
return JSON.stringify(helper(obj, 0), null, 2);
}
/**
* Base class for all types of messages in a conversation. It includes
* properties like `content`, `name`, and `additional_kwargs`. It also
* includes methods like `toDict()` and `_getType()`.
*/
class BaseMessage extends serializable_js_1.Serializable {
get lc_aliases() {
// exclude snake case conversion to pascal case
return {
additional_kwargs: "additional_kwargs",
response_metadata: "response_metadata",
};
}
/**
* Get text content of the message.
*/
get text() {
if (typeof this.content === "string") {
return this.content;
}
if (!Array.isArray(this.content))
return "";
return this.content
.map((c) => {
if (typeof c === "string")
return c;
if (c.type === "text")
return c.text;
return "";
})
.join("");
}
/** The type of the message. */
getType() {
return this._getType();
}
constructor(fields,
/** @deprecated */
kwargs) {
if (typeof fields === "string") {
// eslint-disable-next-line no-param-reassign
fields = {
content: fields,
additional_kwargs: kwargs,
response_metadata: {},
};
}
// Make sure the default value for additional_kwargs is passed into super() for serialization
if (!fields.additional_kwargs) {
// eslint-disable-next-line no-param-reassign
fields.additional_kwargs = {};
}
if (!fields.response_metadata) {
// eslint-disable-next-line no-param-reassign
fields.response_metadata = {};
}
super(fields);
Object.defineProperty(this, "lc_namespace", {
enumerable: true,
configurable: true,
writable: true,
value: ["langchain_core", "messages"]
});
Object.defineProperty(this, "lc_serializable", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/** The content of the message. */
Object.defineProperty(this, "content", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** The name of the message sender in a multi-user chat. */
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Additional keyword arguments */
Object.defineProperty(this, "additional_kwargs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Response metadata. For example: response headers, logprobs, token counts. */
Object.defineProperty(this, "response_metadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* An optional unique identifier for the message. This should ideally be
* provided by the provider/model which created the message.
*/
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.name = fields.name;
this.content = fields.content;
this.additional_kwargs = fields.additional_kwargs;
this.response_metadata = fields.response_metadata;
this.id = fields.id;
}
toDict() {
return {
type: this._getType(),
data: this.toJSON()
.kwargs,
};
}
static lc_name() {
return "BaseMessage";
}
// Can't be protected for silly reasons
get _printableFields() {
return {
id: this.id,
content: this.content,
name: this.name,
additional_kwargs: this.additional_kwargs,
response_metadata: this.response_metadata,
};
}
// this private method is used to update the ID for the runtime
// value as well as in lc_kwargs for serialisation
_updateId(value) {
this.id = value;
// lc_attributes wouldn't work here, because jest compares the
// whole object
this.lc_kwargs.id = value;
}
get [Symbol.toStringTag]() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this.constructor.lc_name();
}
// Override the default behavior of console.log
[Symbol.for("nodejs.util.inspect.custom")](depth) {
if (depth === null) {
return this;
}
const printable = stringifyWithDepthLimit(this._printableFields, Math.max(4, depth));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return `${this.constructor.lc_name()} ${printable}`;
}
}
exports.BaseMessage = BaseMessage;
function isOpenAIToolCallArray(value) {
return (Array.isArray(value) &&
value.every((v) => typeof v.index === "number"));
}
exports.isOpenAIToolCallArray = isOpenAIToolCallArray;
function _mergeDicts(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
left,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
right
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
const merged = { ...left };
for (const [key, value] of Object.entries(right)) {
if (merged[key] == null) {
merged[key] = value;
}
else if (value == null) {
continue;
}
else if (typeof merged[key] !== typeof value ||
Array.isArray(merged[key]) !== Array.isArray(value)) {
throw new Error(`field[${key}] already exists in the message chunk, but with a different type.`);
}
else if (typeof merged[key] === "string") {
if (key === "type") {
// Do not merge 'type' fields
continue;
}
merged[key] += value;
}
else if (typeof merged[key] === "object" && !Array.isArray(merged[key])) {
merged[key] = _mergeDicts(merged[key], value);
}
else if (Array.isArray(merged[key])) {
merged[key] = _mergeLists(merged[key], value);
}
else if (merged[key] === value) {
continue;
}
else {
console.warn(`field[${key}] already exists in this message chunk and value has unsupported type.`);
}
}
return merged;
}
exports._mergeDicts = _mergeDicts;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function _mergeLists(left, right) {
if (left === undefined && right === undefined) {
return undefined;
}
else if (left === undefined || right === undefined) {
return left || right;
}
else {
const merged = [...left];
for (const item of right) {
if (typeof item === "object" &&
"index" in item &&
typeof item.index === "number") {
const toMerge = merged.findIndex((leftItem) => leftItem.index === item.index);
if (toMerge !== -1) {
merged[toMerge] = _mergeDicts(merged[toMerge], item);
}
else {
merged.push(item);
}
}
else if (typeof item === "object" &&
"text" in item &&
item.text === "") {
// No-op - skip empty text blocks
continue;
}
else {
merged.push(item);
}
}
return merged;
}
}
exports._mergeLists = _mergeLists;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function _mergeObj(left, right) {
if (!left && !right) {
throw new Error("Cannot merge two undefined objects.");
}
if (!left || !right) {
return left || right;
}
else if (typeof left !== typeof right) {
throw new Error(`Cannot merge objects of different types.\nLeft ${typeof left}\nRight ${typeof right}`);
}
else if (typeof left === "string" && typeof right === "string") {
return (left + right);
}
else if (Array.isArray(left) && Array.isArray(right)) {
return _mergeLists(left, right);
}
else if (typeof left === "object" && typeof right === "object") {
return _mergeDicts(left, right);
}
else if (left === right) {
return left;
}
else {
throw new Error(`Can not merge objects of different types.\nLeft ${left}\nRight ${right}`);
}
}
exports._mergeObj = _mergeObj;
/**
* Represents a chunk of a message, which can be concatenated with other
* message chunks. It includes a method `_merge_kwargs_dict()` for merging
* additional keyword arguments from another `BaseMessageChunk` into this
* one. It also overrides the `__add__()` method to support concatenation
* of `BaseMessageChunk` instances.
*/
class BaseMessageChunk extends BaseMessage {
}
exports.BaseMessageChunk = BaseMessageChunk;
function _isMessageFieldWithRole(x) {
return typeof x.role === "string";
}
exports._isMessageFieldWithRole = _isMessageFieldWithRole;
function isBaseMessage(messageLike) {
return typeof messageLike?._getType === "function";
}
exports.isBaseMessage = isBaseMessage;
function isBaseMessageChunk(messageLike) {
return (isBaseMessage(messageLike) &&
typeof messageLike.concat === "function");
}
exports.isBaseMessageChunk = isBaseMessageChunk;