mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 11:14:04 +00:00
Allow viewing keyinfo on request logs (#9568)
* feat(view_logs.tsx): show model id + api base in request logs easier debugging * fix(index.tsx): fix length of api base easier viewing * build(ui/): initial commit allowing user to click into key from request logs allows easier debugging of 'what key is this?
This commit is contained in:
parent
d58fe5a9f9
commit
c453a91849
5 changed files with 72 additions and 11 deletions
|
@ -5,7 +5,7 @@ import { useSearchParams } from "next/navigation";
|
|||
import { jwtDecode } from "jwt-decode";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { defaultOrg } from "@/components/common_components/default_org";
|
||||
import { Team } from "@/components/key_team_helpers/key_list";
|
||||
import { KeyResponse, Team } from "@/components/key_team_helpers/key_list";
|
||||
import Navbar from "@/components/navbar";
|
||||
import UserDashboard from "@/components/user_dashboard";
|
||||
import ModelDashboard from "@/components/model_dashboard";
|
||||
|
@ -344,6 +344,7 @@ export default function CreateKeyPage() {
|
|||
userRole={userRole}
|
||||
token={token}
|
||||
accessToken={accessToken}
|
||||
allTeams={teams as Team[] ?? []}
|
||||
/>
|
||||
) : (
|
||||
<Usage
|
||||
|
|
|
@ -2372,6 +2372,7 @@ export const testConnectionRequest = async (
|
|||
// ... existing code ...
|
||||
export const keyInfoV1Call = async (accessToken: string, key: string) => {
|
||||
try {
|
||||
console.log("entering keyInfoV1Call");
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`;
|
||||
url = `${url}?key=${key}`; // Add key as query parameter
|
||||
|
||||
|
@ -2384,13 +2385,16 @@ export const keyInfoV1Call = async (accessToken: string, key: string) => {
|
|||
// Remove body since this is a GET request
|
||||
});
|
||||
|
||||
console.log("response", response);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
message.error("Failed to fetch key info - " + errorData);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("data", data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch key info:", error);
|
||||
|
|
|
@ -6,12 +6,14 @@ import { CountryCell } from "./country_cell";
|
|||
import { getProviderLogoAndName } from "../provider_info_helpers";
|
||||
import { Tooltip } from "antd";
|
||||
import { TimeCell } from "./time_cell";
|
||||
import { Button } from "@tremor/react";
|
||||
|
||||
export type LogEntry = {
|
||||
request_id: string;
|
||||
api_key: string;
|
||||
team_id: string;
|
||||
model: string;
|
||||
model_id: string;
|
||||
api_base?: string;
|
||||
call_type: string;
|
||||
spend: number;
|
||||
|
@ -30,6 +32,7 @@ export type LogEntry = {
|
|||
requester_ip_address?: string;
|
||||
messages: string | any[] | Record<string, any>;
|
||||
response: string | any[] | Record<string, any>;
|
||||
onKeyHashClick?: (keyHash: string) => void;
|
||||
};
|
||||
|
||||
export const columns: ColumnDef<LogEntry>[] = [
|
||||
|
@ -140,9 +143,16 @@ export const columns: ColumnDef<LogEntry>[] = [
|
|||
accessorKey: "metadata.user_api_key",
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "-");
|
||||
const onKeyHashClick = info.row.original.onKeyHashClick;
|
||||
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<span className="font-mono max-w-[15ch] truncate block">{value}</span>
|
||||
<span
|
||||
className="font-mono max-w-[15ch] truncate block cursor-pointer hover:text-blue-600"
|
||||
onClick={() => onKeyHashClick?.(value)}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { uiSpendLogsCall } from "../networking";
|
||||
import { uiSpendLogsCall, keyInfoV1Call } from "../networking";
|
||||
import { DataTable } from "./table";
|
||||
import { columns, LogEntry } from "./columns";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
|
@ -12,12 +12,15 @@ import { RequestResponsePanel } from "./columns";
|
|||
import { ErrorViewer } from './ErrorViewer';
|
||||
import { internalUserRoles } from "../../utils/roles";
|
||||
import { ConfigInfoMessage } from './ConfigInfoMessage';
|
||||
|
||||
import { Tooltip } from "antd";
|
||||
import { KeyResponse, Team } from "../key_team_helpers/key_list";
|
||||
import KeyInfoView from "../key_info_view";
|
||||
interface SpendLogsTableProps {
|
||||
accessToken: string | null;
|
||||
token: string | null;
|
||||
userRole: string | null;
|
||||
userID: string | null;
|
||||
allTeams: Team[];
|
||||
}
|
||||
|
||||
interface PaginatedResponse {
|
||||
|
@ -38,6 +41,7 @@ export default function SpendLogsTable({
|
|||
token,
|
||||
userRole,
|
||||
userID,
|
||||
allTeams,
|
||||
}: SpendLogsTableProps) {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
@ -63,14 +67,34 @@ export default function SpendLogsTable({
|
|||
const [tempKeyHash, setTempKeyHash] = useState("");
|
||||
const [selectedTeamId, setSelectedTeamId] = useState("");
|
||||
const [selectedKeyHash, setSelectedKeyHash] = useState("");
|
||||
const [selectedKeyInfo, setSelectedKeyInfo] = useState<KeyResponse | null>(null);
|
||||
const [selectedKeyIdInfoView, setSelectedKeyIdInfoView] = useState<string | null>(null);
|
||||
const [selectedFilter, setSelectedFilter] = useState("Team ID");
|
||||
const [filterByCurrentUser, setFilterByCurrentUser] = useState(
|
||||
userRole && internalUserRoles.includes(userRole)
|
||||
);
|
||||
|
||||
const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchKeyInfo = async () => {
|
||||
if (selectedKeyIdInfoView && accessToken) {
|
||||
const keyData = await keyInfoV1Call(accessToken, selectedKeyIdInfoView);
|
||||
console.log("keyData", keyData);
|
||||
|
||||
const keyResponse: KeyResponse = {
|
||||
...keyData["info"],
|
||||
"token": selectedKeyIdInfoView,
|
||||
"api_key": selectedKeyIdInfoView,
|
||||
};
|
||||
setSelectedKeyInfo(keyResponse);
|
||||
}
|
||||
};
|
||||
fetchKeyInfo();
|
||||
}, [selectedKeyIdInfoView, accessToken]);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
|
@ -206,7 +230,11 @@ export default function SpendLogsTable({
|
|||
|
||||
// No need for additional filtering since we're now handling this in the API call
|
||||
return matchesSearch;
|
||||
}) || [];
|
||||
|
||||
}).map(log => ({
|
||||
...log,
|
||||
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash)
|
||||
})) || [];
|
||||
|
||||
// Add this function to handle manual refresh
|
||||
const handleRefresh = () => {
|
||||
|
@ -242,7 +270,10 @@ export default function SpendLogsTable({
|
|||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-semibold">Request Logs</h1>
|
||||
</div>
|
||||
|
||||
{selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? (
|
||||
<KeyInfoView keyId={selectedKeyIdInfoView} keyData={selectedKeyInfo} accessToken={accessToken} userID={userID} userRole={userRole} teams={allTeams} onClose={() => setSelectedKeyIdInfoView(null)} />
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="border-b px-6 py-4">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0">
|
||||
|
@ -594,6 +625,8 @@ export default function SpendLogsTable({
|
|||
expandedRequestId={expandedRequestId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -671,18 +704,25 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
|
|||
<span className="font-medium w-1/3">Model:</span>
|
||||
<span>{row.original.model}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Model ID:</span>
|
||||
<span>{row.original.model_id}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Provider:</span>
|
||||
<span>{row.original.custom_llm_provider || "-"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">API Base:</span>
|
||||
<Tooltip title={row.original.api_base || "-"}>
|
||||
<span className="max-w-[15ch] truncate block">{row.original.api_base || "-"}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Start Time:</span>
|
||||
<span>{row.original.startTime}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">End Time:</span>
|
||||
<span>{row.original.endTime}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex">
|
||||
|
@ -712,6 +752,11 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
|
|||
}`}>
|
||||
{(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">End Time:</span>
|
||||
<span>{row.original.endTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,6 +25,7 @@ interface DataTableProps<TData, TValue> {
|
|||
isLoading?: boolean;
|
||||
expandedRequestId?: string | null;
|
||||
onRowExpand?: (requestId: string | null) => void;
|
||||
setSelectedKeyIdInfoView?: (keyId: string | null) => void;
|
||||
}
|
||||
|
||||
export function DataTable<TData extends { request_id: string }, TValue>({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue