Haystack with a custom OpenAI-compatible endpoint

In short: Haystack's OpenAI components take api_base_url and append the operation path themselves, so the value stops at the version segment, a full request address produces a doubled route. The embedder is a second integration with its own routed path, and vector dimensions are written into the document store at index time, so changing the embedding model without recreating the store fails later. Provider options travel in generation kwargs, which the component forwards without validating.

Haystack is a Python orchestration framework, and its LLM components are thin wrappers over the OpenAI SDK. That is good news for a gateway: the same api_base_url parameter that redirects to Azure OpenAI also redirects to any OpenAI-compatible endpoint, and nothing in your pipeline graph changes.

It is also the source of the only real trap. Two components read that parameter, they append different paths to it, and neither complains about a wrong value until you run the pipeline.

You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment

One parameter, two components

The generator and the embedder are separate components with separate base URLs:

ComponentImportWhat it appends
Generatorhaystack.components.generatorsthe chat completions route
Chat generatorhaystack.components.generators.chatthe chat completions route
Embedderhaystack.components.embeddersthe embeddings route

Because both are built on the OpenAI SDK, both expect a base URL that stops at the version segment. The version segment is yours; the operation path is appended by the component. Pasting a full request URL into api_base_url produces a doubled route, and the error that comes back is a 404 that names the concatenated path rather than the field you set.

The rule is the opposite of the one some other clients in this index use. Aider, for instance, wants the full path in some configurations. Haystack wants the root.

The key is a Secret object, not a string

api_key accepts a plain string, and that is the fastest way to get a first call working And the fastest way to ship a credential. The intended shape is a Secret:

The distinction matters at deploy time. from_env_var defers the lookup, so a variable set by your process manager after import is still found. A plain string captured at import is not.

Note that a local or self-hosted endpoint still needs some key value. The OpenAI SDK refuses to construct a client without one, so a placeholder token is the standard workaround rather than a misconfiguration.

Embeddings are a second Integration, not a detail

The most expensive mistake in a Haystack migration is treating the embedder as a copy of the generator. Vector dimensions are a property of the model, and they are written into the document store when you index.

If you keep the same embedding model name against a different endpoint, dimensions match and nothing changes. If you switch the embedding model, the store's configured dimension no longer matches the vectors the embedder returns, and the failure appears at write time rather than at configuration time. Recreating the store is the fix, and it is worth checking the returned vector length on a single document before indexing a corpus.

Prompt caching travels in extra body fields

Provider-side caching is not part of the OpenAI schema, so it is exposed through extra_body and forwarded by whichever gateway you use. In Haystack that means generation_kwargs, which are passed through to the request rather than interpreted by the component:

generation_kwargs={
    "temperature": 0,
    "extra_body": {"cache_control": {"type": "ephemeral"}},
}

Because the component does not validate these, a gateway that ignores an unknown field is indistinguishable from one that honours it until you compare bills. Keep the cacheable prefix, the system prompt and tool definitions, stable and in a fixed order, or the cache never hits regardless of the header.

What a RAG pipeline actually costs

A document question-answering pipeline is an unusual workload: the retriever supplies most of the input, and the answer is short.

A working configuration here runs about 200k input and 40k output tokens a day, which is 4M input and 0.8M output across 20 working days. The ratio is the reason the generator and the embedder should be sized separately The embedder has no output tokens at all, so a blended per-model comparison hides it entirely.

Per 1M input / output tokens and a 20-day month
ModelGateway rate
in / out per 1M tokens
One day20 days
gpt-5.6-terra$1.00 / $6.00$0.44$8.80
claude-sonnet-5$1.00 / $5.00$0.40$8.00
deepseek-v4-pro$0.66 / $1.98$0.21$4.22
glm-5.3$0.70 / $2.2$0.23$4.56

One day = 200k input + 40k output on this page's workload. 20 days = 4M input and 0.8M output. Rates checked 2026-10-15, gateway rates move with upstream promotions, so verify the current number in your dashboard before committing to a budget.

Across 221 catalogue models that publish both rates, the median output rate is 4.0× the input rate, and 69% of them (153 models) charge at least 4× more for output than input. The other 624 raw catalogue entries are dropped by cleaning (test entries, dated snapshots and non-chat variants) not for a missing rate. This is why an agent workload is decided on the output column, not the input one everyone quotes.

If you are picking a generation model for retrieval-augmented work, the <a href="/guides/cheapest-models-by-workload/">re-ranked index</a> sorts by workload shape rather than by input price, which is the comparison a retrieved-context pipeline actually needs.

FAQ

FAQ

Does Haystack need the base URL to include /v1?

Yes, as the trailing element. The components append the operation path, so the base URL has to include the version segment and stop there. A full request address in that field produces a doubled path.

Can I use one endpoint for the generator and the embedder?

Yes. Both take api_base_url, and a gateway that serves both routes can back the whole pipeline. Check that the embeddings route is actually exposed, some gateways proxy chat completions only.

Why does my document store reject embeddings after I changed the endpoint?

Almost always a dimension mismatch rather than an endpoint problem. Dimensions belong to the embedding model, and the store was sized when you indexed. Keeping the same model name keeps the dimension; changing the model requires recreating the store.

Do I need a real API key for a local server?

No. The SDK requires a non-empty value to construct a client, so a placeholder token is expected. That is a construction requirement, not authentication.

How do I pass provider-specific options like cache control?

Through generation_kwargs, which are forwarded to the request rather than interpreted by the component. Whether they take effect depends on the gateway, and the component cannot tell you either way.

Related

Get API access

Rate cards: GPT and Claude