Repository: Helicone/helicone
Pull request #3486: fixed chat parsing to parse arrays with images better
Author: login: chitalian
Created: 2025-03-22T02:26:57Z | Merged: 2025-03-22T02:27:06Z



Diff:
diff --git a/bifrost/packages/llm-mapper/mappers/openai/chat.ts b/bifrost/packages/llm-mapper/mappers/openai/chat.ts
index 551ad9fea4..1a58606b2c 100644
--- a/bifrost/packages/llm-mapper/mappers/openai/chat.ts
+++ b/bifrost/packages/llm-mapper/mappers/openai/chat.ts
@@ -29,7 +29,6 @@ const getRequestText = (requestBody: any): string => {
     const lastMessage = messages.at(-1);
     if (!lastMessage) return "";
 
-    // Handle function calls in request messages
     if (lastMessage.function_call || lastMessage.tool_calls) {
       return formatStreamingToolCalls(
         lastMessage.tool_calls || [lastMessage.function_call]
@@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
+  }
+
+  return {
+    role: msg.role,
+    _type: "image",
+    content: msg.content.text || "",
+    image_url: msg.content.image_url.url,
+  };
+};
+
+const processTextMessage = (msg: any): Message => {
+  return {
+    content: getFormattedMessageContent(msg.content) || "",
+    role: msg.role || "user",
+    _type: getContentType(msg as any),
+  };
+};
+
+const openAIMessageToHeliconeMessage = (msg: any): Message => {
+  if (msg.function_call || msg.tool_calls) {
+    return processFunctionCall(msg);
+  }
+
+  if (msg.role === "tool" || msg.role === "function") {
+    return processToolResponse(msg);
+  }
+
+  if (Array.isArray(msg.content)) {
+    return processArrayContent(msg);
+  }
+
+  if (msg.content?.type === "image_url") {
+    return processSingleImage(msg);
+  }
+
+  return processTextMessage(msg);
+};
+
+export const getRequestMessages = (request: any): Message[] => {
+  return request.messages?.map(openAIMessageToHeliconeMessage) ?? [];
 };
 
 const getLLMSchemaResponse = (response: any) => {
@@ -220,40 +254,7 @@ const getLLMSchemaResponse = (response: any) => {
         const message = choice?.message;
         if (!message) return null;
 
-        // Handle function calls
-        if (message.function_call || message.tool_calls) {
-          const toolCallMsg = handleToolCalls(message);
-          return {
-            ...toolCallMsg,
-            content: toolCallMsg.content || "",
-            _type: "functionCall",
-          };
-        }
-
-        // Handle function/tool responses
-        if (message.role === "function" || message.role === "tool") {
-          return {
-            content: getFormattedMessageContent(message.content) || "",
-            role: message.role,
-            _type: "function",
-            name: message.name,
-            tool_calls: [
-              {
-                name: message.name,
-                arguments: {
-                  query_result: message.content,
-                },
-              },
-            ],
-          };
-        }
-
-        // Handle regular messages
-        return {
-          content: getFormattedMessageContent(message.content) || "",
-          role: message.role ?? "",
-          _type: getContentType(message),
-        };
+        return openAIMessageToHeliconeMessage(message);
       })
       .filter(Boolean),
     model: response?.model,
diff --git a/packages/__tests__/llm-mapper/chat.test.ts b/packages/__tests__/llm-mapper/chat.test.ts
index 2efdbf911d..1651ac4997 100644
--- a/packages/__tests__/llm-mapper/chat.test.ts
+++ b/packages/__tests__/llm-mapper/chat.test.ts
@@ -139,10 +139,21 @@ describe("OpenAI Chat Mapper", () => {
       expect(result.schema.request?.messages).toBeDefined();
       expect(result.schema.request?.messages).toHaveLength(1);
       expect(result.schema.request?.messages?.[0]).toEqual({
+        _type: "contentArray",
+        contentArray: [
+          {
+            _type: "message",
+            content: "[REDACTED TEXT]",
+            role: "user",
+          },
+          {
+            _type: "image",
+            content: "",
+            image_url: "https://redacted.amazonaws.com/example-image.jpg",
+            role: "user",
+          },
+        ],
         role: "user",
-        _type: "image",
-        image_url: "https://redacted.amazonaws.com/example-image.jpg",
-        content: "[REDACTED TEXT]",
       } as Message);
 
       // Verify schema structure
diff --git a/packages/llm-mapper/mappers/openai/chat.ts b/packages/llm-mapper/mappers/openai/chat.ts
index 551ad9fea4..1a58606b2c 100644
--- a/packages/llm-mapper/mappers/openai/chat.ts
+++ b/packages/llm-mapper/mappers/openai/chat.ts
@@ -29,7 +29,6 @@ const getRequestText = (requestBody: any): string => {
     const lastMessage = messages.at(-1);
     if (!lastMessage) return "";
 
-    // Handle function calls in request messages
     if (lastMessage.function_call || lastMessage.tool_calls) {
       return formatStreamingToolCalls(
         lastMessage.tool_calls || [lastMessage.function_call]
@@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
+  }
+
+  return {
+    role: msg.role,
+    _type: "image",
+    content: msg.content.text || "",
+    image_url: msg.content.image_url.url,
+  };
+};
+
+const processTextMessage = (msg: any): Message => {
+  return {
+    content: getFormattedMessageContent(msg.content) || "",
+    role: msg.role || "user",
+    _type: getContentType(msg as any),
+  };
+};
+
+const openAIMessageToHeliconeMessage = (msg: any): Message => {
+  if (msg.function_call || msg.tool_calls) {
+    return processFunctionCall(msg);
+  }
+
+  if (msg.role === "tool" || msg.role === "function") {
+    return processToolResponse(msg);
+  }
+
+  if (Array.isArray(msg.content)) {
+    return processArrayContent(msg);
+  }
+
+  if (msg.content?.type === "image_url") {
+    return processSingleImage(msg);
+  }
+
+  return processTextMessage(msg);
+};
+
+export const getRequestMessages = (request: any): Message[] => {
+  return request.messages?.map(openAIMessageToHeliconeMessage) ?? [];
 };
 
 const getLLMSchemaResponse = (response: any) => {
@@ -220,40 +254,7 @@ const getLLMSchemaResponse = (response: any) => {
         const message = choice?.message;
         if (!message) return null;
 
-        // Handle function calls
-        if (message.function_call || message.tool_calls) {
-          const toolCallMsg = handleToolCalls(message);
-          return {
-            ...toolCallMsg,
-            content: toolCallMsg.content || "",
-            _type: "functionCall",
-          };
-        }
-
-        // Handle function/tool responses
-        if (message.role === "function" || message.role === "tool") {
-          return {
-            content: getFormattedMessageContent(message.content) || "",
-            role: message.role,
-            _type: "function",
-            name: message.name,
-            tool_calls: [
-              {
-                name: message.name,
-                arguments: {
-                  query_result: message.content,
-                },
-              },
-            ],
-          };
-        }
-
-        // Handle regular messages
-        return {
-          content: getFormattedMessageContent(message.content) || "",
-          role: message.role ?? "",
-          _type: getContentType(message),
-        };
+        return openAIMessageToHeliconeMessage(message);
       })
       .filter(Boolean),
     model: response?.model,
diff --git a/valhalla/jawn/src/packages/llm-mapper/mappers/openai/chat.ts b/valhalla/jawn/src/packages/llm-mapper/mappers/openai/chat.ts
index 551ad9fea4..1a58606b2c 100644
--- a/valhalla/jawn/src/packages/llm-mapper/mappers/openai/chat.ts
+++ b/valhalla/jawn/src/packages/llm-mapper/mappers/openai/chat.ts
@@ -29,7 +29,6 @@ const getRequestText = (requestBody: any): string => {
     const lastMessage = messages.at(-1);
     if (!lastMessage) return "";
 
-    // Handle function calls in request messages
     if (lastMessage.function_call || lastMessage.tool_calls) {
       return formatStreamingToolCalls(
         lastMessage.tool_calls || [lastMessage.function_call]
@@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
+  }
+
+  return {
+    role: msg.role,
+    _type: "image",
+    content: msg.content.text || "",
+    image_url: msg.content.image_url.url,
+  };
+};
+
+const processTextMessage = (msg: any): Message => {
+  return {
+    content: getFormattedMessageContent(msg.content) || "",
+    role: msg.role || "user",
+    _type: getContentType(msg as any),
+  };
+};
+
+const openAIMessageToHeliconeMessage = (msg: any): Message => {
+  if (msg.function_call || msg.tool_calls) {
+    return processFunctionCall(msg);
+  }
+
+  if (msg.role === "tool" || msg.role === "function") {
+    return processToolResponse(msg);
+  }
+
+  if (Array.isArray(msg.content)) {
+    return processArrayContent(msg);
+  }
+
+  if (msg.content?.type === "image_url") {
+    return processSingleImage(msg);
+  }
+
+  return processTextMessage(msg);
+};
+
+export const getRequestMessages = (request: any): Message[] => {
+  return request.messages?.map(openAIMessageToHeliconeMessage) ?? [];
 };
 
 const getLLMSchemaResponse = (response: any) => {
@@ -220,40 +254,7 @@ const getLLMSchemaResponse = (response: any) => {
         const message = choice?.message;
         if (!message) return null;
 
-        // Handle function calls
-        if (message.function_call || message.tool_calls) {
-          const toolCallMsg = handleToolCalls(message);
-          return {
-            ...toolCallMsg,
-            content: toolCallMsg.content || "",
-            _type: "functionCall",
-          };
-        }
-
-        // Handle function/tool responses
-        if (message.role === "function" || message.role === "tool") {
-          return {
-            content: getFormattedMessageContent(message.content) || "",
-            role: message.role,
-            _type: "function",
-            name: message.name,
-            tool_calls: [
-              {
-                name: message.name,
-                arguments: {
-                  query_result: message.content,
-                },
-              },
-            ],
-          };
-        }
-
-        // Handle regular messages
-        return {
-          content: getFormattedMessageContent(message.content) || "",
-          role: message.role ?? "",
-          _type: getContentType(message),
-        };
+        return openAIMessageToHeliconeMessage(message);
       })
       .filter(Boolean),
     model: response?.model,
diff --git a/web/packages/llm-mapper/mappers/openai/chat.ts b/web/packages/llm-mapper/mappers/openai/chat.ts
index 551ad9fea4..1a58606b2c 100644
--- a/web/packages/llm-mapper/mappers/openai/chat.ts
+++ b/web/packages/llm-mapper/mappers/openai/chat.ts
@@ -29,7 +29,6 @@ const getRequestText = (requestBody: any): string => {
     const lastMessage = messages.at(-1);
     if (!lastMessage) return "";
 
-    // Handle function calls in request messages
     if (lastMessage.function_call || lastMessage.tool_calls) {
       return formatStreamingToolCalls(
         lastMessage.tool_calls || [lastMessage.function_call]
@@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
+  }
+
+  return {
+    role: msg.role,
+    _type: "image",
+    content: msg.content.text || "",
+    image_url: msg.content.image_url.url,
+  };
+};
+
+const processTextMessage = (msg: any): Message => {
+  return {
+    content: getFormattedMessageContent(msg.content) || "",
+    role: msg.role || "user",
+    _type: getContentType(msg as any),
+  };
+};
+
+const openAIMessageToHeliconeMessage = (msg: any): Message => {
+  if (msg.function_call || msg.tool_calls) {
+    return processFunctionCall(msg);
+  }
+
+  if (msg.role === "tool" || msg.role === "function") {
+    return processToolResponse(msg);
+  }
+
+  if (Array.isArray(msg.content)) {
+    return processArrayContent(msg);
+  }
+
+  if (msg.content?.type === "image_url") {
+    return processSingleImage(msg);
+  }
+
+  return processTextMessage(msg);
+};
+
+export const getRequestMessages = (request: any): Message[] => {
+  return request.messages?.map(openAIMessageToHeliconeMessage) ?? [];
 };
 
 const getLLMSchemaResponse = (response: any) => {
@@ -220,40 +254,7 @@ const getLLMSchemaResponse = (response: any) => {
         const message = choice?.message;
         if (!message) return null;
 
-        // Handle function calls
-        if (message.function_call || message.tool_calls) {
-          const toolCallMsg = handleToolCalls(message);
-          return {
-            ...toolCallMsg,
-            content: toolCallMsg.content || "",
-            _type: "functionCall",
-          };
-        }
-
-        // Handle function/tool responses
-        if (message.role === "function" || message.role === "tool") {
-          return {
-            content: getFormattedMessageContent(message.content) || "",
-            role: message.role,
-            _type: "function",
-            name: message.name,
-            tool_calls: [
-              {
-                name: message.name,
-                arguments: {
-                  query_result: message.content,
-                },
-              },
-            ],
-          };
-        }
-
-        // Handle regular messages
-        return {
-          content: getFormattedMessageContent(message.content) || "",
-          role: message.role ?? "",
-          _type: getContentType(message),
-        };
+        return openAIMessageToHeliconeMessage(message);
       })
       .filter(Boolean),
     model: response?.model,
diff --git a/worker/src/packages/llm-mapper/mappers/openai/chat.ts b/worker/src/packages/llm-mapper/mappers/openai/chat.ts
index 551ad9fea4..1a58606b2c 100644
--- a/worker/src/packages/llm-mapper/mappers/openai/chat.ts
+++ b/worker/src/packages/llm-mapper/mappers/openai/chat.ts
@@ -29,7 +29,6 @@ const getRequestText = (requestBody: any): string => {
     const lastMessage = messages.at(-1);
     if (!lastMessage) return "";
 
-    // Handle function calls in request messages
     if (lastMessage.function_call || lastMessage.tool_calls) {
       return formatStreamingToolCalls(
         lastMessage.tool_calls || [lastMessage.function_call]
@@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
+  }
+
+  return {
+    role: msg.role,
+    _type: "image",
+    content: msg.content.text || "",
+    image_url: msg.content.image_url.url,
+  };
+};
+
+const processTextMessage = (msg: any): Message => {
+  return {
+    content: getFormattedMessageContent(msg.content) || "",
+    role: msg.role || "user",
+    _type: getContentType(msg as any),
+  };
+};
+
+const openAIMessageToHeliconeMessage = (msg: any): Message => {
+  if (msg.function_call || msg.tool_calls) {
+    return processFunctionCall(msg);
+  }
+
+  if (msg.role === "tool" || msg.role === "function") {
+    return processToolResponse(msg);
+  }
+
+  if (Array.isArray(msg.content)) {
+    return processArrayContent(msg);
+  }
+
+  if (msg.content?.type === "image_url") {
+    return processSingleImage(msg);
+  }
+
+  return processTextMessage(msg);
+};
+
+export const getRequestMessages = (request: any): Message[] => {
+  return request.messages?.map(openAIMessageToHeliconeMessage) ?? [];
 };
 
 const getLLMSchemaResponse = (response: any) => {
@@ -220,40 +254,7 @@ const getLLMSchemaResponse = (response: any) => {
         const message = choice?.message;
         if (!message) return null;
 
-        // Handle function calls
-        if (message.function_call || message.tool_calls) {
-          const toolCallMsg = handleToolCalls(message);
-          return {
-            ...toolCallMsg,
-            content: toolCallMsg.content || "",
-            _type: "functionCall",
-          };
-        }
-
-        // Handle function/tool responses
-        if (message.role === "function" || message.role === "tool") {
-          return {
-            content: getFormattedMessageContent(message.content) || "",
-            role: message.role,
-            _type: "function",
-            name: message.name,
-            tool_calls: [
-              {
-                name: message.name,
-                arguments: {
-                  query_result: message.content,
-                },
-              },
-            ],
-          };
-        }
-
-        // Handle regular messages
-        return {
-          content: getFormattedMessageContent(message.content) || "",
-          role: message.role ?? "",
-          _type: getContentType(message),
-        };
+        return openAIMessageToHeliconeMessage(message);
       })
       .filter(Boolean),
     model: response?.model,


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

Refactored message processing logic in chat.ts across multiple packages to improve handling of arrays containing mixed content types (images and text) and standardize message transformation.

- Introduced new `openAIMessageToHeliconeMessage` function as central router for message type detection and processing
- Split large `getRequestMessages` into focused helper functions (`processFunctionCall`, `processToolResponse`, `processArrayContent`, `processSingleImage`)
- Enhanced array content handling to process each item uniformly while maintaining original structure
- Added support for both string and object formats in image URL handling
- Improved test coverage for mixed content scenarios including single images, tool calls, and streaming responses

<sub>6 file(s) reviewed, 6 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=helicone_3486)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T02:28:45Z

Review comments:
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
path: valhalla/jawn/src/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: No null check on msg.content before accessing image_url property. Could throw if content is null.

```suggestion
  if (msg.content && typeof msg.content.image_url === "string") {
    return {
      role: msg.role,
      _type: "image",
      content: msg.content.text || "",
      image_url: msg.content.image_url,
    };
```
created_at: 2025-03-22T02:27:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
path: web/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: No null/undefined check for msg.content before mapping. Could throw runtime error if content is null.

```suggestion
    contentArray: (msg.content || []).map((item: any) => {
      if (item.type === "text") {
        return {
          content: item.text || "",
          role: msg.role || "user",
          _type: "message",
        };
      } else if (item.type === "image_url") {
        return {
          role: msg.role || "user",
          _type: "image",
          content: "",
          image_url: item.image_url.url,
        };
      }
```
created_at: 2025-03-22T02:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
path: web/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: Potential undefined error if item.image_url or item.image_url.url is undefined

```suggestion
          content: "",
          image_url: item.image_url?.url || "",
        };
```
created_at: 2025-03-22T02:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
path: web/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: Missing null check for msg.content.image_url before accessing it as a string

```suggestion
  if (msg.content?.image_url && typeof msg.content.image_url === "string") {
    return {
      role: msg.role,
      _type: "image",
      content: msg.content.text || "",
      image_url: msg.content.image_url,
    };
```
created_at: 2025-03-22T02:28:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
path: worker/src/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: No null check on msg.content before mapping. Could throw if content is undefined.

```suggestion
    contentArray: (msg.content || []).map((item: any) => {
      if (item.type === "text") {
        return {
          content: item.text || "",
          role: msg.role || "user",
          _type: "message",
        };
      } else if (item.type === "image_url") {
        return {
          role: msg.role || "user",
          _type: "image",
          content: "",
          image_url: item.image_url.url,
        };
      }
      return {
        content: JSON.stringify(item),
        role: msg.role || "user",
        _type: "message",
      };
    }),
```
created_at: 2025-03-22T02:28:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,75 +130,110 @@ export const getResponseText = (
   }
 };
 
-export const getRequestMessages = (request: any): Message[] => {
-  return (
-    request.messages?.map((msg: any) => {
-      // Handle function calls first
-      if (msg.function_call || msg.tool_calls) {
-        const toolCallMsg = handleToolCalls(msg);
-        return {
-          ...toolCallMsg,
-          content: toolCallMsg.content || "",
-          _type: "functionCall",
-        };
-      }
+const processFunctionCall = (msg: any): Message => {
+  const toolCallMsg = handleToolCalls(msg);
+  return {
+    ...toolCallMsg,
+    content: toolCallMsg.content || "",
+    _type: "functionCall",
+  };
+};
 
-      // Handle tool responses
-      if (msg.role === "tool" || msg.role === "function") {
-        const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+  const toolResponseMsg = handleToolResponse(msg);
+  return {
+    ...toolResponseMsg,
+    content: toolResponseMsg.content || "",
+    _type: "function",
+    name: msg.name,
+    tool_calls: [
+      {
+        name: msg.name,
+        arguments: {
+          query_result: msg.content,
+        },
+      },
+    ],
+  };
+};
+
+const processArrayContent = (msg: any): Message => {
+  // Process each item in the array uniformly
+  return {
+    contentArray: msg.content.map((item: any) => {
+      if (item.type === "text") {
         return {
-          ...toolResponseMsg,
-          content: toolResponseMsg.content || "",
-          _type: "function",
-          name: msg.name,
-          tool_calls: [
-            {
-              name: msg.name,
-              arguments: {
-                query_result: msg.content,
-              },
-            },
-          ],
+          content: item.text || "",
+          role: msg.role || "user",
+          _type: "message",
         };
-      }
-
-      // Handle array content (e.g. text + image)
-      if (Array.isArray(msg.content)) {
-        const textContent = msg.content.find(
-          (item: any) => item.type === "text"
-        );
-        const imageContent = msg.content.find(
-          (item: any) => item.type === "image_url"
-        );
-
-        if (textContent && imageContent) {
-          return {
-            role: msg.role,
-            _type: "image",
-            content: textContent.text || "",
-            image_url: imageContent.image_url.url,
-          };
-        }
-      }
-
-      // Handle single image content
-      if (msg.content?.type === "image_url") {
+      } else if (item.type === "image_url") {
         return {
-          role: msg.role,
+          role: msg.role || "user",
           _type: "image",
-          content: msg.content.text || "",
-          image_url: msg.content.image_url.url,
+          content: "",
+          image_url: item.image_url.url,
         };
       }
-
-      // Handle regular messages
       return {
-        content: getFormattedMessageContent(msg.content) || "",
+        content: JSON.stringify(item),
         role: msg.role || "user",
-        _type: getContentType(msg as any),
+        _type: "message",
       };
-    }) ?? []
-  );
+    }),
+    _type: "contentArray",
+    role: msg.role,
+  };
+};
+
+const processSingleImage = (msg: any): Message => {
+  if (typeof msg.content.image_url === "string") {
+    return {
+      role: msg.role,
+      _type: "image",
+      content: msg.content.text || "",
+      image_url: msg.content.image_url,
+    };
path: worker/src/packages/llm-mapper/mappers/openai/chat.ts
user: login: greptile-apps[bot]
body: logic: No null check on msg.content.image_url before accessing. Could throw if content or image_url is undefined.

```suggestion
  if (msg.content?.image_url && typeof msg.content.image_url === "string") {
    return {
      role: msg.role,
      _type: "image",
      content: msg.content.text || "",
      image_url: msg.content.image_url,
    };
```
created_at: 2025-03-22T02:28:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-22T02:27:02Z
body: [vc]: #pNXnKCaiDxvfYHKhUsf/CYDLzlgvqgFACR4mpXrjayY=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJoZWxpY29uZSIsInJvb3REaXJlY3RvcnkiOiJ3ZWIiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vaGVsaWNvbmUvaGVsaWNvbmUvM0N3eHlzanlhdXduQnJYeXMzMmhkQm1HUDdjZSIsInByZXZpZXdVcmwiOiJoZWxpY29uZS1naXQtbmVzdGVkLWNvbnRlbnQtcGFyc2luZy1oZWxpY29uZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IlBFTkRJTkciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiaGVsaWNvbmUtZ2l0LW5lc3RlZC1jb250ZW50LXBhcnNpbmctaGVsaWNvbmUudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJoZWxpY29uZS1ldSIsInJvb3REaXJlY3RvcnkiOiJ3ZWIiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vaGVsaWNvbmUvaGVsaWNvbmUtZXUvRlZORkhNNUVwQWNOSDZIOFpWcFN2NVhhWTl1biIsInByZXZpZXdVcmwiOiJoZWxpY29uZS1ldS1naXQtbmVzdGVkLWNvbnRlbnQtcGFyc2luZy1oZWxpY29uZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IlBFTkRJTkciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiaGVsaWNvbmUtZXUtZ2l0LW5lc3RlZC1jb250ZW50LXBhcnNpbmctaGVsaWNvbmUudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJoZWxpY29uZS1iaWZyb3N0Iiwicm9vdERpcmVjdG9yeSI6ImJpZnJvc3QiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vaGVsaWNvbmUvaGVsaWNvbmUtYmlmcm9zdC8zcjhnMzhrQldRTHNvWjhYOGtxc2FzN3QzRTM0IiwicHJldmlld1VybCI6ImhlbGljb25lLWJpZnJvc3QtZ2l0LW5lc3RlZC1jb250ZW50LXBhcnNpbmctaGVsaWNvbmUudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJQRU5ESU5HIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImhlbGljb25lLWJpZnJvc3QtZ2l0LW5lc3RlZC1jb250ZW50LXBhcnNpbmctaGVsaWNvbmUudmVyY2VsLmFwcCJ9fV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **helicone** | 🔄 Building ([Inspect](https://vercel.com/helicone/helicone/3CwxysjyauwnBrXys32hdBmGP7ce)) | [Visit Preview](https://helicone-git-nested-content-parsing-helicone.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/helicone-git-nested-content-parsing-helicone.vercel.app?via=pr-comment-feedback-link) | Mar 22, 2025 2:27am |
| **helicone-bifrost** | 🔄 Building ([Inspect](https://vercel.com/helicone/helicone-bifrost/3r8g38kBWQLsoZ8X8kqsas7t3E34)) | [Visit Preview](https://helicone-bifrost-git-nested-content-parsing-helicone.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/helicone-bifrost-git-nested-content-parsing-helicone.vercel.app?via=pr-comment-feedback-link) | Mar 22, 2025 2:27am |
| **helicone-eu** | 🔄 Building ([Inspect](https://vercel.com/helicone/helicone-eu/FVNFHM5EpAcNH6H8ZVpSv5XaY9un)) | [Visit Preview](https://helicone-eu-git-nested-content-parsing-helicone.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/helicone-eu-git-nested-content-parsing-helicone.vercel.app?via=pr-comment-feedback-link) | Mar 22, 2025 2:27am |




author_association: NONE
user: login: promptless[bot]
created_at: 2025-03-22T02:28:41Z
body: 📝 Documentation updates detected! You can review documentation updates [here](https://app.gopromptless.ai/change-history/03f0607c-067d-43b2-bfa8-499d5ca6f143)
author_association: CONTRIBUTOR
user: login: fumedev[bot]
created_at: 2025-03-22T02:51:26Z
body: ## Summary
- Improved chat parsing to better handle arrays containing images.
- Introduced new helper functions to process function calls, tool responses, and array content.
- Refactored existing message processing logic for clarity and maintainability.
- Updated tests to verify the new structure for messages containing mixed content types (text and images).
author_association: NONE