cd /news/ai-tools/show-hn-anypick-open-library-for-fil… · home topics ai-tools article
[ARTICLE · art-123451] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

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.

read5 min views1 publishedSep 8, 2026
Show HN: Anypick, open library for filtering LLMs based on capabilites and price
Image: Michielbdejong (auto-discovered)

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/
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
vercel Vercel AI Gateway GET /v1/models ❌ none (no benchmark feed) optional VERCEL_AI_GATEWAY_API_KEY (raises rate ceiling) 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):

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:

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 =mustnot
requires_reasoning as above
requires_structured_outputs as above
benchmarks min_benchmarks list of BenchmarkThreshold ; passall (AND)
max_benchmarks list of BenchmarkThreshold ; passall (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:

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 for full semantics, and docs/strategies.md for the pickers that run on the filtered set.

── more in #ai-tools 4 stories · sorted by recency
── more on @anypick 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-anypick-open…] indexed:0 read:5min 2026-09-08 ·