[KB] Script ingest Guida UI V2 nella KB AI (eseguito: 8 chunk, scope SYSTEM)
ARIA ora conosce la nuova interfaccia V2 (docs/kb/V2_UI_NAVIGAZIONE.md indicizzato in Qdrant nis2_kb scope SYSTEM). Header documenta il workaround mount (copia sotto application/). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1c64211685
commit
2b197440f6
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* NIS2 Agile - Ingest documento "Guida UI V2 (Bootstrap Italia)" nella KB (RAG)
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* Indicizza docs/kb/V2_UI_NAVIGAZIONE.md nella collection Qdrant `nis2_kb` con
|
||||||
|
* scope SYSTEM, cosi' che l'assistente AI (ARIA) sappia guidare l'utente nella
|
||||||
|
* nuova interfaccia V2 (sidebar, pagine, moduli).
|
||||||
|
*
|
||||||
|
* ESEGUIRE SU HETZNER (richiede Qdrant + Voyage). NB: il container nis2-app monta
|
||||||
|
* SOLO application/ e public/ (NON scripts/), quindi per girare via `docker exec`
|
||||||
|
* copiare temporaneamente questo script + il doc sotto application/ ed eseguire:
|
||||||
|
* cp scripts/ingest-v2-ui-doc.php application/_ingest-v2.php
|
||||||
|
* cp docs/kb/V2_UI_NAVIGAZIONE.md application/_v2_ui_doc.md # + adattare i path a __DIR__
|
||||||
|
* docker exec -i nis2-app php /var/www/nis2-agile/application/_ingest-v2.php
|
||||||
|
* rm application/_ingest-v2.php application/_v2_ui_doc.md
|
||||||
|
* (eseguito con successo il 2026-06-12: 8 chunk, scope SYSTEM).
|
||||||
|
*
|
||||||
|
* Idempotente: cancella i chunk SYSTEM di questa stessa fonte prima del re-upsert.
|
||||||
|
* Opzioni: --dry-run (estrae e mostra le statistiche senza upsert)
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (PHP_SAPI !== 'cli') { fwrite(STDERR, "Solo CLI\n"); exit(1); }
|
||||||
|
|
||||||
|
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(__DIR__));
|
||||||
|
if (!defined('APP_PATH')) define('APP_PATH', BASE_PATH . '/application');
|
||||||
|
|
||||||
|
require_once APP_PATH . '/config/env.php';
|
||||||
|
require_once APP_PATH . '/config/config.php';
|
||||||
|
require_once APP_PATH . '/config/database.php';
|
||||||
|
require_once APP_PATH . '/services/EmbedService.php';
|
||||||
|
require_once APP_PATH . '/services/VectorService.php';
|
||||||
|
|
||||||
|
$opts = getopt('', ['dry-run']);
|
||||||
|
$dryRun = isset($opts['dry-run']);
|
||||||
|
|
||||||
|
$DOC_FILE = BASE_PATH . '/docs/kb/V2_UI_NAVIGAZIONE.md';
|
||||||
|
$SOURCE = 'NIS2 Agile — Guida interfaccia V2 (Bootstrap Italia)';
|
||||||
|
$TITLE = 'Interfaccia V2 NIS2 Agile — navigazione e moduli';
|
||||||
|
|
||||||
|
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
|
||||||
|
|
||||||
|
function chunkText(string $text, int $size = 2000, int $overlap = 200): array
|
||||||
|
{
|
||||||
|
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
|
||||||
|
$chunks = []; $len = mb_strlen($text, 'UTF-8'); $start = 0;
|
||||||
|
while ($start < $len) {
|
||||||
|
$take = min($size, $len - $start);
|
||||||
|
$piece = mb_substr($text, $start, $take, 'UTF-8');
|
||||||
|
if (trim($piece) !== '') $chunks[] = $piece;
|
||||||
|
if ($start + $take >= $len) break;
|
||||||
|
$start += ($size - $overlap);
|
||||||
|
}
|
||||||
|
return $chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuid(): string
|
||||||
|
{
|
||||||
|
$b = random_bytes(16);
|
||||||
|
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
|
||||||
|
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
|
||||||
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
logln('=== Ingest Guida UI V2 nella KB (scope SYSTEM) ===');
|
||||||
|
if ($dryRun) logln('MODALITA DRY-RUN: nessun upsert.');
|
||||||
|
|
||||||
|
if (!is_file($DOC_FILE)) { logln("ERRORE: doc non trovato {$DOC_FILE}"); exit(1); }
|
||||||
|
$text = (string) file_get_contents($DOC_FILE);
|
||||||
|
$text = preg_replace('/[ \t]+/', ' ', $text);
|
||||||
|
$text = preg_replace('/\n{3,}/', "\n\n", trim($text));
|
||||||
|
if (strlen($text) < 200) { logln('ERRORE: testo troppo breve.'); exit(1); }
|
||||||
|
|
||||||
|
$header = "DOCUMENTO: Guida all'interfaccia V2 di NIS2 Agile (Bootstrap Italia).\n"
|
||||||
|
. "USO: aiutare l'utente a navigare la UI, trovare moduli e funzioni.\n\n";
|
||||||
|
$chunks = chunkText($header . $text, 2000, 200);
|
||||||
|
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
|
||||||
|
|
||||||
|
if ($dryRun) { logln('=== DRY-RUN completato ==='); exit(0); }
|
||||||
|
|
||||||
|
$embed = new EmbedService();
|
||||||
|
$vector = new VectorService();
|
||||||
|
$vector->ensureCollection($embed->dims);
|
||||||
|
|
||||||
|
// Idempotenza: rimuovi i chunk SYSTEM esistenti per questa fonte
|
||||||
|
try {
|
||||||
|
$vector->deleteByFilter(['must' => [
|
||||||
|
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
|
||||||
|
['key' => 'source', 'match' => ['value' => $SOURCE]],
|
||||||
|
]]);
|
||||||
|
} catch (Exception $e) { logln(' (warning) delete precedente: ' . $e->getMessage()); }
|
||||||
|
|
||||||
|
$docUuid = uuid();
|
||||||
|
$points = [];
|
||||||
|
foreach ($chunks as $i => $chunk) {
|
||||||
|
$vec = null;
|
||||||
|
for ($try = 1; $try <= 5; $try++) {
|
||||||
|
try { $vec = $embed->embed($chunk); break; }
|
||||||
|
catch (Throwable $e) {
|
||||||
|
if ($try === 5) { logln(" ERRORE embed chunk {$i} dopo 5 tentativi: " . $e->getMessage()); throw $e; }
|
||||||
|
logln(" retry embed chunk {$i} (tentativo {$try})");
|
||||||
|
sleep($try);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$points[] = [
|
||||||
|
'id' => uuid(),
|
||||||
|
'vector' => $vec,
|
||||||
|
'payload' => [
|
||||||
|
'doc_uuid' => $docUuid,
|
||||||
|
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
|
||||||
|
'chunk' => $chunk,
|
||||||
|
'entity_type' => 'guida_prodotto',
|
||||||
|
'source' => $SOURCE,
|
||||||
|
'lang' => 'it',
|
||||||
|
'scope' => 'SYSTEM',
|
||||||
|
'consulting_firm_id' => null,
|
||||||
|
'organization_id' => null,
|
||||||
|
'shared_with_orgs' => [],
|
||||||
|
'uploaded_by' => 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
foreach (array_chunk($points, 64) as $batch) {
|
||||||
|
$vector->upsertBatch($batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracking MySQL (best-effort)
|
||||||
|
try {
|
||||||
|
$stmt = Database::getInstance()->prepare(
|
||||||
|
"INSERT INTO kb_uploaded_documents
|
||||||
|
(qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status)
|
||||||
|
VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"
|
||||||
|
);
|
||||||
|
$stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]);
|
||||||
|
} catch (Exception $e) { logln(' (warning) tracking insert: ' . $e->getMessage()); }
|
||||||
|
|
||||||
|
logln("=== OK indicizzato: doc_uuid={$docUuid}, " . count($chunks) . " chunk ===");
|
||||||
Reference in New Issue
Block a user