#!/usr/bin/env python3 """ Landvex Artikelproduktion med RIKTIGT LLM-anrop Använder Anthropic Claude API """ import json import os import sys from datetime import datetime, timezone from pathlib import Path import anthropic # API-konfiguration ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") DEFAULT_MODEL = "claude-sonnet-5" def log(msg): ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") print(f"[{ts}] {msg}", flush=True) def ladda_prompt(sokvag): """Ladda systemprompt från fil.""" if not Path(sokvag).exists(): return "Du är en expert på infrastruktur och teknisk dokumentation." with open(sokvag, "r", encoding="utf-8") as f: return f.read() def generera_artikel(amne, doman, system_prompt, webb_data=None): """ Generera en komplett artikel med Anthropic Claude. Args: amne: Objektets namn (t.ex. "Avstängningsventil") doman: Domänkod (t.ex. "VAT") system_prompt: Systemprompt från fil webb_data: Resultat från webbsökning (valfritt) Returns: Dict med komplett artikeldata """ if not ANTHROPIC_API_KEY: raise ValueError("ANTHROPIC_API_KEY måste sättas") client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) # Bygg användarprompt med webbdata om tillgängligt webb_kontext = "" if webb_data: webb_kontext = "\n\n## Webbresearch:\n" for i, result in enumerate(webb_data[:3], 1): webb_kontext += f"\n### Källa {i}: {result.get('title', 'Okänd')}\n" webb_kontext += f"URL: {result.get('url', 'N/A')}\n" content = result.get('content', result.get('snippet', '')) webb_kontext += f"Innehåll: {content[:800]}...\n" user_prompt = f"""Generera en komplett artikel om följande infrastrukturobjekt: Objekt: {amne} Domän: {doman} {webb_kontext} Returnera SVARET SOM JSON i följande format: {{ "slug": "url-vanlig-slug-pa-engelska", "namn": {{ "sv": "Svenskt namn", "en": "Engelskt namn" }}, "beskrivning": "Detaljerad beskrivning av objektet, dess funktion och användningsområden (minst 300 ord på svenska)", "teknik": {{ "material": ["Material som används"], "dimensioner": "Typiska dimensioner", "standarder": ["Tillämpliga standarder och normer"], "livslangd": "Förväntad livslängd" }}, "anvandningsomraden": ["Lista över användningsområden"], "relaterade_objekt": ["Lista över relaterade infrastrukturobjekt"], "bilder": [ {{ "beskrivning": "Beskrivning av vad bilden ska visa", "alt_text": "Alt-text för tillgänglighet" }} ], "kallor": [ {{ "titel": "Källans titel", "url": "https://example.com", "typ": "webbsida|standard|dokument" }} ] }} VIKTIGT: 1. Returnera ENDAST JSON, ingen annan text före eller efter. 2. Beskrivningen ska vara på SVENSKA och minst 300 ord. 3. Inkludera tekniska detaljer som är specifika för objektet. 4. Använd domän-expertis: för VAT (vatten) inkludera tryckklasser, för TRP (trafik) inkludera belastningsklasser, etc.""" log(f"Anropar Claude API för: {amne}") # Anropa LLM response = client.messages.create( model=DEFAULT_MODEL, max_tokens=4000, system=system_prompt, messages=[ {"role": "user", "content": user_prompt} ] ) # Parsa JSON-svar try: # Hitta text content (skip thinking blocks) content = None for block in response.content: if hasattr(block, 'text'): content = block.text break if not content: raise ValueError("Ingen text hittades i svaret") # Extrahera JSON om det är omgivet av markdown if "```json" in content: content = content.split("```json")[1].split("```")[0].strip() elif "```" in content: content = content.split("```")[1].split("```")[0].strip() result = json.loads(content) log(f"✓ Artikel genererad: {amne}") return result except (json.JSONDecodeError, ValueError) as e: log(f"✗ Kunde inte parsa LLM-svar: {e}") if content: log(f"Rått svar: {content[:500]}...") raise def producera_kandidatpost(post, agent_id, system_prompt, webb_data=None): """ Produktion av komplett kandidatpost med LLM. """ ko_id = post["ko_id"] amne = post["mal"] doman = post["doman"] log(f"Produktion startar: {ko_id} - {amne}") # Generera artikel med LLM artikel = generera_artikel(amne, doman, system_prompt, webb_data) # Bygg kandidatpost kandidat_id = f"K-{datetime.now(timezone.utc).strftime('%Y%m%d')}-{agent_id}-{ko_id}" kandidat = { "kandidat_id": kandidat_id, "atgard": "ny", "serie": post.get("serie", {"typ": "klass", "tier": 2}), "paket_ref": [], "patch": {}, "post": { "posttyp": "objektklass" if post["serie"]["typ"] == "klass" else "produktmodell", "slug": artikel.get("slug", amne.lower().replace(" ", "-")), "namn": artikel.get("namn", {"sv": amne, "en": amne}), "doman": doman, "identitet": { "status": "aktiv", "beskrivning": artikel.get("beskrivning", f"{amne} — infrastrukturkomponent") }, "teknik": artikel.get("teknik", {}), "geografi": { "marknader": ["US", "EU", "UK", "CA", "AU"] }, "standarder": [], "ai": { "ocr": { "falt": [] } }, "problem": [], "relationer": [ {"typ": "relaterad_till", "mal": rel} for rel in artikel.get("relaterade_objekt", []) ], "proveniens": { "skapad": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "uppdaterad": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "verifieringsniva": "obekraftad", "konfidens": 0.7, "kallor": artikel.get("kallor", []) } }, "verifieringsniva_forslag": "obekraftad", "motivering": f"LLM-genererad post för {amne} med tekniska detaljer och källhänvisningar" } # Lägg till tier eller parent if post["serie"]["typ"] == "klass": kandidat["post"]["tier"] = post["serie"]["tier"] else: kandidat["post"]["parent"] = post.get("parent", "") # Lägg till standarder från artikel if "standarder" in artikel.get("teknik", {}): for std in artikel["teknik"]["standarder"]: kandidat["post"]["standarder"].append({ "beteckning": std, "organ": "Standardiseringsorgan", "roll": "referens" }) # Lägg till ledtrådar som standarder om tillgängliga if "ledtrad_standard" in post: kandidat["post"]["standarder"].append({ "beteckning": post["ledtrad_standard"], "organ": "Standardiseringsorgan", "roll": "referens" }) log(f"✓ Kandidatpost klar: {kandidat_id}") return kandidat if __name__ == "__main__": # Test if len(sys.argv) < 3: print("Användning: python3 producera_artikel.py '' ''") sys.exit(1) amne = sys.argv[1] doman = sys.argv[2] system_prompt = ladda_prompt("lvx-artikelautomation-prompt.md") try: result = generera_artikel(amne, doman, system_prompt) print(json.dumps(result, ensure_ascii=False, indent=2)) except Exception as e: log(f"Fel: {e}") sys.exit(1)