[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
@@ -564,4 +564,58 @@ PROMPT;
|
||||
'model_used' => $this->model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration 012-014: Q&A grounded sulla KB multi-livello.
|
||||
*
|
||||
* Esegue una RAG search sui documenti visibili all'utente (SYSTEM/FIRM/ORG)
|
||||
* e inietta i top-K chunks nel system prompt prima di chiamare Claude.
|
||||
*
|
||||
* Se Voyage/Qdrant non sono disponibili, ricade su Claude diretto senza grounding.
|
||||
*
|
||||
* @param string $question Domanda dell'utente
|
||||
* @param array $userContext ['user_id', 'organization_id', 'consulting_firm_id']
|
||||
* @return array ['answer'=>string, 'sources'=>array, 'rag_used'=>bool]
|
||||
*/
|
||||
public function askWithRag(string $question, array $userContext): array
|
||||
{
|
||||
$sources = [];
|
||||
$contextBlock = '';
|
||||
$ragUsed = false;
|
||||
|
||||
// Tenta RAG: se fallisce, prosegui senza grounding (degradazione graceful)
|
||||
try {
|
||||
require_once __DIR__ . '/RagService.php';
|
||||
$rag = new RagService();
|
||||
$hits = $rag->searchForUser($question, $userContext, 5, 0.28);
|
||||
if (!empty($hits)) {
|
||||
$contextBlock = $rag->formatContext($hits);
|
||||
$sources = array_map(fn($h) => [
|
||||
'title' => $h['title'],
|
||||
'scope' => $h['scope'],
|
||||
'score' => $h['score'],
|
||||
], $hits);
|
||||
$ragUsed = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('[AIService::askWithRag] RAG failed, fallback diretto: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$systemPrompt = "Sei un esperto consulente di cybersecurity NIS2 (EU 2022/2555) e D.Lgs. 138/2024.\n"
|
||||
. "Rispondi in modo preciso e cita le fonti del contesto quando rilevanti.\n";
|
||||
if (!empty($contextBlock)) {
|
||||
$systemPrompt .= "\n## Contesto documentale (knowledge base)\n" . $contextBlock
|
||||
. "\n\nQuando rispondi, cita esplicitamente i numeri tra parentesi quadre [1], [2], ... che corrispondono ai documenti del contesto.";
|
||||
} else {
|
||||
$systemPrompt .= "\nNon e' disponibile contesto documentale specifico per questa domanda. Rispondi con la tua conoscenza generale e indica esplicitamente che non hai trovato fonti nella knowledge base.";
|
||||
}
|
||||
|
||||
$answer = $this->callAPI($question, $systemPrompt);
|
||||
|
||||
return [
|
||||
'answer' => $answer,
|
||||
'sources' => $sources,
|
||||
'rag_used' => $ragUsed,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user