Skip to content

Commit 7ef5989

Browse files
committed
Address PR comments
1 parent b867db2 commit 7ef5989

File tree

4 files changed

+131
-123
lines changed

4 files changed

+131
-123
lines changed

apps/opik-frontend/src/components/pages/CompareOptimizationsPage/BestPrompt.tsx

Lines changed: 10 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import { ArrowRight, Split } from "lucide-react";
44
import isUndefined from "lodash/isUndefined";
55
import isObject from "lodash/isObject";
66
import isArray from "lodash/isArray";
7-
import isString from "lodash/isString";
87
import get from "lodash/get";
9-
import first from "lodash/first";
108

119
import { OPTIMIZATION_PROMPT_KEY } from "@/constants/experiments";
1210
import useAppStore from "@/store/AppStore";
@@ -26,6 +24,12 @@ import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper";
2624
import { LLM_MESSAGE_ROLE_NAME_MAP } from "@/constants/llm";
2725
import MarkdownPreview from "@/components/shared/MarkdownPreview/MarkdownPreview";
2826
import { cn } from "@/lib/utils";
27+
import {
28+
extractOpenAIMessages,
29+
formatMessagesAsText,
30+
extractMessageContent,
31+
OpenAIMessage,
32+
} from "@/lib/prompt";
2933
import {
3034
Dialog,
3135
DialogContent,
@@ -38,101 +42,7 @@ type BestPromptProps = {
3842
optimization: Optimization;
3943
experiment: Experiment;
4044
scoreMap: Record<string, { score: number; percentage?: number }>;
41-
experiments: Experiment[];
42-
};
43-
44-
type OpenAIMessage = {
45-
role: string;
46-
content:
47-
| string
48-
| Array<{ type: string; text?: string; [key: string]: unknown }>;
49-
};
50-
51-
/**
52-
* Extracts text content from OpenAI message format.
53-
* Handles both string content and array content (extracts text from {type: "text", text: "..."} items).
54-
*/
55-
const extractMessageContent = (
56-
content:
57-
| string
58-
| Array<{ type: string; text?: string; [key: string]: unknown }>
59-
| unknown,
60-
): string => {
61-
if (isString(content)) {
62-
return content;
63-
}
64-
65-
if (isArray(content)) {
66-
const textParts: string[] = [];
67-
for (const item of content) {
68-
if (
69-
isObject(item) &&
70-
"type" in item &&
71-
item.type === "text" &&
72-
"text" in item &&
73-
isString(item.text)
74-
) {
75-
textParts.push(item.text);
76-
}
77-
}
78-
return textParts.join("\n");
79-
}
80-
81-
return "";
82-
};
83-
84-
/**
85-
* Validates if an array contains valid OpenAI message objects.
86-
*/
87-
const isValidOpenAIMessages = (messages: unknown[]): boolean => {
88-
return messages.every(
89-
(msg: unknown) =>
90-
isObject(msg) &&
91-
"role" in msg &&
92-
isString(msg.role) &&
93-
("content" in msg || "text" in msg),
94-
);
95-
};
96-
97-
/**
98-
* Extracts OpenAI messages from various data formats.
99-
* Handles both array format and object with messages property.
100-
*/
101-
const extractOpenAIMessages = (data: unknown): OpenAIMessage[] | null => {
102-
// Check if it's an array of messages (OpenAI format)
103-
if (isArray(data)) {
104-
if (isValidOpenAIMessages(data)) {
105-
return data as OpenAIMessage[];
106-
}
107-
}
108-
109-
// Check if it's an object with a messages array
110-
if (isObject(data) && "messages" in data) {
111-
const promptObj = data as { messages?: unknown };
112-
if (isArray(promptObj.messages)) {
113-
if (isValidOpenAIMessages(promptObj.messages)) {
114-
return promptObj.messages as OpenAIMessage[];
115-
}
116-
}
117-
}
118-
119-
return null;
120-
};
121-
122-
/**
123-
* Formats OpenAI messages as readable text.
124-
*/
125-
const formatMessagesAsText = (messages: OpenAIMessage[]): string => {
126-
return messages
127-
.map((msg) => {
128-
const roleName =
129-
LLM_MESSAGE_ROLE_NAME_MAP[
130-
msg.role as keyof typeof LLM_MESSAGE_ROLE_NAME_MAP
131-
] || msg.role;
132-
const content = extractMessageContent(msg.content);
133-
return `${roleName}: ${content}`;
134-
})
135-
.join("\n\n");
45+
baselineExperiment?: Experiment | null;
13646
};
13747

13848
/**
@@ -166,7 +76,7 @@ const BestPrompt: React.FC<BestPromptProps> = ({
16676
optimization,
16777
experiment,
16878
scoreMap,
169-
experiments,
79+
baselineExperiment,
17080
}) => {
17181
const workspaceName = useAppStore((state) => state.activeWorkspaceName);
17282
const [diffOpen, setDiffOpen] = useState(false);
@@ -210,14 +120,6 @@ const BestPrompt: React.FC<BestPromptProps> = ({
210120
: toString(promptData);
211121
}, [promptData, messages]);
212122

213-
const baselineExperiment = useMemo(() => {
214-
if (!experiments || experiments.length === 0) return null;
215-
const sorted = experiments
216-
.slice()
217-
.sort((e1, e2) => e1.created_at.localeCompare(e2.created_at));
218-
return first(sorted);
219-
}, [experiments]);
220-
221123
const baselinePrompt = useMemo(() => {
222124
if (!baselineExperiment) return null;
223125
const val = get(
@@ -264,8 +166,8 @@ const BestPrompt: React.FC<BestPromptProps> = ({
264166
<div
265167
className={cn(
266168
"comet-body-s-accented",
267-
percentage > 0 && "text-green-600 dark:text-green-500",
268-
percentage < 0 && "text-red-600 dark:text-red-500",
169+
percentage > 0 && "text-success",
170+
percentage < 0 && "text-destructive",
269171
percentage === 0 && "text-muted-slate",
270172
)}
271173
>

apps/opik-frontend/src/components/pages/CompareOptimizationsPage/CompareOptimizationsPage.tsx

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,12 @@ const CompareOptimizationsPage: React.FC = () => {
208208
[experiments, search],
209209
);
210210

211-
const { scoreMap, bestExperiment } = useMemo(() => {
211+
const { scoreMap, bestExperiment, baselineExperiment } = useMemo(() => {
212212
const retVal: {
213213
scoreMap: Record<string, { score: number; percentage?: number }>;
214214
baseScore: number;
215215
bestExperiment?: Experiment;
216+
baselineExperiment?: Experiment;
216217
} = {
217218
scoreMap: {},
218219
baseScore: 0,
@@ -223,6 +224,8 @@ const CompareOptimizationsPage: React.FC = () => {
223224
.slice()
224225
.sort((e1, e2) => e1.created_at.localeCompare(e2.created_at));
225226

227+
retVal.baselineExperiment = sortedRows[0];
228+
226229
if (
227230
!optimization?.objective_name ||
228231
!experiments.length ||
@@ -397,19 +400,6 @@ const CompareOptimizationsPage: React.FC = () => {
397400

398401
return (
399402
<>
400-
<style>{`
401-
.compare-optimizations-table thead[data-sticky-vertical] {
402-
top: 0 !important;
403-
}
404-
405-
.compare-optimizations-table thead:before {
406-
height: 0px !important;
407-
}
408-
409-
.compare-optimizations-table table {
410-
border-bottom: 1px solid hsl(var(--border)) !important;
411-
}
412-
`}</style>
413403
<PageBodyScrollContainer>
414404
<PageBodyStickyContainer
415405
className="pb-4 pt-6"
@@ -510,7 +500,7 @@ const CompareOptimizationsPage: React.FC = () => {
510500
experiment={bestExperiment}
511501
optimization={optimization}
512502
scoreMap={scoreMap}
513-
experiments={experiments}
503+
baselineExperiment={baselineExperiment}
514504
></BestPrompt>
515505
) : null}
516506
</div>

apps/opik-frontend/src/lib/prompt.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import mustache from "mustache";
2+
import isString from "lodash/isString";
3+
import isArray from "lodash/isArray";
4+
import isObject from "lodash/isObject";
5+
6+
import { LLM_MESSAGE_ROLE_NAME_MAP } from "@/constants/llm";
27

38
export const getPromptMustacheTags = (template: string) => {
49
const parsedTemplate = mustache.parse(template);
@@ -19,3 +24,100 @@ export const safelyGetPromptMustacheTags = (template: string) => {
1924
return false;
2025
}
2126
};
27+
28+
export type OpenAIMessage = {
29+
role: string;
30+
content:
31+
| string
32+
| Array<{ type: string; text?: string; [key: string]: unknown }>;
33+
};
34+
35+
/**
36+
* Extracts text content from OpenAI message format.
37+
* Handles both string content and array content (extracts text from {type: "text", text: "..."} items).
38+
*/
39+
export const extractMessageContent = (
40+
content:
41+
| string
42+
| Array<{ type: string; text?: string; [key: string]: unknown }>
43+
| unknown,
44+
): string => {
45+
if (isString(content)) {
46+
return content;
47+
}
48+
49+
if (isArray(content)) {
50+
const textParts: string[] = [];
51+
for (const item of content) {
52+
if (
53+
isObject(item) &&
54+
"type" in item &&
55+
item.type === "text" &&
56+
"text" in item &&
57+
isString(item.text)
58+
) {
59+
textParts.push(item.text);
60+
}
61+
}
62+
return textParts.join("\n");
63+
}
64+
65+
return "";
66+
};
67+
68+
/**
69+
* Validates if an array contains valid OpenAI message objects.
70+
*/
71+
export const isValidOpenAIMessages = (
72+
messages: unknown[],
73+
): messages is OpenAIMessage[] => {
74+
return messages.every(
75+
(msg: unknown) =>
76+
isObject(msg) &&
77+
"role" in msg &&
78+
isString((msg as { role: unknown }).role) &&
79+
"content" in msg,
80+
);
81+
};
82+
83+
/**
84+
* Extracts OpenAI messages from various data formats.
85+
* Handles both array format and object with messages property.
86+
*/
87+
export const extractOpenAIMessages = (
88+
data: unknown,
89+
): OpenAIMessage[] | null => {
90+
// Check if it's an array of messages (OpenAI format)
91+
if (isArray(data) && isValidOpenAIMessages(data)) {
92+
return data;
93+
}
94+
95+
// Check if it's an object with a messages array
96+
if (isObject(data) && "messages" in data) {
97+
const promptObj = data as { messages?: unknown };
98+
if (
99+
isArray(promptObj.messages) &&
100+
isValidOpenAIMessages(promptObj.messages)
101+
) {
102+
return promptObj.messages;
103+
}
104+
}
105+
106+
return null;
107+
};
108+
109+
/**
110+
* Formats OpenAI messages as readable text.
111+
*/
112+
export const formatMessagesAsText = (messages: OpenAIMessage[]): string => {
113+
return messages
114+
.map((msg) => {
115+
const roleName =
116+
LLM_MESSAGE_ROLE_NAME_MAP[
117+
msg.role as keyof typeof LLM_MESSAGE_ROLE_NAME_MAP
118+
] || msg.role;
119+
const content = extractMessageContent(msg.content);
120+
return `${roleName}: ${content}`;
121+
})
122+
.join("\n\n");
123+
};

apps/opik-frontend/src/main.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,20 @@
586586
}
587587
}
588588

589+
.compare-optimizations-table {
590+
thead[data-sticky-vertical] {
591+
top: 0 !important;
592+
}
593+
594+
thead:before {
595+
height: 0 !important;
596+
}
597+
598+
table {
599+
border-bottom: 1px solid hsl(var(--border)) !important;
600+
}
601+
}
602+
589603
.comet-table {
590604
/* resets padding for all nested cells wrappers */
591605
td [data-cell-wrapper="true"] [data-cell-wrapper="true"],

0 commit comments

Comments
 (0)