Files
boc/projects/landvex/datafabrik/agent_orkestrering.py
T
Bernt f4f853d94b landvex: Datafabrik + Vision + Infrastruktur för 50GB skalning
Datafabrik:
- Skördare: crawler, källvitlista, upphandlingsskördare
- Extraktor: LLM-baserad schemastyrd extraktion
- Upplösare: Entitetsupplösning och deduplicering
- Köer: Schemalagd / kunddriven / fält
- Agentorkestrering: 20+ parallella agenter

Vision:
- Identify-modell: ResNet50 + kontrastivt lärande
- Träningspipeline: NT-Xent loss
- Vektordatabas: FAISS för snabb sökning
- OCR-pipeline: Typskyltsläsning

Infrastruktur:
- Docker Compose production
- Terraform för AWS ECS
- Prometheus + Grafana monitorering
- Neo4j + FAISS + MinIO + Redis
2026-07-05 06:25:51 +00:00

242 lines
8.5 KiB
Python

#!/usr/bin/env python3
"""
Landvex Agent Orkestrering
Koordinerar 50GB agentkraft för skalad datainsamling.
"""
import asyncio
import json
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional
from dataclasses import dataclass, asdict
from enum import Enum
class AgentStatus(Enum):
LEDIG = "ledig"
KOR = "kor"
KLAR = "klar"
FEL = "fel"
@dataclass
class AgentUppgift:
uppgift_id: str
typ: str # "skorda", "extrahera", "upplos", "trana"
doman: str # TRP, ELN, VAT, etc.
tier: int # 1, 2, 3
prioritet: int # 1-10
parametrar: Dict
status: str = "pending"
resultat: Optional[Dict] = None
startad: Optional[str] = None
avslutad: Optional[str] = None
class LandvexAgentOrkestrerare:
"""Orkestrerar agenter för parallell datainsamling."""
def __init__(
self,
max_parallella: int = 20,
ko_dir: Path = Path("/data/koer"),
output_dir: Path = Path("/data/output"),
):
self.max_parallella = max_parallella
self.ko_dir = ko_dir
self.output_dir = output_dir
self.agenter: Dict[str, Dict] = {}
self.ko: List[AgentUppgift] = []
self.statistik = {
"skordade": 0,
"extraherade": 0,
"upplosta": 0,
"tränade": 0,
"fel": 0,
}
def skapa_uppgifter_for_tier(self, tier: int, domäner: List[str]) -> List[AgentUppgift]:
"""Skapa uppgifter för en specifik tier."""
uppgifter = []
for doman in domäner:
# Skördningsuppgifter
uppgifter.append(AgentUppgift(
uppgift_id=f"skorda-{doman}-t{tier}-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}",
typ="skorda",
doman=doman,
tier=tier,
prioritet=10 - tier, # Tier 1 = högst prioritet
parametrar={"kallor": "alla", "max_dokument": 1000},
))
# Extraktionsuppgifter
uppgifter.append(AgentUppgift(
uppgift_id=f"extrahera-{doman}-t{tier}-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}",
typ="extrahera",
doman=doman,
tier=tier,
prioritet=9 - tier,
parametrar={"batch_storlek": 100},
))
# Entitetsupplösning
uppgifter.append(AgentUppgift(
uppgift_id=f"upplos-{doman}-t{tier}-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}",
typ="upplos",
doman=doman,
tier=tier,
prioritet=8 - tier,
parametrar={"tröskel": 0.85},
))
return uppgifter
async def kör_agent(self, uppgift: AgentUppgift) -> Dict:
"""Kör en agent för en specifik uppgift."""
uppgift.startad = datetime.utcnow().isoformat()
uppgift.status = "kor"
try:
if uppgift.typ == "skorda":
resultat = await self._kör_skordare(uppgift)
elif uppgift.typ == "extrahera":
resultat = await self._kör_extraktor(uppgift)
elif uppgift.typ == "upplos":
resultat = await self._kör_upplosare(uppgift)
elif uppgift.typ == "trana":
resultat = await self._kör_tranare(uppgift)
else:
raise ValueError(f"Okänd uppgiftstyp: {uppgift.typ}")
uppgift.status = "klar"
uppgift.resultat = resultat
self.statistik[f"{uppgift.typ}ade"] += 1
except Exception as e:
uppgift.status = "fel"
uppgift.resultat = {"fel": str(e)}
self.statistik["fel"] += 1
uppgift.avslutad = datetime.utcnow().isoformat()
return uppgift.resultat
async def _kör_skordare(self, uppgift: AgentUppgift) -> Dict:
"""Kör skördare för en domän."""
# Anropa crawler.py
process = await asyncio.create_subprocess_exec(
"python3", "skordare/crawler.py",
"--doman", uppgift.doman,
"--output", str(self.output_dir / "skordat"),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
return {
"returncode": process.returncode,
"stdout": stdout.decode()[:1000],
"stderr": stderr.decode()[:1000],
}
async def _kör_extraktor(self, uppgift: AgentUppgift) -> Dict:
"""Kör extraktor för skördade dokument."""
process = await asyncio.create_subprocess_exec(
"python3", "extraktor/extraktor.py",
"--input", str(self.output_dir / "skordat"),
"--output", str(self.output_dir / "extraherat"),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
return {
"returncode": process.returncode,
"stdout": stdout.decode()[:1000],
"stderr": stderr.decode()[:1000],
}
async def _kör_upplosare(self, uppgift: AgentUppgift) -> Dict:
"""Kör entitetsupplösare."""
process = await asyncio.create_subprocess_exec(
"python3", "upplosare/entitetsupplosare.py",
"--input", str(self.output_dir / "extraherat"),
"--output", str(self.output_dir / "upplost"),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
return {
"returncode": process.returncode,
"stdout": stdout.decode()[:1000],
"stderr": stderr.decode()[:1000],
}
async def _kör_tranare(self, uppgift: AgentUppgift) -> Dict:
"""Kör vision-träning."""
process = await asyncio.create_subprocess_exec(
"python3", "../vision/traening/trainer.py",
"--dataset", str(self.output_dir / "dataset"),
"--output", str(self.output_dir / "modeller"),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
return {
"returncode": process.returncode,
"stdout": stdout.decode()[:1000],
"stderr": stderr.decode()[:1000],
}
async def kör_alla(self, uppgifter: List[AgentUppgift]):
"""Kör alla uppgifter med begränsad parallellism."""
self.ko = sorted(uppgifter, key=lambda x: x.prioritet, reverse=True)
print(f"🚀 Startar {len(self.ko)} uppgifter (max {self.max_parallella} parallella)")
# Kör i batcher
while self.ko:
batch = self.ko[:self.max_parallella]
self.ko = self.ko[self.max_parallella:]
print(f"\n📦 Kör batch: {len(batch)} uppgifter")
tasks = [self.kör_agent(u) for u in batch]
resultat = await asyncio.gather(*tasks, return_exceptions=True)
for uppgift, res in zip(batch, resultat):
status = "" if uppgift.status == "klar" else ""
print(f" {status} {uppgift.typ} {uppgift.doman} T{uppgift.tier}: {uppgift.status}")
print(f"\n📊 Statistik: {self.statistik}")
def spara_rapport(self):
"""Spara körningsrapport."""
rapport = {
"tidstampel": datetime.utcnow().isoformat(),
"statistik": self.statistik,
"uppgifter": [asdict(u) for u in self.ko],
}
fil_path = self.output_dir / f"rapport_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json"
with open(fil_path, 'w') as f:
json.dump(rapport, f, indent=2)
print(f"\n📝 Rapport sparad: {fil_path}")
async def main():
"""Demo: orkestrera agenter för Tier 1."""
orkestrerare = LandvexAgentOrkestrerare(max_parallella=5)
# Skapa uppgifter för Tier 1 (alla 9 domäner)
domäner = ["TRP", "ELN", "VAT", "TEL", "BYG", "PRK", "JVG", "HMN", "FLG"]
uppgifter = orkestrerare.skapa_uppgifter_for_tier(tier=1, domäner=domäner)
print(f"📋 Skapade {len(uppgifter)} uppgifter för Tier 1")
# Kör
await orkestrerare.kör_alla(uppgifter)
orkestrerare.spara_rapport()
if __name__ == "__main__":
asyncio.run(main())