import gradio as gr from fastapi import FastAPI, HTTPException from pydantic import BaseModel import json import os import asyncio import aiohttp from typing import List, Optional, Literal import time import logging import re # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI( title="SoActi AI Quiz API", description="Separat AI-tjeneste for quiz-generering som SoActi kan bruke", version="1.0.0" ) # Request/Response models class QuizRequest(BaseModel): tema: str språk: Literal["no", "en"] = "no" antall_spørsmål: int = 5 type: Literal["sted", "rute"] = "sted" vanskelighetsgrad: int = 3 class QuizQuestion(BaseModel): spørsmål: str alternativer: List[str] korrekt_svar: int forklaring: str class QuizResponse(BaseModel): success: bool questions: List[QuizQuestion] metadata: dict message: str class HealthResponse(BaseModel): status: str models_loaded: bool api_key_configured: bool uptime_seconds: float # Global state start_time = time.time() api_key = os.getenv("HUGGINGFACE_API_KEY") @app.get("/", response_model=dict) async def root(): """Root endpoint med API-informasjon""" return { "service": "SoActi AI Quiz API", "version": "1.0.0", "status": "running", "endpoints": { "generate": "/generate-quiz", "health": "/health", "models": "/models", "docs": "/docs" }, "description": "Separat AI-tjeneste for quiz-generering" } @app.get("/health", response_model=HealthResponse) async def health_check(): """Helsesjekk for tjenesten""" uptime = time.time() - start_time return HealthResponse( status="healthy", models_loaded=True, api_key_configured=bool(api_key), uptime_seconds=uptime ) @app.get("/models") async def get_available_models(): """Liste over tilgjengelige AI-modeller""" return { "norwegian_models": [ { "name": "NbAiLab/nb-gpt-j-6B", "description": "Beste for norsk - Nasjonalbiblioteket", "cost": "Gratis", "quality": "Høy (norsk)" } ], "english_models": [ { "name": "meta-llama/Llama-2-70b-chat-hf", "description": "Beste kvalitet - Premium", "cost": "Premium ($$$)", "quality": "Exceptional" }, { "name": "mistralai/Mistral-7B-Instruct-v0.1", "description": "Balansert kvalitet/kostnad", "cost": "Premium ($$)", "quality": "Meget høy" } ], "fallback_models": [ { "name": "google/flan-t5-small", "description": "Gratis fallback", "cost": "Gratis", "quality": "Middels" } ] } @app.post("/generate-quiz", response_model=QuizResponse) async def generate_quiz(request: QuizRequest): """Hovedendepunkt for quiz-generering""" try: logger.info(f"Genererer quiz for tema: {request.tema} ({request.språk})") # Valider request if not request.tema.strip(): raise HTTPException(status_code=400, detail="Tema kan ikke være tomt") if request.antall_spørsmål < 1 or request.antall_spørsmål > 20: raise HTTPException(status_code=400, detail="Antall spørsmål må være mellom 1 og 20") # Generer quiz from quiz_generator import QuizGenerator generator = QuizGenerator(api_key) start_time = time.time() questions = await generator.generate_quiz(request) generation_time = time.time() - start_time if not questions: raise HTTPException(status_code=500, detail="Kunne ikke generere spørsmål") logger.info(f"Genererte {len(questions)} spørsmål på {generation_time:.2f}s") return QuizResponse( success=True, questions=questions, metadata={ "generation_time": round(generation_time, 2), "model_used": generator.get_model_used(), "method": generator.get_generation_method(), "tema": request.tema, "språk": request.språk }, message=f"Genererte {len(questions)} spørsmål om '{request.tema}'" ) except HTTPException: raise except Exception as e: logger.error(f"Feil ved quiz-generering: {str(e)}") raise HTTPException(status_code=500, detail=f"Intern feil: {str(e)}") @app.post("/validate-quiz") async def validate_quiz(questions: List[QuizQuestion]): """Valider genererte quiz-spørsmål""" try: from quiz_validator import QuizValidator validator = QuizValidator() results = [] for i, question in enumerate(questions): validation = validator.validate_question(question.dict()) results.append({ "question_index": i, "valid": validation["valid"], "score": validation["score"], "issues": validation["issues"] }) overall_score = sum(r["score"] for r in results) / len(results) return { "overall_valid": all(r["valid"] for r in results), "overall_score": round(overall_score, 2), "question_results": results } except Exception as e: raise HTTPException(status_code=500, detail=f"Valideringsfeil: {str(e)}") # Gradio interface for testing def gradio_generate_quiz(tema, språk, antall, type_val, vanskelighet): """Gradio wrapper for quiz generation""" try: import asyncio request = QuizRequest( tema=tema, språk=språk, antall_spørsmål=antall, type=type_val, vanskelighetsgrad=vanskelighet ) # Run async function in sync context loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) response = loop.run_until_complete(generate_quiz(request)) loop.close() # Format for display output = f"✅ {response.message}\n\n" output += f"🤖 Modell: {response.metadata.get('model_used', 'Ukjent')}\n" output += f"⏱️ Tid: {response.metadata.get('generation_time', 0)}s\n" output += f"🔧 Metode: {response.metadata.get('method', 'Ukjent')}\n\n" for i, q in enumerate(response.questions, 1): output += f"📝 **Spørsmål {i}:** {q.spørsmål}\n" for j, alt in enumerate(q.alternativer): marker = "✅" if j == q.korrekt_svar else "❌" output += f" {chr(65+j)}) {alt} {marker}\n" output += f"💡 **Forklaring:** {q.forklaring}\n\n" return output except Exception as e: retur