Skip to content

Commit c487176

Browse files
authored
chore!: remove legacy generators (#11421)
1 parent f420e06 commit c487176

20 files changed

Lines changed: 248 additions & 2506 deletions

MIGRATION.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,134 @@ builder = PromptBuilder(
437437
)
438438
builder.run(name="John") # greeting renders as ""
439439
```
440+
441+
### Generators removed
442+
443+
**What changed:** `OpenAIGenerator`, `AzureOpenAIGenerator`, `HuggingFaceAPIGenerator`, and `HuggingFaceLocalGenerator` have been removed.
444+
Generators living in Haystack Core Integrations will also be removed soon.
445+
Their chat counterparts (`OpenAIChatGenerator`, `AzureOpenAIChatGenerator`, `HuggingFaceAPIChatGenerator`, `HuggingFaceLocalChatGenerator`) are the replacement. As of Haystack 3.0, all ChatGenerators also accept a plain `str` as input, so the migration rarely requires structural changes.
446+
447+
**Why:** Over time, Generators became shallow wrappers over the ChatGenerators, converting `str → ChatMessage → str` around the exact same model calls. All new features (tool calling, structured outputs, etc.) were introduced only in ChatGenerators, leaving the legacy classes behind. They were also a source of confusion for newcomers and an unnecessary duplication of code and tests.
448+
449+
**How to migrate:**
450+
451+
#### Direct usage (running a generator from Python code)
452+
453+
Before (v2.x):
454+
```python
455+
from haystack.components.generators import OpenAIGenerator
456+
457+
gen = OpenAIGenerator()
458+
result = gen.run("What is NLP?")
459+
text = result["replies"][0] # str
460+
meta = result["meta"][0] # dict with model metadata
461+
```
462+
463+
After (v3.0):
464+
```python
465+
from haystack.components.generators.chat import OpenAIChatGenerator
466+
467+
gen = OpenAIChatGenerator()
468+
result = gen.run("What is NLP?") # str input accepted directly
469+
reply = result["replies"][0] # ChatMessage
470+
text = reply.text # str
471+
meta = reply.meta # dict with model metadata (now on the message)
472+
```
473+
474+
#### System prompt
475+
476+
Before (v2.x):
477+
```python
478+
from haystack.components.generators import OpenAIGenerator
479+
480+
gen = OpenAIGenerator(system_prompt="You are concise.")
481+
result = gen.run("What is NLP?")
482+
```
483+
484+
After (v3.0):
485+
```python
486+
from haystack.components.generators.chat import OpenAIChatGenerator
487+
from haystack.dataclasses import ChatMessage
488+
489+
gen = OpenAIChatGenerator()
490+
result = gen.run([
491+
ChatMessage.from_system("You are concise."),
492+
ChatMessage.from_user("What is NLP?"),
493+
])
494+
```
495+
496+
#### Pipeline usage
497+
498+
Pipelines that connected `PromptBuilder` (output: `str`) to a legacy Generator continue to work unchanged when you swap in a ChatGenerator. The Haystack pipeline type system automatically converts `str` to `list[ChatMessage]` at the connection edge.
499+
500+
Before (v2.x):
501+
```python
502+
from haystack.components.generators import OpenAIGenerator
503+
from haystack.components.builders import PromptBuilder
504+
505+
pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
506+
pipeline.add_component("llm", OpenAIGenerator())
507+
pipeline.connect("prompt_builder", "llm") # str → str
508+
```
509+
510+
After (v3.0), minimal change (smart connection still works):
511+
```python
512+
from haystack.components.generators.chat import OpenAIChatGenerator
513+
from haystack.components.builders import PromptBuilder
514+
515+
pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
516+
pipeline.add_component("llm", OpenAIChatGenerator())
517+
pipeline.connect("prompt_builder", "llm") # str → list[ChatMessage], auto-converted
518+
```
519+
520+
Alternatively, for an idiomatic v3 pipeline use `ChatPromptBuilder`:
521+
```python
522+
from haystack.components.generators.chat import OpenAIChatGenerator
523+
from haystack.components.builders import ChatPromptBuilder
524+
from haystack.dataclasses import ChatMessage
525+
526+
template = [ChatMessage.from_user(prompt_template)]
527+
pipeline.add_component("prompt_builder", ChatPromptBuilder(template=template))
528+
pipeline.add_component("llm", OpenAIChatGenerator())
529+
pipeline.connect("prompt_builder.prompt", "llm.messages")
530+
```
531+
532+
#### `meta` output socket removed
533+
534+
Legacy Generators exposed a second output socket `meta: list[dict]`. ChatGenerators do not; per-reply metadata is embedded in each `ChatMessage.meta`. If you had a pipeline connection to `llm.meta`, remove it. `AnswerBuilder` already handles this automatically (it reads metadata from `ChatMessage.meta` when the replies are `list[ChatMessage]`).
535+
536+
Before (v2.x):
537+
```python
538+
pipeline.connect("llm.replies", "answer_builder.replies")
539+
pipeline.connect("llm.meta", "answer_builder.meta") # separate meta socket
540+
```
541+
542+
After (v3.0):
543+
```python
544+
pipeline.connect("llm.replies", "answer_builder.replies")
545+
# no meta connection needed; AnswerBuilder reads it from ChatMessage.meta
546+
```
547+
548+
### `DALLEImageGenerator` renamed to `OpenAIImageGenerator`
549+
550+
**What changed:** `DALLEImageGenerator` has been renamed to `OpenAIImageGenerator` and moved to `haystack.components.generators.openai_image_generator`. The API and parameters are otherwise unchanged.
551+
552+
**Why:** OpenAI retired the DALL-E model family. The new name reflects that the component works with the full OpenAI image generation API and is no longer tied to a specific model family.
553+
554+
**How to migrate:**
555+
556+
Before (v2.x):
557+
```python
558+
from haystack.components.generators import DALLEImageGenerator
559+
560+
generator = DALLEImageGenerator(model="dall-e-3")
561+
result = generator.run("A photo of a red apple")
562+
```
563+
564+
After (v3.0):
565+
```python
566+
from haystack.components.generators import OpenAIImageGenerator
567+
568+
generator = OpenAIImageGenerator(model="gpt-image-2")
569+
result = generator.run("A photo of a red apple")
570+
```

e2e/pipelines/test_rag_pipelines_e2e.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99

1010
from haystack import Document, Pipeline
1111
from haystack.components.builders.answer_builder import AnswerBuilder
12-
from haystack.components.builders.prompt_builder import PromptBuilder
12+
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
1313
from haystack.components.embedders import SentenceTransformersDocumentEmbedder, SentenceTransformersTextEmbedder
14-
from haystack.components.generators import OpenAIGenerator
14+
from haystack.components.generators.chat import OpenAIChatGenerator
1515
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever, InMemoryEmbeddingRetriever
1616
from haystack.components.writers import DocumentWriter
17+
from haystack.dataclasses import ChatMessage
1718
from haystack.document_stores.in_memory import InMemoryDocumentStore
1819

1920

@@ -23,24 +24,21 @@
2324
)
2425
def test_bm25_rag_pipeline(tmp_path):
2526
# Create the RAG pipeline
26-
prompt_template = """
27-
Given these documents, answer the question.\nDocuments:
28-
{% for doc in documents %}
29-
{{ doc.content }}
30-
{% endfor %}
31-
32-
\nQuestion: {{question}}
33-
\nAnswer:
34-
"""
27+
prompt_template = [
28+
ChatMessage.from_user(
29+
"Given these documents, answer the question.\nDocuments:\n"
30+
"{% for doc in documents %}\n {{ doc.content }}\n{% endfor %}\n"
31+
"\nQuestion: {{question}}\nAnswer:"
32+
)
33+
]
3534
rag_pipeline = Pipeline()
3635
rag_pipeline.add_component(instance=InMemoryBM25Retriever(document_store=InMemoryDocumentStore()), name="retriever")
37-
rag_pipeline.add_component(instance=PromptBuilder(template=prompt_template), name="prompt_builder")
38-
rag_pipeline.add_component(instance=OpenAIGenerator(), name="llm")
36+
rag_pipeline.add_component(instance=ChatPromptBuilder(template=prompt_template), name="prompt_builder")
37+
rag_pipeline.add_component(instance=OpenAIChatGenerator(), name="llm")
3938
rag_pipeline.add_component(instance=AnswerBuilder(), name="answer_builder")
4039
rag_pipeline.connect("retriever", "prompt_builder.documents")
41-
rag_pipeline.connect("prompt_builder", "llm")
40+
rag_pipeline.connect("prompt_builder.prompt", "llm.messages")
4241
rag_pipeline.connect("llm.replies", "answer_builder.replies")
43-
rag_pipeline.connect("llm.meta", "answer_builder.meta")
4442
rag_pipeline.connect("retriever", "answer_builder.documents")
4543

4644
# Serialize the pipeline to YAML
@@ -86,30 +84,27 @@ def test_bm25_rag_pipeline(tmp_path):
8684
)
8785
def test_embedding_retrieval_rag_pipeline(tmp_path):
8886
# Create the RAG pipeline
89-
prompt_template = """
90-
Given these documents, answer the question.\nDocuments:
91-
{% for doc in documents %}
92-
{{ doc.content }}
93-
{% endfor %}
94-
95-
\nQuestion: {{question}}
96-
\nAnswer:
97-
"""
87+
prompt_template = [
88+
ChatMessage.from_user(
89+
"Given these documents, answer the question.\nDocuments:\n"
90+
"{% for doc in documents %}\n {{ doc.content }}\n{% endfor %}\n"
91+
"\nQuestion: {{question}}\nAnswer:"
92+
)
93+
]
9894
rag_pipeline = Pipeline()
9995
rag_pipeline.add_component(
10096
instance=SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"), name="text_embedder"
10197
)
10298
rag_pipeline.add_component(
10399
instance=InMemoryEmbeddingRetriever(document_store=InMemoryDocumentStore()), name="retriever"
104100
)
105-
rag_pipeline.add_component(instance=PromptBuilder(template=prompt_template), name="prompt_builder")
106-
rag_pipeline.add_component(instance=OpenAIGenerator(), name="llm")
101+
rag_pipeline.add_component(instance=ChatPromptBuilder(template=prompt_template), name="prompt_builder")
102+
rag_pipeline.add_component(instance=OpenAIChatGenerator(), name="llm")
107103
rag_pipeline.add_component(instance=AnswerBuilder(), name="answer_builder")
108104
rag_pipeline.connect("text_embedder", "retriever")
109105
rag_pipeline.connect("retriever", "prompt_builder.documents")
110-
rag_pipeline.connect("prompt_builder", "llm")
106+
rag_pipeline.connect("prompt_builder.prompt", "llm.messages")
111107
rag_pipeline.connect("llm.replies", "answer_builder.replies")
112-
rag_pipeline.connect("llm.meta", "answer_builder.meta")
113108
rag_pipeline.connect("retriever", "answer_builder.documents")
114109

115110
# Serialize the pipeline to JSON

haystack/components/builders/prompt_builder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ class PromptBuilder:
4444
#### In a Pipeline
4545
4646
This is an example of a RAG pipeline where PromptBuilder renders a custom prompt template and fills it
47-
with the contents of the retrieved documents and a query. The rendered prompt is then sent to a Generator.
47+
with the contents of the retrieved documents and a query. The rendered prompt is then sent to a ChatGenerator.
4848
```python
4949
from haystack import Pipeline, Document
5050
from haystack.utils import Secret
51-
from haystack.components.generators import OpenAIGenerator
51+
from haystack.components.generators.chat import OpenAIChatGenerator
5252
from haystack.components.builders.prompt_builder import PromptBuilder
5353
5454
# in a real world use case documents could come from a retriever, web, or any other source
@@ -65,7 +65,7 @@ class PromptBuilder:
6565
\"\"\"
6666
p = Pipeline()
6767
p.add_component(instance=PromptBuilder(template=prompt_template), name="prompt_builder")
68-
p.add_component(instance=OpenAIGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")), name="llm")
68+
p.add_component(instance=OpenAIChatGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")), name="llm")
6969
p.connect("prompt_builder", "llm")
7070
7171
question = "Where does Joe live?"

haystack/components/generators/__init__.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,10 @@
77

88
from lazy_imports import LazyImporter
99

10-
_import_structure = {
11-
"openai": ["OpenAIGenerator"],
12-
"azure": ["AzureOpenAIGenerator"],
13-
"hugging_face_local": ["HuggingFaceLocalGenerator"],
14-
"hugging_face_api": ["HuggingFaceAPIGenerator"],
15-
"openai_dalle": ["DALLEImageGenerator"],
16-
}
10+
_import_structure = {"openai_image_generator": ["OpenAIImageGenerator"]}
1711

1812
if TYPE_CHECKING:
19-
from .azure import AzureOpenAIGenerator as AzureOpenAIGenerator
20-
from .hugging_face_api import HuggingFaceAPIGenerator as HuggingFaceAPIGenerator
21-
from .hugging_face_local import HuggingFaceLocalGenerator as HuggingFaceLocalGenerator
22-
from .openai import OpenAIGenerator as OpenAIGenerator
23-
from .openai_dalle import DALLEImageGenerator as DALLEImageGenerator
13+
from .openai_image_generator import OpenAIImageGenerator as OpenAIImageGenerator
2414

2515
else:
2616
sys.modules[__name__] = LazyImporter(name=__name__, module_file=__file__, import_structure=_import_structure)

0 commit comments

Comments
 (0)