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
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Landvex Extraktor — LLM-baserad schemastyrd extraktion
|
||||
Dokument in, strukturerade fakta ut.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
# Schema för extraktion (från lvx-objekt-schema.json)
|
||||
EXTRAKTIONS_SCHEMA = {
|
||||
"objektklass": {
|
||||
"required": ["lvx_id", "posttyp", "namn", "doman", "proveniens"],
|
||||
"fields": [
|
||||
"lvx_id", "posttyp", "slug", "namn", "doman", "parent", "tier",
|
||||
"identitet", "teknik", "historik", "geografi", "standarder",
|
||||
"ai", "problem", "dokumentation", "relationer", "proveniens"
|
||||
]
|
||||
},
|
||||
"produktmodell": {
|
||||
"required": ["lvx_id", "posttyp", "namn", "doman", "parent", "proveniens"],
|
||||
"fields": [
|
||||
"lvx_id", "posttyp", "slug", "namn", "doman", "parent", "tier",
|
||||
"identitet", "teknik", "historik", "geografi", "standarder",
|
||||
"ai", "problem", "dokumentation", "relationer", "proveniens"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
EXTRAKTIONS_PROMPT = """Du är Landvex Extraktor — en noggrann forskarassistent specialiserad på infrastrukturobjekt.
|
||||
|
||||
DIN UPPGIFT: Analysera följande dokument och extrahera strukturerade fakta om infrastrukturobjekt.
|
||||
|
||||
REGLER:
|
||||
1. Extrahera ENDAST fakta som finns i dokumentet — uppfinn inget
|
||||
2. Varje faktum måste ha en källa (URL eller dokumentreferens)
|
||||
3. Använd Landvex-schemat strikt
|
||||
4. Om något är osäkert, markera med låg konfidens
|
||||
5. Problem/Kända fel kräver alltid källa
|
||||
|
||||
OUTPUT: JSON enligt följande struktur:
|
||||
{
|
||||
"kandidater": [
|
||||
{
|
||||
"atgard": "ny" | "uppdatera",
|
||||
"posttyp": "objektklass" | "produktmodell",
|
||||
"post": { ... },
|
||||
"patch": { ... },
|
||||
"verifieringsniva_forslag": "obekraftad" | "kallbelagd" | "faltverifierad" | "tillverkarbekraftad",
|
||||
"kallor": [
|
||||
{"titel": "...", "url": "...", "hamtad": "2026-07-05"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
DOKUMENT:
|
||||
{content}
|
||||
"""
|
||||
|
||||
class LandvexExtraktor:
|
||||
def __init__(self, api_key: Optional[str] = None):
|
||||
self.client = AsyncOpenAI(api_key=api_key or os.getenv("OPENAI_API_KEY"))
|
||||
self.model = "gpt-4o-mini" # Kostnadseffektiv för volym
|
||||
|
||||
async def extrahera(self, dokument: str, url: str, doman: str) -> dict:
|
||||
"""Extrahera strukturerade fakta från ett dokument."""
|
||||
|
||||
prompt = EXTRAKTIONS_PROMPT.format(content=dokument[:15000]) # Begränsa längd
|
||||
|
||||
try:
|
||||
response = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "Du är Landvex Extraktor. Extrahera strukturerade fakta om infrastrukturobjekt."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.1, # Låg för determinism
|
||||
)
|
||||
|
||||
resultat = json.loads(response.choices[0].message.content)
|
||||
|
||||
# Lägg till proveniens
|
||||
for kand in resultat.get("kandidater", []):
|
||||
kand.setdefault("kallor", []).append({
|
||||
"titel": f"Extraherat från {url}",
|
||||
"url": url,
|
||||
"hamtad": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
return resultat
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Extraktionsfel: {e}")
|
||||
return {"kandidater": [], "fel": str(e)}
|
||||
|
||||
async def extrahera_batch(self, dokument: List[dict]) -> List[dict]:
|
||||
"""Extrahera från flera dokument parallellt."""
|
||||
import asyncio
|
||||
|
||||
uppgifter = [
|
||||
self.extrahera(d["html"], d["url"], d.get("doman", "TRP"))
|
||||
for d in dokument
|
||||
]
|
||||
|
||||
resultat = await asyncio.gather(*uppgifter, return_exceptions=True)
|
||||
return [r for r in resultat if not isinstance(r, Exception)]
|
||||
|
||||
async def main():
|
||||
"""Demo: extrahera från testdokument."""
|
||||
extraktor = LandvexExtraktor()
|
||||
|
||||
test_doc = """
|
||||
ABB Kabeldon CDC-LT är ett lågspänningskabelskåp för utomhusbruk.
|
||||
Material: Varmförzinkad stålplåt, IP34D enligt SS-EN 60529.
|
||||
Standard: SS-EN 61439-5 (provnings- och konstruktionskrav).
|
||||
Tillverkare: ABB Kabeldon, Alingsås.
|
||||
Lås: Trekantslås standard, cylinderlås som tillval.
|
||||
"""
|
||||
|
||||
resultat = await extraktor.extrahera(test_doc, "https://example.com/test", "ELN")
|
||||
print(json.dumps(resultat, ensure_ascii=False, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user