Skip to content

Commit

Permalink
Agent leaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
lasryaric committed Dec 22, 2023
1 parent eb5c21f commit efd1867
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 19 deletions.
6 changes: 4 additions & 2 deletions front/lib/api/assistant/agent_usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,11 @@ async function populateUsageIfNeeded({
export async function getAgentUsage({
workspaceId,
agentConfigurationId,
providedRedis,
}: {
workspaceId: string;
agentConfigurationId: string;
providedRedis?: Awaited<ReturnType<typeof redisClient>>;
}): Promise<AgentUsageType> {
let redis: Awaited<ReturnType<typeof redisClient>> | null = null;

Expand All @@ -184,7 +186,7 @@ export async function getAgentUsage({
});

try {
redis = await redisClient();
redis = providedRedis ?? (await redisClient());
await populateUsageIfNeeded({
agentConfigurationId,
workspaceId,
Expand All @@ -210,7 +212,7 @@ export async function getAgentUsage({
timePeriodSec: rankingTimeframeSec,
};
} finally {
if (redis) {
if (redis && !providedRedis) {
await redis.quit();
}
}
Expand Down
11 changes: 11 additions & 0 deletions front/lib/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ export async function redisClient() {

return client;
}

export async function safeRedisClient<T>(
fn: (client: Awaited<ReturnType<typeof redisClient>>) => PromiseLike<T>
): Promise<T> {
const client = await redisClient();
try {
return await fn(client);
} finally {
await client.quit();
}
}
27 changes: 27 additions & 0 deletions front/lib/swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GetRunBlockResponseBody } from "@app/pages/api/w/[wId]/apps/[aId]/runs/
import { GetRunStatusResponseBody } from "@app/pages/api/w/[wId]/apps/[aId]/runs/[runId]/status";
import { GetAgentConfigurationsResponseBody } from "@app/pages/api/w/[wId]/assistant/agent_configurations";
import { GetAgentUsageResponseBody } from "@app/pages/api/w/[wId]/assistant/agent_configurations/[aId]/usage";
import { GetAgentConfigurationsLeaderboardResponseBody } from "@app/pages/api/w/[wId]/assistant/agent_configurations/leaderboard";
import { GetAgentNamesResponseBody } from "@app/pages/api/w/[wId]/assistant/agent_configurations/names";
import { GetDataSourcesResponseBody } from "@app/pages/api/w/[wId]/data_sources";
import { GetDocumentsResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/documents";
Expand Down Expand Up @@ -468,6 +469,32 @@ export function useAgentConfigurations({
};
}

export function useAgentsLeaderboard({
workspaceId,
agentsGetView,
}: {
workspaceId: string;
agentsGetView: AgentsGetViewType;
}) {
const agentsLeaderboardFetcher: Fetcher<GetAgentConfigurationsLeaderboardResponseBody> =
fetcher;
const viewQueryString =
typeof agentsGetView === "string"
? `view=${agentsGetView}`
: `conversationId=${agentsGetView.conversationId}`;
const { data, error, mutate } = useSWR(
`/api/w/${workspaceId}/assistant/agent_configurations/leaderboard?${viewQueryString}`,
agentsLeaderboardFetcher
);

return {
agentsLeaderboard: data ? data.agentConfigurationsWithUsage : [],
isAgentsLeaderboardLoading: !error && !data,
isAgentsLeaderboardError: error,
mutateAgentsLeaderboard: mutate,
};
}

export function useAgentUsage({
workspaceId,
agentConfigurationId,
Expand Down
114 changes: 114 additions & 0 deletions front/pages/api/w/[wId]/assistant/agent_configurations/leaderboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
AgentConfigurationTypeWithUsage,
GetAgentConfigurationsLeaderboardQuerySchema,
} from "@dust-tt/types";
import { ReturnedAPIErrorType } from "@dust-tt/types";
import { isLeft } from "fp-ts/lib/Either";
import * as reporter from "io-ts-reporters";
import { NextApiRequest, NextApiResponse } from "next";

import { getAgentUsage } from "@app/lib/api/assistant/agent_usage";
import { getAgentConfigurations } from "@app/lib/api/assistant/configuration";
import { Authenticator, getSession } from "@app/lib/auth";
import { safeRedisClient } from "@app/lib/redis";
import { apiError, withLogging } from "@app/logger/withlogging";

export type GetAgentConfigurationsLeaderboardResponseBody = {
agentConfigurationsWithUsage: AgentConfigurationTypeWithUsage[];
};

async function handler(
req: NextApiRequest,
res: NextApiResponse<
GetAgentConfigurationsLeaderboardResponseBody | ReturnedAPIErrorType
>
): Promise<void> {
const session = await getSession(req, res);
const auth = await Authenticator.fromSession(
session,
req.query.wId as string
);
const owner = auth.workspace();
if (!owner) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "workspace_not_found",
message: "The workspace you're trying to modify was not found.",
},
});
}

switch (req.method) {
case "GET": {
if (!auth.isUser()) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "app_auth_error",
message: "Only the workspace users can see Assistants.",
},
});
}
const queryValidation =
GetAgentConfigurationsLeaderboardQuerySchema.decode(req.query);
if (isLeft(queryValidation)) {
const pathError = reporter.formatValidationErrors(queryValidation.left);
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: `Invalid query parameters: ${pathError}`,
},
});
}
const { view } = queryValidation.right;

