optimize retriever
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
// =========================================================
|
// =========================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user