🤗

MAS Konzept

🥘

Architektur eines Kulinarischen MAS

Ein Deep-Dive in die Orchestrierung von Multi-Agenten-Systemen (MAS) für "The Infinite Cookbook". Wir zerlegen komplexe Aufgaben (Recherche, Strukturierung, Bildgenerierung, Layout) in spezialisierte Haystack 2.0 Pipelines und verbinden sie über Hugging Face Open-Source LLMs zu einem autonomen System.

🏗️ 1. Das Konzept: Haystack 2.0

Die Evolution der KI verlangt nach neuen Architekturen. Der Übergang von Haystack 1.x zu 2.0 markiert einen Paradigmenwechsel: Frühere Versionen waren auf lineare Ketten ausgelegt, Haystack 2.0 führt Komponenten und Pipelines als dynamische Rechengraphen ein.

Diese Graphen unterstützen Kontrollflüsse, Schleifen und präzises Routing. Ein Agent in diesem Ökosystem ist weit mehr als ein Chatbot; er ist ein System, das ein LLM als Steuerungsorgan nutzt, um Aufgaben zu planen und an spezialisierte Sub-Pipelines (andere Agenten) zu delegieren. Typprüfung an den Ein- und Ausgabesockets sichert dabei die Stabilität vor der Laufzeit.

🤗 2. Hugging Face Inference Provider

Hugging Face ist der zentrale Marktplatz für Open-Source-Intelligenz. Im Juli 2025 stellte die Inference API auf chat_completion-Endpunkte um. Haystack bindet diese Modelle über die HuggingFaceAPIChatGenerator-Komponente ein. Durch dynamische Suffixe wie :fastest oder Provider-Wahl können wir Lasten optimieren:

Infrastruktur-Option Charakteristika Anwendungsfall im MAS
Serverless Inference API Kostenlos/Rate-limited, schnell Prototyping und einfache Zusammenfassungen (z.B. Qwen 72B).
Inference Endpoints Dedizierte Instanzen, /h Abrechnung Produktion, garantierte Latenz, Bildgenerierung (SDXL / FLUX).
Text Generation Inference (TGI) Optimiertes Serving, JSON-Guiding Komplexe Datenextraktion mit strikter Schema-Einhaltung.

🔍 3. Agent 1: Forschung & Suche

Die erste Phase ist die Deep Research Phase. Dieser Agent durchsucht das Web iterativ nach authentischen Rezepten, regionalen Varianten und historischen Hintergründen. Er nutzt Tools, um Links zu besuchen, HTML zu bereinigen und die Ergebnisse nach Relevanz (Context Optimization) zu ranken, bevor sie ans LLM gehen.

agents/researcher.py
from haystack import Pipeline
from haystack.components.websearch import SerperDevWebSearch
from haystack.components.fetchers import LinkContentFetcher
from haystack.components.converters import HTMLToDocument
from haystack.components.rankers import TransformersSimilarityRanker

def build_research_pipeline() -> Pipeline:
    pipe = Pipeline()

    # Komponenten initialisieren
    pipe.add_component("search", SerperDevWebSearch(api_key="DEIN_API_KEY"))
    pipe.add_component("fetcher", LinkContentFetcher())
    pipe.add_component("html_converter", HTMLToDocument())
    
    # Der Ranker sortiert Dokumente nach Relevanz zur Suchanfrage aus
    pipe.add_component("ranker", TransformersSimilarityRanker(model="cross-encoder/ms-marco-MiniLM-L-6-v2"))

    # Den gerichteten Graphen aufbauen
    pipe.connect("search.links", "fetcher.urls")
    pipe.connect("fetcher.streams", "html_converter.sources")
    pipe.connect("html_converter.documents", "ranker.documents")

    return pipe

📋 4. Agent 2: Struktur & Pydantic

Der Zusammenfassungs-Agent wandelt die unstrukturierten Forschungsnotizen in ein präzises, maschinenlesbares Rezept um. Das ist kritisch, da Inkonsistenzen das HTML-Layout gefährden. Haystack 2.0 erzwingt strukturierte Ausgaben über Pydantic-Modelle (besonders mächtig in Kombination mit Hugging Face TGI-Instanzen).

schemas/recipe.py
from pydantic import BaseModel
from typing import List, Dict, Literal

# Strikte Typisierung eliminiert Halluzinationen in der Struktur
class Ingredient(BaseModel):
    name: str
    amount: float
    unit: str

class RecipeStructure(BaseModel):
    recipe_title: str
    servings: int
    ingredients: List[Ingredient]
    instructions: List[str]
    nutrients: Dict[str, float]
    difficulty: Literal["Einfach", "Mittel", "Schwer"]

