Skip to content
Closed
Show file tree
Hide file tree
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
1 change: 0 additions & 1 deletion vite-app/dist/assets/index-BIhepl19.css

This file was deleted.

75 changes: 75 additions & 0 deletions vite-app/dist/assets/index-BU2GF__f.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions vite-app/dist/assets/index-BU2GF__f.js.map

Large diffs are not rendered by default.

137 changes: 0 additions & 137 deletions vite-app/dist/assets/index-DaovgarD.js

This file was deleted.

1 change: 0 additions & 1 deletion vite-app/dist/assets/index-DaovgarD.js.map

This file was deleted.

1 change: 1 addition & 0 deletions vite-app/dist/assets/index-DvKW7FQL.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions vite-app/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EP | Log Viewer</title>
<link rel="icon" href="/assets/favicon-BkAAWQga.png" />
<script type="module" crossorigin src="/assets/index-DaovgarD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BIhepl19.css">
<script type="module" crossorigin src="/assets/index-BU2GF__f.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DvKW7FQL.css">
</head>
<body>
<div id="root"></div>
Expand Down
85 changes: 82 additions & 3 deletions vite-app/src/components/EvaluationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
import { ChatInterface } from "./ChatInterface";
import { MetadataSection } from "./MetadataSection";
import { LogsSection } from "./LogsSection";
import { TokenDebugView } from "./TokenDebugView";
import StatusIndicator from "./StatusIndicator";
import { state } from "../App";
import { TableCell, TableRowInteractive } from "./TableContainer";
Expand Down Expand Up @@ -336,12 +337,87 @@ const ToolsSection = observer(
)
);

function buildToolDeclareContent(tools: EvaluationRowType["tools"]): string {
if (!tools?.length) return "";
const blocks = tools
.map((tool) => {
const fn = (tool as any)?.function || {};
const properties = fn.parameters?.properties || {};
const actionEnum = Array.isArray(properties.action?.enum)
? properties.action.enum.map((value: string) => `"${value}"`).join(" | ")
: "string";
return [
`// ${fn.description || "Tool declaration."}`,
`type ${fn.name || "tool"} = (_: {`,
` // ${properties.action?.description || "Tool argument."}`,
` action: ${actionEnum},`,
" [k: string]: never",
"}) => any;",
].join("\n");
})
.join("\n");

return `# Tools\n\n## functions\nnamespace functions {\n${blocks}\n}`;
}

function buildPromptFaithfulMessages(
messages: EvaluationRowType["messages"],
tools: EvaluationRowType["tools"]
): EvaluationRowType["messages"] {
const toolDeclareContent = buildToolDeclareContent(tools);
if (!toolDeclareContent) return messages;
const nextMessages = [...(messages || [])];
const firstSystemIdx = nextMessages.findIndex(
(message) => message?.role === "system"
);
if (firstSystemIdx === -1) {
return [{ role: "system", content: toolDeclareContent } as any, ...nextMessages];
}

const firstSystem = nextMessages[firstSystemIdx] as any;
const existingContent =
typeof firstSystem?.content === "string"
? firstSystem.content
: Array.isArray(firstSystem?.content)
? firstSystem.content
.map((part: any) => {
if (part?.type === "text") return part.text || "";
if (part?.type === "image_url") return "[Image]";
return JSON.stringify(part);
})
.join("")
: firstSystem?.content != null
? JSON.stringify(firstSystem.content)
: "";

nextMessages[firstSystemIdx] = {
...firstSystem,
content: existingContent
? `${toolDeclareContent}\n\n${existingContent}`
: toolDeclareContent,
} as any;
return nextMessages;
}

