Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dylan/s3en 2393 update UI tracing tab to show the prompt version being used v2 #154

Merged
Show file tree
Hide file tree
Changes from 4 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
37 changes: 37 additions & 0 deletions app/api/prompt-ids/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { authOptions } from "@/lib/auth/options";
import { TraceService } from "@/lib/services/trace_service";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
const session = await getServerSession(authOptions);

if (!session || !session.user) {
redirect("/login");
}

try {
const projectId = req.nextUrl.searchParams.get("projectId") as string;

if (!projectId) {
return NextResponse.json(
{ error: "Please provide a projectId" },
{ status: 400 }
);
}

const traceService = new TraceService();
const promptIDs = await traceService.GetPromptsInProject(projectId);
return NextResponse.json(
{ promptIDs },
{
status: 200,
}
);
} catch (error) {
return NextResponse.json(JSON.stringify({ error }), {
status: 400,
});
}
}
22 changes: 21 additions & 1 deletion components/project/traces/trace-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import ClearIcon from "@mui/icons-material/Clear";
import { Check, ChevronsUpDown } from "lucide-react";
import { useEffect, useState } from "react";

import { PromptCombobox } from "@/components/shared/prompt-combobox";
import { UserCombobox } from "@/components/shared/user-combobox";
import { SpanAttributes } from "@/lib/ts_sdk_constants";
import VendorDropdown from "./vendor-dropdown";
Expand All @@ -43,6 +44,7 @@ export default function FilterDialog({
const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
const [advancedFilters, setAdvancedFilters] = useState<any[]>([]);
const [selectedUserId, setSelectedUserId] = useState<string>("");
const [selectedPromptId, setSelectedPromptId] = useState<string>("");

useEffect(() => {
if (!open) {
Expand Down Expand Up @@ -97,6 +99,15 @@ export default function FilterDialog({
});
}

if (selectedPromptId) {
convertedAdvancedFilters.push({
key: "prompt_id",
operation: "EQUALS",
value: selectedPromptId,
type: "attribute",
});
}

onApplyFilters({
filters: [...convertedFilters, ...convertedAdvancedFilters],
});
Expand Down Expand Up @@ -181,19 +192,28 @@ export default function FilterDialog({
setSelectedUser={setSelectedUserId}
/>
</div>
<div>
<h4 className="mt-4">Prompt Id</h4>
<PromptCombobox
selectedPrompt={selectedPromptId}
setSelectedPrompt={setSelectedPromptId}
/>
</div>
<DialogFooter>
<Button variant={"outline"} onClick={onClose}>
Cancel
</Button>
{(selectedFilters.length > 0 ||
advancedFilters.length > 0 ||
selectedUserId !== "") && (
selectedUserId !== "" ||
selectedPromptId !== "") && (
<Button
variant={"destructive"}
onClick={() => {
setSelectedFilters([]);
setAdvancedFilters([]);
setSelectedUserId("");
setSelectedPromptId("");
}}
>
<ClearIcon className="h-4 w-4" />
Expand Down
6 changes: 6 additions & 0 deletions components/project/traces/trace-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const TraceRow = ({
let model: string = "";
let vendor: string = "";
let userId: string = "";
let promptId: string = "";
let promptVersion: string = "";
let prompts: any[] = [];
let responses: any[] = [];
let events: any[] = [];
Expand All @@ -55,6 +57,8 @@ export const TraceRow = ({
prompts.push(attributes["llm.prompts"]);
responses.push(attributes["llm.responses"]);
}
promptId = attributes["prompt_id"];
promptVersion = attributes["prompt_version"];
if (!model) {
model = attributes["llm.model"] || "";
}
Expand Down Expand Up @@ -158,6 +162,8 @@ export const TraceRow = ({
<p className="text-xs font-semibold col-span-2"></p>
)}
<p className="text-xs font-semibold">{userId}</p>
<p className="text-xs font-semibold">{promptId}</p>
<p className="text-xs font-semibold">{promptVersion}</p>
<p className="text-xs">
{tokenCounts?.input_tokens || tokenCounts?.prompt_tokens}
{tokenCounts?.input_tokens || tokenCounts?.prompt_tokens ? "/" : ""}
Expand Down
2 changes: 2 additions & 0 deletions components/project/traces/traces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export default function Traces({ email }: { email: string }) {
<p className="text-xs font-medium col-span-2">Input</p>
<p className="text-xs font-medium col-span-2">Output</p>
<p className="text-xs font-medium">User ID</p>
<p className="text-xs font-medium">Prompt ID</p>
<p className="text-xs font-medium">Prompt Version</p>
<p className="text-xs font-medium">Input / Output / Total Tokens</p>
<p className="text-xs font-medium">Token Cost</p>
<p className="text-xs font-medium">Duration(ms)</p>
Expand Down
135 changes: 135 additions & 0 deletions components/shared/prompt-combobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"use client";

import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";

import { Check, ChevronsUpDown } from "lucide-react";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";

import { useQuery } from "react-query";
import { toast } from "sonner";

export function PromptCombobox({
setSelectedPrompt,
selectedPrompt,
}: {
setSelectedPrompt: (prompt: string) => void;
selectedPrompt?: string;
}) {
const project_id = useParams()?.project_id as string;
const [open, setOpen] = useState(false);
const [selectedPromptId, setSelectedPromptIdState] = useState(
selectedPrompt || ""
);
const [searchQuery, setSearchQuery] = useState("");
const [promptIds, setPromptIds] = useState<string[]>([]);
const [showLoader, setShowLoader] = useState(false);
const [internalSelectedPrompt, setInternalSelectedPrompt] =
useState(selectedPrompt);

const handleSelectPrompt = (currentValue: string) => {
const newPromptId = currentValue === selectedPromptId ? "" : currentValue;
setSelectedPromptIdState(newPromptId);
setSelectedPrompt(newPromptId);

setOpen(false);
};

useEffect(() => {
setSelectedPromptIdState(selectedPrompt || "");
setInternalSelectedPrompt(selectedPrompt || "");
}, [selectedPrompt]);

const fetchPromptIds = useQuery({
queryKey: ["fetch-prompt-ids-query", project_id],
queryFn: async () => {
const response = await fetch(`/api/prompt-ids?projectId=${project_id}`);

if (!response.ok) {
throw new Error("Failed to fetch prompt ids");
}
const result = await response.json();
return result;
},
onSuccess: (data: { promptIDs: any }) => {
setPromptIds(data?.promptIDs || []);
},
onError: (error) => {
setShowLoader(false);
toast.error("Failed to fetch prompt ids", {
description: error instanceof Error ? error.message : String(error),
});
},
});

if (fetchPromptIds.isLoading) {
return <div>Loading...</div>;
}

const onInputChange = (value: string) => {
setSearchQuery(value);
};

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[220px] justify-between"
>
{selectedPromptId ? selectedPromptId : "Filter by prompt id..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[220px] p-0">
<Command>
<CommandInput
placeholder="Search prompts..."
value={searchQuery}
onValueChange={onInputChange}
/>
<CommandEmpty>No attribute found.</CommandEmpty>
<CommandGroup>
{promptIds.map((id: string) => (
<CommandItem
key={id}
value={id}
onSelect={(currentValue) => {
setSelectedPromptIdState(
currentValue === selectedPromptId ? "" : currentValue
);
setSelectedPrompt(
currentValue === selectedPromptId ? "" : currentValue
);
handleSelectPrompt(currentValue);
setOpen(false);
}}
>
<Check
className={`mr-2 h-4 w-4 ${
selectedPromptId === id ? "opacity-100" : "opacity-0"
}`}
/>
{id}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}
23 changes: 23 additions & 0 deletions lib/services/trace_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export interface ITraceService {
) => Promise<number>;
AddSpans: (spans: Span[], project_id: string) => Promise<void>;
GetUsersInProject: (project_id: string) => Promise<string[]>;
GetPromptsInProject: (project_id: string) => Promise<string[]>;
GetModelsInProject: (project_id: string) => Promise<string[]>;
}

Expand Down Expand Up @@ -122,6 +123,28 @@ export class TraceService implements ITraceService {
}
}

async GetPromptsInProject(project_id: string): Promise<string[]> {
try {
const tableExists = await this.client.checkTableExists(project_id);
if (!tableExists) {
return [];
}
const query = sql
.select([
`DISTINCT JSONExtractString(attributes, 'prompt_id') AS prompt_id`,
])
.from(project_id);
const result: any[] = await this.client.find(query);
return result
.map((row) => row.prompt_id)
.filter((prompt_id) => prompt_id !== "");
} catch (error) {
throw new Error(
`An error occurred while trying to get the prompts ${error}`
);
}
}

async GetModelsInProject(project_id: string): Promise<string[]> {
try {
const tableExists = await this.client.checkTableExists(project_id);
Expand Down