Files
pi-qmem/gateway/models.py
T
enne2 86ac1996fb feat(private): memorie riservate escluse dalle ricerche standard
- gateway: campo private (bool) in MemoryIn; filtro must_not private=true
  di default in search_filter; include_private in SearchIn per ricerche
  esplicite; campo private esposto in format_results
- estensione: parametro private in qmem_store, include_private in qmem_search
  (con descrizioni e avvertenze sui prompt dei modelli)
- testato su brain: record private invisibile alla ricerca standard,
  visibile solo con include_private=true; topic esplicito da solo non sblocca
- deploy: /opt/memory/gateway ricostruito (container memory-gateway)
2026-08-25 20:14:28 +02:00

60 lines
3.0 KiB
Python

"""Schemi Pydantic del Memory Gateway."""
from __future__ import annotations
from datetime import datetime
from typing import Literal, Optional
from pydantic import BaseModel, Field, field_validator
from config import MAX_TEXT_LEN
class MemoryLink(BaseModel):
target_id: str = Field(..., description="UUID del record target collegato")
predicate: str = Field(default="part_of", max_length=64, description="Tipo di relazione: parent_of, part_of, relates_to, supersedes...")
weight: float = Field(default=1.0, ge=0.0, le=1.0)
class MemoryIn(BaseModel):
text: str = Field(min_length=1, max_length=MAX_TEXT_LEN)
kind: Literal["decision", "fact", "episode", "preference"] = "fact"
agent_id: Optional[str] = Field(default=None, max_length=64, description="Solo provenienza, nessun isolamento")
project_id: str = Field(min_length=1, max_length=64, description="OBBLIGATORIO: progetto/dominio di appartenenza (kebab-case)")
scope: Literal["agent", "project", "org"] = "agent"
source: Optional[str] = Field(default=None, max_length=256)
confidence: Literal["high", "medium", "low"] = Field(default="medium", description="Affidabilità del record")
expires_at: Optional[str] = None
supersedes_id: Optional[str] = None
supersede_reason: Optional[str] = Field(default=None, max_length=512)
parent_id: Optional[str] = Field(default=None, description="UUID del record genitore per gerarchia/subtopic")
level: Optional[Literal["L1_ROOT", "L2_SUBTOPIC", "L3_DETAIL"]] = Field(default=None, description="Livello gerarchico")
topic: Optional[str] = Field(default=None, max_length=128, description="Topic gerarchico")
links: Optional[list[MemoryLink]] = Field(default=None, description="Collegamenti semantici e relazionali")
private: bool = Field(default=False, description="Riservato: escluso dalle ricerche standard, visibile solo con include_private o topic esplicito")
@field_validator("expires_at")
@classmethod
def _validate_expires_at(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return v
try:
datetime.fromisoformat(v.replace("Z", "+00:00"))
except ValueError:
raise ValueError("expires_at deve essere una data ISO 8601 valida (es. 2026-09-01T00:00:00Z)")
return v
class SearchIn(BaseModel):
query: str = Field(min_length=1, max_length=512)
kind: Optional[Literal["decision", "fact", "episode", "preference"]] = None
project_id: Optional[str] = None
scope: Optional[Literal["agent", "project", "org"]] = None
include_superseded: bool = False
min_score: Optional[float] = Field(default=None, ge=0.0, le=1.0)
top_k: int = Field(default=5, ge=1, le=20)
hybrid: bool = False
parent_id: Optional[str] = None
level: Optional[Literal["L1_ROOT", "L2_SUBTOPIC", "L3_DETAIL"]] = None
topic: Optional[str] = None
include_private: bool = Field(default=False, description="Includi i record privati (solo ricerche esplicite)")