const ChatInterfaceSection = observer(
({ messages }: { messages: EvaluationRowType["messages"] }) => (
<ChatInterface messages={messages} />
({
messages,
tools,
}: {
messages: EvaluationRowType["messages"];
tools: EvaluationRowType["tools"];
}) => (
<ChatInterface messages={buildPromptFaithfulMessages(messages, tools)} />
)
);

const TokenDebugSection = observer(
({ extra }: { extra: Record<string, any> | undefined }) => {
if (!extra?.token_turn_traces?.length && !extra?.full_episode) return null;
return <TokenDebugView extra={extra} />;
}
);

const ExpandedContent = observer(
({
row,
Expand All @@ -368,9 +444,12 @@ const ExpandedContent = observer(
<div className="flex gap-3 w-fit">
{/* Left Column - Chat Interface */}
<div className="min-w-0">
<ChatInterfaceSection messages={messages} />
<ChatInterfaceSection messages={messages} tools={tools} />
</div>

{/* Token Debug Column */}
<TokenDebugSection extra={(execution_metadata as any)?.extra} />

{/* Middle Column - Logs */}
<LogsSection rolloutId={row.execution_metadata?.rollout_id} inputMetadata={row.input_metadata} />

Expand Down
77 changes: 67 additions & 10 deletions vite-app/src/components/MessageBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,87 @@ export const MessageBubble = ({ message }: { message: Message }) => {
const isTool = message.role === "tool";
const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
const hasFunctionCall = message.function_call;
const hideMessageContent = message.role === "assistant" && hasToolCalls;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve assistant text when tool calls carry real content

The new hideMessageContent condition suppresses all assistant message text whenever tool_calls is present, which drops legitimate assistant narration in responses that include both natural-language content and tool calls. In those cases the transcript becomes incomplete (only tool-call cards remain), so this should be gated on payload-like/empty content rather than every assistant message with tool_calls.

Useful? React with 👍 / 👎.


// Get the message content as a string
const reasoning = (message as any).reasoning_content as string | undefined;
const titleLabel =
message.role === "system" && message.name ? message.name : message.role;
const getMessageContent = () => {
if (typeof message.content === "string") {
return message.content;
} else if (Array.isArray(message.content)) {
return message.content
.map((part) =>
part.type === "text" ? part.text : JSON.stringify(part)
)
.map((part) => {
if (part.type === "text") return part.text;
if (part.type === "image_url") return "[Image]";
return JSON.stringify(part);
})
.join("");
} else {
return JSON.stringify(message.content);
}
};

const messageContent = getMessageContent();
const messageContent = hideMessageContent ? "" : getMessageContent();
const hasMessageContent = messageContent.trim().length > 0;
const isLongMessage = messageContent.length > 200; // Threshold for considering a message "long"
const displayContent =
isLongMessage && !isExpanded
? messageContent.substring(0, 200) + "..."
: messageContent;

const renderContent = () => {
if (hideMessageContent) {
return null;
}
if (typeof message.content === "string") {
return isLongMessage && !isExpanded
? message.content.substring(0, 200) + "..."
: message.content;
} else if (Array.isArray(message.content)) {
let currentLength = 0;
const parts = [];
const limit = 200;

for (let i = 0; i < message.content.length; i++) {
const part = message.content[i];

if (!isExpanded && currentLength >= limit) {
break;
}

if (part.type === "image_url") {
parts.push(
<div key={i} className="my-2">
<img
src={part.image_url.url}
alt="Trace content"
className="max-w-full h-auto rounded border border-gray-200"
style={{ maxHeight: "300px" }}
/>
</div>
);
} else if (part.type === "text") {
const text = part.text;
if (!isExpanded && currentLength + text.length > limit) {
const remaining = limit - currentLength;
if (remaining > 0) {
parts.push(<span key={i}>{text.substring(0, remaining)}...</span>);
}
currentLength += text.length;
break;
} else {
parts.push(<span key={i}>{text}</span>);
currentLength += text.length;
}
} else {
const str = JSON.stringify(part);
parts.push(<span key={i}>{str}</span>);
currentLength += str.length;
}
}
return <div className="flex flex-col">{parts}</div>;
} else {
return JSON.stringify(message.content);
}
};

const handleCopy = async () => {
try {
Expand Down Expand Up @@ -110,10 +167,10 @@ export const MessageBubble = ({ message }: { message: Message }) => {
hasMessageContent ? "pr-8" : ""
}`}
>
{message.role}
{titleLabel}
</div>
<div className="whitespace-pre-wrap break-words overflow-hidden text-xs">
{displayContent}
{renderContent()}
</div>
{isLongMessage && (
<button
Expand Down
Loading