base.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import { Serializable } from "../load/serializable.js";
  2. import { isDataContentBlock, } from "./content_blocks.js";
  3. export function mergeContent(firstContent, secondContent) {
  4. // If first content is a string
  5. if (typeof firstContent === "string") {
  6. if (firstContent === "") {
  7. return secondContent;
  8. }
  9. if (typeof secondContent === "string") {
  10. return firstContent + secondContent;
  11. }
  12. else if (Array.isArray(secondContent) &&
  13. secondContent.some((c) => isDataContentBlock(c))) {
  14. return [
  15. {
  16. type: "text",
  17. source_type: "text",
  18. text: firstContent,
  19. },
  20. ...secondContent,
  21. ];
  22. }
  23. else {
  24. return [{ type: "text", text: firstContent }, ...secondContent];
  25. }
  26. // If both are arrays
  27. }
  28. else if (Array.isArray(secondContent)) {
  29. return (_mergeLists(firstContent, secondContent) ?? [
  30. ...firstContent,
  31. ...secondContent,
  32. ]);
  33. }
  34. else {
  35. if (secondContent === "") {
  36. return firstContent;
  37. }
  38. else if (Array.isArray(firstContent) &&
  39. firstContent.some((c) => isDataContentBlock(c))) {
  40. return [
  41. ...firstContent,
  42. {
  43. type: "file",
  44. source_type: "text",
  45. text: secondContent,
  46. },
  47. ];
  48. }
  49. else {
  50. return [...firstContent, { type: "text", text: secondContent }];
  51. }
  52. }
  53. }
  54. /**
  55. * 'Merge' two statuses. If either value passed is 'error', it will return 'error'. Else
  56. * it will return 'success'.
  57. *
  58. * @param {"success" | "error" | undefined} left The existing value to 'merge' with the new value.
  59. * @param {"success" | "error" | undefined} right The new value to 'merge' with the existing value
  60. * @returns {"success" | "error"} The 'merged' value.
  61. */
  62. export function _mergeStatus(left, right) {
  63. if (left === "error" || right === "error") {
  64. return "error";
  65. }
  66. return "success";
  67. }
  68. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  69. function stringifyWithDepthLimit(obj, depthLimit) {
  70. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  71. function helper(obj, currentDepth) {
  72. if (typeof obj !== "object" || obj === null || obj === undefined) {
  73. return obj;
  74. }
  75. if (currentDepth >= depthLimit) {
  76. if (Array.isArray(obj)) {
  77. return "[Array]";
  78. }
  79. return "[Object]";
  80. }
  81. if (Array.isArray(obj)) {
  82. return obj.map((item) => helper(item, currentDepth + 1));
  83. }
  84. const result = {};
  85. for (const key of Object.keys(obj)) {
  86. result[key] = helper(obj[key], currentDepth + 1);
  87. }
  88. return result;
  89. }
  90. return JSON.stringify(helper(obj, 0), null, 2);
  91. }
  92. /**
  93. * Base class for all types of messages in a conversation. It includes
  94. * properties like `content`, `name`, and `additional_kwargs`. It also
  95. * includes methods like `toDict()` and `_getType()`.
  96. */
  97. export class BaseMessage extends Serializable {
  98. get lc_aliases() {
  99. // exclude snake case conversion to pascal case
  100. return {
  101. additional_kwargs: "additional_kwargs",
  102. response_metadata: "response_metadata",
  103. };
  104. }
  105. /**
  106. * Get text content of the message.
  107. */
  108. get text() {
  109. if (typeof this.content === "string") {
  110. return this.content;
  111. }
  112. if (!Array.isArray(this.content))
  113. return "";
  114. return this.content
  115. .map((c) => {
  116. if (typeof c === "string")
  117. return c;
  118. if (c.type === "text")
  119. return c.text;
  120. return "";
  121. })
  122. .join("");
  123. }
  124. /** The type of the message. */
  125. getType() {
  126. return this._getType();
  127. }
  128. constructor(fields,
  129. /** @deprecated */
  130. kwargs) {
  131. if (typeof fields === "string") {
  132. // eslint-disable-next-line no-param-reassign
  133. fields = {
  134. content: fields,
  135. additional_kwargs: kwargs,
  136. response_metadata: {},
  137. };
  138. }
  139. // Make sure the default value for additional_kwargs is passed into super() for serialization
  140. if (!fields.additional_kwargs) {
  141. // eslint-disable-next-line no-param-reassign
  142. fields.additional_kwargs = {};
  143. }
  144. if (!fields.response_metadata) {
  145. // eslint-disable-next-line no-param-reassign
  146. fields.response_metadata = {};
  147. }
  148. super(fields);
  149. Object.defineProperty(this, "lc_namespace", {
  150. enumerable: true,
  151. configurable: true,
  152. writable: true,
  153. value: ["langchain_core", "messages"]
  154. });
  155. Object.defineProperty(this, "lc_serializable", {
  156. enumerable: true,
  157. configurable: true,
  158. writable: true,
  159. value: true
  160. });
  161. /** The content of the message. */
  162. Object.defineProperty(this, "content", {
  163. enumerable: true,
  164. configurable: true,
  165. writable: true,
  166. value: void 0
  167. });
  168. /** The name of the message sender in a multi-user chat. */
  169. Object.defineProperty(this, "name", {
  170. enumerable: true,
  171. configurable: true,
  172. writable: true,
  173. value: void 0
  174. });
  175. /** Additional keyword arguments */
  176. Object.defineProperty(this, "additional_kwargs", {
  177. enumerable: true,
  178. configurable: true,
  179. writable: true,
  180. value: void 0
  181. });
  182. /** Response metadata. For example: response headers, logprobs, token counts. */
  183. Object.defineProperty(this, "response_metadata", {
  184. enumerable: true,
  185. configurable: true,
  186. writable: true,
  187. value: void 0
  188. });
  189. /**
  190. * An optional unique identifier for the message. This should ideally be
  191. * provided by the provider/model which created the message.
  192. */
  193. Object.defineProperty(this, "id", {
  194. enumerable: true,
  195. configurable: true,
  196. writable: true,
  197. value: void 0
  198. });
  199. this.name = fields.name;
  200. this.content = fields.content;
  201. this.additional_kwargs = fields.additional_kwargs;
  202. this.response_metadata = fields.response_metadata;
  203. this.id = fields.id;
  204. }
  205. toDict() {
  206. return {
  207. type: this._getType(),
  208. data: this.toJSON()
  209. .kwargs,
  210. };
  211. }
  212. static lc_name() {
  213. return "BaseMessage";
  214. }
  215. // Can't be protected for silly reasons
  216. get _printableFields() {
  217. return {
  218. id: this.id,
  219. content: this.content,
  220. name: this.name,
  221. additional_kwargs: this.additional_kwargs,
  222. response_metadata: this.response_metadata,
  223. };
  224. }
  225. // this private method is used to update the ID for the runtime
  226. // value as well as in lc_kwargs for serialisation
  227. _updateId(value) {
  228. this.id = value;
  229. // lc_attributes wouldn't work here, because jest compares the
  230. // whole object
  231. this.lc_kwargs.id = value;
  232. }
  233. get [Symbol.toStringTag]() {
  234. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  235. return this.constructor.lc_name();
  236. }
  237. // Override the default behavior of console.log
  238. [Symbol.for("nodejs.util.inspect.custom")](depth) {
  239. if (depth === null) {
  240. return this;
  241. }
  242. const printable = stringifyWithDepthLimit(this._printableFields, Math.max(4, depth));
  243. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  244. return `${this.constructor.lc_name()} ${printable}`;
  245. }
  246. }
  247. export function isOpenAIToolCallArray(value) {
  248. return (Array.isArray(value) &&
  249. value.every((v) => typeof v.index === "number"));
  250. }
  251. export function _mergeDicts(
  252. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  253. left,
  254. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  255. right
  256. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  257. ) {
  258. const merged = { ...left };
  259. for (const [key, value] of Object.entries(right)) {
  260. if (merged[key] == null) {
  261. merged[key] = value;
  262. }
  263. else if (value == null) {
  264. continue;
  265. }
  266. else if (typeof merged[key] !== typeof value ||
  267. Array.isArray(merged[key]) !== Array.isArray(value)) {
  268. throw new Error(`field[${key}] already exists in the message chunk, but with a different type.`);
  269. }
  270. else if (typeof merged[key] === "string") {
  271. if (key === "type") {
  272. // Do not merge 'type' fields
  273. continue;
  274. }
  275. merged[key] += value;
  276. }
  277. else if (typeof merged[key] === "object" && !Array.isArray(merged[key])) {
  278. merged[key] = _mergeDicts(merged[key], value);
  279. }
  280. else if (Array.isArray(merged[key])) {
  281. merged[key] = _mergeLists(merged[key], value);
  282. }
  283. else if (merged[key] === value) {
  284. continue;
  285. }
  286. else {
  287. console.warn(`field[${key}] already exists in this message chunk and value has unsupported type.`);
  288. }
  289. }
  290. return merged;
  291. }
  292. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  293. export function _mergeLists(left, right) {
  294. if (left === undefined && right === undefined) {
  295. return undefined;
  296. }
  297. else if (left === undefined || right === undefined) {
  298. return left || right;
  299. }
  300. else {
  301. const merged = [...left];
  302. for (const item of right) {
  303. if (typeof item === "object" &&
  304. "index" in item &&
  305. typeof item.index === "number") {
  306. const toMerge = merged.findIndex((leftItem) => leftItem.index === item.index);
  307. if (toMerge !== -1) {
  308. merged[toMerge] = _mergeDicts(merged[toMerge], item);
  309. }
  310. else {
  311. merged.push(item);
  312. }
  313. }
  314. else if (typeof item === "object" &&
  315. "text" in item &&
  316. item.text === "") {
  317. // No-op - skip empty text blocks
  318. continue;
  319. }
  320. else {
  321. merged.push(item);
  322. }
  323. }
  324. return merged;
  325. }
  326. }
  327. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  328. export function _mergeObj(left, right) {
  329. if (!left && !right) {
  330. throw new Error("Cannot merge two undefined objects.");
  331. }
  332. if (!left || !right) {
  333. return left || right;
  334. }
  335. else if (typeof left !== typeof right) {
  336. throw new Error(`Cannot merge objects of different types.\nLeft ${typeof left}\nRight ${typeof right}`);
  337. }
  338. else if (typeof left === "string" && typeof right === "string") {
  339. return (left + right);
  340. }
  341. else if (Array.isArray(left) && Array.isArray(right)) {
  342. return _mergeLists(left, right);
  343. }
  344. else if (typeof left === "object" && typeof right === "object") {
  345. return _mergeDicts(left, right);
  346. }
  347. else if (left === right) {
  348. return left;
  349. }
  350. else {
  351. throw new Error(`Can not merge objects of different types.\nLeft ${left}\nRight ${right}`);
  352. }
  353. }
  354. /**
  355. * Represents a chunk of a message, which can be concatenated with other
  356. * message chunks. It includes a method `_merge_kwargs_dict()` for merging
  357. * additional keyword arguments from another `BaseMessageChunk` into this
  358. * one. It also overrides the `__add__()` method to support concatenation
  359. * of `BaseMessageChunk` instances.
  360. */
  361. export class BaseMessageChunk extends BaseMessage {
  362. }
  363. export function _isMessageFieldWithRole(x) {
  364. return typeof x.role === "string";
  365. }
  366. export function isBaseMessage(messageLike) {
  367. return typeof messageLike?._getType === "function";
  368. }
  369. export function isBaseMessageChunk(messageLike) {
  370. return (isBaseMessage(messageLike) &&
  371. typeof messageLike.concat === "function");
  372. }