Skip to content

fix: support Haystack 3.0's lazy OpenAI client initialization in OpenAI-inheriting generators#3536

Open
julian-risch wants to merge 3 commits into
mainfrom
fix/lazy-openai-client-compat
Open

fix: support Haystack 3.0's lazy OpenAI client initialization in OpenAI-inheriting generators#3536
julian-risch wants to merge 3 commits into
mainfrom
fix/lazy-openai-client-compat

Conversation

@julian-risch

@julian-risch julian-risch commented Jul 2, 2026

Copy link
Copy Markdown
Member

Related Issues

  • Part of deepset-ai/haystack-private#446

Haystack 3.0 no longer creates the OpenAI clients in __init__: self.client/self.async_client are None until warm_up()/warm_up_async(), the _is_warmed_up flag is gone, and a missing API key raises at warm-up instead of at init. This breaks the ten integrations that subclass OpenAIChatGenerator/OpenAIGenerator/OpenAIResponsesChatGenerator/the OpenAI embedders.

Proposed Changes:

Source changes

  • mistral, openrouter (run/run_async overrides): replace the removed if not self._is_warmed_up: self.warm_up() with an unconditional self.warm_up() (idempotent on both versions); in run_async, call warm_up_async() when it exists (3.0) so the async client is created on the running event loop; narrow the now-Optional clients for mypy with the same assert-after-warm-up idiom that haystack 3.0's own run() uses.
  • perplexity chat generator: only wrap the clients with the Perplexity attribution headers in __init__ when they exist (2.x); add warm_up/warm_up_async overrides that apply the headers when 3.0 creates the clients.
  • perplexity embedders: the attribution header is injected via http_client_kwargs, but the serialized self.http_client_kwargs intentionally keeps the user-provided value — under 3.0 the client is built from that attribute at warm-up, losing the header. Add warm_up/warm_up_async overrides that swap the enriched kwargs in during client creation (no-ops on 2.x, which has no embedder warm-up and builds the client in __init__).

Test changes (aimlapi, cometapi, meta_llama, mistral, nvidia, openrouter, orcarouter, perplexity, stackit, togetherai)

  • test_init_default/test_init_with_parameters: call component.warm_up() before asserting on client attributes (harmless on 2.x, required on 3.0).
  • test_init_fail_wo_api_key/test_from_dict_fail_wo_env_var: run init/from_dict and warm_up() inside pytest.raises(ValueError), accepting the error from either version's raise point. The error message is identical on both.
  • test_init_default_async: converted to async tests that await component.warm_up_async() when available, since 3.0 only creates the async client there.

How did you test it?

  • Haystack 2.x: unit tests pass.
  • Haystack v3 branch (installed git+https://github.com/deepset-ai/haystack.git@v3 into the test envs; for the seven suites that also need the ToolInvoker import guard I verified on a local merge with test: guard ToolInvoker imports so chat-generator tests run under Haystack 3.0 #3535):
    • test:types is clean in all ten — the _is_warmed_up/OpenAI | None mypy errors from the sweep are gone.
    • All lazy-client test failures are fixed. The only remaining v3 unit failures are addressed in other opened PRs.

Notes for the reviewer

Checklist

🤖 Generated with Claude Code

Haystack 3.0 no longer creates the OpenAI clients in __init__: they are
None until warm_up()/warm_up_async(), and a missing API key raises at
warm-up instead of init. Adapt the OpenAI-inheriting integrations while
keeping 2.x behavior:

- mistral, openrouter: replace the removed _is_warmed_up flag with an
  unconditional idempotent warm_up() (warm_up_async in run_async when
  available) and narrow the now-Optional clients for mypy.
- perplexity: only wrap the clients with attribution headers in __init__
  when they exist (2.x); override warm_up/warm_up_async to inject the
  headers when 3.0 creates the clients.
- tests: warm the component up before asserting on client attributes and
  accept the API-key error from either __init__ (2.x) or warm_up (3.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (meta_llama)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (orcarouter)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (nvidia)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (openrouter)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/openrouter/src/haystack_integrations/components/generators/openrouter/chat
  chat_generator.py 434
Project Total  

This report was generated by python-coverage-comment-action

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (cometapi)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (stackit)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (togetherai)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (aimlapi)

This PR does not seem to contain any modification to coverable code.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (mistral)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/mistral/src/haystack_integrations/components/generators/mistral/chat
  chat_generator.py 389-390, 432, 463-464
Project Total  

This report was generated by python-coverage-comment-action

@julian-risch julian-risch marked this pull request as ready for review July 2, 2026 15:22
@julian-risch julian-risch requested a review from a team as a code owner July 2, 2026 15:22
@julian-risch julian-risch requested review from sjrl and removed request for a team July 2, 2026 15:22
…dder

perplexity's ruff config forbids assert outside tests (S101); raise a
RuntimeError instead when the client is missing, which narrows the
Optional type for mypy just as well.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage report (perplexity)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/perplexity/src/haystack_integrations/components/embedders/perplexity
  document_embedder.py 157-166, 176-185
  text_embedder.py 129-138, 148-157
  integrations/perplexity/src/haystack_integrations/components/generators/perplexity/chat
  chat_generator.py 153, 164-167
Project Total  

This report was generated by python-coverage-comment-action

max_retries=max_retries,
http_client_kwargs=_http_client_kwargs_with_attribution(http_client_kwargs),
)
self.http_client_kwargs = http_client_kwargs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if instead of the new warm_up methods we should instead store the original user provided http_client_kwargs under a new key like self._http_client_kwargs = http_client_kwargs and then update the to_dict method to use this internal key.

Then I would set self.http_client_kwargs = _http_client_kwargs_with_attribution(http_client_kwargs) which would avoid the need for the new warm_up methods. WDYT?

Review feedback on #3536: drop the warm-up dev comments in run(), and replace
the assert / raise-RuntimeError narrowing with type: ignore[union-attr]
comments, consistent with how the repo usually silences mypy for attributes
that are Optional only with haystack-ai >= 3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants