aee0f09db8
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Landvex Vision — Vektordatabas
|
|
Lagrar och söker bild- och textembeddings.
|
|
"""
|
|
import json
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from typing import List, Dict, Tuple, Optional
|
|
from dataclasses import dataclass
|
|
|
|
import faiss # Facebook AI Similarity Search
|
|
|
|
@dataclass
|
|
class VektorsokResultat:
|
|
lvx_id: str
|
|
distans: float
|
|
metadata: dict
|
|
|
|
class LandvexVektordatabas:
|
|
"""Vektordatabas för bild- och textembeddings."""
|
|
|
|
def __init__(self, dimension: int = 2048, index_type: str = "FlatIP"):
|
|
self.dimension = dimension
|
|
self.index_type = index_type
|
|
|
|
# FAISS index för snabb sökning
|
|
if index_type == "FlatIP":
|
|
self.index = faiss.IndexFlatIP(dimension) # Inner product (cosine om normaliserad)
|
|
elif index_type == "IVF":
|
|
nlist = 100 # Antal kluster
|
|
quantizer = faiss.IndexFlatIP(dimension)
|
|
self.index = faiss.IndexIVFFlat(quantizer, dimension, nlist)
|
|
else:
|
|
raise ValueError(f"Okänd index-typ: {index_type}")
|
|
|
|
# Mappning index_id → lvx_id
|
|
self.id_map: List[str] = []
|
|
self.metadata: Dict[str, dict] = {}
|
|
|
|
def lagg_till(self, lvx_id: str, embedding: np.ndarray, metadata: dict):
|
|
"""Lägg till embedding i databasen."""
|
|
# Normalisera för cosine similarity
|
|
embedding = embedding / np.linalg.norm(embedding)
|
|
embedding = embedding.reshape(1, -1).astype('float32')
|
|
|
|
self.index.add(embedding)
|
|
self.id_map.append(lvx_id)
|
|
self.metadata[lvx_id] = metadata
|
|
|
|
def sok(self, query: np.ndarray, top_k: int = 5) -> List[VektorsokResultat]:
|
|
"""Sök närmaste grannar."""
|
|
query = query / np.linalg.norm(query)
|
|
query = query.reshape(1, -1).astype('float32')
|
|
|
|
distanser, indices = self.index.search(query, top_k)
|
|
|
|
resultat = []
|
|
for dist, idx in zip(distanser[0], indices[0]):
|
|
if idx < 0 or idx >= len(self.id_map):
|
|
continue
|
|
|
|
lvx_id = self.id_map[idx]
|
|
resultat.append(VektorsokResultat(
|
|
lvx_id=lvx_id,
|
|
distans=float(dist),
|
|
metadata=self.metadata.get(lvx_id, {})
|
|
))
|
|
|
|
return resultat
|
|
|
|
def spara(self, path: Path):
|
|
"""Spara index och metadata."""
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Spara FAISS-index
|
|
faiss.write_index(self.index, str(path / "index.faiss"))
|
|
|
|
# Spara metadata
|
|
with open(path / "metadata.json", 'w') as f:
|
|
json.dump({
|
|
"id_map": self.id_map,
|
|
"metadata": self.metadata,
|
|
"dimension": self.dimension,
|
|
"index_type": self.index_type,
|
|
}, f, indent=2)
|
|
|
|
print(f" 💾 Vektordatabas sparad: {path}")
|
|
|
|
def ladda(self, path: Path):
|
|
"""Ladda index och metadata."""
|
|
self.index = faiss.read_index(str(path / "index.faiss"))
|
|
|
|
with open(path / "metadata.json", 'r') as f:
|
|
data = json.load(f)
|
|
|
|
self.id_map = data["id_map"]
|
|
self.metadata = data["metadata"]
|
|
self.dimension = data["dimension"]
|
|
self.index_type = data["index_type"]
|
|
|
|
print(f" 📂 Vektordatabas laddad: {len(self.id_map)} vektorer")
|
|
|
|
def main():
|
|
"""Demo: vektordatabas."""
|
|
print("🔍 Landvex Vektordatabas")
|
|
|
|
db = LandvexVektordatabas(dimension=128) # Låg dimension för demo
|
|
|
|
# Lägg till exempel-embeddings
|
|
np.random.seed(42)
|
|
for i in range(100):
|
|
emb = np.random.randn(128)
|
|
db.lagg_till(
|
|
lvx_id=f"LVX-TRP-{1000+i:04d}",
|
|
embedding=emb,
|
|
metadata={
|
|
"namn_sv": f"Testobjekt {i}",
|
|
"verifieringsniva": "kallbelagd",
|
|
}
|
|
)
|
|
|
|
# Sök
|
|
query = np.random.randn(128)
|
|
resultat = db.sok(query, top_k=5)
|
|
|
|
print("\n🎯 Sökresultat:")
|
|
for r in resultat:
|
|
print(f" {r.lvx_id}: distans={r.distans:.3f}, {r.metadata['namn_sv']}")
|
|
|
|
# Spara och ladda
|
|
db.spara(Path("/tmp/landvex-vectordb"))
|
|
|
|
db2 = LandvexVektordatabas()
|
|
db2.ladda(Path("/tmp/landvex-vectordb"))
|
|
|
|
print(f"\n✅ Databas: {len(db2.id_map)} vektorer")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|