[FEAT] Knowledge Base RAG multi-livello (SYSTEM/FIRM/ORG) + Qdrant + Voyage
- KnowledgeBaseController: ingest, list, firmOrgs, search, delete - VectorService (Qdrant + buildAuthzFilter), EmbedService (Voyage), RagService (pipeline) - AIService::askWithRag con fallback graceful - docker-compose: servizio qdrant + env Voyage (chiave da .env/vault, no hardcoded) - SQL 012 consulting_firms, 013 firm_assignments + kb_uploaded_documents - public/kb.html + kb.js (upload, lista, search preview) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9b53ca3ba1
commit
a7a21faa82
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - RagService
|
||||
*
|
||||
* Combina EmbedService + VectorService per cercare nella KB multi-livello
|
||||
* filtrando per il contesto utente (Migration 012-014).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/VectorService.php';
|
||||
require_once __DIR__ . '/EmbedService.php';
|
||||
|
||||
class RagService
|
||||
{
|
||||
private VectorService $vector;
|
||||
private EmbedService $embed;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->vector = new VectorService();
|
||||
$this->embed = new EmbedService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cerca i top-k chunks visibili all'utente.
|
||||
*
|
||||
* @param array $userContext ['user_id', 'organization_id', 'consulting_firm_id']
|
||||
* @return array Lista chunks con title, content, score, scope
|
||||
*/
|
||||
public function searchForUser(string $question, array $userContext, int $topK = 5, float $minScore = 0.28): array
|
||||
{
|
||||
$vector = $this->embed->embed($question);
|
||||
$filter = VectorService::buildAuthzFilter($userContext);
|
||||
$hits = $this->vector->search($vector, $filter, $topK, $minScore);
|
||||
|
||||
$out = [];
|
||||
foreach ($hits as $h) {
|
||||
$p = $h['payload'] ?? [];
|
||||
$out[] = [
|
||||
'id' => $h['id'] ?? null,
|
||||
'score' => round($h['score'] ?? 0, 4),
|
||||
'title' => $p['title'] ?? '',
|
||||
'content' => $p['chunk'] ?? '',
|
||||
'scope' => $p['scope'] ?? null,
|
||||
'source' => $p['source'] ?? null,
|
||||
'lang' => $p['lang'] ?? 'it',
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatta i risultati in un blocco di testo da iniettare nel system prompt Claude.
|
||||
*/
|
||||
public function formatContext(array $hits): string
|
||||
{
|
||||
if (empty($hits)) return '';
|
||||
$blocks = [];
|
||||
foreach ($hits as $i => $h) {
|
||||
$idx = $i + 1;
|
||||
$blocks[] = "[$idx] {$h['title']} (scope={$h['scope']}, score={$h['score']})\n{$h['content']}";
|
||||
}
|
||||
return implode("\n\n---\n\n", $blocks);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user