{"slug": "show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price", "title": "Show HN: Anypick, open library for filtering LLMs based on capabilites and price", "summary": "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.", "body_md": "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.\n\nTwo implementations share the same design, fixtures, and behavior:\n\n- `anypick-python/`\n- `anypick-ts/`\n\n``` python\nimport os\nfrom anypick import anypick, ModelFilters\n\nsel = anypick(\n    filters=ModelFilters(\n        max_prompt_price=2e-6,\n        min_context_length=128_000,\n        requires_tools=True,\n    ),\n    strategy=\"cheapest\",\n    openrouter_api_key=os.environ[\"OPENROUTER_API_KEY\"],\n)\nprint(sel.model.id)\njs\nimport { anypick, ModelFilters } from \"anypick\";\n\nconst sel = await anypick({\n  filters: new ModelFilters({\n    maxPromptPrice: 2e-6,\n    minContextLength: 128_000,\n    requiresTools: true,\n  }),\n  strategy: \"cheapest\",\n  openrouterApiKey: process.env.OPENROUTER_API_KEY,\n});\n\nconsole.log(sel.model.id);\n```\n\nanypick v1 ships two model providers; wiring differs by capability:\n\n| provider | source | models | benchmarks | auth | docs | \n|---|---|---|---|---|---|\n| `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) | \n| `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) | \n\n`openrouter` (the default) is the only provider with both a model catalog and\nbenchmarks, so it supports every strategy. `vercel` is **models-only**: pair it\nwith `NoopBenchmarkObtainer`, and only price-only strategies (` cheapest`) are\nmeaningful — `cheapest_with_floor`, `best_score`, and `best_value` find no\nscores and behave as if every model's score is unknown.\n\nSelect a provider with the `obtainer` argument (or pass obtainers directly):\n\n``` python\nfrom anypick import anypick, ModelFilters, VercelModelObtainer, NoopBenchmarkObtainer\n\nsel = anypick(\n    filters=ModelFilters(requires_tools=True),\n    strategy=\"cheapest\",\n    obtainer=\"vercel\",   # or (VercelModelObtainer(), NoopBenchmarkObtainer())\n)\n```\n\nPrefer fine-grained control? Pass a custom model and/or benchmark obtainer\nindividually with `model_obtainer` / `benchmark_obtainer` — they override the\ncorresponding side of `obtainer`:\n\n``` python\nfrom anypick import anypick, MyModelObtainer, MyBenchmarkObtainer\n\nsel = anypick(\n    filters=ModelFilters(requires_tools=True),\n    model_obtainer=MyModelObtainer(),      # custom model catalog\n    benchmark_obtainer=MyBenchmarkObtainer(),  # custom benchmark feed\n)\n```\n\nFilters reduce `(models, scores)` to a subset before a strategy picks a\nwinner. Build them ergonomically with `ModelFilters`, or as composable\npredicates with `pred.*` — both compile to the same engine.\n\nEverything is optional; `None` means \"no constraint on this dimension\". Clauses\napply in the order listed (the order reported by `NoModelsFound.survivors_by_clause`).\n\n| category | field | semantics | \n|---|---|---|\n| ids / makers | `include_ids` | whitelist — keep only ids in the list | \n|  | `exclude_ids` | blacklist — drop ids in the list | \n|  | `include_makers` | whitelist — keep only makers in the list | \n|  | `exclude_makers` | blacklist — drop makers in the list | \n| price (USD/token) | `min_prompt_price` | `prompt_price ≥` value | \n|  | `max_prompt_price` | `prompt_price ≤` value | \n|  | `min_completion_price` | `completion_price ≥` value | \n|  | `max_completion_price` | `completion_price ≤` value | \n|  | `min_expected_cost` | `α·prompt + β·completion ≥` value | \n|  | `max_expected_cost` | `α·prompt + β·completion ≤` value | \n|  | `expected_cost_weights` | `(α, β)` for expected-cost bounds & strategies | \n|  | `max_cache_read_price` | `cache_read_price ≤` value (0.0 if n/a) | \n| context | `min_context_length` | `context_length ≥` value | \n|  | `max_context_length` | `context_length ≤` value | \n| modalities | `modalities_in` | inputs ⊇ given set | \n|  | `modalities_exactly` | inputs == given set | \n|  | `excludes_modalities` | inputs ∩ given set = ∅ | \n|  | `output_modalities_in` | outputs ⊇ given set | \n|  | `output_modalities_exactly` | outputs == given set | \n|  | `excludes_output_modalities` | outputs ∩ given set = ∅ | \n| capabilities (tri-state) | `requires_tools` | `None` =ignore ·`True` =must support ·`False` =must**not** | \n|  | `requires_reasoning` | as above | \n|  | `requires_structured_outputs` | as above | \n| benchmarks | `min_benchmarks` | list of `BenchmarkThreshold` ; pass**all** (AND) | \n|  | `max_benchmarks` | list of `BenchmarkThreshold` ; pass**all** (AND) | \n\nA **maker** is the `id` prefix before the first `/` (e.g. `\"openai\"` for\n`\"openai/gpt-4o\"`). Makers without a `/` have no maker: a maker whitelist drops\nthem, a blacklist leaves them alone. Sigils are preserved verbatim\n(OpenRouter's `~deepseek/...` has maker `~deepseek`).\n\n`BenchmarkThreshold` narrows by `source` (`\"artificial-analysis\"`,\n`\"openrouter\"`, `\"design-arena\"`), `task_type` (`coding | intelligence | agentic`), and `benchmark_type` (a specific OpenRouter benchmark field, e.g.\n`gpqa_diamond`), with `min`/` max` on the source's native scale.\n\nBenchmark matching: a model passes a threshold iff **at least one** of its\nscores matching the threshold's `source`/` task_type`/` benchmark_type`\n(wildcards) satisfies the bound. **Unknown ≠ zero** — a threshold with only\n`source`/` task_type` and no `min`/` max` keeps scoreless models.\n\nFor negation, alternation, or custom logic, compose predicates with `&` (and),\n`|` (or), and `~` (not); negate a capability to forbid it:\n\n``` python\nfrom anypick import pred\n\nf = (pred.maker_in([\"openai\", \"anthropic\"])\n     & pred.price_below(prompt=2e-6, completion=8e-6)\n     & pred.price_above(prompt=1e-7)          # skip free tier\n     & pred.context_at_least(128_000)\n     & pred.context_at_most(1_000_000)\n     & pred.modalities_exactly([\"text\", \"image\"])\n     & pred.supports_tools()\n     & ~pred.supports_reasoning()              # forbid reasoning\n     & pred.benchmark_above(task_type=\"coding\", min=60))\n```\n\n| predicate | keeps model if | \n|---|---|\n| `pred.id_in([...])` /`id_not_in([...])` | id is / isn't in the list | \n| `pred.maker_in([...])` /`maker_not_in([...])` | maker is / isn't in the list | \n| `pred.price_below(*, prompt=None, completion=None)` | each given price ≤ bound | \n| `pred.price_above(*, prompt=None, completion=None)` | each given price ≥ bound | \n| `pred.expected_cost_below(max, weights=(α,β))` | `α·prompt+β·completion ≤ max` | \n| `pred.expected_cost_above(min, weights=(α,β))` | `α·prompt+β·completion ≥ min` | \n| `pred.cache_read_price_below(max)` | `cache_read_price ≤ max` | \n| `pred.context_at_least(n)` /`context_at_most(n)` | `context_length ≥ / ≤ n` | \n| `pred.modalities_in([...])` /`modalities_exactly([...])` /`modalities_not_in([...])` | inputs ⊇ / == / ∩ ∅ | \n| `pred.output_modalities_in([...])` /`output_modalities_exactly([...])` /`output_modalities_not_in([...])` | outputs ⊇ / == / ∩ ∅ | \n| `pred.supports_tools()` /`supports_reasoning()` /`supports_structured_outputs()` | flag true (use `~` to forbid) | \n| `pred.benchmark_above(*, source=None, task_type=None, benchmark_type=None, min=None)` | ≥1 matching score ≥ `min` | \n| `pred.benchmark_below(*, ..., max=None)` | ≥1 matching score ≤ `max` | \n\nApply filters directly with `apply_filters(models, scores, filters)`; `pick_best`\nand `anypick` call it internally. See [`docs/filters.md`](/gi-dellav/anypick/blob/main/docs/filters.md) for\nfull semantics, and [`docs/strategies.md`](/gi-dellav/anypick/blob/main/docs/strategies.md) for the pickers\nthat run on the filtered set.\n\n- [`docs/`](/gi-dellav/anypick/blob/main/docs) — full design and API reference\n- [`anypick-python/README.md`](/gi-dellav/anypick/blob/main/anypick-python/README.md)\n- [`anypick-ts/README.md`](/gi-dellav/anypick/blob/main/anypick-ts/README.md)", "url": "https://wpnews.pro/news/show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price", "canonical_source": "https://github.com/gi-dellav/anypick", "published_at": "2026-09-08 14:42:30+00:00", "updated_at": "2026-09-08 14:59:11.585618+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "artificial-intelligence"], "entities": ["Anypick", "OpenRouter", "Vercel AI Gateway"], "alternates": {"html": "https://wpnews.pro/news/show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price", "markdown": "https://wpnews.pro/news/show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price.md", "text": "https://wpnews.pro/news/show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price.txt", "jsonld": "https://wpnews.pro/news/show-hn-anypick-open-library-for-filtering-llms-based-on-capabilites-and-price.jsonld"}}