optimize retriever

This commit is contained in:
team2
2026-02-27 19:27:13 +01:00
parent 44be40a24d
commit 5e004c99f4
6 changed files with 41 additions and 42 deletions

View File

@@ -98,7 +98,7 @@ class ModelGenerationConfigController extends AbstractController
$prompt = trim((string) $request->request->get('prompt')); $prompt = trim((string) $request->request->get('prompt'));
if ($prompt !== '') { if ($prompt !== '') {
$results = $retriever->retrieveForConfig($prompt, $config); $results = $retriever->retrieveInternal($prompt, $config);
} }
} }

View File

@@ -14,16 +14,18 @@ use App\Vector\VectorSearchClient;
final class NdjsonHybridRetriever implements RetrieverInterface final class NdjsonHybridRetriever implements RetrieverInterface
{ {
private const VECTOR_SCORE_THRESHOLD = 0.75; private const VECTOR_SCORE_THRESHOLD = 0.72;
private const HARD_MAX_CHUNKS = 200; private const HARD_MAX_CHUNKS = 120;
private const HARD_MAX_VECTORK = 200; private const HARD_MAX_VECTORK = 250;
private const LIST_BONUS = 1.5;
/** /**
* Tags dürfen nur ein kleiner Bonus sein (kein Gate/Filter). * Tags must only provide a small bonus (never act as a gate/filter).
* Enterprise Default: klein halten, sonst dominieren Tags wieder. * Enterprise default: keep it low, otherwise tags will dominate ranking again.
*/ */
private const TAG_SCORE_BONUS = 0.5; private const TAG_SCORE_BONUS = 0.1 * (1 - self::VECTOR_SCORE_THRESHOLD);
public function __construct( public function __construct(
private readonly NdjsonChunkLookup $lookup, private readonly NdjsonChunkLookup $lookup,
@@ -47,21 +49,18 @@ final class NdjsonHybridRetriever implements RetrieverInterface
return $this->retrieveInternal($prompt, $config); return $this->retrieveInternal($prompt, $config);
} }
public function retrieveForConfig(string $prompt, ModelGenerationConfig $config): array
{
return $this->retrieveInternal($prompt, $config);
}
private function retrieveInternal(string $prompt, ModelGenerationConfig $config): array public function retrieveInternal(string $prompt, ModelGenerationConfig $config): array
{ {
$limit = max(1, min($config->getRetrievalMaxChunks(), self::HARD_MAX_CHUNKS)); $limit = max(1, min($config->getRetrievalMaxChunks(), self::HARD_MAX_CHUNKS));
$vectorTopKBase = max(1, min($config->getRetrievalVectorTopK(), self::HARD_MAX_VECTORK)); $vectorTopKBase = max(1, min($config->getRetrievalVectorTopK(), self::HARD_MAX_VECTORK));
// Wichtig: List-Detection bleibt auf Originalprompt (sonst entfernst du "zeige/liste" etc.) // Important: list-intent detection must run on the original prompt
// (cleaning might remove "show/list" etc.).
$isListQuery = $this->intentLite->isListQuery($prompt); $isListQuery = $this->intentLite->isListQuery($prompt);
// ------------------------------------------------- // -------------------------------------------------
// CLEAN QUERY (nur für Retrieval: Tags + Vector) // CLEAN QUERY (retrieval-only: tag routing + vector search)
// ------------------------------------------------- // -------------------------------------------------
$cleanQuery = $this->queryCleaner->clean($prompt); $cleanQuery = $this->queryCleaner->clean($prompt);
if ($cleanQuery === '') { if ($cleanQuery === '') {
@@ -69,7 +68,7 @@ final class NdjsonHybridRetriever implements RetrieverInterface
} }
// ------------------------------------------------- // -------------------------------------------------
// 1) Tag Routing (bereinigte Query) -> NUR Bonus // 1) Tag routing (cleaned query) -> bonus only
// ------------------------------------------------- // -------------------------------------------------
$candidateDocIds = $this->tagRouting->route($cleanQuery); $candidateDocIds = $this->tagRouting->route($cleanQuery);
$candidateSet = null; $candidateSet = null;
@@ -79,29 +78,29 @@ final class NdjsonHybridRetriever implements RetrieverInterface
} }
// ------------------------------------------------- // -------------------------------------------------
// 2) TopK bestimmen // 2) Determine TopK
// ------------------------------------------------- // -------------------------------------------------
$topK = $vectorTopKBase; $topK = $vectorTopKBase;
// List mode: höhere Abdeckung, um mehr Dokumente zu ranken // List mode: increase coverage to rank more documents
if ($isListQuery) { if ($isListQuery) {
$topK = (int)round($vectorTopKBase * 2.5); $topK = (int)round($vectorTopKBase * self::LIST_BONUS);
} }
$topK = max(1, min($topK, self::HARD_MAX_VECTORK)); $topK = max(1, min($topK, self::HARD_MAX_VECTORK));
// ------------------------------------------------- // -------------------------------------------------
// 3) Vector Search (immer GLOBAL; Tags sind KEIN Filter) // 3) Vector search (always GLOBAL; tags are NOT a filter)
// ------------------------------------------------- // -------------------------------------------------
$hits = $this->vectorClient->search($cleanQuery, $topK); $hits = $this->vectorClient->search($cleanQuery, $topK);
if ($hits === []) { if ($hits === []) {
// Tags dürfen NICHT als Fallback wirken (sonst wieder zu mächtig) // Tags must NOT act as a fallback (otherwise they become too powerful again).
return []; return [];
} }
// ------------------------------------------------- // -------------------------------------------------
// 4) ChunkIds + Scores sammeln (raw) // 4) Collect chunkIds + scores (raw)
// ------------------------------------------------- // -------------------------------------------------
/** @var array<string,float> $rawScoreByChunkId */ /** @var array<string,float> $rawScoreByChunkId */
$rawScoreByChunkId = []; $rawScoreByChunkId = [];
@@ -113,14 +112,14 @@ final class NdjsonHybridRetriever implements RetrieverInterface
$raw = (float)$hit['score']; $raw = (float)$hit['score'];
// Threshold wird auf RAW Score angewendet (Qualitätsgate) // Apply the threshold to the RAW score (quality gate)
if ($raw < self::VECTOR_SCORE_THRESHOLD) { if ($raw < self::VECTOR_SCORE_THRESHOLD) {
continue; continue;
} }
$chunkId = (string)$hit['chunk_id']; $chunkId = (string)$hit['chunk_id'];
// Falls mehrfach: den besten raw score behalten // If a chunk appears multiple times, keep the best raw score
if (!isset($rawScoreByChunkId[$chunkId]) || $raw > $rawScoreByChunkId[$chunkId]) { if (!isset($rawScoreByChunkId[$chunkId]) || $raw > $rawScoreByChunkId[$chunkId]) {
$rawScoreByChunkId[$chunkId] = $raw; $rawScoreByChunkId[$chunkId] = $raw;
} }
@@ -130,11 +129,11 @@ final class NdjsonHybridRetriever implements RetrieverInterface
return []; return [];
} }
// Lookup liefert docId + Text etc. // Lookup returns document_id + text etc.
$rows = $this->lookup->findByChunkIds(array_keys($rawScoreByChunkId)); $rows = $this->lookup->findByChunkIds(array_keys($rawScoreByChunkId));
// ------------------------------------------------- // -------------------------------------------------
// 5) Adjusted Score (Tag Bonus) + Ranking // 5) Adjusted score (tag bonus) + ranking
// ------------------------------------------------- // -------------------------------------------------
/** @var array<string,float> $adjScoreByChunkId */ /** @var array<string,float> $adjScoreByChunkId */
$adjScoreByChunkId = []; $adjScoreByChunkId = [];
@@ -174,7 +173,7 @@ final class NdjsonHybridRetriever implements RetrieverInterface
$rankedChunkIds = array_keys($adjScoreByChunkId); $rankedChunkIds = array_keys($adjScoreByChunkId);
// ------------------------------------------------- // -------------------------------------------------
// 6) Listenmodus → Dokument-Ranking (mit Tag-Bonus in Scores) // 6) List mode -> document ranking (with tag bonus in scores)
// ------------------------------------------------- // -------------------------------------------------
if ($isListQuery) { if ($isListQuery) {
$rankedDocIds = $this->rankDocumentsFromAdjustedScores($adjScoreByChunkId, $rows); $rankedDocIds = $this->rankDocumentsFromAdjustedScores($adjScoreByChunkId, $rows);
@@ -189,7 +188,7 @@ final class NdjsonHybridRetriever implements RetrieverInterface
} }
// ------------------------------------------------- // -------------------------------------------------
// 7) Normaler Chunk-Modus (nach adjusted Ranking) // 7) Normal chunk mode (by adjusted ranking)
// ------------------------------------------------- // -------------------------------------------------
return $this->collectTexts($rankedChunkIds, $rows, $limit); return $this->collectTexts($rankedChunkIds, $rows, $limit);
} }
@@ -199,7 +198,7 @@ final class NdjsonHybridRetriever implements RetrieverInterface
// ========================================================= // =========================================================
// ========================================================= // =========================================================
// DOCUMENT RANKING (Adjusted Scores incl. Tag Bonus) // DOCUMENT RANKING (Adjusted scores incl. tag bonus)
// ========================================================= // =========================================================
/** /**

View File

@@ -64,7 +64,7 @@ final class ModelGenerationConfigManager
// Enterprise RAG Warnbereich (optional, nur Logging) // Enterprise RAG Warnbereich (optional, nur Logging)
if ($config->getTemperature() > 0.7) { if ($config->getTemperature() > 0.7) {
// hier könntest du optional Logging einbauen // hier könntest man optional Logging einbauen
} }
} }
} }

View File

@@ -8,11 +8,11 @@ use App\Entity\DocumentTag;
use App\Entity\Tag; use App\Entity\Tag;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
final class TagNdjsonExporter final readonly class TagNdjsonExporter
{ {
public function __construct( public function __construct(
private EntityManagerInterface $em, private EntityManagerInterface $em,
private string $tagsNdjsonPath, private string $tagsNdjsonPath,
) {} ) {}
/** /**

View File

@@ -10,11 +10,11 @@ use App\Entity\DocumentTag;
use App\Service\TagRebuildJobService; use App\Service\TagRebuildJobService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
final class TagService final readonly class TagService
{ {
public function __construct( public function __construct(
private EntityManagerInterface $em, private EntityManagerInterface $em,
private TagRebuildJobService $jobs, private TagRebuildJobService $jobs,
) {} ) {}
// ========================================================= // =========================================================

View File

@@ -7,17 +7,17 @@ namespace App\Tag;
use App\Index\IndexMetaManager; use App\Index\IndexMetaManager;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
final class TagVectorIndexBuilder final readonly class TagVectorIndexBuilder
{ {
public function __construct( public function __construct(
private readonly string $pythonBin, private string $pythonBin,
private readonly string $scriptPath, private string $scriptPath,
private readonly string $tagsNdjsonPath, private string $tagsNdjsonPath,
private readonly string $vectorTagsIndexPath, private string $vectorTagsIndexPath,
private readonly string $embeddingModel, private string $embeddingModel,
private readonly int $timeoutSeconds, private int $timeoutSeconds,
private readonly LoggerInterface $agentLogger, private LoggerInterface $agentLogger,
private readonly IndexMetaManager $metaManager, // ✅ NEU private IndexMetaManager $metaManager, // ✅ NEU
) {} ) {}
public function build(): void public function build(): void