Skip to content

Commit

Permalink
Merge branch 'main' into thiagohora/OPIK-138_add_last_updated_trace_a…
Browse files Browse the repository at this point in the history
…t_field
  • Loading branch information
thiagohora authored Oct 4, 2024
2 parents 62a187e + e702307 commit dcf7e6a
Show file tree
Hide file tree
Showing 16 changed files with 522 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ dataset.insert([
```

:::tip
Opik automatically deduplicates items that are inserted into a dataset when using the Python SDK. This means that you can insert the same item multiple times without duplicating it in the dataset.
:::

Instead of using the `DatasetItem` class, you can also use a dictionary to insert items to a dataset. The dictionary should have the `input` key while the `expected_output` and `metadata` are optional:

```python
Expand All @@ -56,8 +59,6 @@ dataset.insert([
{"input": {"user_question": "What is the capital of France?"}, "expected_output": {"assistant_answer": "Paris"}},
])
```
:::


You can also insert items from a JSONL file:

Expand Down
6 changes: 6 additions & 0 deletions apps/opik-frontend/e2e/pages/components/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export class Table {
.first();
}

getCellLocatorByCellId(value: string, id: string) {
return this.getRowLocatorByCellText(value).locator(
`td[data-cell-id$="_${id}"]`,
);
}

async hasRowCount(count: number) {
await expect(this.tBody.locator("tr")).toHaveCount(count);
}
Expand Down
9 changes: 6 additions & 3 deletions apps/opik-frontend/e2e/test-data/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ export const TRACE_2 = {
prompt:
"Translate the following sentence into Spanish: 'Good morning, how are you?'",
},
expected_output: {
response: "Buenos días, ¿cómo estás?",
},
start_time: "2024-10-02T12:06:16.346Z",
end_time: "2024-10-02T12:06:28.846Z",
};

export const TRACE_SCORE = {
name: "hallucination-trace",
source: "sdk",
value: 1,
};
10 changes: 2 additions & 8 deletions apps/opik-frontend/e2e/tests/traces/feedback-score.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { FeedbackScoreData } from "@e2e/entities";
import { expect, test } from "@e2e/fixtures";
import { CATEGORICAL_FEEDBACK_DEFINITION } from "@e2e/test-data";
import { CATEGORICAL_FEEDBACK_DEFINITION, TRACE_SCORE } from "@e2e/test-data";

const SPAN_SCORE: FeedbackScoreData = {
name: "hallucination-span",
source: "sdk",
value: 0,
};

const TRACE_SCORE: FeedbackScoreData = {
name: "hallucination-trace",
source: "sdk",
value: 1,
};

test.describe("Feedback scores - Display", () => {
test("Check in table and sidebar", async ({
page,
Expand All @@ -23,7 +17,7 @@ test.describe("Feedback scores - Display", () => {
tracesPage,
}) => {
await span.addScore(SPAN_SCORE);
await trace1.addScore(TRACE_SCORE);
await trace1.addScore(TRACE_SCORE as FeedbackScoreData);

// Trace table column
await tracesPage.goto(project.id);
Expand Down
10 changes: 2 additions & 8 deletions apps/opik-frontend/e2e/tests/traces/trace-panel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { test } from "@e2e/fixtures";
import { expect } from "@playwright/test";
import { FeedbackScoreData } from "@e2e/entities";
import { SPAN_1, SPAN_2, TRACE_1 } from "@e2e/test-data";

const TRACE_SCORE: FeedbackScoreData = {
name: "hallucination-trace",
source: "sdk",
value: 1,
};
import { SPAN_1, SPAN_2, TRACE_1, TRACE_SCORE } from "@e2e/test-data";

const TRACE_TAG_NAME = "trace_tag_test";
const SPAN_TAG_NAME = "span_tag_test";
Expand Down Expand Up @@ -98,7 +92,7 @@ test.describe("Trace panel", () => {
span,
tracesPage,
}) => {
await trace1.addScore(TRACE_SCORE);
await trace1.addScore(TRACE_SCORE as FeedbackScoreData);
await tracesPage.goto(project.id);
await tracesPage.openSidePanel(trace1.name);

Expand Down
115 changes: 112 additions & 3 deletions apps/opik-frontend/e2e/tests/traces/traces-table.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,127 @@
import { expect, test } from "@e2e/fixtures";
import { SPAN_1, TRACE_SCORE } from "@e2e/test-data";
import { FeedbackScoreData } from "@e2e/entities";

test.describe("Traces table", () => {
test("Check trace/span creation", async ({
test("Check data visibility", async ({
project,
trace1,
span,
tracesPage,
}) => {
await trace1.addScore(TRACE_SCORE as FeedbackScoreData);
await tracesPage.goto(project.id);
await expect(tracesPage.title).toBeVisible();
await tracesPage.table.checkIsExist(trace1.name);
await tracesPage.columns.selectAll();
const timeFormat =
/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{2} (0[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/;

// test traces visibility
const traceData = trace1.original as {
id: string;
input: object;
output: object;
metadata: object;
tags: string[];
};

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "id"),
).toHaveText(`${traceData.id.slice(0, 6)}...`);

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "start_time"),
).toHaveText(timeFormat);

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "end_time"),
).toHaveText(timeFormat);

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "input"),
).toHaveText(JSON.stringify(traceData.input, null, 2));

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "output"),
).toHaveText(JSON.stringify(traceData.output, null, 2));

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "metadata"),
).toHaveText(JSON.stringify(traceData.metadata, null, 2));

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "feedback_scores"),
).toHaveText(TRACE_SCORE.name + TRACE_SCORE.value);

await expect(
tracesPage.table.getCellLocatorByCellId(trace1.name, "tags"),
).toHaveText(traceData.tags.sort().join(""));

await expect(
tracesPage.table.getCellLocatorByCellId(
trace1.name,
"usage_total_tokens",
),
).toHaveText(String(SPAN_1.usage.total_tokens));

await expect(
tracesPage.table.getCellLocatorByCellId(
trace1.name,
"usage_prompt_tokens",
),
).toHaveText(String(SPAN_1.usage.prompt_tokens));

await expect(
tracesPage.table.getCellLocatorByCellId(
trace1.name,
"usage_completion_tokens",
),
).toHaveText(String(SPAN_1.usage.completion_tokens));

await tracesPage.switchToLLMCalls();
await tracesPage.table.checkIsNotExist(trace1.name);
await tracesPage.table.checkIsExist(span.name);

// test spans visibility
const spanData = span.original as {
id: string;
name: string;
output: object;
tags: string[];
};

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "id"),
).toHaveText(`${spanData.id.slice(0, 6)}...`);

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "start_time"),
).toHaveText(timeFormat);

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "end_time"),
).toHaveText(timeFormat);

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "output"),
).toHaveText(JSON.stringify(spanData.output, null, 2));

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "tags"),
).toHaveText(spanData.tags.sort().join(""));

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "usage_total_tokens"),
).toHaveText(String(SPAN_1.usage.total_tokens));

await expect(
tracesPage.table.getCellLocatorByCellId(span.name, "usage_prompt_tokens"),
).toHaveText(String(SPAN_1.usage.prompt_tokens));

await expect(
tracesPage.table.getCellLocatorByCellId(
span.name,
"usage_completion_tokens",
),
).toHaveText(String(SPAN_1.usage.completion_tokens));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@ import get from "lodash/get";
import { useToast } from "@/components/ui/use-toast";
import api, { EXPERIMENTS_REST_ENDPOINT } from "@/api/api";

type UseExperimentItemBatchDeleteMutationParams = {
type UseExperimentBatchDeleteMutationParams = {
ids: string[];
};

const useExperimentItemBatchDeleteMutation = () => {
const useExperimentBatchDeleteMutation = () => {
const queryClient = useQueryClient();
const { toast } = useToast();

return useMutation({
mutationFn: async ({ ids }: UseExperimentItemBatchDeleteMutationParams) => {
const { data } = await api.post(
`${EXPERIMENTS_REST_ENDPOINT}items/delete`,
{
ids: ids,
},
);
mutationFn: async ({ ids }: UseExperimentBatchDeleteMutationParams) => {
const { data } = await api.post(`${EXPERIMENTS_REST_ENDPOINT}delete`, {
ids: ids,
});
return data;
},
onError: (error) => {
Expand All @@ -34,14 +31,12 @@ const useExperimentItemBatchDeleteMutation = () => {
variant: "destructive",
});
},
onSettled: (data, error, variables, context) => {
if (context) {
return queryClient.invalidateQueries({
queryKey: ["compare-experiments"],
});
}
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["experiments"],
});
},
});
};

export default useExperimentItemBatchDeleteMutation;
export default useExperimentBatchDeleteMutation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { MoreHorizontal, Trash } from "lucide-react";
import React, { useCallback, useRef, useState } from "react";
import { CellContext } from "@tanstack/react-table";
import ConfirmDialog from "@/components/shared/ConfirmDialog/ConfirmDialog";
import { Experiment } from "@/types/datasets";
import useExperimentBatchDeleteMutation from "@/api/datasets/useExperimentBatchDeleteMutation";

export const ExperimentRowActionsCell: React.FunctionComponent<
CellContext<Experiment, unknown>
> = ({ row }) => {
const resetKeyRef = useRef(0);
const experiment = row.original;
const [open, setOpen] = useState<boolean>(false);

const experimentBatchDeleteMutation = useExperimentBatchDeleteMutation();

const deleteExperimentsHandler = useCallback(() => {
experimentBatchDeleteMutation.mutate({
ids: [experiment.id],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [experiment]);

return (
<div
className="flex size-full items-center justify-end"
onClick={(e) => e.stopPropagation()}
>
<ConfirmDialog
key={`delete-${resetKeyRef.current}`}
open={open}
setOpen={setOpen}
onConfirm={deleteExperimentsHandler}
title={`Delete ${experiment.name}`}
description="Are you sure you want to delete this experiment?"
confirmText="Delete experiment"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="minimal" size="icon">
<span className="sr-only">Actions menu</span>
<MoreHorizontal className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem
onClick={() => {
setOpen(true);
resetKeyRef.current = resetKeyRef.current + 1;
}}
>
<Trash className="mr-2 size-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
Loading

0 comments on commit dcf7e6a

Please sign in to comment.