if (view === "admin_internal" && !auth.isDustSuperUser()) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "app_auth_error",
message: "Only Dust Super Users can see admin_internal agents.",
},
});
}
const agentConfigurations = await getAgentConfigurations(auth, view);
const agentConfigurationsWithUsage = await safeRedisClient(
async (client) => {
return await Promise.all(
agentConfigurations.map(
async (
agentConfiguration
): Promise<AgentConfigurationTypeWithUsage> => {
return {
...agentConfiguration,
usage: await getAgentUsage({
providedRedis: client,
workspaceId: owner.sId,
agentConfigurationId: agentConfiguration.sId,
}),
};
}
)
);
}
);

return res
.status(200)
.json({ agentConfigurationsWithUsage: agentConfigurationsWithUsage });
}

default:
return apiError(req, res, {
status_code: 405,
api_error: {
type: "method_not_supported_error",
message: "The method passed is not supported, GET is expected.",
},
});
}
}

export default withLogging(handler);
99 changes: 82 additions & 17 deletions front/pages/w/[wId]/assistant/gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Page, Searchbar, Tab } from "@dust-tt/sparkle";
import { Button, DropdownMenu, Page, Searchbar, Tab } from "@dust-tt/sparkle";
import {
AgentConfigurationType,
AgentsGetViewType,
Expand All @@ -17,7 +17,7 @@ import AppLayout from "@app/components/sparkle/AppLayout";
import { AppLayoutSimpleCloseTitle } from "@app/components/sparkle/AppLayoutTitle";
import { subNavigationConversations } from "@app/components/sparkle/navigation";
import { Authenticator, getSession, getUserFromSession } from "@app/lib/auth";
import { useAgentConfigurations } from "@app/lib/swr";
import { useAgentConfigurations, useAgentsLeaderboard } from "@app/lib/swr";
import { subFilter } from "@app/lib/utils";

const { GA_TRACKING_ID = "" } = process.env;
Expand Down Expand Up @@ -91,15 +91,48 @@ export default function AssistantsGallery({
workspaceId: owner.sId,
agentsGetView,
});
const [assistantSearch, setAssistantSearch] = useState<string>("");

const filtered = agentConfigurations.filter((a) => {
return (
subFilter(assistantSearch.toLowerCase(), a.name.toLowerCase()) &&
a.status === "active"
);
const { agentsLeaderboard } = useAgentsLeaderboard({
workspaceId: owner.sId,
agentsGetView,
});

const [orderBy, setOrderBy] = useState<"name" | "usage">("name");
const [assistantSearch, setAssistantSearch] = useState<string>("");

let agentsToDisplay: AgentConfigurationType[] = [];

switch (orderBy) {
case "name": {
agentsToDisplay = agentConfigurations
.filter((a) => {
return (
subFilter(assistantSearch.toLowerCase(), a.name.toLowerCase()) &&
a.status === "active"
);
})
.sort((a, b) => {
return a.name.localeCompare(b.name);
});
break;
}
case "usage": {
agentsToDisplay = agentsLeaderboard
.filter((a) => {
return (
subFilter(assistantSearch.toLowerCase(), a.name.toLowerCase()) &&
a.status === "active"
);
})
.sort((a, b) => {
return b.usage.messageCount - a.usage.messageCount;
});
break;
}
default:
assertNever(orderBy);
}

const [showDetails, setShowDetails] = useState<AgentConfigurationType | null>(
null
);
Expand Down Expand Up @@ -179,17 +212,49 @@ export default function AssistantsGallery({
<div className="pb-16">
<Page.Vertical gap="xl" align="stretch">
<Tab tabs={tabs} />
<Searchbar
name="search"
placeholder="Assistant name"
value={assistantSearch}
onChange={(s) => {
setAssistantSearch(s);
}}
/>
<div className="flex flex-row space-x-4">
<div className="flex-grow">
<Searchbar
name="search"
placeholder="Assistant name"
value={assistantSearch}
onChange={(s) => {
setAssistantSearch(s);
}}
/>
</div>
<div className="shrink-0">
<DropdownMenu>
<DropdownMenu.Button>
<Button
type="select"
labelVisible={true}
label={`Order by: ${orderBy}`}
variant="tertiary"
hasMagnifying={false}
size="sm"
/>
</DropdownMenu.Button>
<DropdownMenu.Items origin="bottomRight">
<DropdownMenu.Item
key="name"
label="Name"
onClick={() => setOrderBy("name")}
/>
<DropdownMenu.Item
key="usage"
label="Usage"
onClick={() => {
setOrderBy("usage");
}}
/>
</DropdownMenu.Items>
</DropdownMenu>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2">
{filtered.map((a) => (
{agentsToDisplay.map((a) => (
<AssistantPreview
key={a.sId}
owner={owner}
Expand Down
11 changes: 11 additions & 0 deletions types/src/front/api_handlers/internal/agent_configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export const GetAgentConfigurationsQuerySchema = t.type({
conversationId: t.union([t.string, t.undefined]),
});

export const GetAgentConfigurationsLeaderboardQuerySchema = t.type({
view: t.union([
t.literal("list"),
t.literal("workspace"),
t.literal("published"),
t.literal("global"),
t.literal("admin_internal"),
t.literal("all"),
]),
});

export const PostOrPatchAgentConfigurationRequestBodySchema = t.type({
assistant: t.type({
name: t.string,
Expand Down
4 changes: 4 additions & 0 deletions types/src/front/assistant/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ export type AgentConfigurationType = {
generation: AgentGenerationConfigurationType | null;
};

export type AgentConfigurationTypeWithUsage = AgentConfigurationType & {
usage: AgentUsageType;
};

export type AgentUsageType = {
userCount: number;
messageCount: number;
Expand Down

0 comments on commit efd1867

Please sign in to comment.