Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 82 additions & 61 deletions apps/opik-frontend/src/lib/traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
import isString from "lodash/isString";
import { TAG_VARIANTS } from "@/components/ui/tag";
import { ExperimentItem } from "@/types/datasets";
import { TRACE_VISIBILITY_MODE } from "@/types/traces";

const TRACE_VISIBILITY_MODE = {
default: "default_visibility",
};

const MESSAGES_DIVIDER = `\n\n ----------------- \n\n`;
const MESSAGE_HEADER = (type: string) => `---[ ${type.toUpperCase()} MESSAGE ]---\n`;

Check failure on line 17 in apps/opik-frontend/src/lib/traces.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎·`

export const generateTagVariant = (label: string) => {
const hash = md5(label);
Expand Down Expand Up @@ -56,26 +60,35 @@
"messages" in message &&
isArray(message.messages)
) {
const lastMessage = last(message.messages);
if (lastMessage && isObject(lastMessage) && "content" in lastMessage) {
if (isString(lastMessage.content) && lastMessage.content.length > 0) {
return lastMessage.content;
} else if (isArray(lastMessage.content)) {
const lastTextContent = findLast(
lastMessage.content,
(c) => c.type === "text",
);

if (
lastTextContent &&
"text" in lastTextContent &&
isString(lastTextContent.text) &&
lastTextContent.text.length > 0
) {
return lastTextContent.text;
const extractedMessages: string[] = [];

for (const msg of message.messages) {
if (isObject(msg) && "role" in msg && "content" in msg) {
const role = get(msg, "role", "unknown");

if (isString(msg.content) && msg.content.length > 0) {
extractedMessages.push(MESSAGE_HEADER(role) + msg.content);
} else if (isArray(msg.content)) {
const lastTextContent = findLast(
msg.content,
(c) => isObject(c) && "type" in c && c.type === "text",
);

if (
lastTextContent &&
"text" in lastTextContent &&
isString(lastTextContent.text) &&
lastTextContent.text.length > 0
) {
extractedMessages.push(MESSAGE_HEADER(role) + lastTextContent.text);
}
}
}
}

if (extractedMessages.length > 0) {
return extractedMessages.join(MESSAGES_DIVIDER);
}
} else if (
config.type === "output" &&
isObject(message) &&
Expand Down Expand Up @@ -106,18 +119,20 @@
"input" in message &&
isArray(message.input)
) {
const userMessages = message.input.filter(
(m) =>
isObject(m) &&
"role" in m &&
m.role === "user" &&
"content" in m &&
isString(m.content) &&
m.content !== "",
);
const userMessages = message.input
.filter(
(m) =>
isObject(m) &&
"role" in m &&
m.role === "user" &&
"content" in m &&
isString(m.content) &&
m.content !== "",
)
.map((m) => MESSAGE_HEADER(m.role as string) + m.content);

if (userMessages.length > 0) {
return userMessages.map((m) => m.content).join(MESSAGES_DIVIDER);
return userMessages.join(MESSAGES_DIVIDER);
}
} else if (
config.type === "output" &&
Expand All @@ -136,9 +151,9 @@
isArray(m.content),
);

const userMessages = assistantMessageObjects.reduce<string[]>((acc, m) => {
return acc.concat(
m.content
const assistantMessages = assistantMessageObjects.reduce<string[]>(
(acc, m) => {
const textContents = m.content
.filter(
(c: unknown) =>
isObject(c) &&
Expand All @@ -148,12 +163,20 @@
isString(c.text) &&
c.text !== "",
)
.map((c: { text: string }) => c.text),
);
}, []);
.map((c: { text: string }) => c.text);

if (userMessages.length > 0) {
return userMessages.join(MESSAGES_DIVIDER);
if (textContents.length > 0) {
const combinedContent = textContents.join("\n\n");
acc.push(MESSAGE_HEADER("assistant") + combinedContent);
}

return acc;
},
[],
);

if (assistantMessages.length > 0) {
return assistantMessages.join(MESSAGES_DIVIDER);
}
}

Expand Down Expand Up @@ -207,39 +230,34 @@
"messages" in message &&
isArray(message.messages)
) {
// Find the first human message
const humanMessages = message.messages.filter(
(m) =>
isObject(m) &&
"type" in m &&
m.type === "human" &&
"content" in m &&
isString(m.content) &&
m.content !== "",
);
const humanMessages = message.messages
.filter(
(m) =>
isObject(m) &&
"type" in m &&
m.type === "human" &&
"content" in m &&
isString(m.content) &&
m.content !== "",
)
.map((m) => MESSAGE_HEADER(m.type as string) + m.content);

if (humanMessages.length > 0) {
return humanMessages[0].content;
return humanMessages.join(MESSAGES_DIVIDER);
}
} else if (
config.type === "output" &&
isObject(message) &&
"messages" in message &&
isArray(message.messages)
) {
// Get the last AI message, and extract the string output from the various supported formats
const aiMessages = [];
const aiMessages: string[] = [];

// Iterate on all AI messages
for (const m of message.messages) {
if (isObject(m) && "type" in m && m.type === "ai" && "content" in m) {
// The message can either contains a string attribute named `content`
if (isString(m.content)) {
aiMessages.push(m.content);
}
// Or content can be an array with text content. For example when using OpenAI chat model with the Responses API
// https://python.langchain.com/docs/integrations/chat/openai/#responses-api
else if (isArray(m.content)) {
aiMessages.push(MESSAGE_HEADER(m.type) + m.content);
} else if (isArray(m.content)) {
const textItems = m.content.filter(
(c) =>
isObject(c) &&
Expand All @@ -250,16 +268,19 @@
c.text !== "",
);

// Check that there is only one text item
if (textItems.length === 1) {
aiMessages.push(textItems[0].text);
if (textItems.length > 0) {
// FIX 2: Standardized the join separator to "\n\n" for consistency.
const content = textItems
.map((item: { text: string }) => item.text)
.join("\n\n");
aiMessages.push(MESSAGE_HEADER(m.type) + content);
}
}
}
}

if (aiMessages.length > 0) {
return last(aiMessages);
return aiMessages.join(MESSAGES_DIVIDER);
}
}
};
Expand Down Expand Up @@ -465,4 +486,4 @@
prettified: false,
} as PrettifyMessageResponse;
}
};
};

Check failure on line 489 in apps/opik-frontend/src/lib/traces.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎`
Loading