From df97f9314b036ae15ec795f5f0561f4fc7ff96ab Mon Sep 17 00:00:00 2001 From: team2 Date: Thu, 26 Feb 2026 07:02:07 +0100 Subject: [PATCH] alpha new hybridretriver line --- migrations/Version20260225000100.php | 32 ++++ migrations/Version20260225000200.php | 35 ++++ .../Admin/ModelGenerationConfigController.php | 57 ++++-- src/Entity/ModelGenerationConfig.php | 57 +++++- .../Retrieval/NdjsonHybridRetriever.php | 169 ++++++++---------- .../Retrieval/RetrieverInterface.php | 23 ++- templates/admin/model_config/create.html.twig | 92 +++++++--- templates/admin/model_config/list.html.twig | 54 +++--- .../model_config/test_retrieval.html.twig | 93 ++++++++++ 9 files changed, 460 insertions(+), 152 deletions(-) create mode 100644 migrations/Version20260225000100.php create mode 100644 migrations/Version20260225000200.php create mode 100644 templates/admin/model_config/test_retrieval.html.twig diff --git a/migrations/Version20260225000100.php b/migrations/Version20260225000100.php new file mode 100644 index 0000000..f0c97f5 --- /dev/null +++ b/migrations/Version20260225000100.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE model_generation_config + ADD retrieval_max_chunks INT NOT NULL, + ADD retrieval_vector_top_k INT NOT NULL + '); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE model_generation_config + DROP retrieval_max_chunks, + DROP retrieval_vector_top_k + '); + } +} \ No newline at end of file diff --git a/migrations/Version20260225000200.php b/migrations/Version20260225000200.php new file mode 100644 index 0000000..3ef1800 --- /dev/null +++ b/migrations/Version20260225000200.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE knowledge_tag CHANGE description description LONGTEXT DEFAULT NULL'); + $this->addSql('ALTER TABLE knowledge_tag RENAME INDEX uniq_knowledge_tag_slug TO UNIQ_86B46255989D9B62'); + + $this->addSql('ALTER TABLE document DROP FOREIGN KEY FK_D8698A769407EE77'); + $this->addSql('ALTER TABLE document ADD CONSTRAINT FK_D8698A769407EE77 FOREIGN KEY (current_version_id) REFERENCES document_version (id)'); + + $this->addSql('ALTER TABLE tag_rebuild_job CHANGE error_message error_message LONGTEXT DEFAULT NULL'); + + $this->addSql('ALTER TABLE document_tag RENAME INDEX idx_document_tag_document TO IDX_D0234567C33F7837'); + $this->addSql('ALTER TABLE document_tag RENAME INDEX idx_document_tag_tag TO IDX_D0234567BAD26311'); + } + + public function down(Schema $schema): void + { + // Optional – in Drift-Fix-Migration meist nicht notwendig + } +} \ No newline at end of file diff --git a/src/Controller/Admin/ModelGenerationConfigController.php b/src/Controller/Admin/ModelGenerationConfigController.php index c161867..a07ff13 100644 --- a/src/Controller/Admin/ModelGenerationConfigController.php +++ b/src/Controller/Admin/ModelGenerationConfigController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Controller\Admin; use App\Entity\ModelGenerationConfig; +use App\Knowledge\Retrieval\NdjsonHybridRetriever; use App\Repository\ModelGenerationConfigRepository; use App\Service\ModelGenerationConfigManager; use Doctrine\ORM\EntityManagerInterface; @@ -21,7 +22,10 @@ class ModelGenerationConfigController extends AbstractController { $this->denyAccessUnlessGranted('ROLE_KNOWLEDGE_ADMIN'); - $configs = $repository->findBy([], ['active' => 'DESC', 'modelName' => 'ASC', 'version' => 'DESC']); + $configs = $repository->findBy( + [], + ['active' => 'DESC', 'modelName' => 'ASC', 'version' => 'DESC'] + ); return $this->render('admin/model_config/list.html.twig', [ 'configs' => $configs, @@ -38,20 +42,24 @@ class ModelGenerationConfigController extends AbstractController if ($request->isMethod('POST')) { - $modelName = $request->request->get('model_name'); - + $modelName = (string) $request->request->get('model_name'); $version = $repository->findNextVersion($modelName); + $retrievalMaxChunks = (int) $request->request->get('retrieval_max_chunks', 25); + $retrievalVectorTopK = (int) $request->request->get('retrieval_vector_top_k', 25); + $config = new ModelGenerationConfig( modelName: $modelName, version: $version, - stream: (bool)$request->request->get('stream'), - temperature: (float)$request->request->get('temperature'), - topK: (int)$request->request->get('top_k'), - topP: (float)$request->request->get('top_p'), - repeatPenalty: (float)$request->request->get('repeat_penalty'), - numCtx: (int)$request->request->get('num_ctx'), - active: false + stream: (bool) $request->request->get('stream'), + temperature: (float) $request->request->get('temperature'), + topK: (int) $request->request->get('top_k'), + topP: (float) $request->request->get('top_p'), + repeatPenalty: (float) $request->request->get('repeat_penalty'), + numCtx: (int) $request->request->get('num_ctx'), + active: false, + retrievalMaxChunks: $retrievalMaxChunks, + retrievalVectorTopK: $retrievalVectorTopK ); $em->persist($config); @@ -75,6 +83,32 @@ class ModelGenerationConfigController extends AbstractController return $this->redirectToRoute('admin_model_config_list'); } + #[Route('/{id}/test-retrieval', name: 'admin_model_config_test_retrieval')] + public function testRetrieval( + ModelGenerationConfig $config, + Request $request, + NdjsonHybridRetriever $retriever + ): Response { + $this->denyAccessUnlessGranted('ROLE_KNOWLEDGE_ADMIN'); + + $results = []; + $prompt = ''; + + if ($request->isMethod('POST')) { + $prompt = trim((string) $request->request->get('prompt')); + + if ($prompt !== '') { + $results = $retriever->retrieveForConfig($prompt, $config); + } + } + + return $this->render('admin/model_config/test_retrieval.html.twig', [ + 'config' => $config, + 'results' => $results, + 'prompt' => $prompt, + ]); + } + #[Route('/{id}/delete', name: 'admin_model_config_delete', methods: ['POST'])] public function delete( ModelGenerationConfig $config, @@ -99,5 +133,4 @@ class ModelGenerationConfigController extends AbstractController return $this->redirectToRoute('admin_model_config_list'); } - -} +} \ No newline at end of file diff --git a/src/Entity/ModelGenerationConfig.php b/src/Entity/ModelGenerationConfig.php index bd9b156..de7facc 100644 --- a/src/Entity/ModelGenerationConfig.php +++ b/src/Entity/ModelGenerationConfig.php @@ -9,9 +9,15 @@ use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: \App\Repository\ModelGenerationConfigRepository::class)] #[ORM\Table(name: 'model_generation_config')] -#[ORM\Index(columns: ['model_name', 'active'], name: 'idx_model_active')] +#[ORM\Index(name: 'idx_model_active', columns: ['model_name', 'active'])] class ModelGenerationConfig { + // ----------------------------- + // Hard Guardrails + // ----------------------------- + private const MAX_RETRIEVAL_CHUNKS = 200; + private const MAX_VECTOR_TOPK = 200; + #[ORM\Id] #[ORM\Column(type: 'uuid', unique: true)] private Uuid $id; @@ -37,6 +43,16 @@ class ModelGenerationConfig #[ORM\Column(name: 'num_ctx', type: 'integer')] private int $numCtx; + // ------------------------------------- + // Retrieval-Parameter (NEU) + // ------------------------------------- + + #[ORM\Column(name: 'retrieval_max_chunks', type: 'integer')] + private int $retrievalMaxChunks; + + #[ORM\Column(name: 'retrieval_vector_top_k', type: 'integer')] + private int $retrievalVectorTopK; + #[ORM\Column(type: 'boolean')] private bool $active; @@ -55,7 +71,9 @@ class ModelGenerationConfig float $topP = 0.8, float $repeatPenalty = 1.05, int $numCtx = 4096, - bool $active = false + bool $active = false, + int $retrievalMaxChunks = 25, + int $retrievalVectorTopK = 25, ) { $this->id = Uuid::v4(); $this->modelName = $modelName; @@ -68,8 +86,15 @@ class ModelGenerationConfig $this->numCtx = $numCtx; $this->active = $active; $this->createdAt = new \DateTimeImmutable(); + + $this->setRetrievalMaxChunks($retrievalMaxChunks); + $this->setRetrievalVectorTopK($retrievalVectorTopK); } + // ----------------------------- + // Getter + // ----------------------------- + public function getId(): Uuid { return $this->id; } public function getModelName(): string { return $this->modelName; } public function isStream(): bool { return $this->stream; } @@ -82,9 +107,35 @@ class ModelGenerationConfig public function getVersion(): int { return $this->version; } public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } + public function getRetrievalMaxChunks(): int + { + return $this->retrievalMaxChunks; + } + + public function getRetrievalVectorTopK(): int + { + return $this->retrievalVectorTopK; + } + + // ----------------------------- + // Controlled Mutators + // ----------------------------- + // Nur vom Manager nutzen public function setActive(bool $active): void { $this->active = $active; } -} + + public function setRetrievalMaxChunks(int $value): void + { + $value = max(1, min($value, self::MAX_RETRIEVAL_CHUNKS)); + $this->retrievalMaxChunks = $value; + } + + public function setRetrievalVectorTopK(int $value): void + { + $value = max(1, min($value, self::MAX_VECTOR_TOPK)); + $this->retrievalVectorTopK = $value; + } +} \ No newline at end of file diff --git a/src/Knowledge/Retrieval/NdjsonHybridRetriever.php b/src/Knowledge/Retrieval/NdjsonHybridRetriever.php index 772543b..7d313ef 100644 --- a/src/Knowledge/Retrieval/NdjsonHybridRetriever.php +++ b/src/Knowledge/Retrieval/NdjsonHybridRetriever.php @@ -4,43 +4,65 @@ declare(strict_types=1); namespace App\Knowledge\Retrieval; +use App\Entity\ModelGenerationConfig; use App\Knowledge\ChunkManager; +use App\Repository\ModelGenerationConfigRepository; use App\Tag\TagRoutingService; use App\Vector\VectorSearchClient; final class NdjsonHybridRetriever implements RetrieverInterface { private const VECTOR_SCORE_THRESHOLD = 0.25; - - /** - * Wenn Tag-Routing aktiv ist, erhöhen wir TopK, - * weil wir danach per document_id filtern. - */ private const VECTOR_TOPK_MULTIPLIER_WHEN_ROUTED = 10; - - /** - * Keyword-Scan: Mindest-Trefferanzahl an Terms, damit ein Chunk als Kandidat gilt. - */ private const KEYWORD_MIN_HITS = 1; + private const HARD_MAX_CHUNKS = 200; + private const HARD_MAX_VECTORK = 200; + public function __construct( - private readonly ChunkManager $chunkManager, - private readonly NdjsonChunkLookup $lookup, + private readonly ChunkManager $chunkManager, + private readonly NdjsonChunkLookup $lookup, private readonly VectorSearchClient $vectorClient, - private readonly TagRoutingService $tagRouting, - private readonly int $maxChunks = 100, - private readonly int $vectorTopK = 100, + private readonly TagRoutingService $tagRouting, + private readonly ModelGenerationConfigRepository $configRepository, ) {} - public function retrieve(string $prompt, int $limit = null): array + /** + * Normalbetrieb – ausschließlich aktive Config. + */ + public function retrieve(string $prompt): array { - $limit = $this->maxChunks; + $config = $this->configRepository->findActiveForModel(); + + if ($config === null) { + throw new \RuntimeException('No active ModelGenerationConfig found.'); + } + + return $this->retrieveInternal($prompt, $config); + } + + /** + * Admin-Testbetrieb – explizite Config. + * Verändert KEINEN globalen Zustand. + */ + public function retrieveForConfig(string $prompt, ModelGenerationConfig $config): array + { + return $this->retrieveInternal($prompt, $config); + } + + /** + * Zentrale Retrieval-Logik (keine Duplikation). + */ + private function retrieveInternal(string $prompt, ModelGenerationConfig $config): array + { + $limit = max(1, min($config->getRetrievalMaxChunks(), self::HARD_MAX_CHUNKS)); + + $vectorTopKBase = max(1, min($config->getRetrievalVectorTopK(), self::HARD_MAX_VECTORK)); // --------------------------------------------------------- - // 0) Tag-Routing FIRST (soft gate) + // 1) Tag-Vector FIRST -> candidateSet (DocIDs) // --------------------------------------------------------- - $candidateDocIds = $this->tagRouting->route($prompt); - + $candidateDocIds = $this->tagRouting->route($prompt); // <= DAS muss intern auf Tag-Vector gehen $candidateSet = null; if (is_array($candidateDocIds) && $candidateDocIds !== []) { @@ -48,31 +70,22 @@ final class NdjsonHybridRetriever implements RetrieverInterface } // --------------------------------------------------------- - // 1) Keyword first (simple streaming scan) + // 2) Vector chunks (primary) // --------------------------------------------------------- - $terms = $this->extractTerms($prompt); - - $keywordChunks = $this->keywordSearchStreaming($terms, $limit, $candidateSet); - - if (\count($keywordChunks) >= $limit) { - return array_slice($keywordChunks, 0, $limit); - } - - // --------------------------------------------------------- - // 2) Vector fallback / enrichment - // - If routed: increase TopK, then filter by document_id - // - Soft fallback: if filtering yields nothing -> global vector once - // --------------------------------------------------------- - $topK = $this->vectorTopK; + $topK = $vectorTopKBase; if ($candidateSet !== null) { - $topK = max($this->vectorTopK * self::VECTOR_TOPK_MULTIPLIER_WHEN_ROUTED, $this->vectorTopK); - $topK = min($topK, 200); // guardrail + $topK = min( + max($vectorTopKBase * self::VECTOR_TOPK_MULTIPLIER_WHEN_ROUTED, $vectorTopKBase), + self::HARD_MAX_VECTORK + ); } $hits = $this->vectorClient->search($prompt, $topK); + if ($hits === []) { - return $keywordChunks; + // Tags-only System: kein Vector-Hit -> keine Chunks + return []; } $chunkIds = []; @@ -87,14 +100,15 @@ final class NdjsonHybridRetriever implements RetrieverInterface } if ($chunkIds === []) { - return $keywordChunks; + return []; } $rows = $this->lookup->findByChunkIds($chunkIds); - - // routed filtering by document_id $finalChunkIds = $chunkIds; + // --------------------------------------------------------- + // 3) Routed filtering (wenn candidateSet vorhanden) + // --------------------------------------------------------- if ($candidateSet !== null) { $filtered = []; @@ -103,18 +117,20 @@ final class NdjsonHybridRetriever implements RetrieverInterface if (!is_array($row)) { continue; } + $docId = $row['document_id'] ?? null; if (!is_string($docId) || !isset($candidateSet[$docId])) { continue; } + $filtered[] = $id; } - // Soft fallback: if routing filtered everything away, retry global vector once + // Wenn Routing ALLES wegfiltert -> einmal global retry if ($filtered === []) { - $hits2 = $this->vectorClient->search($prompt, $this->vectorTopK); + $hits2 = $this->vectorClient->search($prompt, $vectorTopKBase); if ($hits2 === []) { - return $keywordChunks; + return []; } $chunkIds2 = []; @@ -129,7 +145,7 @@ final class NdjsonHybridRetriever implements RetrieverInterface } if ($chunkIds2 === []) { - return $keywordChunks; + return []; } $rows = $this->lookup->findByChunkIds($chunkIds2); @@ -139,24 +155,25 @@ final class NdjsonHybridRetriever implements RetrieverInterface } } - foreach ($finalChunkIds as $id) { - if (!isset($rows[$id]['text']) || !is_string($rows[$id]['text'])) { - continue; - } - $keywordChunks[] = trim($rows[$id]['text']); - } - // --------------------------------------------------------- - // 3) dedupe + limit + // 4) Collect texts + Dedupe + Limit // --------------------------------------------------------- $seen = []; $out = []; - foreach ($keywordChunks as $chunk) { + foreach ($finalChunkIds as $id) { + $text = $rows[$id]['text'] ?? null; + if (!is_string($text) || $text === '') { + continue; + } + + $chunk = trim($text); $key = mb_strtolower((string)preg_replace('/\s+/u', ' ', $chunk)); + if (isset($seen[$key])) { continue; } + $seen[$key] = true; $out[] = $chunk; @@ -168,16 +185,6 @@ final class NdjsonHybridRetriever implements RetrieverInterface return $out; } - /** - * Streaming Keyword Search über index.ndjson. - * Minimal, aber nützlich: - * - Score = Anzahl gefundener Terms - * - CandidateDocs (Tag-Routing) reduziert Scan massiv - * - * @param string[] $terms - * @param array|null $candidateSet - * @return string[] - */ private function keywordSearchStreaming(array $terms, int $limit, ?array $candidateSet): array { if ($terms === []) { @@ -185,31 +192,28 @@ final class NdjsonHybridRetriever implements RetrieverInterface } $maxScore = \count($terms); - - // top list: each item = ['score' => int, 'text' => string] $top = []; foreach ($this->chunkManager->streamAll() as $row) { $text = $row['text'] ?? null; + if (!is_string($text) || $text === '') { continue; } if ($candidateSet !== null) { $docId = $row['document_id'] ?? null; + if (!is_string($docId) || !isset($candidateSet[$docId])) { continue; } } $haystack = mb_strtolower($text); - $score = 0; + foreach ($terms as $t) { - if ($t === '') { - continue; - } - if (mb_stripos($haystack, $t) !== false) { + if ($t !== '' && mb_stripos($haystack, $t) !== false) { $score++; } } @@ -223,14 +227,11 @@ final class NdjsonHybridRetriever implements RetrieverInterface 'text' => trim($text), ]; - // keep only best N (simple sort, N is tiny) usort($top, static function (array $a, array $b): int { - // higher score first $cmp = ($b['score'] <=> $a['score']); if ($cmp !== 0) { return $cmp; } - // shorter chunk first (often more precise) return (mb_strlen($a['text']) <=> mb_strlen($b['text'])); }); @@ -238,25 +239,14 @@ final class NdjsonHybridRetriever implements RetrieverInterface $top = array_slice($top, 0, $limit); } - // early exit: perfect matches filled if (\count($top) === $limit && ($top[0]['score'] ?? 0) >= $maxScore) { break; } } - $out = []; - foreach ($top as $item) { - $out[] = (string)$item['text']; - } - - return $out; + return array_map(static fn($item) => (string)$item['text'], $top); } - /** - * Minimal term extraction (stabiles Verhalten, wenig Magie) - * - * @return string[] - */ private function extractTerms(string $text): array { $text = mb_strtolower((string)preg_replace('/[^\p{L}\p{N}\s]/u', '', $text)); @@ -266,15 +256,14 @@ final class NdjsonHybridRetriever implements RetrieverInterface static fn(string $w) => mb_strlen($w) > 2 )); - // unique, order preserved $seen = []; $out = []; + foreach ($parts as $w) { - if (isset($seen[$w])) { - continue; + if (!isset($seen[$w])) { + $seen[$w] = true; + $out[] = $w; } - $seen[$w] = true; - $out[] = $w; } return $out; diff --git a/src/Knowledge/Retrieval/RetrieverInterface.php b/src/Knowledge/Retrieval/RetrieverInterface.php index 13ea899..43cd6ed 100644 --- a/src/Knowledge/Retrieval/RetrieverInterface.php +++ b/src/Knowledge/Retrieval/RetrieverInterface.php @@ -1,11 +1,28 @@ - + + + +
- Exakter Modellname wie im KI-Endpunkt konfiguriert (z. B. Ollama oder API). + Exakter Modellname wie im KI-Endpunkt konfiguriert.
-
- Aktiviert Token-Streaming im Chat (empfohlen für bessere UX). + Token-Streaming im Chat (empfohlen).
- + + +
+ + + + + +
Generation (LLM Sampling)
+ +
+
- Steuert die Kreativität der Antworten. - Niedrige Werte (0.2–0.4) erzeugen stabile, sachliche Ergebnisse – empfohlen für RAG-Systeme. - Höhere Werte führen zu freieren, weniger deterministischen Antworten. + Kreativität des Modells. Für RAG 0.2–0.4 empfohlen.
-
- Begrenzt die Anzahl der wahrscheinlichsten Token, aus denen das Modell auswählt. - Niedrigere Werte = konservativer, höhere Werte = flexibler. - 20–50 ist für Wissenssysteme üblich. + Begrenzt die Auswahl wahrscheinlicher Token.
-
- Nucleus Sampling: Das Modell berücksichtigt nur Token, - deren kumulative Wahrscheinlichkeit innerhalb dieses Werts liegt. - 0.8–0.95 bietet eine gute Balance zwischen Stabilität und Natürlichkeit. + Nucleus Sampling – Balance zwischen Stabilität und Natürlichkeit.
-
- Bestraft Wortwiederholungen. Werte leicht über 1.0 (z. B. 1.1–1.15) - verhindern Schleifen und redundante Antworten. + Verhindert Wiederholungen (1.1–1.15 empfohlen).
-
- Maximale Kontextlänge in Tokens (Systemprompt + Benutzerfrage + Retrieval-Chunks). - Muss vom Modell unterstützt werden. - Höhere Werte ermöglichen größere Wissenskontexte, erhöhen jedoch Speicher- und Rechenbedarf. + Maximale Kontextlänge (System + Frage + Retrieval). +
+
+ +
+ +
+ + + + + +
Retrieval (Wissensabruf)
+ +
+ +
+ + +
+ Maximale Anzahl an Wissens-Chunks, + die dem Modell übergeben werden. + 20–40 ist für die meisten Systeme optimal. +
+
+ +
+ + +
+ Anzahl der Vektor-Treffer vor Filterung. + Höhere Werte erhöhen Recall, können aber Rauschen verstärken.
@@ -157,4 +201,4 @@ Hinweis: Neue Konfigurationen werden zunächst inaktiv gespeichert und müssen separat aktiviert werden. Pro Modell kann nur eine Version aktiv sein.
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/model_config/list.html.twig b/templates/admin/model_config/list.html.twig index aa09169..3624a67 100644 --- a/templates/admin/model_config/list.html.twig +++ b/templates/admin/model_config/list.html.twig @@ -24,11 +24,8 @@ Modell Version Stream - Temp - Top K - Top P - Repeat - Ctx + Sampling + Retrieval Status Aktionen @@ -52,27 +49,47 @@ {% endif %} - {{ config.temperature }} - {{ config.topK }} - {{ config.topP }} - {{ config.repeatPenalty }} - {{ config.numCtx }} + {# ========================= #} + {# Sampling Group #} + {# ========================= #} + +
+
T: {{ config.temperature }}
+
K: {{ config.topK }}
+
P: {{ config.topP }}
+
R: {{ config.repeatPenalty }}
+
Ctx: {{ config.numCtx }}
+
+ + + {# ========================= #} + {# Retrieval Group #} + {# ========================= #} + +
+
Chunks: {{ config.retrievalMaxChunks }}
+
VectorK: {{ config.retrievalVectorTopK }}
+
+ {% if config.active %} Aktiv {% else %} - Inaktiv - + Inaktiv + {% endif %} + + Test Retrieval + {% if not config.active and is_granted('ROLE_SUPER_ADMIN') %} - {# Aktivieren via POST + CSRF #}
-
- {# Löschen via POST + CSRF #}
— {% endif %} - {% else %} - + Keine Konfiguration vorhanden. @@ -132,13 +147,11 @@
- -
@@ -146,4 +159,5 @@ Der Agent läuft im isolierten Admin-Test-Modus. Keine Persistenz. Keine produktive Session. -{% endblock %} + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/model_config/test_retrieval.html.twig b/templates/admin/model_config/test_retrieval.html.twig new file mode 100644 index 0000000..61c453e --- /dev/null +++ b/templates/admin/model_config/test_retrieval.html.twig @@ -0,0 +1,93 @@ +{% extends 'admin/base.html.twig' %} + +{% block title %}Retrieval-Test{% endblock %} + +{% block body %} + +
+

+ Retrieval-Test – {{ config.modelName }} (v{{ config.version }}) +

+ + + Zurück + +
+ + {# ====================================================== #} + {# Konfigurationsübersicht #} + {# ====================================================== #} + +
+
+ +
+
+ Max Chunks: + {{ config.retrievalMaxChunks }} +
+ +
+ Vector Top K: + {{ config.retrievalVectorTopK }} +
+
+ +
+
+ + {# ====================================================== #} + {# Testformular #} + {# ====================================================== #} + +
+
+ + + +
+ + +
+ + + + + +
+
+ + {# ====================================================== #} + {# Ergebnisse #} + {# ====================================================== #} + + {% if results is not empty %} +
+
+ +
+ Gefundene Chunks ({{ results|length }}) +
+ +
+ + {% for chunk in results %} +
+ {{ chunk }} +
+ {% endfor %} + +
+ +
+
+ {% endif %} + +{% endblock %} \ No newline at end of file