ReqLLM: One Elixir API Across 21 LLM Providers, and the Cost of That Uniformity
Composable Elixir library for LLM interactions built on Req and Finch
At a glance
- What is it?
- ReqLLM is an Apache-2.0 Elixir library that wraps provider APIs behind Req and Finch and normalizes their requests and responses. It is a good fit for Elixir teams calling several providers from one codebase, and a poor fit for anyone who needs a provider feature the shared layer does not model.
- Who is it for?
- Adopt ReqLLM if your Elixir service already talks to more than one model provider and you want a single call site, a single response shape and a model registry you do not maintain yourself. Skip it if you depend on one provider only, or on an API surface the shared layer does not cover.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Elixir, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem ReqLLM solves: provider APIs that disagree with each other
Every LLM provider ships its own request body, its own option names and its own response envelope. A team that calls Anthropic for one task and OpenAI for another ends up maintaining two client modules, two error shapes and two sets of retry logic. ReqLLM's stated goal is to remove that duplication: it is, in the README's words, a package that standardizes requests and responses across providers. The audience is Elixir developers who already build on Req and Finch and who would rather not hand-roll a provider abstraction. The README claims 1,205 models across 21 implemented provider integrations, drawn from a catalog called LLMDB, with 1,218 models across 22 provider namespaces if you count google_vertex_anthropic as a separate namespace. Those numbers describe catalog breadth, not verified compatibility, and the README is careful to separate the two: a recorded specs column tracks how many model specs exist as fixtures. OpenRouter shows 234 recorded specs against 364 catalog models. Alibaba and Alibaba Cloud Bailian (China) show 0 recorded specs against 50 and 82 catalog models respectively. The gap between those columns is the honest signal in the table.
How the two-layer design splits calls from streams
ReqLLM has a high-level API and a transport layer underneath it. The high-level functions are described as Vercel AI SDK-inspired: generate_text/3, stream_text/3, generate_object/4 and others. They normalize requests and responses so the calling code does not branch on provider. Below that sit provider transports, and here the design splits in a way worth understanding before you adopt it. Req handles request and response calls. Finch handles streaming. The README states the reason plainly in a footnote: streaming uses Finch directly due to known Req limitations with SSE responses. Provider callbacks translate model metadata, options, bodies and responses behind the same public API, which means adding a provider is a matter of implementing that callback surface rather than touching the call sites. The practical consequence is that a non-streaming call and a streaming call do not travel the same path through the stack. If you are debugging a stream that stalls, you are debugging Finch and SSE handling, not the Req pipeline you may have instrumented. That is a real architectural seam, not a detail.
Installing ReqLLM and picking a model string
The recommended install path uses Igniter: mix igniter.install req_llm. The manual path adds {:req_llm, "~> 1.6"} to your deps list in mix.exs and then runs mix deps.get. Two providers need extra dependencies that are deliberately optional, so a project that never touches them pulls nothing extra. For amazon_bedrock you add {:ex_aws_auth, "~> 1.4"}, which covers AWS Signature V4 request signing with IAM credentials, STS and bidirectional streaming. For google_vertex you add {:goth, "~> 1.4"}, which covers Google Cloud OAuth through Application Default Credentials, refresh tokens, workload identity and the metadata server. The README notes that if you use one of these providers without the matching dependency, ReqLLM raises an error naming the package to add. It also notes two paths that avoid the extra libraries entirely: Bedrock's API-key auth and Vertex's service-account JWT signing. Model selection is a string of the form provider:model, as in "anthropic:claude-haiku-4-5". Credentials come from .env files or environment variables, handled by ReqLLM.Keys. The README's quick start shows generate_text!/3 returning a plain string and generate_object!/4 taking a schema keyword list such as [name: [type: :string, required: true], age: [type: :pos_integer]] and returning a map like %{name: "John Doe", age: 30}. Structured output is therefore part of the core API rather than a bolt-on.
Where the shared abstraction runs out
A single normalized interface is a lossy interface. Anything a provider exposes that does not have a slot in the shared model cannot be reached through the high-level functions, and ReqLLM's own table shows how uneven the surface is. Text is universal, but 92 non-text operation models are tracked across embedding, image generation, text-to-speech, transcription, rerank and OCR, and they are distributed very unevenly. OpenAI carries text, embedding, image, speech and transcription. Cohere carries text and rerank. ElevenLabs carries speech only. MiniMax carries text and video. Groq carries text, speech and transcription. If your workload is a rerank call against a provider whose rerank support is not modeled, the unified API does not help you. The second limitation is the recorded specs column. Providers with 0 recorded specs have no fixture coverage listed, which means the compatibility claim for those models rests on the catalog entry rather than on a recorded interaction. That is not the same as saying they are broken; it means you are the one who finds out. The third limitation is structural: because streaming bypasses Req, any middleware, instrumentation or retry configuration you attach at the Req level does not apply to streams. Teams that standardize on Req plugins for observability should plan for that asymmetry.
The alternative: OpenRouter as a single endpoint instead of a client-side abstraction
The obvious alternative is to stop abstracting on the client and route everything through OpenRouter instead. ReqLLM's own table shows why this is a genuine fork in approach rather than a like-for-like swap: OpenRouter appears in the registry with 364 catalog models and 234 recorded specs, the largest single entry. With that route, one HTTP endpoint and one credential replace N provider integrations, and the provider-selection logic moves to the request body rather than to your dependency tree. The difference is where the abstraction lives. ReqLLM keeps you talking to each provider directly and normalizes in Elixir, which means your traffic does not pass through an intermediary and your provider credentials stay yours. OpenRouter puts the normalization on a hosted service, which means one integration to maintain but also one more party in the request path and a dependency on that service's model coverage. A team already standardized on Req and Finch gets more from ReqLLM, because the transport is the one they already run. A team that wants the smallest possible client surface, or that is not on Elixir at all, gets more from the gateway.
Maintenance cost and the Apache-2.0 licence
The release cadence is visible in the recent tags: v1.22.0 on 2026-09-04, v1.21.1 on 2026-08-26, v1.21.0 on 2026-08-23. Three releases in roughly two weeks, with the repository last pushed on 2026-09-09. That pace is what you would expect from a library tracking provider APIs that change on their own schedules, and it sets the upgrade cost: you are subscribing to a moving target, and the project moves to keep up. Pinning with a pessimistic constraint such as {:req_llm, "~> 1.6"} limits how far a mix deps.update can carry you, but it also means you periodically have to move the constraint forward yourself. The fixture suite, described as 622 unique recorded model specs, is the mechanism that makes those bumps safer, because a recorded spec is a regression test against real provider behavior. That is also why the 0-recorded-spec providers are the ones to watch on upgrade. The licence is Apache-2.0, which is permissive and includes an explicit patent grant. That is a statement about the licence text, not legal advice; if you redistribute the library or embed it in a product with unusual licensing constraints, have counsel read the terms rather than taking this summary as sufficient.
Who should adopt ReqLLM
Adopt it if you are writing Elixir, you already depend on Req or Finch, and your application genuinely calls more than one provider. The payoff is concentrated in exactly that case: one call site, one response shape, one place to change a model string, and a catalog you do not have to maintain by hand. Structured output through generate_object!/4 and streaming through stream_text/3 being part of the same surface is the part that saves the most code. Do not adopt it if you call a single provider and use only its text endpoint. You would be adding a normalization layer whose whole purpose is to hide differences you never encounter, and you would inherit the release cadence for no benefit. Do not adopt it if your workload depends on a provider capability that the operation surface does not list, or on a provider sitting at 0 recorded specs, without first confirming that the specific call you need is supported. Before you commit, resolve your exact model string against the registry, check the recorded specs column for that provider, and confirm whether your path is a Req call or a Finch stream, because that determines which layer you will be debugging when something goes wrong.
Editorial conclusion
Adopt ReqLLM if your Elixir service already talks to more than one model provider and you want a single call site, a single response shape and a model registry you do not maintain yourself. Skip it if you depend on one provider only, or on an API surface the shared layer does not cover. Before committing, run mix igniter.install req_llm, then check that your exact model string resolves in the registry and that its provider has recorded specs in the README table; providers listed with 0 recorded specs have no compatibility ledger to lean on.
Community notes