# Nutzungshinweis für die Pipeline:
# generator = HuggingFaceAPIChatGenerator(
#     api_type="text_generation_inference",
#     generation_kwargs={"response_format": RecipeStructure.schema_json()}
# )

🎨 5. Agent 3: Visionär (Bildgenerierung)

Ein Kochbuch braucht Ästhetik. Da Sprachmodelle keinen Text in Bilder konvertieren, implementieren wir eine benutzerdefinierte Haystack-Komponente. Diese nimmt den Rezepttitel auf, optimiert den Prompt ("Professionelle Food-Fotografie, Makro...") und steuert via diffusers ein Modell wie Stable Diffusion XL oder FLUX an.

components/image_generator.py
from haystack import component
from haystack.dataclasses import ImageContent
# import diffusers ...

@component
class StableDiffusionGenerator:
    def __init__(self, model_id="stabilityai/stable-diffusion-xl-base-1.0"):
        self.model_id = model_id
        # Initialisierung z.B. Pipeline Laden inkl. FP16 Optimierung

    # Definition des Output-Sockets für die Validierung vor der Laufzeit
    @component.output_types(image=ImageContent)
    def run(self, prompt: str):
        # image = self.pipe(prompt).images[0]
        # base64_str = convert_to_base64(image)
        
        # Rückgabe als Haystack 2.0 Multimodal Objekt
        return {"image": ImageContent.from_base64(base64_str)}

💻 6. Agent 4: Meta-Aggregator (HTML)

Der letzte Agent orchestriert die Präsentation. Er empfängt das strukturierte Rezept (als Dictionary/JSON) und das generierte Bild (als Base64) und verpackt beides über Jinja2-Templating.

templates/recipe_card.html
<!-- Das Template wird vom HTML-Agenten mit Daten befüllt -->
<div class="recipe-card">
    <h1>{{ recipe_title }}</h1>
    
    <!-- Base64 Einbettung erspart externe Bild-Hostings -->
    <img src="data:image/png;base64,{{ image_base64 }}" alt="{{ recipe_title }}">
    
    <div class="meta">
        <span>Schwierigkeit: {{ difficulty }}</span>
    </div>
    
    <h3>Zutaten</h3>
    <ul>
        {% for ingredient in ingredients %}
            <li>{{ ingredient.amount }} {{ ingredient.unit }} {{ ingredient.name}}</li>
        {% endfor %}
    </ul>
    
    <h3>Zubereitung</h3>
    <ol>
        {% for step in instructions %}
            <li>{{ step }}</li>
        {% endfor %}
    </ol>
</div>

🚦 7. Orchestrierung & Fehlerbehandlung

Um diese Agenten zu vereinen, nutzen wir den "Agent-as-a-Tool"-Ansatz. Ein Koordinator-Agent kapselt die Pipelines in Werkzeugen. So kann das Haupt-LLM dynamisch entscheiden, wann es recherchiert und wann es Bilder generiert.

Für Robustheit sorgt der ConditionalRouter. Liefert ein Tool einen Fehler (z.B. keine Suchergebnisse), leitet der Router den Datenfluss um, sodass das LLM seinen Such-Prompt anpassen kann, anstatt abzustürzen.

tools/agent_tools.py
from haystack.tools import Tool
from haystack.components.routers import ConditionalRouter
from agents.researcher import build_research_pipeline

# Pipeline laden
research_pipe = build_research_pipeline()

# Pipeline als aufrufbares Werkzeug für das LLM verpacken
research_tool = Tool(
    name="research_tool",
    description="Durchsucht das Web nach authentischen Rezepten.",
    parameters={"query": {"type": "string"}},
    function=research_pipe.run
)

# Fehlerbehandlung: Ein Router, der den Pfad basierend auf Tool-Erfolg wählt
routes = [
    {
        "condition": "{{ 'error' in tool_result.text | lower }}",
        "output": "{{ tool_result }}",
        "output_name": "error_route",
        "output_type": str
    },
    {
        "condition": "{{ 'error' not in tool_result.text | lower }}",
        "output": "{{ tool_result }}",
        "output_name": "success_route",
        "output_type": str
    }
]
router = ConditionalRouter(routes)

📁 8. Repository-Struktur: Der Master-Plan

Alle Code-Schnipsel aus Kapitel 3 bis 7 fügen sich nun zusammen. Ein komplexes MAS sollte niemals ein Monolith sein (alles in einem Skript). Eine monolithische Architektur würde das Testen einzelner Pipelines fast unmöglich machen und zu Spaghetti-Code beim Routing führen.

Dank der modularen Architektur von Haystack 2.0 spiegelt sich die fachliche Trennung direkt im Repository wider:

