- 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>
65 lines
2.0 KiB
PHP
65 lines
2.0 KiB
PHP
<?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);
|
|
}
|
|
}
|