In Haystack 3.0, what replaced OpenAIGenerator and what must you change?
answer
- Legacy generator family removed in 3.0
- Chat counterparts are the only line now
- Replies changed type, not just class name
- Read .text on each reply
- Plain string input still accepted
basics
~20 sHaystack 3.0 removed the legacy string-in generators, OpenAIGenerator among them. OpenAIChatGenerator replaces it: it accepts ChatMessage objects, or a plain string, and returns replies as ChatMessage objects, so downstream code must read reply.text instead of a raw string.
solid answer
~40 sHaystack 3.0 deleted the legacy generator line — `OpenAIGenerator`, `AzureOpenAIGenerator`, `HuggingFaceAPIGenerator`, `HuggingFaceLocalGenerator` — in favour of their chat counterparts such as `OpenAIChatGenerator`. The shape change is the part that bites: the old component took a prompt string and emitted `replies` as a list of strings, while the chat generator takes `messages` as a list of `ChatMessage` and emits `replies` as a list of `ChatMessage`. So anything reading the output must change from `replies[0]` to `replies[0].text`. Migration on the input side is gentler, because since 2.30 chat generators also accept a plain `str`, so a `PromptBuilder` wired straight into a generator can keep its connection. Move to `ChatPromptBuilder` when you want a real system role. Tool calling, `streaming_callback` and structured output all live on the chat line, which is why the split was collapsed.
code
python · 11 linesfrom haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.utils import Secret
generator = OpenAIChatGenerator(
model="gpt-4o-mini",
api_key=Secret.from_env_var("OPENAI_API_KEY"),
)
result = generator.run([ChatMessage.from_user("Name one Haystack component.")])
print(result["replies"][0].text)go deeper
Know that the chat generator is what you use now, that you give it messages or a string, and that each reply is a ChatMessage whose text you read with .text rather than using the reply directly.
Explain the socket-shape change on both sides, name which legacy generators were removed and their replacements, and say why the input side stayed compatible while the output side did not.
Drive a real migration: order the changes so the repo stays runnable, hunt down every consumer of replies including tests and logging, regenerate serialized pipeline files, and verify streaming and tool paths still behave.
Own the framework-upgrade decision — when to take a breaking major, what the collapse of two component families says about where model APIs are heading, and how you keep prompt and generator changes independently reviewable.
## What actually changed For most of the Haystack 2.x era there were two parallel families of generation components. The plain generators — `OpenAIGenerator` and friends — modelled a completion: a string in, strings out. The chat generators modelled a conversation: role-tagged messages in, messages out. Every provider integration had to ship both, every tutorial had to pick one, and every feature that only makes sense in a chat setting (tool calls, multi-turn history, assistant-side metadata) could only be added to one of them. Haystack 3.0 ends that. The legacy generators were deprecated through late 2.x and removed in 3.0: `OpenAIGenerator`, `AzureOpenAIGenerator`, `HuggingFaceAPIGenerator` and `HuggingFaceLocalGenerator` are gone, and the chat equivalents — `OpenAIChatGenerator`, `AzureOpenAIChatGenerator`, `HuggingFaceAPIChatGenerator`, `HuggingFaceLocalChatGenerator` — are the supported path. ## The socket shape, before and after The old component's `run()` took `prompt: str` and declared `replies: list[str]` plus `meta: list[dict]`. The chat generator takes `messages` and declares `replies` as a list of `ChatMessage`. A `ChatMessage` carries a role, its text (readable as `.text`), any tool calls the model requested, and per-reply metadata. That is the migration's real cost. Every place downstream of the generator that treated a reply as a string — an answer builder, a test assertion, a logging line, a custom component — has to reach through the message object now. It is a mechanical change, but it is not a no-op, and "just swap the class name" is the answer that gets a candidate caught. ## Why the input side is easy Starting from Haystack 2.30, chat generators also accept a plain `str` in place of a message list, wrapping it as a user turn. That was added specifically so the migration would not force every pipeline to be rewired at once: a `PromptBuilder` producing a string can still feed a chat generator, and the edge stays valid. Upgrading to `ChatPromptBuilder` is then a separate, optional step you take when you want an actual system message, few-shot assistant turns, or per-message metadata rather than one flat block of text. ## What the chat line buys you Collapsing to one family is not only tidiness. The features that matter in production only exist there: - **Tools.** A chat generator accepts a `tools` argument and can return tool calls inside its replies. The completion shape had nowhere to put them. - **Streaming.** Pass a `streaming_callback` and the component invokes it with `StreamingChunk` objects as tokens arrive; Haystack ships `print_streaming_chunk` for the common case of printing text and tool events. Note that streaming works with a single response, so keep the candidate count at one. - **Structured output and provider knobs** ride along in `generation_kwargs`, which is passed through to the provider call. - **Multi-turn state.** Because the input is a message list, conversation history is just a longer list, not a string you concatenated yourself. ## Configuration you should name `OpenAIChatGenerator` takes `model`, an `api_key` as a `Secret` (defaulting to reading `OPENAI_API_KEY` from the environment), `generation_kwargs` for provider parameters, `streaming_callback`, `tools`, and `api_base_url` for OpenAI-compatible endpoints. That last one is worth knowing: pointing `api_base_url` at a compatible gateway or local server is how many teams run this component against something other than OpenAI without writing a new integration. ## Migration in practice The order that keeps a codebase runnable: swap the generator class and its arguments first, since the connection from a string-producing builder still validates; then fix every consumer of `replies` to read `.text`; then, if you want roles, replace the prompt builder with the chat variant and rewrite the template as a message list. Haystack 3.0 ships a migration guide with a scanner that flags v2 patterns, which is worth mentioning as the pragmatic route for a large repository — but you still own the semantic review of anything that touched reply text. ## What still bites Two residual traps. First, serialized pipelines: a YAML file that names a removed component's import path will not load under 3.0, so stored pipeline definitions need regenerating, not just the Python. Second, tests that asserted on a plain string reply pass silently in neither direction — they fail loudly, which is the good case; the bad case is code that formats `replies[0]` into a template and now emits a message repr into a user-facing string.
- How do you stream tokens out of OpenAIChatGenerator?Pass a `streaming_callback`. Haystack ships `print_streaming_chunk`, which prints text and tool events; a custom callback receives `StreamingChunk` objects as they arrive. Streaming only works with a single candidate response, so do not raise the response count in `generation_kwargs`. Note the component's `replies` socket still emits complete messages at the end — the callback is the only place partial output appears, so anything downstream still sees whole replies.
- An old pipeline wires a string-producing prompt builder straight into the generator. What is the minimum change?Swap in `OpenAIChatGenerator` and leave the connection alone: since Haystack 2.30 chat generators accept a plain `str` as input, so the edge still validates. What you must change is every consumer of `replies`, which now holds `ChatMessage` objects rather than strings. Moving to `ChatPromptBuilder` is a separate step, worth doing when you want a genuine system role rather than a preamble.
- Why did Haystack collapse two generator families into one instead of keeping both?Every provider integration had to implement both surfaces, and each new capability — tool calling, tool-call streaming, multi-turn history, assistant metadata — only fits the message-shaped one. Maintaining a completion twin doubled the integration surface for a shape that could not express current model features. Since chat generators accept a bare string, the completion case survives as a convenience rather than as a second component family.
saying these in an interview costs you the question
- Claims OpenAIGenerator is still current in Haystack 3.0
- Expects replies to be a list of plain strings
- Thinks the chat generator refuses a plain string input
- Assumes swapping the class needs no downstream changes
- Forgets serialized YAML pipelines reference the removed class