Show HN: Anypick, open library for filtering LLMs based on capabilites and price Anypick, an open-source library for filtering and selecting LLMs across providers, has been released on GitHub. It offers two implementations in Python and TypeScript, supporting filters on capabilities, pricing, and benchmarks, with strategies like 'cheapest' to pick a winner. The default provider is OpenRouter, which includes both model catalogs and benchmarks, while Vercel AI Gateway is models-only. Select the best LLM across providers using filters over capabilities, pricing, and benchmarks, then pick a winner with a strategy. anypick only selects — you wire the chosen model into your chat client. Two implementations share the same design, fixtures, and behavior: - anypick-python/ - anypick-ts/ python import os from anypick import anypick, ModelFilters sel = anypick filters=ModelFilters max prompt price=2e-6, min context length=128 000, requires tools=True, , strategy="cheapest", openrouter api key=os.environ "OPENROUTER API KEY" , print sel.model.id js import { anypick, ModelFilters } from "anypick"; const sel = await anypick { filters: new ModelFilters { maxPromptPrice: 2e-6, minContextLength: 128 000, requiresTools: true, } , strategy: "cheapest", openrouterApiKey: process.env.OPENROUTER API KEY, } ; console.log sel.model.id ; anypick v1 ships two model providers; wiring differs by capability: | provider | source | models | benchmarks | auth | docs | |---|---|---|---|---|---| | openrouter | OpenRouter API | ✅ GET /api/v1/models | ✅ GET /api/v1/benchmarks | optional OPENROUTER API KEY required for benchmarks, 30 req/min · 500 req/day | docs/providers/openrouter.md /gi-dellav/anypick/blob/main/docs/providers/openrouter.md | | vercel | Vercel AI Gateway | ✅ GET /v1/models | ❌ none no benchmark feed | optional VERCEL AI GATEWAY API KEY raises rate ceiling | docs/providers/vercel.md /gi-dellav/anypick/blob/main/docs/providers/vercel.md | openrouter the default is the only provider with both a model catalog and benchmarks, so it supports every strategy. vercel is models-only : pair it with NoopBenchmarkObtainer , and only price-only strategies cheapest are meaningful — cheapest with floor , best score , and best value find no scores and behave as if every model's score is unknown. Select a provider with the obtainer argument or pass obtainers directly : python from anypick import anypick, ModelFilters, VercelModelObtainer, NoopBenchmarkObtainer sel = anypick filters=ModelFilters requires tools=True , strategy="cheapest", obtainer="vercel", or VercelModelObtainer , NoopBenchmarkObtainer Prefer fine-grained control? Pass a custom model and/or benchmark obtainer individually with model obtainer / benchmark obtainer — they override the corresponding side of obtainer : python from anypick import anypick, MyModelObtainer, MyBenchmarkObtainer sel = anypick filters=ModelFilters requires tools=True , model obtainer=MyModelObtainer , custom model catalog benchmark obtainer=MyBenchmarkObtainer , custom benchmark feed Filters reduce models, scores to a subset before a strategy picks a winner. Build them ergonomically with ModelFilters , or as composable predicates with pred. — both compile to the same engine. Everything is optional; None means "no constraint on this dimension". Clauses apply in the order listed the order reported by NoModelsFound.survivors by clause . | category | field | semantics | |---|---|---| | ids / makers | include ids | whitelist — keep only ids in the list | | | exclude ids | blacklist — drop ids in the list | | | include makers | whitelist — keep only makers in the list | | | exclude makers | blacklist — drop makers in the list | | price USD/token | min prompt price | prompt price ≥ value | | | max prompt price | prompt price ≤ value | | | min completion price | completion price ≥ value | | | max completion price | completion price ≤ value | | | min expected cost | α·prompt + β·completion ≥ value | | | max expected cost | α·prompt + β·completion ≤ value | | | expected cost weights | α, β for expected-cost bounds & strategies | | | max cache read price | cache read price ≤ value 0.0 if n/a | | context | min context length | context length ≥ value | | | max context length | context length ≤ value | | modalities | modalities in | inputs ⊇ given set | | | modalities exactly | inputs == given set | | | excludes modalities | inputs ∩ given set = ∅ | | | output modalities in | outputs ⊇ given set | | | output modalities exactly | outputs == given set | | | excludes output modalities | outputs ∩ given set = ∅ | | capabilities tri-state | requires tools | None =ignore · True =must support · False =must not | | | requires reasoning | as above | | | requires structured outputs | as above | | benchmarks | min benchmarks | list of BenchmarkThreshold ; pass all AND | | | max benchmarks | list of BenchmarkThreshold ; pass all AND | A maker is the id prefix before the first / e.g. "openai" for "openai/gpt-4o" . Makers without a / have no maker: a maker whitelist drops them, a blacklist leaves them alone. Sigils are preserved verbatim OpenRouter's ~deepseek/... has maker ~deepseek . BenchmarkThreshold narrows by source "artificial-analysis" , "openrouter" , "design-arena" , task type coding | intelligence | agentic , and benchmark type a specific OpenRouter benchmark field, e.g. gpqa diamond , with min / max on the source's native scale. Benchmark matching: a model passes a threshold iff at least one of its scores matching the threshold's source / task type / benchmark type wildcards satisfies the bound. Unknown ≠ zero — a threshold with only source / task type and no min / max keeps scoreless models. For negation, alternation, or custom logic, compose predicates with & and , | or , and ~ not ; negate a capability to forbid it: python from anypick import pred f = pred.maker in "openai", "anthropic" & pred.price below prompt=2e-6, completion=8e-6 & pred.price above prompt=1e-7 skip free tier & pred.context at least 128 000 & pred.context at most 1 000 000 & pred.modalities exactly "text", "image" & pred.supports tools & ~pred.supports reasoning forbid reasoning & pred.benchmark above task type="coding", min=60 | predicate | keeps model if | |---|---| | pred.id in ... / id not in ... | id is / isn't in the list | | pred.maker in ... / maker not in ... | maker is / isn't in the list | | pred.price below , prompt=None, completion=None | each given price ≤ bound | | pred.price above , prompt=None, completion=None | each given price ≥ bound | | pred.expected cost below max, weights= α,β | α·prompt+β·completion ≤ max | | pred.expected cost above min, weights= α,β | α·prompt+β·completion ≥ min | | pred.cache read price below max | cache read price ≤ max | | pred.context at least n / context at most n | context length ≥ / ≤ n | | pred.modalities in ... / modalities exactly ... / modalities not in ... | inputs ⊇ / == / ∩ ∅ | | pred.output modalities in ... / output modalities exactly ... / output modalities not in ... | outputs ⊇ / == / ∩ ∅ | | pred.supports tools / supports reasoning / supports structured outputs | flag true use ~ to forbid | | pred.benchmark above , source=None, task type=None, benchmark type=None, min=None | ≥1 matching score ≥ min | | pred.benchmark below , ..., max=None | ≥1 matching score ≤ max | Apply filters directly with apply filters models, scores, filters ; pick best and anypick call it internally. See docs/filters.md /gi-dellav/anypick/blob/main/docs/filters.md for full semantics, and docs/strategies.md /gi-dellav/anypick/blob/main/docs/strategies.md for the pickers that run on the filtered set. - docs/ /gi-dellav/anypick/blob/main/docs — full design and API reference - anypick-python/README.md /gi-dellav/anypick/blob/main/anypick-python/README.md - anypick-ts/README.md /gi-dellav/anypick/blob/main/anypick-ts/README.md