Spaces:
Running
Running
Update index.html
Browse files- index.html +188 -118
index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
<!-- Tailwind CSS -->
|
| 9 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
|
| 11 |
-
<!-- Highlight.js for Code Formatting
|
| 12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 14 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
|
@@ -16,7 +16,7 @@
|
|
| 16 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
| 17 |
|
| 18 |
<!-- Google Fonts (Inter & Source Code Pro for HF feel) -->
|
| 19 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Source+Code+Pro:wght@400;500&display=swap" rel="stylesheet">
|
| 20 |
|
| 21 |
<script>
|
| 22 |
tailwind.config = {
|
|
@@ -47,19 +47,19 @@
|
|
| 47 |
|
| 48 |
<style>
|
| 49 |
body { font-family: 'Inter', sans-serif; background-color: #ffffff; color: #111827; }
|
| 50 |
-
pre code { font-family: 'Source Code Pro', monospace; border-radius: 0.75rem; font-size: 0.875rem; padding: 1.25rem; }
|
| 51 |
-
.prose h2 { margin-top:
|
| 52 |
-
.prose h3 { margin-top:
|
| 53 |
.prose p { margin-bottom: 1.25rem; line-height: 1.8; color: #4B5563; }
|
| 54 |
.prose table { width: 100%; border-collapse: collapse; margin-bottom: 2rem; border-radius: 0.5rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
| 55 |
.prose th, .prose td { border: 1px solid #E5E7EB; padding: 1rem; text-align: left; font-size: 0.95rem; }
|
| 56 |
.prose th { background-color: #F9FAFB; font-weight: 600; color: #111827; }
|
| 57 |
|
| 58 |
/* Tooltip / Copy Button */
|
| 59 |
-
.copy-btn { position: absolute; top: 0.75rem; right: 0.75rem; background: rgba(255,255,255,0.1); color: #D1D5DB; border: 1px solid rgba(255,255,255,0.2); padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: all 0.2s; }
|
| 60 |
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
| 61 |
-
.code-wrapper { position: relative; margin-bottom: 2rem; border-radius: 0.75rem; background: #282c34; }
|
| 62 |
-
.file-label { position: absolute; top: -
|
| 63 |
|
| 64 |
/* Custom Scrollbar for Sidebar */
|
| 65 |
#sidebar::-webkit-scrollbar { width: 6px; }
|
|
@@ -107,9 +107,9 @@
|
|
| 107 |
<div class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 ml-3 mt-6">Orchestrierung & Code</div>
|
| 108 |
<ul class="space-y-1 text-sm font-medium">
|
| 109 |
<li><a href="#orchestration" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">7. Routing & Tool-Calling</a></li>
|
| 110 |
-
<li><a href="#repo" class="flex items-center gap-2 py-2 px-3 bg-hfYellow/20 text-yellow-
|
| 111 |
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
|
| 112 |
-
8. Repositorystruktur
|
| 113 |
</a></li>
|
| 114 |
</ul>
|
| 115 |
</div>
|
|
@@ -125,42 +125,43 @@
|
|
| 125 |
<span class="text-4xl">🥘</span>
|
| 126 |
<h1 class="text-3xl font-bold text-gray-900 m-0">Architektur eines Kulinarischen MAS</h1>
|
| 127 |
</div>
|
| 128 |
-
<p class="text-gray-700 text-lg leading-relaxed m-0">Ein
|
| 129 |
</div>
|
| 130 |
|
| 131 |
<section id="intro">
|
| 132 |
<h2><span>🏗️</span> 1. Das Konzept: Haystack 2.0</h2>
|
| 133 |
-
<p>
|
|
|
|
| 134 |
</section>
|
| 135 |
|
| 136 |
<section id="hf-infra">
|
| 137 |
<h2><span>🤗</span> 2. Hugging Face Inference Provider</h2>
|
| 138 |
-
<p>
|
| 139 |
|
| 140 |
<div class="overflow-x-auto">
|
| 141 |
<table>
|
| 142 |
<thead>
|
| 143 |
<tr>
|
| 144 |
-
<th>Infrastruktur</th>
|
| 145 |
<th>Charakteristika</th>
|
| 146 |
-
<th>
|
| 147 |
</tr>
|
| 148 |
</thead>
|
| 149 |
<tbody>
|
| 150 |
<tr>
|
| 151 |
-
<td><strong>Serverless API</strong></td>
|
| 152 |
-
<td>Kostenlos,
|
| 153 |
-
<td
|
| 154 |
</tr>
|
| 155 |
<tr>
|
| 156 |
<td><strong>Inference Endpoints</strong></td>
|
| 157 |
-
<td>
|
| 158 |
-
<td
|
| 159 |
</tr>
|
| 160 |
<tr>
|
| 161 |
-
<td><strong>
|
| 162 |
-
<td>
|
| 163 |
-
<td
|
| 164 |
</tr>
|
| 165 |
</tbody>
|
| 166 |
</table>
|
|
@@ -169,65 +170,48 @@
|
|
| 169 |
|
| 170 |
<section id="agent1">
|
| 171 |
<h2><span>🔍</span> 3. Agent 1: Forschung & Suche</h2>
|
| 172 |
-
<p>Dieser
|
| 173 |
-
|
|
|
|
|
|
|
| 174 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 175 |
<pre><code class="language-python">from haystack import Pipeline
|
| 176 |
from haystack.components.websearch import SerperDevWebSearch
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
research_pipe.add_component("ranker", TransformersSimilarityRanker())
|
| 181 |
-
# ... verbindungen</code></pre>
|
| 182 |
-
</div>
|
| 183 |
-
</section>
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
<p>Um die unstrukturierten Forschungsnotizen maschinenlesbar zu machen, zwingen wir das LLM mittels <strong>Pydantic</strong> in ein striktes JSON-Schema. Das eliminiert Halluzinationen in der Datenstruktur.</p>
|
| 188 |
-
</section>
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
|
| 200 |
-
|
| 201 |
-
<
|
| 202 |
-
<p>Die Magie passiert im Koordinator-Agenten. Anstatt alle Pipelines händisch zu verketten, packen wir jede Pipeline in ein Haystack <code>Tool</code>-Objekt. Ein übergeordnetes LLM entscheidet dann dynamisch, welches Tool aufgerufen werden muss.</p>
|
| 203 |
</section>
|
| 204 |
|
| 205 |
-
<
|
| 206 |
-
|
| 207 |
-
<
|
| 208 |
-
<span>📁</span> 8. Die Code-Struktur (Vom Konzept zur Realität)
|
| 209 |
-
</h2>
|
| 210 |
-
<p>Ein Monolith (alles in einem Skript) führt bei MAS unweigerlich ins Chaos. Eine saubere Repository-Struktur spiegelt die Modularität des Konzepts wider. Hier siehst du, wie die konkreten Dateien in einem echten Projekt aussehen.</p>
|
| 211 |
|
| 212 |
-
<div class="
|
| 213 |
-
infinite-cookbook/<br>
|
| 214 |
-
├── <span class="font-bold text-gray-900">schemas/</span><br>
|
| 215 |
-
│ └── <a href="#file-schema" class="text-blue-600 hover:underline">recipe.py</a> <span class="text-gray-400 italic"># Pydantic Modelle</span><br>
|
| 216 |
-
├── <span class="font-bold text-gray-900">agents/</span><br>
|
| 217 |
-
│ └── <a href="#file-researcher" class="text-blue-600 hover:underline">researcher.py</a> <span class="text-gray-400 italic"># Pipeline Definitionen</span><br>
|
| 218 |
-
├── <span class="font-bold text-gray-900">tools/</span><br>
|
| 219 |
-
│ └── <a href="#file-tools" class="text-blue-600 hover:underline">agent_tools.py</a> <span class="text-gray-400 italic"># Wrapper: Pipeline -> Tool</span><br>
|
| 220 |
-
└── <a href="#file-main" class="text-blue-600 hover:underline font-bold">main.py</a> <span class="text-gray-400 italic"># Orchestrierungs-Loop</span>
|
| 221 |
-
</div>
|
| 222 |
-
|
| 223 |
-
<h3 id="file-schema" class="flex items-center gap-2 mt-10"><svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg> 1. Datenschemata</h3>
|
| 224 |
-
<p>Wir definieren zentral, wie unser Endprodukt auszusehen hat.</p>
|
| 225 |
-
<div class="code-wrapper mt-4">
|
| 226 |
<div class="file-label">schemas/recipe.py</div>
|
| 227 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 228 |
<pre><code class="language-python">from pydantic import BaseModel
|
| 229 |
-
from typing import List, Literal
|
| 230 |
|
|
|
|
| 231 |
class Ingredient(BaseModel):
|
| 232 |
name: str
|
| 233 |
amount: float
|
|
@@ -238,68 +222,151 @@ class RecipeStructure(BaseModel):
|
|
| 238 |
servings: int
|
| 239 |
ingredients: List[Ingredient]
|
| 240 |
instructions: List[str]
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
</div>
|
|
|
|
| 243 |
|
| 244 |
-
|
| 245 |
-
<
|
| 246 |
-
<
|
| 247 |
-
|
|
|
|
|
|
|
| 248 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 249 |
-
<pre><code class="language-python">from haystack import
|
| 250 |
-
from haystack.
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
pipe.add_component("fetcher", LinkContentFetcher())
|
| 261 |
-
pipe.add_component("html_converter", HTMLToDocument())
|
| 262 |
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
</div>
|
|
|
|
| 269 |
|
| 270 |
-
|
| 271 |
-
<
|
| 272 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
<div class="file-label">tools/agent_tools.py</div>
|
| 274 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 275 |
<pre><code class="language-python">from haystack.tools import Tool
|
|
|
|
| 276 |
from agents.researcher import build_research_pipeline
|
| 277 |
-
# from agents.structurer import build_structuring_pipeline
|
| 278 |
|
| 279 |
-
#
|
| 280 |
research_pipe = build_research_pipeline()
|
| 281 |
-
# structurer_pipe = build_structuring_pipeline()
|
| 282 |
|
| 283 |
-
#
|
| 284 |
research_tool = Tool(
|
| 285 |
name="research_tool",
|
| 286 |
-
description="
|
| 287 |
-
parameters={
|
| 288 |
-
"type": "object",
|
| 289 |
-
"properties": {
|
| 290 |
-
"query": {"type": "string", "description": "Der Suchbegriff, z.B. 'original Pad Thai'"}
|
| 291 |
-
},
|
| 292 |
-
"required": ["query"]
|
| 293 |
-
},
|
| 294 |
function=research_pipe.run
|
| 295 |
)
|
| 296 |
|
| 297 |
-
#
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
</div>
|
|
|
|
| 300 |
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
<div class="code-wrapper mt-4">
|
| 304 |
<div class="file-label">main.py</div>
|
| 305 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
@@ -309,11 +376,11 @@ from haystack.components.generators.chat import HuggingFaceAPIChatGenerator
|
|
| 309 |
from haystack.components.tools import ToolInvoker
|
| 310 |
from haystack.dataclasses import ChatMessage
|
| 311 |
|
| 312 |
-
#
|
| 313 |
from tools.agent_tools import cookbook_tools
|
| 314 |
|
| 315 |
def main():
|
| 316 |
-
# 1. Der Koordinator: Ein starkes Modell, das Tool-Calling beherrscht
|
| 317 |
llm = HuggingFaceAPIChatGenerator(
|
| 318 |
api_type="serverless_inference_api",
|
| 319 |
model="Qwen/Qwen2.5-72B-Instruct",
|
|
@@ -323,12 +390,12 @@ def main():
|
|
| 323 |
# 2. Der Invoker: Führt die vom LLM gewählten Tools aus
|
| 324 |
invoker = ToolInvoker(tools=cookbook_tools)
|
| 325 |
|
| 326 |
-
# 3. Der
|
| 327 |
coordinator = Pipeline()
|
| 328 |
coordinator.add_component("llm", llm)
|
| 329 |
coordinator.add_component("invoker", invoker)
|
| 330 |
|
| 331 |
-
# Zyklische Verbindung für iteratives Arbeiten
|
| 332 |
coordinator.connect("llm.replies", "invoker.messages")
|
| 333 |
coordinator.connect("invoker.tool_messages", "llm.messages")
|
| 334 |
|
|
@@ -338,11 +405,11 @@ def main():
|
|
| 338 |
|
| 339 |
print(f"🤖 Koordinator startet Aufgabe: {user_prompt}")
|
| 340 |
|
| 341 |
-
# Run Loop
|
|
|
|
|
|
|
| 342 |
result = coordinator.run({"llm": {"messages": messages}})
|
| 343 |
|
| 344 |
-
# Im echten MAS läuft dies in einer Schleife (ConditionalRouter),
|
| 345 |
-
# bis das LLM entscheidet: "Ich bin fertig und nutze kein Tool mehr."
|
| 346 |
print("Fertiges Ergebnis:", result["llm"]["replies"][0].text)
|
| 347 |
|
| 348 |
if __name__ == "__main__":
|
|
@@ -353,7 +420,7 @@ if __name__ == "__main__":
|
|
| 353 |
<span class="text-2xl">💡</span>
|
| 354 |
<div>
|
| 355 |
<strong class="text-gray-900 block mb-1">Die Architektur-Philosophie:</strong>
|
| 356 |
-
Diese Aufteilung erlaubt es dir,
|
| 357 |
</div>
|
| 358 |
</div>
|
| 359 |
|
|
@@ -379,7 +446,7 @@ if __name__ == "__main__":
|
|
| 379 |
sidebar.classList.toggle('hidden');
|
| 380 |
});
|
| 381 |
|
| 382 |
-
// Smooth scroll adjustment for fixed header
|
| 383 |
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 384 |
anchor.addEventListener('click', function (e) {
|
| 385 |
e.preventDefault();
|
|
@@ -392,8 +459,11 @@ if __name__ == "__main__":
|
|
| 392 |
a.classList.remove('bg-hfYellow/20', 'text-yellow-800', 'font-semibold');
|
| 393 |
a.classList.add('text-gray-600');
|
| 394 |
});
|
| 395 |
-
|
| 396 |
-
this.
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
document.querySelector(this.getAttribute('href')).scrollIntoView({
|
| 399 |
behavior: 'smooth'
|
|
|
|
| 8 |
<!-- Tailwind CSS -->
|
| 9 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
|
| 11 |
+
<!-- Highlight.js for Code Formatting -->
|
| 12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 14 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
|
|
|
| 16 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
| 17 |
|
| 18 |
<!-- Google Fonts (Inter & Source Code Pro for HF feel) -->
|
| 19 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Source+Code+Pro:wght@400;500;600&display=swap" rel="stylesheet">
|
| 20 |
|
| 21 |
<script>
|
| 22 |
tailwind.config = {
|
|
|
|
| 47 |
|
| 48 |
<style>
|
| 49 |
body { font-family: 'Inter', sans-serif; background-color: #ffffff; color: #111827; }
|
| 50 |
+
pre code { font-family: 'Source Code Pro', monospace; border-radius: 0.75rem; font-size: 0.875rem; padding: 1.5rem 1.25rem 1.25rem 1.25rem; }
|
| 51 |
+
.prose h2 { margin-top: 3.5rem; margin-bottom: 1.25rem; font-weight: 700; font-size: 1.75rem; color: #111827; border-bottom: 1px solid #E5E7EB; padding-bottom: 0.5rem; display: flex; align-items: center; gap: 0.5rem; }
|
| 52 |
+
.prose h3 { margin-top: 2.5rem; margin-bottom: 1rem; font-weight: 600; font-size: 1.25rem; color: #374151; }
|
| 53 |
.prose p { margin-bottom: 1.25rem; line-height: 1.8; color: #4B5563; }
|
| 54 |
.prose table { width: 100%; border-collapse: collapse; margin-bottom: 2rem; border-radius: 0.5rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
| 55 |
.prose th, .prose td { border: 1px solid #E5E7EB; padding: 1rem; text-align: left; font-size: 0.95rem; }
|
| 56 |
.prose th { background-color: #F9FAFB; font-weight: 600; color: #111827; }
|
| 57 |
|
| 58 |
/* Tooltip / Copy Button */
|
| 59 |
+
.copy-btn { position: absolute; top: 0.75rem; right: 0.75rem; background: rgba(255,255,255,0.1); color: #D1D5DB; border: 1px solid rgba(255,255,255,0.2); padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: all 0.2s; z-index: 20; }
|
| 60 |
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
| 61 |
+
.code-wrapper { position: relative; margin-top: 1.5rem; margin-bottom: 2rem; border-radius: 0.75rem; background: #282c34; }
|
| 62 |
+
.file-label { position: absolute; top: -14px; left: 16px; background: #FFD21E; color: #111827; padding: 0.15rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 700; z-index: 10; font-family: 'Source Code Pro', monospace; border: 2px solid #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
| 63 |
|
| 64 |
/* Custom Scrollbar for Sidebar */
|
| 65 |
#sidebar::-webkit-scrollbar { width: 6px; }
|
|
|
|
| 107 |
<div class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 ml-3 mt-6">Orchestrierung & Code</div>
|
| 108 |
<ul class="space-y-1 text-sm font-medium">
|
| 109 |
<li><a href="#orchestration" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">7. Routing & Tool-Calling</a></li>
|
| 110 |
+
<li><a href="#repo" class="flex items-center gap-2 py-2 px-3 bg-hfYellow/20 text-yellow-900 rounded-lg hover:bg-hfYellow/30 transition">
|
| 111 |
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
|
| 112 |
+
8. Repositorystruktur
|
| 113 |
</a></li>
|
| 114 |
</ul>
|
| 115 |
</div>
|
|
|
|
| 125 |
<span class="text-4xl">🥘</span>
|
| 126 |
<h1 class="text-3xl font-bold text-gray-900 m-0">Architektur eines Kulinarischen MAS</h1>
|
| 127 |
</div>
|
| 128 |
+
<p class="text-gray-700 text-lg leading-relaxed m-0">Ein Deep-Dive in die Orchestrierung von Multi-Agenten-Systemen (MAS) für <strong>"The Infinite Cookbook"</strong>. 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.</p>
|
| 129 |
</div>
|
| 130 |
|
| 131 |
<section id="intro">
|
| 132 |
<h2><span>🏗️</span> 1. Das Konzept: Haystack 2.0</h2>
|
| 133 |
+
<p>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 <strong>Komponenten und Pipelines als dynamische Rechengraphen</strong> ein.</p>
|
| 134 |
+
<p>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.</p>
|
| 135 |
</section>
|
| 136 |
|
| 137 |
<section id="hf-infra">
|
| 138 |
<h2><span>🤗</span> 2. Hugging Face Inference Provider</h2>
|
| 139 |
+
<p>Hugging Face ist der zentrale Marktplatz für Open-Source-Intelligenz. Im Juli 2025 stellte die Inference API auf <code>chat_completion</code>-Endpunkte um. Haystack bindet diese Modelle über die <code>HuggingFaceAPIChatGenerator</code>-Komponente ein. Durch dynamische Suffixe wie <code>:fastest</code> oder Provider-Wahl können wir Lasten optimieren:</p>
|
| 140 |
|
| 141 |
<div class="overflow-x-auto">
|
| 142 |
<table>
|
| 143 |
<thead>
|
| 144 |
<tr>
|
| 145 |
+
<th>Infrastruktur-Option</th>
|
| 146 |
<th>Charakteristika</th>
|
| 147 |
+
<th>Anwendungsfall im MAS</th>
|
| 148 |
</tr>
|
| 149 |
</thead>
|
| 150 |
<tbody>
|
| 151 |
<tr>
|
| 152 |
+
<td><strong>Serverless Inference API</strong></td>
|
| 153 |
+
<td>Kostenlos/Rate-limited, schnell</td>
|
| 154 |
+
<td>Prototyping und einfache Zusammenfassungen (z.B. Qwen 72B).</td>
|
| 155 |
</tr>
|
| 156 |
<tr>
|
| 157 |
<td><strong>Inference Endpoints</strong></td>
|
| 158 |
+
<td>Dedizierte Instanzen, /h Abrechnung</td>
|
| 159 |
+
<td>Produktion, garantierte Latenz, Bildgenerierung (SDXL / FLUX).</td>
|
| 160 |
</tr>
|
| 161 |
<tr>
|
| 162 |
+
<td><strong>Text Generation Inference (TGI)</strong></td>
|
| 163 |
+
<td>Optimiertes Serving, JSON-Guiding</td>
|
| 164 |
+
<td>Komplexe Datenextraktion mit strikter Schema-Einhaltung.</td>
|
| 165 |
</tr>
|
| 166 |
</tbody>
|
| 167 |
</table>
|
|
|
|
| 170 |
|
| 171 |
<section id="agent1">
|
| 172 |
<h2><span>🔍</span> 3. Agent 1: Forschung & Suche</h2>
|
| 173 |
+
<p>Die erste Phase ist die <em>Deep Research Phase</em>. 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.</p>
|
| 174 |
+
|
| 175 |
+
<div class="code-wrapper">
|
| 176 |
+
<div class="file-label">agents/researcher.py</div>
|
| 177 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 178 |
<pre><code class="language-python">from haystack import Pipeline
|
| 179 |
from haystack.components.websearch import SerperDevWebSearch
|
| 180 |
+
from haystack.components.fetchers import LinkContentFetcher
|
| 181 |
+
from haystack.components.converters import HTMLToDocument
|
| 182 |
+
from haystack.components.rankers import TransformersSimilarityRanker
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
def build_research_pipeline() -> Pipeline:
|
| 185 |
+
pipe = Pipeline()
|
|
|
|
|
|
|
| 186 |
|
| 187 |
+
# Komponenten initialisieren
|
| 188 |
+
pipe.add_component("search", SerperDevWebSearch(api_key="DEIN_API_KEY"))
|
| 189 |
+
pipe.add_component("fetcher", LinkContentFetcher())
|
| 190 |
+
pipe.add_component("html_converter", HTMLToDocument())
|
| 191 |
+
|
| 192 |
+
# Der Ranker sortiert Dokumente nach Relevanz zur Suchanfrage aus
|
| 193 |
+
pipe.add_component("ranker", TransformersSimilarityRanker(model="cross-encoder/ms-marco-MiniLM-L-6-v2"))
|
| 194 |
|
| 195 |
+
# Den gerichteten Graphen aufbauen
|
| 196 |
+
pipe.connect("search.links", "fetcher.urls")
|
| 197 |
+
pipe.connect("fetcher.streams", "html_converter.sources")
|
| 198 |
+
pipe.connect("html_converter.documents", "ranker.documents")
|
| 199 |
|
| 200 |
+
return pipe</code></pre>
|
| 201 |
+
</div>
|
|
|
|
| 202 |
</section>
|
| 203 |
|
| 204 |
+
<section id="agent2">
|
| 205 |
+
<h2><span>📋</span> 4. Agent 2: Struktur & Pydantic</h2>
|
| 206 |
+
<p>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 <strong>Pydantic-Modelle</strong> (besonders mächtig in Kombination mit Hugging Face TGI-Instanzen).</p>
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
+
<div class="code-wrapper">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
<div class="file-label">schemas/recipe.py</div>
|
| 210 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 211 |
<pre><code class="language-python">from pydantic import BaseModel
|
| 212 |
+
from typing import List, Dict, Literal
|
| 213 |
|
| 214 |
+
# Strikte Typisierung eliminiert Halluzinationen in der Struktur
|
| 215 |
class Ingredient(BaseModel):
|
| 216 |
name: str
|
| 217 |
amount: float
|
|
|
|
| 222 |
servings: int
|
| 223 |
ingredients: List[Ingredient]
|
| 224 |
instructions: List[str]
|
| 225 |
+
nutrients: Dict[str, float]
|
| 226 |
+
difficulty: Literal["Einfach", "Mittel", "Schwer"]
|
| 227 |
+
|
| 228 |
+
# Nutzungshinweis für die Pipeline:
|
| 229 |
+
# generator = HuggingFaceAPIChatGenerator(
|
| 230 |
+
# api_type="text_generation_inference",
|
| 231 |
+
# generation_kwargs={"response_format": RecipeStructure.schema_json()}
|
| 232 |
+
# )</code></pre>
|
| 233 |
</div>
|
| 234 |
+
</section>
|
| 235 |
|
| 236 |
+
<section id="agent3">
|
| 237 |
+
<h2><span>🎨</span> 5. Agent 3: Visionär (Bildgenerierung)</h2>
|
| 238 |
+
<p>Ein Kochbuch braucht Ästhetik. Da Sprachmodelle keinen Text in Bilder konvertieren, implementieren wir eine <strong>benutzerdefinierte Haystack-Komponente</strong>. Diese nimmt den Rezepttitel auf, optimiert den Prompt ("Professionelle Food-Fotografie, Makro...") und steuert via <code>diffusers</code> ein Modell wie Stable Diffusion XL oder FLUX an.</p>
|
| 239 |
+
|
| 240 |
+
<div class="code-wrapper">
|
| 241 |
+
<div class="file-label">components/image_generator.py</div>
|
| 242 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 243 |
+
<pre><code class="language-python">from haystack import component
|
| 244 |
+
from haystack.dataclasses import ImageContent
|
| 245 |
+
# import diffusers ...
|
| 246 |
+
|
| 247 |
+
@component
|
| 248 |
+
class StableDiffusionGenerator:
|
| 249 |
+
def __init__(self, model_id="stabilityai/stable-diffusion-xl-base-1.0"):
|
| 250 |
+
self.model_id = model_id
|
| 251 |
+
# Initialisierung z.B. Pipeline Laden inkl. FP16 Optimierung
|
| 252 |
+
|
| 253 |
+
# Definition des Output-Sockets für die Validierung vor der Laufzeit
|
| 254 |
+
@component.output_types(image=ImageContent)
|
| 255 |
+
def run(self, prompt: str):
|
| 256 |
+
# image = self.pipe(prompt).images[0]
|
| 257 |
+
# base64_str = convert_to_base64(image)
|
| 258 |
+
|
| 259 |
+
# Rückgabe als Haystack 2.0 Multimodal Objekt
|
| 260 |
+
return {"image": ImageContent.from_base64(base64_str)}</code></pre>
|
| 261 |
+
</div>
|
| 262 |
+
</section>
|
| 263 |
|
| 264 |
+
<section id="agent4">
|
| 265 |
+
<h2><span>💻</span> 6. Agent 4: Meta-Aggregator (HTML)</h2>
|
| 266 |
+
<p>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 <strong>Jinja2-Templating</strong>.</p>
|
| 267 |
+
|
| 268 |
+
<div class="code-wrapper">
|
| 269 |
+
<div class="file-label">templates/recipe_card.html</div>
|
| 270 |
+
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 271 |
+
<pre><code class="language-xml"><!-- Das Template wird vom HTML-Agenten mit Daten befüllt -->
|
| 272 |
+
<div class="recipe-card">
|
| 273 |
+
<h1>{{ recipe_title }}</h1>
|
| 274 |
|
| 275 |
+
<!-- Base64 Einbettung erspart externe Bild-Hostings -->
|
| 276 |
+
<img src="data:image/png;base64,{{ image_base64 }}" alt="{{ recipe_title }}">
|
|
|
|
|
|
|
| 277 |
|
| 278 |
+
<div class="meta">
|
| 279 |
+
<span>Schwierigkeit: {{ difficulty }}</span>
|
| 280 |
+
</div>
|
| 281 |
|
| 282 |
+
<h3>Zutaten</h3>
|
| 283 |
+
<ul>
|
| 284 |
+
{% for ingredient in ingredients %}
|
| 285 |
+
<li>{{ ingredient.amount }} {{ ingredient.unit }} {{ ingredient.name}}</li>
|
| 286 |
+
{% endfor %}
|
| 287 |
+
</ul>
|
| 288 |
+
|
| 289 |
+
<h3>Zubereitung</h3>
|
| 290 |
+
<ol>
|
| 291 |
+
{% for step in instructions %}
|
| 292 |
+
<li>{{ step }}</li>
|
| 293 |
+
{% endfor %}
|
| 294 |
+
</ol>
|
| 295 |
+
</div></code></pre>
|
| 296 |
</div>
|
| 297 |
+
</section>
|
| 298 |
|
| 299 |
+
<section id="orchestration">
|
| 300 |
+
<h2><span>🚦</span> 7. Orchestrierung & Fehlerbehandlung</h2>
|
| 301 |
+
<p>Um diese Agenten zu vereinen, nutzen wir den <strong>"Agent-as-a-Tool"</strong>-Ansatz. Ein Koordinator-Agent kapselt die Pipelines in Werkzeugen. So kann das Haupt-LLM dynamisch entscheiden, wann es recherchiert und wann es Bilder generiert.</p>
|
| 302 |
+
|
| 303 |
+
<p>Für Robustheit sorgt der <code>ConditionalRouter</code>. 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.</p>
|
| 304 |
+
|
| 305 |
+
<div class="code-wrapper">
|
| 306 |
<div class="file-label">tools/agent_tools.py</div>
|
| 307 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
| 308 |
<pre><code class="language-python">from haystack.tools import Tool
|
| 309 |
+
from haystack.components.routers import ConditionalRouter
|
| 310 |
from agents.researcher import build_research_pipeline
|
|
|
|
| 311 |
|
| 312 |
+
# Pipeline laden
|
| 313 |
research_pipe = build_research_pipeline()
|
|
|
|
| 314 |
|
| 315 |
+
# Pipeline als aufrufbares Werkzeug für das LLM verpacken
|
| 316 |
research_tool = Tool(
|
| 317 |
name="research_tool",
|
| 318 |
+
description="Durchsucht das Web nach authentischen Rezepten.",
|
| 319 |
+
parameters={"query": {"type": "string"}},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
function=research_pipe.run
|
| 321 |
)
|
| 322 |
|
| 323 |
+
# Fehlerbehandlung: Ein Router, der den Pfad basierend auf Tool-Erfolg wählt
|
| 324 |
+
routes = [
|
| 325 |
+
{
|
| 326 |
+
"condition": "{{ 'error' in tool_result.text | lower }}",
|
| 327 |
+
"output": "{{ tool_result }}",
|
| 328 |
+
"output_name": "error_route",
|
| 329 |
+
"output_type": str
|
| 330 |
+
},
|
| 331 |
+
{
|
| 332 |
+
"condition": "{{ 'error' not in tool_result.text | lower }}",
|
| 333 |
+
"output": "{{ tool_result }}",
|
| 334 |
+
"output_name": "success_route",
|
| 335 |
+
"output_type": str
|
| 336 |
+
}
|
| 337 |
+
]
|
| 338 |
+
router = ConditionalRouter(routes)</code></pre>
|
| 339 |
</div>
|
| 340 |
+
</section>
|
| 341 |
|
| 342 |
+
<!-- KAPITEL 8: REPOSITORY UND CODE -->
|
| 343 |
+
<section id="repo">
|
| 344 |
+
<h2 class="bg-hfYellow/20 -mx-6 px-6 py-4 rounded-xl text-yellow-900 border-l-4 border-hfYellow">
|
| 345 |
+
<span>📁</span> 8. Repository-Struktur: Der Master-Plan
|
| 346 |
+
</h2>
|
| 347 |
+
<p>Alle Code-Schnipsel aus Kapitel 3 bis 7 fügen sich nun zusammen. Ein komplexes MAS sollte <strong>niemals ein Monolith</strong> 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.</p>
|
| 348 |
+
|
| 349 |
+
<p>Dank der modularen Architektur von Haystack 2.0 spiegelt sich die fachliche Trennung direkt im Repository wider:</p>
|
| 350 |
+
|
| 351 |
+
<div class="bg-gray-50 border border-gray-200 rounded-lg p-5 mb-8 font-mono text-sm text-gray-700 leading-relaxed shadow-sm">
|
| 352 |
+
infinite-cookbook/<br>
|
| 353 |
+
├── <span class="font-bold text-gray-900">schemas/</span><br>
|
| 354 |
+
│ └── <span class="text-blue-600">recipe.py</span> <span class="text-gray-400 italic"># (Kapitel 4) Pydantic Modelle</span><br>
|
| 355 |
+
├── <span class="font-bold text-gray-900">components/</span><br>
|
| 356 |
+
│ └── <span class="text-blue-600">image_generator.py</span> <span class="text-gray-400 italic"># (Kapitel 5) Custom @component</span><br>
|
| 357 |
+
├── <span class="font-bold text-gray-900">templates/</span><br>
|
| 358 |
+
│ └── <span class="text-blue-600">recipe_card.html</span> <span class="text-gray-400 italic"># (Kapitel 6) Jinja2 Template</span><br>
|
| 359 |
+
├── <span class="font-bold text-gray-900">agents/</span><br>
|
| 360 |
+
│ ├── <span class="text-blue-600">researcher.py</span> <span class="text-gray-400 italic"># (Kapitel 3) Web-Search Pipeline</span><br>
|
| 361 |
+
│ └── <span class="text-gray-500">structurer.py ...</span><br>
|
| 362 |
+
├── <span class="font-bold text-gray-900">tools/</span><br>
|
| 363 |
+
│ └── <span class="text-blue-600">agent_tools.py</span> <span class="text-gray-400 italic"># (Kapitel 7) Pipeline -> Tool Wrapper</span><br>
|
| 364 |
+
└── <span class="text-blue-600 font-bold">main.py</span> <span class="text-gray-400 italic"># Orchestrierungs-Loop (siehe unten)</span>
|
| 365 |
+
</div>
|
| 366 |
+
|
| 367 |
+
<h3>Das Herzstück: <code>main.py</code></h3>
|
| 368 |
+
<p>Weil wir alle Komplexität in die Unterordner ausgelagert haben, ist die <code>main.py</code> unglaublich elegant. Sie initialisiert das Haupt-LLM, importiert die fertige Werkzeugkiste und startet die Interaktions-Schleife.</p>
|
| 369 |
+
|
| 370 |
<div class="code-wrapper mt-4">
|
| 371 |
<div class="file-label">main.py</div>
|
| 372 |
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
|
|
| 376 |
from haystack.components.tools import ToolInvoker
|
| 377 |
from haystack.dataclasses import ChatMessage
|
| 378 |
|
| 379 |
+
# Der saubere Import aus unserer Architektur (Kapitel 7)
|
| 380 |
from tools.agent_tools import cookbook_tools
|
| 381 |
|
| 382 |
def main():
|
| 383 |
+
# 1. Der Koordinator: Ein starkes Modell (z.B. Qwen), das Tool-Calling beherrscht
|
| 384 |
llm = HuggingFaceAPIChatGenerator(
|
| 385 |
api_type="serverless_inference_api",
|
| 386 |
model="Qwen/Qwen2.5-72B-Instruct",
|
|
|
|
| 390 |
# 2. Der Invoker: Führt die vom LLM gewählten Tools aus
|
| 391 |
invoker = ToolInvoker(tools=cookbook_tools)
|
| 392 |
|
| 393 |
+
# 3. Der Orchestrierungs-Graph aufbauen
|
| 394 |
coordinator = Pipeline()
|
| 395 |
coordinator.add_component("llm", llm)
|
| 396 |
coordinator.add_component("invoker", invoker)
|
| 397 |
|
| 398 |
+
# Zyklische Verbindung für iteratives Arbeiten (Das LLM evaluiert Tool-Outputs)
|
| 399 |
coordinator.connect("llm.replies", "invoker.messages")
|
| 400 |
coordinator.connect("invoker.tool_messages", "llm.messages")
|
| 401 |
|
|
|
|
| 405 |
|
| 406 |
print(f"🤖 Koordinator startet Aufgabe: {user_prompt}")
|
| 407 |
|
| 408 |
+
# Run Loop
|
| 409 |
+
# Im echten MAS läuft dies in Verbindung mit dem Router in einer Schleife,
|
| 410 |
+
# bis das LLM entscheidet: "Ich bin fertig, hier ist das finale HTML."
|
| 411 |
result = coordinator.run({"llm": {"messages": messages}})
|
| 412 |
|
|
|
|
|
|
|
| 413 |
print("Fertiges Ergebnis:", result["llm"]["replies"][0].text)
|
| 414 |
|
| 415 |
if __name__ == "__main__":
|
|
|
|
| 420 |
<span class="text-2xl">💡</span>
|
| 421 |
<div>
|
| 422 |
<strong class="text-gray-900 block mb-1">Die Architektur-Philosophie:</strong>
|
| 423 |
+
Diese Aufteilung erlaubt es dir, das Modell in <code>main.py</code> auszutauschen, die Such-Logik in <code>researcher.py</code> 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!
|
| 424 |
</div>
|
| 425 |
</div>
|
| 426 |
|
|
|
|
| 446 |
sidebar.classList.toggle('hidden');
|
| 447 |
});
|
| 448 |
|
| 449 |
+
// Smooth scroll adjustment for fixed header
|
| 450 |
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 451 |
anchor.addEventListener('click', function (e) {
|
| 452 |
e.preventDefault();
|
|
|
|
| 459 |
a.classList.remove('bg-hfYellow/20', 'text-yellow-800', 'font-semibold');
|
| 460 |
a.classList.add('text-gray-600');
|
| 461 |
});
|
| 462 |
+
|
| 463 |
+
if(this.getAttribute('href') !== '#repo') {
|
| 464 |
+
this.classList.remove('text-gray-600');
|
| 465 |
+
this.classList.add('bg-hfYellow/20', 'text-yellow-800', 'font-semibold');
|
| 466 |
+
}
|
| 467 |
|
| 468 |
document.querySelector(this.getAttribute('href')).scrollIntoView({
|
| 469 |
behavior: 'smooth'
|