infinite-cookbook/
├── schemas/
│ └── recipe.py # (Kapitel 4) Pydantic Modelle
├── components/
│ └── image_generator.py # (Kapitel 5) Custom @component
├── templates/
│ └── recipe_card.html # (Kapitel 6) Jinja2 Template
├── agents/
│ ├── researcher.py # (Kapitel 3) Web-Search Pipeline
│ └── structurer.py ...
├── tools/
│ └── agent_tools.py # (Kapitel 7) Pipeline -> Tool Wrapper
└── main.py # Orchestrierungs-Loop (siehe unten)

Das Herzstück: main.py

Weil wir alle Komplexität in die Unterordner ausgelagert haben, ist die main.py unglaublich elegant. Sie initialisiert das Haupt-LLM, importiert die fertige Werkzeugkiste und startet die Interaktions-Schleife.

main.py
import os
from haystack import Pipeline
from haystack.components.generators.chat import HuggingFaceAPIChatGenerator
from haystack.components.tools import ToolInvoker
from haystack.dataclasses import ChatMessage

# Der saubere Import aus unserer Architektur (Kapitel 7)
from tools.agent_tools import cookbook_tools

def main():
    # 1. Der Koordinator: Ein starkes Modell (z.B. Qwen), das Tool-Calling beherrscht
    llm = HuggingFaceAPIChatGenerator(
        api_type="serverless_inference_api",
        model="Qwen/Qwen2.5-72B-Instruct",
        tools=cookbook_tools
    )
    
    # 2. Der Invoker: Führt die vom LLM gewählten Tools aus
    invoker = ToolInvoker(tools=cookbook_tools)

    # 3. Der Orchestrierungs-Graph aufbauen
    coordinator = Pipeline()
    coordinator.add_component("llm", llm)
    coordinator.add_component("invoker", invoker)
    
    # Zyklische Verbindung für iteratives Arbeiten
    coordinator.connect("llm.replies", "invoker.messages")
    coordinator.connect("invoker.tool_messages", "llm.messages")

    # 4. System Start
    user_prompt = "Erstelle ein Rezept für ein thailändisches grünes Curry samt HTML und Bild."
    messages = [ChatMessage.from_user(user_prompt)]
    
    print(f"🤖 Koordinator startet Aufgabe: {user_prompt}")
    
    # Run Loop
    result = coordinator.run({"llm": {"messages": messages}})
    
    print("Fertiges Ergebnis:", result["llm"]["replies"][0].text)

if __name__ == "__main__":
    main()
💡
Die Architektur-Philosophie: Diese Aufteilung erlaubt es dir, das Modell in main.py auszutauschen, die Such-Logik in researcher.py komplett neu zu schreiben oder an der HTML-Karte zu basteln, ohne dass die anderen Teile des Multi-Agenten-Systems zerbrechen. Das ist die Stärke von Haystack 2.0!
👨‍🍳

Fazit: Die KI-Küchenbrigade

Man sagt oft: »Viele Köche verderben den Brei.« In der Welt der Künstlichen Intelligenz gilt jedoch genau das Gegenteil – vorausgesetzt, man hat die richtige Architektur.

Das "Infinite Cookbook" illustriert das Grundprinzip eines Multi-Agenten-Systems (MAS) perfekt. Anstatt ein einziges, riesiges "Gott-Modell" mit einem gigantischen Prompt zu überlasten (und zu hoffen, dass es gleichzeitig fehlerfrei recherchiert, programmiert, layoutet und malt), etablieren wir eine hochspezialisierte, arbeitsteilige Küchenbrigade:

👔 Der Küchenchef (Koordinator)

Er kocht nicht selbst. Er nimmt die Bestellung des Gastes entgegen, plant die Schritte und delegiert die Aufgaben an sein Team (Tool Calling).

🛒 Der Einkäufer (Forschung)

Er weiß, auf welchen Märkten (Web) es die besten und authentischsten Zutaten (Daten) gibt. Er sortiert den Müll aus und bringt nur das Beste in die Küche.

🔪 Der Postenchef (Struktur)

Er ist besessen von Präzision. Er nimmt die losen Zutaten und formatiert sie exakt nach der strikten Pydantic-Rezeptvorlage. Keine Halluzinationen, nur Fakten.

📸 Der Food-Stylist (Visionär)

Er interessiert sich nicht für Grammatik oder HTML. Er malt mit Diffusionsmodellen atemberaubende Bilder, die das Gericht perfekt in Szene setzen.

Die wahre Stärke eines MAS ist die Isolation von Komplexität. Wenn das HTML bricht, reparieren wir den Meta-Agenten. Wenn wir ein besseres Open-Source-Modell für JSON finden, tauschen wir den Postenchef aus, ohne dass der Rest der Küche stehenbleibt. Haystack 2.0 liefert uns dafür die Küche – und die Infrastruktur von Hugging Face das Personal.