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 { jwtDecode } from "jwt-decode";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { defaultOrg } from "@/components/common_components/default_org";
|
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 Navbar from "@/components/navbar";
|
||||||
import UserDashboard from "@/components/user_dashboard";
|
import UserDashboard from "@/components/user_dashboard";
|
||||||
import ModelDashboard from "@/components/model_dashboard";
|
import ModelDashboard from "@/components/model_dashboard";
|
||||||
|
@ -344,6 +344,7 @@ export default function CreateKeyPage() {
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
token={token}
|
token={token}
|
||||||
accessToken={accessToken}
|
accessToken={accessToken}
|
||||||
|
allTeams={teams as Team[] ?? []}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Usage
|
<Usage
|
||||||
|
|
|
@ -2372,6 +2372,7 @@ export const testConnectionRequest = async (
|
||||||
// ... existing code ...
|
// ... existing code ...
|
||||||
export const keyInfoV1Call = async (accessToken: string, key: string) => {
|
export const keyInfoV1Call = async (accessToken: string, key: string) => {
|
||||||
try {
|
try {
|
||||||
|
console.log("entering keyInfoV1Call");
|
||||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`;
|
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`;
|
||||||
url = `${url}?key=${key}`; // Add key as query parameter
|
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
|
// Remove body since this is a GET request
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("response", response);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.text();
|
const errorData = await response.text();
|
||||||
handleError(errorData);
|
handleError(errorData);
|
||||||
throw new Error("Network response was not ok");
|
message.error("Failed to fetch key info - " + errorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
console.log("data", data);
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch key info:", 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 { getProviderLogoAndName } from "../provider_info_helpers";
|
||||||
import { Tooltip } from "antd";
|
import { Tooltip } from "antd";
|
||||||
import { TimeCell } from "./time_cell";
|
import { TimeCell } from "./time_cell";
|
||||||
|
import { Button } from "@tremor/react";
|
||||||
|
|
||||||
export type LogEntry = {
|
export type LogEntry = {
|
||||||
request_id: string;
|
request_id: string;
|
||||||
api_key: string;
|
api_key: string;
|
||||||
team_id: string;
|
team_id: string;
|
||||||
model: string;
|
model: string;
|
||||||
|
model_id: string;
|
||||||
api_base?: string;
|
api_base?: string;
|
||||||
call_type: string;
|
call_type: string;
|
||||||
spend: number;
|
spend: number;
|
||||||
|
@ -30,6 +32,7 @@ export type LogEntry = {
|
||||||
requester_ip_address?: string;
|
requester_ip_address?: string;
|
||||||
messages: string | any[] | Record<string, any>;
|
messages: string | any[] | Record<string, any>;
|
||||||
response: string | any[] | Record<string, any>;
|
response: string | any[] | Record<string, any>;
|
||||||
|
onKeyHashClick?: (keyHash: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const columns: ColumnDef<LogEntry>[] = [
|
export const columns: ColumnDef<LogEntry>[] = [
|
||||||
|
@ -140,9 +143,16 @@ export const columns: ColumnDef<LogEntry>[] = [
|
||||||
accessorKey: "metadata.user_api_key",
|
accessorKey: "metadata.user_api_key",
|
||||||
cell: (info: any) => {
|
cell: (info: any) => {
|
||||||
const value = String(info.getValue() || "-");
|
const value = String(info.getValue() || "-");
|
||||||
|
const onKeyHashClick = info.row.original.onKeyHashClick;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={value}>
|
<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>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { uiSpendLogsCall } from "../networking";
|
import { uiSpendLogsCall, keyInfoV1Call } from "../networking";
|
||||||
import { DataTable } from "./table";
|
import { DataTable } from "./table";
|
||||||
import { columns, LogEntry } from "./columns";
|
import { columns, LogEntry } from "./columns";
|
||||||
import { Row } from "@tanstack/react-table";
|
import { Row } from "@tanstack/react-table";
|
||||||
|
@ -12,12 +12,15 @@ import { RequestResponsePanel } from "./columns";
|
||||||
import { ErrorViewer } from './ErrorViewer';
|
import { ErrorViewer } from './ErrorViewer';
|
||||||
import { internalUserRoles } from "../../utils/roles";
|
import { internalUserRoles } from "../../utils/roles";
|
||||||
import { ConfigInfoMessage } from './ConfigInfoMessage';
|
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 {
|
interface SpendLogsTableProps {
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
userRole: string | null;
|
userRole: string | null;
|
||||||
userID: string | null;
|
userID: string | null;
|
||||||
|
allTeams: Team[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaginatedResponse {
|
interface PaginatedResponse {
|
||||||
|
@ -38,6 +41,7 @@ export default function SpendLogsTable({
|
||||||
token,
|
token,
|
||||||
userRole,
|
userRole,
|
||||||
userID,
|
userID,
|
||||||
|
allTeams,
|
||||||
}: SpendLogsTableProps) {
|
}: SpendLogsTableProps) {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
@ -63,14 +67,34 @@ export default function SpendLogsTable({
|
||||||
const [tempKeyHash, setTempKeyHash] = useState("");
|
const [tempKeyHash, setTempKeyHash] = useState("");
|
||||||
const [selectedTeamId, setSelectedTeamId] = useState("");
|
const [selectedTeamId, setSelectedTeamId] = useState("");
|
||||||
const [selectedKeyHash, setSelectedKeyHash] = 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 [selectedFilter, setSelectedFilter] = useState("Team ID");
|
||||||
const [filterByCurrentUser, setFilterByCurrentUser] = useState(
|
const [filterByCurrentUser, setFilterByCurrentUser] = useState(
|
||||||
userRole && internalUserRoles.includes(userRole)
|
userRole && internalUserRoles.includes(userRole)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null);
|
const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
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
|
// Close dropdown when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
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
|
// No need for additional filtering since we're now handling this in the API call
|
||||||
return matchesSearch;
|
return matchesSearch;
|
||||||
}) || [];
|
|
||||||
|
}).map(log => ({
|
||||||
|
...log,
|
||||||
|
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash)
|
||||||
|
})) || [];
|
||||||
|
|
||||||
// Add this function to handle manual refresh
|
// Add this function to handle manual refresh
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
|
@ -242,7 +270,10 @@ export default function SpendLogsTable({
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h1 className="text-xl font-semibold">Request Logs</h1>
|
<h1 className="text-xl font-semibold">Request Logs</h1>
|
||||||
</div>
|
</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="bg-white rounded-lg shadow">
|
||||||
<div className="border-b px-6 py-4">
|
<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">
|
<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}
|
expandedRequestId={expandedRequestId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -671,18 +704,25 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
|
||||||
<span className="font-medium w-1/3">Model:</span>
|
<span className="font-medium w-1/3">Model:</span>
|
||||||
<span>{row.original.model}</span>
|
<span>{row.original.model}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="font-medium w-1/3">Model ID:</span>
|
||||||
|
<span>{row.original.model_id}</span>
|
||||||
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="font-medium w-1/3">Provider:</span>
|
<span className="font-medium w-1/3">Provider:</span>
|
||||||
<span>{row.original.custom_llm_provider || "-"}</span>
|
<span>{row.original.custom_llm_provider || "-"}</span>
|
||||||
</div>
|
</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">
|
<div className="flex">
|
||||||
<span className="font-medium w-1/3">Start Time:</span>
|
<span className="font-medium w-1/3">Start Time:</span>
|
||||||
<span>{row.original.startTime}</span>
|
<span>{row.original.startTime}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
|
||||||
<span className="font-medium w-1/3">End Time:</span>
|
|
||||||
<span>{row.original.endTime}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
@ -712,6 +752,11 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
|
||||||
}`}>
|
}`}>
|
||||||
{(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
|
{(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="font-medium w-1/3">End Time:</span>
|
||||||
|
<span>{row.original.endTime}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,7 @@ interface DataTableProps<TData, TValue> {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
expandedRequestId?: string | null;
|
expandedRequestId?: string | null;
|
||||||
onRowExpand?: (requestId: string | null) => void;
|
onRowExpand?: (requestId: string | null) => void;
|
||||||
|
setSelectedKeyIdInfoView?: (keyId: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData extends { request_id: string }, TValue>({
|
export function DataTable<TData extends { request_id: string }, TValue>({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue