-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: add context window and pricing columns to evals database #7747
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,7 +26,7 @@ import { | |||||||||||||||||||||||||||||||
| TIMEOUT_DEFAULT, | ||||||||||||||||||||||||||||||||
| } from "@/lib/schemas" | ||||||||||||||||||||||||||||||||
| import { cn } from "@/lib/utils" | ||||||||||||||||||||||||||||||||
| import { useOpenRouterModels } from "@/hooks/use-open-router-models" | ||||||||||||||||||||||||||||||||
| import { useOpenRouterModels, getModelDetails, getPricingPerMillion } from "@/hooks/use-open-router-models" | ||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| Button, | ||||||||||||||||||||||||||||||||
| FormControl, | ||||||||||||||||||||||||||||||||
|
|
@@ -95,6 +95,21 @@ export function NewRun() { | |||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| if (mode === "openrouter") { | ||||||||||||||||||||||||||||||||
| values.settings = { ...(values.settings || {}), openRouterModelId: model } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Get model details and add to the run | ||||||||||||||||||||||||||||||||
| const modelDetails = getModelDetails(models.data, model) | ||||||||||||||||||||||||||||||||
| if (modelDetails) { | ||||||||||||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When getModelDetails returns null, we silently fall through to the regular createRun without pricing data. Should we log a warning here to help with debugging?
Suggested change
|
||||||||||||||||||||||||||||||||
| const pricing = getPricingPerMillion(modelDetails.pricing) | ||||||||||||||||||||||||||||||||
| const extendedValues = { | ||||||||||||||||||||||||||||||||
| ...values, | ||||||||||||||||||||||||||||||||
| contextWindow: modelDetails.context_length, | ||||||||||||||||||||||||||||||||
| pricePerMillionInputTokens: pricing.input, | ||||||||||||||||||||||||||||||||
| pricePerMillionOutputTokens: pricing.output, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| const { id } = await createRun(extendedValues) | ||||||||||||||||||||||||||||||||
| router.push(`/runs/${id}`) | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const { id } = await createRun(values) | ||||||||||||||||||||||||||||||||
|
|
@@ -103,7 +118,7 @@ export function NewRun() { | |||||||||||||||||||||||||||||||
| toast.error(e instanceof Error ? e.message : "An unknown error occurred.") | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| [mode, model, router], | ||||||||||||||||||||||||||||||||
| [mode, model, models.data, router], | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const onFilterModels = useCallback( | ||||||||||||||||||||||||||||||||
|
|
@@ -112,13 +127,12 @@ export function NewRun() { | |||||||||||||||||||||||||||||||
| modelSearchValueRef.current = search | ||||||||||||||||||||||||||||||||
| modelSearchResultsRef.current.clear() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for (const { | ||||||||||||||||||||||||||||||||
| obj: { id }, | ||||||||||||||||||||||||||||||||
| score, | ||||||||||||||||||||||||||||||||
| } of fuzzysort.go(search, models.data || [], { | ||||||||||||||||||||||||||||||||
| const results = fuzzysort.go(search, models.data || [], { | ||||||||||||||||||||||||||||||||
| key: "name", | ||||||||||||||||||||||||||||||||
| })) { | ||||||||||||||||||||||||||||||||
| modelSearchResultsRef.current.set(id, score) | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for (const result of results) { | ||||||||||||||||||||||||||||||||
| modelSearchResultsRef.current.set(result.obj.id, result.score) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -210,16 +224,18 @@ export function NewRun() { | |||||||||||||||||||||||||||||||
| <CommandList> | ||||||||||||||||||||||||||||||||
| <CommandEmpty>No model found.</CommandEmpty> | ||||||||||||||||||||||||||||||||
| <CommandGroup> | ||||||||||||||||||||||||||||||||
| {models.data?.map(({ id, name }) => ( | ||||||||||||||||||||||||||||||||
| {models.data?.map((modelItem) => ( | ||||||||||||||||||||||||||||||||
| <CommandItem | ||||||||||||||||||||||||||||||||
| key={id} | ||||||||||||||||||||||||||||||||
| value={id} | ||||||||||||||||||||||||||||||||
| key={modelItem.id} | ||||||||||||||||||||||||||||||||
| value={modelItem.id} | ||||||||||||||||||||||||||||||||
| onSelect={onSelectModel}> | ||||||||||||||||||||||||||||||||
| {name} | ||||||||||||||||||||||||||||||||
| {modelItem.name} | ||||||||||||||||||||||||||||||||
| <Check | ||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||
| "ml-auto text-accent group-data-[selected=true]:text-accent-foreground size-4", | ||||||||||||||||||||||||||||||||
| id === model ? "opacity-100" : "opacity-0", | ||||||||||||||||||||||||||||||||
| modelItem.id === model | ||||||||||||||||||||||||||||||||
| ? "opacity-100" | ||||||||||||||||||||||||||||||||
| : "opacity-0", | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
| </CommandItem> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,16 @@ export function Run({ run, taskMetrics }: RunProps) { | |
| return ( | ||
| <> | ||
| <TableRow> | ||
| <TableCell>{run.model}</TableCell> | ||
| <TableCell> | ||
| <div> | ||
| <div>{run.model}</div> | ||
| {run.contextWindow && ( | ||
| <div className="text-xs text-muted-foreground"> | ||
| {(run.contextWindow / 1000).toFixed(0)}k context | ||
| </div> | ||
| )} | ||
| </div> | ||
| </TableCell> | ||
| <TableCell>{run.passed}</TableCell> | ||
| <TableCell>{run.failed}</TableCell> | ||
| <TableCell> | ||
|
|
@@ -76,7 +85,19 @@ export function Run({ run, taskMetrics }: RunProps) { | |
| </div> | ||
| )} | ||
| </TableCell> | ||
| <TableCell>{taskMetrics && formatCurrency(taskMetrics.cost)}</TableCell> | ||
| <TableCell> | ||
| {taskMetrics && ( | ||
| <div> | ||
| <div>{formatCurrency(taskMetrics.cost)}</div> | ||
| {(run.pricePerMillionInputTokens || run.pricePerMillionOutputTokens) && ( | ||
| <div className="text-xs text-muted-foreground"> | ||
| ${run.pricePerMillionInputTokens?.toFixed(2) || "?"}/$ | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pricing display format here shows as "$/$/M" which differs from the format in runs/[id]/run.tsx that shows "Pricing: $ / $ per 1M tokens". Could we standardize the display format across both components for consistency? |
||
| {run.pricePerMillionOutputTokens?.toFixed(2) || "?"}/M | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </TableCell> | ||
| <TableCell>{taskMetrics && formatDuration(taskMetrics.duration)}</TableCell> | ||
| <TableCell> | ||
| <DropdownMenu> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,17 @@ | ||||||||||||||||||||||
| import { z } from "zod" | ||||||||||||||||||||||
| import { useQuery } from "@tanstack/react-query" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Extended schema to include context window and pricing information | ||||||||||||||||||||||
| export const openRouterModelSchema = z.object({ | ||||||||||||||||||||||
| id: z.string(), | ||||||||||||||||||||||
| name: z.string(), | ||||||||||||||||||||||
| context_length: z.number().optional(), | ||||||||||||||||||||||
| pricing: z | ||||||||||||||||||||||
| .object({ | ||||||||||||||||||||||
| prompt: z.union([z.string(), z.number()]).optional(), | ||||||||||||||||||||||
| completion: z.union([z.string(), z.number()]).optional(), | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| .optional(), | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export type OpenRouterModel = z.infer<typeof openRouterModelSchema> | ||||||||||||||||||||||
|
|
@@ -29,4 +37,30 @@ export const useOpenRouterModels = () => | |||||||||||||||||||||
| useQuery({ | ||||||||||||||||||||||
| queryKey: ["getOpenRouterModels"], | ||||||||||||||||||||||
| queryFn: getOpenRouterModels, | ||||||||||||||||||||||
| staleTime: 1000 * 60 * 60, // Cache for 1 hour | ||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 1-hour cache time is hardcoded. Would it be useful to make this configurable for different environments (dev/staging/prod)?
Suggested change
|
||||||||||||||||||||||
| gcTime: 1000 * 60 * 60 * 24, // Keep in cache for 24 hours (gcTime replaces cacheTime in v5) | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Helper function to get model details by ID | ||||||||||||||||||||||
| export const getModelDetails = (models: OpenRouterModel[] | undefined, modelId: string) => { | ||||||||||||||||||||||
| if (!models) return null | ||||||||||||||||||||||
| return models.find((m) => m.id === modelId) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Helper function to convert pricing to per-million tokens | ||||||||||||||||||||||
| export const getPricingPerMillion = (pricing: OpenRouterModel["pricing"]) => { | ||||||||||||||||||||||
| if (!pricing) return { input: undefined, output: undefined } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const parsePrice = (price: string | number | undefined): number | undefined => { | ||||||||||||||||||||||
| if (price === undefined) return undefined | ||||||||||||||||||||||
| const numPrice = typeof price === "string" ? parseFloat(price) : price | ||||||||||||||||||||||
| if (isNaN(numPrice)) return undefined | ||||||||||||||||||||||
| // OpenRouter prices are typically per token, convert to per million | ||||||||||||||||||||||
| return numPrice * 1_000_000 | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| input: parsePrice(pricing.prompt), | ||||||||||||||||||||||
| output: parsePrice(pricing.completion), | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ALTER TABLE "runs" ADD COLUMN "context_window" integer;--> statement-breakpoint | ||
| ALTER TABLE "runs" ADD COLUMN "price_per_million_input_tokens" real;--> statement-breakpoint | ||
| ALTER TABLE "runs" ADD COLUMN "price_per_million_output_tokens" real; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intentional? The function now accepts additional parameters that aren't part of the CreateRun schema. Consider creating a proper extended type for better type safety: