utils.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import { addLangChainErrorFields } from "../errors/index.js";
  2. import { _isToolCall } from "../tools/utils.js";
  3. import { AIMessage, AIMessageChunk } from "./ai.js";
  4. import { isBaseMessage, _isMessageFieldWithRole, } from "./base.js";
  5. import { ChatMessage, ChatMessageChunk, } from "./chat.js";
  6. import { FunctionMessage, FunctionMessageChunk, } from "./function.js";
  7. import { HumanMessage, HumanMessageChunk } from "./human.js";
  8. import { SystemMessage, SystemMessageChunk } from "./system.js";
  9. import { ToolMessage, } from "./tool.js";
  10. function _coerceToolCall(toolCall) {
  11. if (_isToolCall(toolCall)) {
  12. return toolCall;
  13. }
  14. else if (typeof toolCall.id === "string" &&
  15. toolCall.type === "function" &&
  16. typeof toolCall.function === "object" &&
  17. toolCall.function !== null &&
  18. "arguments" in toolCall.function &&
  19. typeof toolCall.function.arguments === "string" &&
  20. "name" in toolCall.function &&
  21. typeof toolCall.function.name === "string") {
  22. // Handle OpenAI tool call format
  23. return {
  24. id: toolCall.id,
  25. args: JSON.parse(toolCall.function.arguments),
  26. name: toolCall.function.name,
  27. type: "tool_call",
  28. };
  29. }
  30. else {
  31. // TODO: Throw an error?
  32. return toolCall;
  33. }
  34. }
  35. function isSerializedConstructor(x) {
  36. return (typeof x === "object" &&
  37. x != null &&
  38. x.lc === 1 &&
  39. Array.isArray(x.id) &&
  40. x.kwargs != null &&
  41. typeof x.kwargs === "object");
  42. }
  43. function _constructMessageFromParams(params) {
  44. let type;
  45. let rest;
  46. // Support serialized messages
  47. if (isSerializedConstructor(params)) {
  48. const className = params.id.at(-1);
  49. if (className === "HumanMessage" || className === "HumanMessageChunk") {
  50. type = "user";
  51. }
  52. else if (className === "AIMessage" || className === "AIMessageChunk") {
  53. type = "assistant";
  54. }
  55. else if (className === "SystemMessage" ||
  56. className === "SystemMessageChunk") {
  57. type = "system";
  58. }
  59. else if (className === "FunctionMessage" ||
  60. className === "FunctionMessageChunk") {
  61. type = "function";
  62. }
  63. else if (className === "ToolMessage" ||
  64. className === "ToolMessageChunk") {
  65. type = "tool";
  66. }
  67. else {
  68. type = "unknown";
  69. }
  70. rest = params.kwargs;
  71. }
  72. else {
  73. const { type: extractedType, ...otherParams } = params;
  74. type = extractedType;
  75. rest = otherParams;
  76. }
  77. if (type === "human" || type === "user") {
  78. return new HumanMessage(rest);
  79. }
  80. else if (type === "ai" || type === "assistant") {
  81. const { tool_calls: rawToolCalls, ...other } = rest;
  82. if (!Array.isArray(rawToolCalls)) {
  83. return new AIMessage(rest);
  84. }
  85. const tool_calls = rawToolCalls.map(_coerceToolCall);
  86. return new AIMessage({ ...other, tool_calls });
  87. }
  88. else if (type === "system") {
  89. return new SystemMessage(rest);
  90. }
  91. else if (type === "developer") {
  92. return new SystemMessage({
  93. ...rest,
  94. additional_kwargs: {
  95. ...rest.additional_kwargs,
  96. __openai_role__: "developer",
  97. },
  98. });
  99. }
  100. else if (type === "tool" && "tool_call_id" in rest) {
  101. return new ToolMessage({
  102. ...rest,
  103. content: rest.content,
  104. tool_call_id: rest.tool_call_id,
  105. name: rest.name,
  106. });
  107. }
  108. else {
  109. const error = addLangChainErrorFields(new Error(`Unable to coerce message from array: only human, AI, system, developer, or tool message coercion is currently supported.\n\nReceived: ${JSON.stringify(params, null, 2)}`), "MESSAGE_COERCION_FAILURE");
  110. throw error;
  111. }
  112. }
  113. export function coerceMessageLikeToMessage(messageLike) {
  114. if (typeof messageLike === "string") {
  115. return new HumanMessage(messageLike);
  116. }
  117. else if (isBaseMessage(messageLike)) {
  118. return messageLike;
  119. }
  120. if (Array.isArray(messageLike)) {
  121. const [type, content] = messageLike;
  122. return _constructMessageFromParams({ type, content });
  123. }
  124. else if (_isMessageFieldWithRole(messageLike)) {
  125. const { role: type, ...rest } = messageLike;
  126. return _constructMessageFromParams({ ...rest, type });
  127. }
  128. else {
  129. return _constructMessageFromParams(messageLike);
  130. }
  131. }
  132. /**
  133. * This function is used by memory classes to get a string representation
  134. * of the chat message history, based on the message content and role.
  135. */
  136. export function getBufferString(messages, humanPrefix = "Human", aiPrefix = "AI") {
  137. const string_messages = [];
  138. for (const m of messages) {
  139. let role;
  140. if (m._getType() === "human") {
  141. role = humanPrefix;
  142. }
  143. else if (m._getType() === "ai") {
  144. role = aiPrefix;
  145. }
  146. else if (m._getType() === "system") {
  147. role = "System";
  148. }
  149. else if (m._getType() === "function") {
  150. role = "Function";
  151. }
  152. else if (m._getType() === "tool") {
  153. role = "Tool";
  154. }
  155. else if (m._getType() === "generic") {
  156. role = m.role;
  157. }
  158. else {
  159. throw new Error(`Got unsupported message type: ${m._getType()}`);
  160. }
  161. const nameStr = m.name ? `${m.name}, ` : "";
  162. const readableContent = typeof m.content === "string"
  163. ? m.content
  164. : JSON.stringify(m.content, null, 2);
  165. string_messages.push(`${role}: ${nameStr}${readableContent}`);
  166. }
  167. return string_messages.join("\n");
  168. }
  169. /**
  170. * Maps messages from an older format (V1) to the current `StoredMessage`
  171. * format. If the message is already in the `StoredMessage` format, it is
  172. * returned as is. Otherwise, it transforms the V1 message into a
  173. * `StoredMessage`. This function is important for maintaining
  174. * compatibility with older message formats.
  175. */
  176. function mapV1MessageToStoredMessage(message) {
  177. // TODO: Remove this mapper when we deprecate the old message format.
  178. if (message.data !== undefined) {
  179. return message;
  180. }
  181. else {
  182. const v1Message = message;
  183. return {
  184. type: v1Message.type,
  185. data: {
  186. content: v1Message.text,
  187. role: v1Message.role,
  188. name: undefined,
  189. tool_call_id: undefined,
  190. },
  191. };
  192. }
  193. }
  194. export function mapStoredMessageToChatMessage(message) {
  195. const storedMessage = mapV1MessageToStoredMessage(message);
  196. switch (storedMessage.type) {
  197. case "human":
  198. return new HumanMessage(storedMessage.data);
  199. case "ai":
  200. return new AIMessage(storedMessage.data);
  201. case "system":
  202. return new SystemMessage(storedMessage.data);
  203. case "function":
  204. if (storedMessage.data.name === undefined) {
  205. throw new Error("Name must be defined for function messages");
  206. }
  207. return new FunctionMessage(storedMessage.data);
  208. case "tool":
  209. if (storedMessage.data.tool_call_id === undefined) {
  210. throw new Error("Tool call ID must be defined for tool messages");
  211. }
  212. return new ToolMessage(storedMessage.data);
  213. case "generic": {
  214. if (storedMessage.data.role === undefined) {
  215. throw new Error("Role must be defined for chat messages");
  216. }
  217. return new ChatMessage(storedMessage.data);
  218. }
  219. default:
  220. throw new Error(`Got unexpected type: ${storedMessage.type}`);
  221. }
  222. }
  223. /**
  224. * Transforms an array of `StoredMessage` instances into an array of
  225. * `BaseMessage` instances. It uses the `mapV1MessageToStoredMessage`
  226. * function to ensure all messages are in the `StoredMessage` format, then
  227. * creates new instances of the appropriate `BaseMessage` subclass based
  228. * on the type of each message. This function is used to prepare stored
  229. * messages for use in a chat context.
  230. */
  231. export function mapStoredMessagesToChatMessages(messages) {
  232. return messages.map(mapStoredMessageToChatMessage);
  233. }
  234. /**
  235. * Transforms an array of `BaseMessage` instances into an array of
  236. * `StoredMessage` instances. It does this by calling the `toDict` method
  237. * on each `BaseMessage`, which returns a `StoredMessage`. This function
  238. * is used to prepare chat messages for storage.
  239. */
  240. export function mapChatMessagesToStoredMessages(messages) {
  241. return messages.map((message) => message.toDict());
  242. }
  243. export function convertToChunk(message) {
  244. const type = message._getType();
  245. if (type === "human") {
  246. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  247. return new HumanMessageChunk({ ...message });
  248. }
  249. else if (type === "ai") {
  250. let aiChunkFields = {
  251. ...message,
  252. };
  253. if ("tool_calls" in aiChunkFields) {
  254. aiChunkFields = {
  255. ...aiChunkFields,
  256. tool_call_chunks: aiChunkFields.tool_calls?.map((tc) => ({
  257. ...tc,
  258. type: "tool_call_chunk",
  259. index: undefined,
  260. args: JSON.stringify(tc.args),
  261. })),
  262. };
  263. }
  264. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  265. return new AIMessageChunk({ ...aiChunkFields });
  266. }
  267. else if (type === "system") {
  268. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  269. return new SystemMessageChunk({ ...message });
  270. }
  271. else if (type === "function") {
  272. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  273. return new FunctionMessageChunk({ ...message });
  274. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  275. }
  276. else if (ChatMessage.isInstance(message)) {
  277. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  278. return new ChatMessageChunk({ ...message });
  279. }
  280. else {
  281. throw new Error("Unknown message type.");
  282. }
  283. }