Files
boc/projects/landvex/vision/embeddings/vektor_databas.py
T
Bernt aee0f09db8 landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar
- Vision: Identify-modell, FAISS, OCR alla testade
- API: Alla 7 integrationstester passerade
- Upplösare: Entitetsupplösning verifierad
2026-07-05 06:41:32 +00:00

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()