LLMs: the free chain, adding keys, OpenRouter and check:llm#
vid2context uses an LLM to clean captions, name speakers, write briefs and answer questions. It works with no money and no keys: keyless lanes answer when nothing else is set up, and every free key you add gives it more rungs to fall back on.
How the chain picks a rung#
A rung is one service, one model and one key. Every LLM provider builds its rungs from env (no network), and the chain (packages/core/src/chain.ts) orders the enabled ones:
- By tier:
local(your machine), thenfree(a hosted free tier with your key), thenkeyless(hosted, no account), thenpaid(only when its key is set). - By priority inside a tier. In the free tier: Groq, Cerebras, OpenRouter free-auto, NVIDIA, Kilo with a key, SambaNova, Cloudflare, Mistral, Zhipu, GitHub, Gemini, SiliconFlow, then Groq's small gpt-oss-20b. In the keyless tier: Kilo, OVH, Pollinations.
- The first rung that answers wins. A rung that fails with a rate limit, a bad key or a retired model cools down for a while, so the next request skips it.
VID2CONTEXT_POLICY=quality orders by each model's quality score instead. local-only keeps everything on your machine. VID2CONTEXT_SKIP=ovh,pollinations drops services, and VID2CONTEXT_KEYLESS=0 turns off every keyless lane (they send text to third parties without an account).
The rung list, with what each one needs, is in docs/providers.md. GET /api/v1/health shows which rungs are enabled right now and which env vars the others need, never the values.
Adding free keys#
Put keys in .env (copy .env.example). Each one adds rungs with their own quota:
| Key | What it adds |
|---|---|
GROQ_API_KEY, CEREBRAS_API_KEY |
fast free tiers, one rung per model |
OPENROUTER_API_KEY |
the best live free model on OpenRouter |
NVIDIA_API_KEY |
Nemotron 3 Super on NVIDIA NIM |
SAMBANOVA_API_KEY |
Llama 3.3 70B |
CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN |
Llama 3.3 70B on Workers AI, 10,000 neurons a day |
MISTRAL_API_KEY, ZHIPU_API_KEY, GITHUB_TOKEN, GEMINI_API_KEY, SILICONFLOW_API_KEY |
more independent free quotas |
KILO_API_KEY |
raises the Kilo gateway limits (it works without one) |
Two env patterns work for every service:
<PROVIDER>_FALLBACK_KEYS=key2,key3adds one rung per extra key (for example two free accounts). Keep<PROVIDER>_API_KEYa single key.<PROVIDER>_MODEL=a,breplaces that service's model list. Free model ids get retired; when a rung keeps failing withnot-found, check the service's model list and set this.
For Cloudflare, a CLOUDFLARE_FALLBACK_KEYS entry can be <account_id>:<token> to pool a second account.
OpenRouter#
OpenRouter retires :free model ids without notice, so vid2context never pins one. The llm/openrouter/free-auto rung:
- Reads the public model list (
GET https://openrouter.ai/api/v1/models, no key needed) and keeps models that are free on both input and output, whose id ends in:free, that take and return text, and that have at least 32k tokens of context. - Ranks them by context length, how recent they are, and a small preference for strong open families (Gemma, Nemotron, Qwen, DeepSeek, Llama, Mistral, gpt-oss, GLM, Kimi). Models of under 7B parameters rank last.
- Keeps that ranking in memory for an hour.
- On each request it tries the best candidate. If that model is retired, rate limited or failing, it tries the next, up to three. A model that failed sits out for a while (30 minutes when retired). A bad key stops at once.
The answer reports the model that actually replied (ChatResult.model), so briefs can say which model wrote them.
OPENROUTER_MODEL=vendor/model,... adds one fixed rung per id. An id without :free is a paid rung, and the chain uses it only when paid rungs are allowed (VID2CONTEXT_ALLOW_PAID is not 0).
Checking what works today: npm run check:llm#
npm run check:llm # every enabled rung, paid ones skipped
npm run check:llm -- --only llm/kilo # by id prefix, service, provider or full id
npm run check:llm -- --skip ovh,pollinations
npm run check:llm -- --include-paid # also call paid rungs (costs a little)
npm run check:llm -- --json # machine-readable
npm run check:llm -- --timeout 30000 --concurrency 2It sends every enabled rung one real request ("What is 17 plus 25? Reply with the number only.") and prints a table:
rung tier service ms correct outcome
llm/kilo/nvidia/nemotron-3-ultra-550b-a55b:free keyless kilo 1080 yes ok
llm/ovh/gpt-oss-120b keyless ovh 45000 - timeout: The operation was aborted due to timeout- ms: time to the full answer.
- correct:
yeswhen the answer is 42 and nothing else,nowhen the rung answered something else. Anousually means a model that ignores instructions or a broken answer parser.-means no answer. - outcome:
ok, or the error code and a short reason.rate-limited-429: the quota is used up for now (keyless lanes allow a few requests per minute).auth-401/auth-403: the key is wrong or lacks permission.not-found-404: the model id was retired, so set<PROVIDER>_MODEL.upstream-5xx: the service is having trouble.timeout: no answer within the timeout (free queues can be slow).
Rungs of the same service run one after another and up to four services run at once, so keyless hosts such as OVH (about 2 requests per minute per model) are not hammered. The exit code is 1 when no rung answered, so the command works as a health check in scripts. Keys are never printed: failure reasons are redacted.