Backup automatico script del 2026-09-27 07:00
This commit is contained in:
1 parent
f347badf3b
commit
09dcbbdadb
24 files changed
+1164
-193
No files matched your search
@@ -2,10 +2,15 @@
|
||||
"""Paperless → Qdrant indexing pipeline."""
|
||||
|
||||
import logging
|
||||
import zlib
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional
|
||||
|
||||
LOGGER = logging.getLogger("loogle_mcp.indexer")
|
||||
|
||||
# Indicizzazioni avviate dai tool MCP: fuori dalla richiesta, una alla volta (thermal gate)
|
||||
_background = ThreadPoolExecutor(max_workers=1, thread_name_prefix="bg-index")
|
||||
|
||||
from ..db import get_conn
|
||||
from . import embeddings, gitea_indexer, paperless, qdrant_store
|
||||
from .text_chunk import chunk_text as _chunk_text
|
||||
@@ -104,7 +109,8 @@ def index_context_snippet(
|
||||
doc_key = f"ctx-{username}-{project_id}"
|
||||
if snippet_id:
|
||||
doc_key = f"{doc_key}-{snippet_id}"
|
||||
doc_id = hash(doc_key) % (2**31)
|
||||
# crc32 e non hash(): hash() cambia a ogni processo e il delete non trovava i vecchi chunk
|
||||
doc_id = zlib.crc32(doc_key.encode()) % (2**31)
|
||||
qdrant_store.delete_by_doc(collection, doc_id)
|
||||
ids = []
|
||||
payloads = []
|
||||
@@ -127,12 +133,82 @@ def index_context_snippet(
|
||||
qdrant_store.upsert_chunks(collection, ids, vectors, payloads)
|
||||
|
||||
|
||||
def index_project_context(username: str, project_id: str) -> dict:
|
||||
"""Indicizza il context.md completo del progetto sostituendo la versione precedente.
|
||||
|
||||
Personali → ctx_{utente}; condivisi → ctx_shared_family (visibili a tutta la famiglia).
|
||||
Così un contesto che cambia (es. inventario) non lascia in ricerca informazioni superate.
|
||||
"""
|
||||
from ..context import store as context_store
|
||||
|
||||
data = context_store.get_project_context(username, project_id, session_limit=0, include_gitea=False)
|
||||
meta = data["meta"]
|
||||
shared = bool(meta.get("shared"))
|
||||
collection = qdrant_store.CTX_SHARED_COLLECTION if shared else qdrant_store.ctx_collection(username)
|
||||
namespace = context_store.SHARED_NS if shared else username
|
||||
drop_project_context(collection, project_id)
|
||||
chunks = _chunk_text(data["context_md"])
|
||||
if not chunks:
|
||||
return {"project_id": project_id, "collection": collection, "chunks": 0}
|
||||
vectors = embeddings.embed_texts(chunks)
|
||||
doc_key = f"ctx-{namespace}-{project_id}"
|
||||
doc_id = zlib.crc32(doc_key.encode()) % (2**31)
|
||||
ids, payloads = [], []
|
||||
for i, chunk in enumerate(chunks):
|
||||
ids.append(f"{doc_key}-chunk-{i}")
|
||||
payloads.append(
|
||||
{
|
||||
"doc_id": doc_id,
|
||||
"project_id": project_id,
|
||||
"chunk_index": i,
|
||||
"title": meta.get("title") or project_id,
|
||||
"text": chunk,
|
||||
"owner": meta.get("owner") or username,
|
||||
"visibility": "family" if shared else "personal",
|
||||
"shared": shared,
|
||||
"updated_by": meta.get("updated_by"),
|
||||
"source": "context",
|
||||
"snippet_id": "full",
|
||||
}
|
||||
)
|
||||
qdrant_store.upsert_chunks(collection, ids, vectors, payloads)
|
||||
return {"project_id": project_id, "collection": collection, "chunks": len(chunks)}
|
||||
|
||||
|
||||
_DIGEST_SNIPPET_PREFIXES = ("daily-", "plan-")
|
||||
|
||||
|
||||
def drop_project_context(collection: str, project_id: str) -> int:
|
||||
"""Rimuove tutti i chunk di un progetto (anche quelli indicizzati a pezzi in passato),
|
||||
tranne gli snippet del digest giornaliero (snippet_id daily-*/plan-*)."""
|
||||
return qdrant_store.delete_matching(
|
||||
collection,
|
||||
"project_id",
|
||||
project_id,
|
||||
lambda payload: not str(payload.get("snippet_id") or "").startswith(_DIGEST_SNIPPET_PREFIXES),
|
||||
)
|
||||
|
||||
|
||||
def submit_background(label: str, fn, *args, **kwargs) -> None:
|
||||
"""Esegue fn in background; gli errori finiscono nel log invece che al client."""
|
||||
|
||||
def _run():
|
||||
try:
|
||||
fn(*args, **kwargs)
|
||||
LOGGER.info("Indicizzazione background completata: %s", label)
|
||||
except Exception:
|
||||
LOGGER.exception("Indicizzazione background fallita: %s", label)
|
||||
|
||||
_background.submit(_run)
|
||||
|
||||
|
||||
def search_knowledge(username: str, query: str, limit: int = 8, is_admin: bool = False) -> list:
|
||||
vectors = embeddings.embed_texts([query])
|
||||
vectors = [embeddings.embed_query(query)]
|
||||
collections = [
|
||||
qdrant_store.SHARED_COLLECTION,
|
||||
qdrant_store.kb_collection(username),
|
||||
qdrant_store.ctx_collection(username),
|
||||
qdrant_store.CTX_SHARED_COLLECTION,
|
||||
]
|
||||
collections.extend(gitea_indexer.gitea_collections_for_user(username, is_admin=is_admin))
|
||||
collections.append(qdrant_store.APPS_SHARED_COLLECTION)
|
||||
@@ -156,8 +232,9 @@ def search_gitea_knowledge(username: str, query: str, limit: int = 8, is_admin:
|
||||
|
||||
|
||||
def search_context(username: str, query: str, limit: int = 8) -> list:
|
||||
vectors = embeddings.embed_texts([query])
|
||||
return qdrant_store.search([qdrant_store.ctx_collection(username)], vectors[0], limit=limit)
|
||||
vectors = [embeddings.embed_query(query)]
|
||||
collections = [qdrant_store.ctx_collection(username), qdrant_store.CTX_SHARED_COLLECTION]
|
||||
return qdrant_store.search(collections, vectors[0], limit=limit)
|
||||
|
||||
|
||||
def list_recent_documents(limit: int = 20) -> list:
|
||||
|
||||