From a04bee5008072ea46dbf155bae3c13bf714cf948 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Fri, 6 Feb 2026 10:27:06 +0100
Subject: [PATCH] integrated privateLLM
---
app.py | 824 ++++++++++++++++++--------
config.ini | 21 +
docu/requirements.txt | 16 +
setupserver.md => docu/setupserver.md | 0
requirements.txt | 6 -
start-python.bat | 15 +-
start-python.ps1 | 19 +-
t1.png | Bin 264431 -> 0 bytes
templates/index.html | 77 ++-
9 files changed, 671 insertions(+), 307 deletions(-)
create mode 100644 config.ini
create mode 100644 docu/requirements.txt
rename setupserver.md => docu/setupserver.md (100%)
delete mode 100644 requirements.txt
delete mode 100644 t1.png
diff --git a/app.py b/app.py
index f24d6ce..857ba88 100644
--- a/app.py
+++ b/app.py
@@ -1,17 +1,33 @@
+# Copyright (c) 2025 Patrick Motsch
+# All rights reserved.
"""
-Belegscanner - KI-Dokumentenanalyse
-Python Flask Web App mit CORS-Unterstützung und Poweron Design
+Private-LLM Service - FastAPI Web App
+Provides AI model endpoints for OCR and Vision processing via Ollama.
+
+Models exposed:
+- poweron-ocr-general (deepseek)
+- poweron-vision-general (qwen2.5)
+- poweron-vision-deep (granite3.2)
"""
-from flask import Flask, render_template, request, jsonify, session, redirect, url_for
-from flask_cors import CORS
-from functools import wraps
-import requests
+import os
+import sys
import base64
import json
import re
-import io
-import os
+import logging
+import time
+from collections import defaultdict
+from typing import Optional, List, Dict, Any
+from contextlib import asynccontextmanager
+
+from fastapi import FastAPI, HTTPException, Depends, Header, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import HTMLResponse
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from pydantic import BaseModel, Field
+import httpx
# PDF Support
try:
@@ -22,79 +38,243 @@ except ImportError:
print("WARNUNG: PyMuPDF nicht installiert. PDF-Support deaktiviert.")
print("Installieren mit: pip install pymupdf")
-app = Flask(__name__)
-app.secret_key = os.environ.get('SECRET_KEY', 'poweron-secret-key-change-in-production')
-CORS(app, supports_credentials=True) # CORS für alle Routen aktivieren
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
+ datefmt="%Y-%m-%d %H:%M:%S",
+)
+logger = logging.getLogger(__name__)
# ============================================================================
-# Authentication
+# Configuration
# ============================================================================
-# Einfache Credentials (für minimalen Schutz)
-AUTH_USERNAME = os.environ.get('AUTH_USERNAME', 'poweron')
-AUTH_PASSWORD = os.environ.get('AUTH_PASSWORD', 'poweron')
+def _loadConfig() -> Dict[str, Any]:
+ """Load configuration from config.ini file."""
+ configPath = os.path.join(os.path.dirname(__file__), "config.ini")
+ config = {
+ "apiKey": None,
+ "ollamaUrl": "http://localhost:11434",
+ "authUsername": "poweron",
+ "authPassword": "poweron",
+ "secretKey": "poweron-secret-key-change-in-production",
+ "rateLimitRequestsPerMinute": 60,
+ "rateLimitBurstSize": 10,
+ }
+
+ if os.path.exists(configPath):
+ try:
+ with open(configPath, "r") as f:
+ for line in f:
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ if "=" in line:
+ key, value = line.split("=", 1)
+ key = key.strip()
+ value = value.strip()
+
+ # Map config keys
+ if key == "PRIVATE_LLM_API_KEY":
+ config["apiKey"] = value
+ elif key == "OLLAMA_URL":
+ config["ollamaUrl"] = value
+ elif key == "AUTH_USERNAME":
+ config["authUsername"] = value
+ elif key == "AUTH_PASSWORD":
+ config["authPassword"] = value
+ elif key == "SECRET_KEY":
+ config["secretKey"] = value
+ elif key == "RATE_LIMIT_REQUESTS_PER_MINUTE":
+ config["rateLimitRequestsPerMinute"] = int(value)
+ elif key == "RATE_LIMIT_BURST_SIZE":
+ config["rateLimitBurstSize"] = int(value)
+ except Exception as e:
+ logger.warning(f"Error loading config.ini: {e}")
+
+ # Override with environment variables if set
+ config["apiKey"] = os.environ.get("PRIVATE_LLM_API_KEY", config["apiKey"])
+ config["ollamaUrl"] = os.environ.get("OLLAMA_URL", config["ollamaUrl"])
+ config["authUsername"] = os.environ.get("AUTH_USERNAME", config["authUsername"])
+ config["authPassword"] = os.environ.get("AUTH_PASSWORD", config["authPassword"])
+ config["secretKey"] = os.environ.get("SECRET_KEY", config["secretKey"])
+ config["rateLimitRequestsPerMinute"] = int(os.environ.get("RATE_LIMIT_REQUESTS_PER_MINUTE", config["rateLimitRequestsPerMinute"]))
+ config["rateLimitBurstSize"] = int(os.environ.get("RATE_LIMIT_BURST_SIZE", config["rateLimitBurstSize"]))
+
+ return config
+
+CONFIG = _loadConfig()
-def _loginRequired(f):
- """Decorator für geschützte Routen"""
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not session.get('logged_in'):
- # Bei API-Calls JSON zurückgeben, sonst redirect
- if request.path.startswith('/api/'):
- return jsonify({'error': 'Nicht autorisiert', 'login_required': True}), 401
- return redirect(url_for('_login'))
- return f(*args, **kwargs)
- return decorated_function
+# ============================================================================
+# Rate Limiting (Token Bucket Algorithm)
+# ============================================================================
+class RateLimiter:
+ """
+ Token bucket rate limiter with per-API-key tracking.
+
+ Each API key gets its own bucket. Tokens are added at a constant rate
+ (requestsPerMinute / 60 per second) up to a maximum burst size.
+ """
+
+ def __init__(self, requestsPerMinute: int = 60, burstSize: int = 10):
+ self.requestsPerMinute = requestsPerMinute
+ self.burstSize = burstSize
+ self.tokensPerSecond = requestsPerMinute / 60.0
+
+ # Track tokens and last update time per API key
+ # Format: {apiKey: {"tokens": float, "lastUpdate": float}}
+ self._buckets: Dict[str, Dict[str, float]] = defaultdict(
+ lambda: {"tokens": burstSize, "lastUpdate": time.time()}
+ )
+
+ def _refillTokens(self, bucket: Dict[str, float]) -> None:
+ """Refill tokens based on elapsed time."""
+ now = time.time()
+ elapsed = now - bucket["lastUpdate"]
+ bucket["tokens"] = min(
+ self.burstSize,
+ bucket["tokens"] + elapsed * self.tokensPerSecond
+ )
+ bucket["lastUpdate"] = now
+
+ def isAllowed(self, apiKey: str) -> tuple[bool, Dict[str, Any]]:
+ """
+ Check if a request is allowed and consume a token if so.
+
+ Returns:
+ Tuple of (allowed: bool, info: dict with remaining tokens and retry_after)
+ """
+ bucket = self._buckets[apiKey]
+ self._refillTokens(bucket)
+
+ if bucket["tokens"] >= 1.0:
+ bucket["tokens"] -= 1.0
+ return True, {
+ "remaining": int(bucket["tokens"]),
+ "limit": self.requestsPerMinute,
+ "resetSeconds": 60
+ }
+ else:
+ # Calculate when the next token will be available
+ retryAfter = (1.0 - bucket["tokens"]) / self.tokensPerSecond
+ return False, {
+ "remaining": 0,
+ "limit": self.requestsPerMinute,
+ "retryAfter": round(retryAfter, 1),
+ "resetSeconds": 60
+ }
+
+ def cleanup(self, maxAgeSeconds: int = 3600) -> int:
+ """Remove stale buckets to prevent memory growth."""
+ now = time.time()
+ staleKeys = [
+ key for key, bucket in self._buckets.items()
+ if now - bucket["lastUpdate"] > maxAgeSeconds
+ ]
+ for key in staleKeys:
+ del self._buckets[key]
+ return len(staleKeys)
+
+
+# Global rate limiter instance
+rateLimiter = RateLimiter(
+ requestsPerMinute=CONFIG["rateLimitRequestsPerMinute"],
+ burstSize=CONFIG["rateLimitBurstSize"]
+)
+
+# Model mapping: external name -> internal Ollama model name
+# Production models (optimized for 32GB RAM server):
+# - deepseek-ocr: 3.34B params, 8K context, ~6.7GB RAM
+# - qwen2.5vl:7b: 8.29B params, 125K context, ~6GB RAM
+# - granite3.2-vision: 2B params, 16K context, ~2.4GB RAM
+MODEL_MAPPING = {
+ "poweron-ocr-general": "deepseek-ocr",
+ "poweron-vision-general": "qwen2.5vl:7b",
+ "poweron-vision-deep": "granite3.2-vision",
+}
+
+# Reverse mapping for lookups
+INTERNAL_TO_EXTERNAL = {v: k for k, v in MODEL_MAPPING.items()}
+
+# ============================================================================
+# Request/Response Models
+# ============================================================================
+
+class AnalyzeRequest(BaseModel):
+ """Request model for document analysis."""
+ imageBase64: Optional[str] = Field(default=None, description="Base64 encoded image")
+ prompt: str = Field(description="Analysis prompt")
+ modelName: str = Field(default="poweron-vision-general", description="Model to use")
+
+class AnalyzeResponse(BaseModel):
+ """Response model for document analysis."""
+ success: bool = Field(description="Whether the analysis was successful")
+ data: Optional[Dict[str, Any]] = Field(default=None, description="Extracted data")
+ rawResponse: Optional[str] = Field(default=None, description="Raw model response")
+ error: Optional[str] = Field(default=None, description="Error message if failed")
+
+class PdfExtractRequest(BaseModel):
+ """Request model for PDF extraction."""
+ pdfBase64: str = Field(description="Base64 encoded PDF")
+ page: Optional[int] = Field(default=None, description="Specific page number (1-indexed)")
+
+class ModelInfo(BaseModel):
+ """Model information."""
+ name: str = Field(description="External model name")
+ internalName: str = Field(description="Internal Ollama model name")
+ isVision: bool = Field(description="Whether it's a vision model")
+ pricePerCall: float = Field(description="Price per call in CHF")
+
+class HealthResponse(BaseModel):
+ """Health check response."""
+ status: str
+ service: str
+ pdfSupport: bool
+ ollamaConnected: bool
+
+class OllamaStatusResponse(BaseModel):
+ """Ollama status response."""
+ connected: bool
+ models: Optional[List[str]] = None
+ visionModels: Optional[List[str]] = None
+ totalModels: Optional[int] = None
+ error: Optional[str] = None
# ============================================================================
# PDF Helper Functions
# ============================================================================
-def _extractImagesFromPdf(pdfBytes, maxPages=5):
- """
- Extrahiert Bilder aus einem PDF.
- Gibt eine Liste von Base64-kodierten Bildern zurück.
- """
+def _extractImagesFromPdf(pdfBytes: bytes, maxPages: int = 5) -> List[Dict[str, Any]]:
+ """Extract images from a PDF."""
if not PDF_SUPPORT:
raise Exception("PDF-Support nicht verfügbar. Bitte PyMuPDF installieren.")
images = []
-
- # PDF öffnen
doc = fitz.open(stream=pdfBytes, filetype="pdf")
-
- # Anzahl der Seiten begrenzen
numPages = min(len(doc), maxPages)
for pageNum in range(numPages):
page = doc[pageNum]
-
- # Seite als Bild rendern (höhere Auflösung für bessere OCR)
- mat = fitz.Matrix(2.0, 2.0) # 2x Zoom für bessere Qualität
+ mat = fitz.Matrix(2.0, 2.0) # 2x Zoom for better quality
pix = page.get_pixmap(matrix=mat)
-
- # In PNG konvertieren
imgBytes = pix.tobytes("png")
- imgBase64 = base64.b64encode(imgBytes).decode('utf-8')
+ imgBase64 = base64.b64encode(imgBytes).decode("utf-8")
images.append({
- 'page': pageNum + 1,
- 'base64': imgBase64,
- 'width': pix.width,
- 'height': pix.height
+ "page": pageNum + 1,
+ "base64": imgBase64,
+ "width": pix.width,
+ "height": pix.height
})
doc.close()
-
return images
-
-def _renderPdfPageAsImage(pdfBytes, pageNum=0, zoom=2.0):
- """
- Rendert eine einzelne PDF-Seite als Bild.
- """
+def _renderPdfPageAsImage(pdfBytes: bytes, pageNum: int = 0, zoom: float = 2.0) -> Dict[str, Any]:
+ """Render a single PDF page as an image."""
if not PDF_SUPPORT:
raise Exception("PDF-Support nicht verfügbar.")
@@ -106,267 +286,399 @@ def _renderPdfPageAsImage(pdfBytes, pageNum=0, zoom=2.0):
page = doc[pageNum]
mat = fitz.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat)
-
imgBytes = pix.tobytes("png")
- imgBase64 = base64.b64encode(imgBytes).decode('utf-8')
+ imgBase64 = base64.b64encode(imgBytes).decode("utf-8")
result = {
- 'base64': imgBase64,
- 'width': pix.width,
- 'height': pix.height,
- 'page': pageNum + 1,
- 'totalPages': len(doc)
+ "base64": imgBase64,
+ "width": pix.width,
+ "height": pix.height,
+ "page": pageNum + 1,
+ "totalPages": len(doc)
}
doc.close()
-
return result
# ============================================================================
# Model Helper Functions
# ============================================================================
-def _isVisionModel(modelName):
- """
- Prüft ob ein Modell ein Vision-Modell ist basierend auf Namenskonventionen.
- Vision-Modelle enthalten typischerweise 'vision', 'vl', 'llava', 'bakllava' im Namen.
- """
+def _isVisionModel(modelName: str) -> bool:
+ """Check if a model is a vision model based on naming conventions."""
if not modelName:
return False
modelLower = modelName.lower()
- visionIndicators = ['vision', 'vl', 'llava', 'bakllava']
+ visionIndicators = ["vision", "vl", "llava", "bakllava", "granite"]
return any(indicator in modelLower for indicator in visionIndicators)
+def _getInternalModelName(externalName: str) -> str:
+ """Get the internal Ollama model name from external name."""
+ return MODEL_MAPPING.get(externalName, externalName)
+
+def _getExternalModelName(internalName: str) -> str:
+ """Get the external model name from internal Ollama name."""
+ return INTERNAL_TO_EXTERNAL.get(internalName, internalName)
# ============================================================================
-# Routes
+# Authentication & Rate Limiting
# ============================================================================
-@app.route('/login', methods=['GET', 'POST'])
-def _login():
- """Login-Seite"""
- error = None
- if request.method == 'POST':
- username = request.form.get('username', '')
- password = request.form.get('password', '')
-
- if username == AUTH_USERNAME and password == AUTH_PASSWORD:
- session['logged_in'] = True
- session['username'] = username
- return redirect(url_for('_index'))
- else:
- error = 'Ungültige Anmeldedaten'
+async def _verifyApiKey(xApiKey: Optional[str] = Header(None, alias="X-API-Key")) -> str:
+ """Verify the API key from header and return it for rate limiting."""
+ if not CONFIG["apiKey"]:
+ # No API key configured, allow all requests (development mode)
+ logger.warning("No API key configured - running in development mode")
+ return "dev-mode"
- return render_template('login.html', error=error)
+ if not xApiKey:
+ raise HTTPException(status_code=401, detail="API key required")
+
+ if xApiKey != CONFIG["apiKey"]:
+ raise HTTPException(status_code=401, detail="Invalid API key")
+
+ return xApiKey
-@app.route('/logout')
-def _logout():
- """Logout"""
- session.clear()
- return redirect(url_for('_login'))
+async def _checkRateLimit(apiKey: str = Depends(_verifyApiKey)) -> str:
+ """Check rate limit for the authenticated API key."""
+ allowed, info = rateLimiter.isAllowed(apiKey)
+
+ if not allowed:
+ raise HTTPException(
+ status_code=429,
+ detail={
+ "error": "Rate limit exceeded",
+ "message": f"Too many requests. Please retry after {info['retryAfter']} seconds.",
+ "retryAfter": info["retryAfter"],
+ "limit": info["limit"],
+ "remaining": info["remaining"]
+ },
+ headers={
+ "Retry-After": str(int(info["retryAfter"])),
+ "X-RateLimit-Limit": str(info["limit"]),
+ "X-RateLimit-Remaining": str(info["remaining"]),
+ "X-RateLimit-Reset": str(info["resetSeconds"])
+ }
+ )
+
+ return apiKey
+# ============================================================================
+# Application Lifecycle
+# ============================================================================
-@app.route('/')
-@_loginRequired
-def _index():
- """Hauptseite mit dem Belegscanner UI"""
- return render_template('index.html')
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """Application lifespan handler."""
+ logger.info("Private-LLM Service starting up...")
+ logger.info(f"Ollama URL: {CONFIG['ollamaUrl']}")
+ logger.info(f"API Key configured: {'Yes' if CONFIG['apiKey'] else 'No (development mode)'}")
+ logger.info(f"PDF Support: {'Enabled' if PDF_SUPPORT else 'Disabled'}")
+ yield
+ logger.info("Private-LLM Service shutting down...")
+# ============================================================================
+# FastAPI Application
+# ============================================================================
-@app.route('/api/analyze', methods=['POST'])
-@_loginRequired
-def _analyzeDocument():
+app = FastAPI(
+ title="PowerOn Private-LLM Service",
+ description="AI model endpoints for OCR and Vision processing",
+ version="1.0.0",
+ lifespan=lifespan,
+)
+
+# CORS Configuration - Allow gateway instances
+ALLOWED_ORIGINS = [
+ "http://localhost:8000",
+ "http://localhost:8080",
+ "http://localhost:5000",
+ "http://127.0.0.1:8000",
+ "http://127.0.0.1:8080",
+ "http://127.0.0.1:5000",
+]
+
+# Add production origins
+PRODUCTION_PATTERNS = [
+ "poweron.swiss",
+ "poweron-center.net",
+]
+
+# Build full origins list with https variants
+for pattern in PRODUCTION_PATTERNS:
+ ALLOWED_ORIGINS.extend([
+ f"https://{pattern}",
+ f"https://www.{pattern}",
+ f"https://api.{pattern}",
+ f"https://gateway.{pattern}",
+ f"https://app.{pattern}",
+ f"https://nyla.{pattern}",
+ f"https://playground.{pattern}",
+ ])
+
+# Allow all subdomains via regex in middleware
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=ALLOWED_ORIGINS,
+ allow_origin_regex=r"https://.*\.(poweron\.swiss|poweron-center\.net)",
+ allow_credentials=True,
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
+ allow_headers=["*"],
+ expose_headers=["*"],
+ max_age=86400,
+)
+
+# Static files and templates (for web UI)
+app.mount("/static", StaticFiles(directory="static"), name="static")
+templates = Jinja2Templates(directory="templates")
+
+# ============================================================================
+# API Routes
+# ============================================================================
+
+@app.get("/api/health", response_model=HealthResponse, tags=["System"])
+async def _healthCheck():
+ """Health check endpoint."""
+ ollamaConnected = False
+ try:
+ async with httpx.AsyncClient(timeout=5.0) as client:
+ response = await client.get(f"{CONFIG['ollamaUrl']}/api/tags")
+ ollamaConnected = response.status_code == 200
+ except Exception:
+ pass
+
+ return HealthResponse(
+ status="ok",
+ service="private-llm",
+ pdfSupport=PDF_SUPPORT,
+ ollamaConnected=ollamaConnected
+ )
+
+@app.get("/api/models", response_model=List[ModelInfo], tags=["Models"])
+async def _listModels(authenticated: bool = Depends(_verifyApiKey)):
+ """List available models with pricing."""
+ models = []
+ for externalName, internalName in MODEL_MAPPING.items():
+ isVision = _isVisionModel(internalName)
+ pricePerCall = 0.10 if isVision else 0.01 # CHF pricing
+
+ models.append(ModelInfo(
+ name=externalName,
+ internalName=internalName,
+ isVision=isVision,
+ pricePerCall=pricePerCall
+ ))
+
+ return models
+
+@app.get("/api/ollama/status", response_model=OllamaStatusResponse, tags=["System"])
+async def _ollamaStatus(authenticated: bool = Depends(_verifyApiKey)):
+ """Check Ollama connection status and list available models."""
+ try:
+ async with httpx.AsyncClient(timeout=10.0) as client:
+ response = await client.get(f"{CONFIG['ollamaUrl']}/api/tags")
+
+ if response.status_code != 200:
+ return OllamaStatusResponse(
+ connected=False,
+ error=f"Ollama responded with status {response.status_code}"
+ )
+
+ data = response.json()
+ models = [m.get("name", "") for m in data.get("models", [])]
+ visionModels = [m for m in models if _isVisionModel(m)]
+
+ return OllamaStatusResponse(
+ connected=True,
+ models=models,
+ visionModels=visionModels,
+ totalModels=len(models)
+ )
+
+ except httpx.ConnectError:
+ return OllamaStatusResponse(
+ connected=False,
+ error="Keine Verbindung zu Ollama. Ist Ollama gestartet?"
+ )
+ except Exception as e:
+ return OllamaStatusResponse(
+ connected=False,
+ error=str(e)
+ )
+
+@app.post("/api/analyze", response_model=AnalyzeResponse, tags=["AI"])
+async def _analyzeDocument(
+ request: AnalyzeRequest,
+ apiKey: str = Depends(_checkRateLimit)
+):
"""
- Analysiert ein Dokument mit Ollama Vision API oder verarbeitet Text mit Non-Vision Modellen
- Erwartet: { imageBase64 (optional bei Non-Vision), prompt, ollamaUrl, modelName }
+ Analyze a document with AI Vision API.
+
+ Supports both vision models (with images) and text models (without images).
"""
try:
- data = request.get_json()
+ # Get internal model name
+ internalModelName = _getInternalModelName(request.modelName)
+ isVision = _isVisionModel(internalModelName)
- imageBase64 = data.get('imageBase64')
- prompt = data.get('prompt')
- ollamaUrl = data.get('ollamaUrl', 'http://localhost:11434')
- modelName = data.get('modelName', 'qwen2.5vl:72b')
+ # Validate request
+ if isVision and not request.imageBase64:
+ raise HTTPException(
+ status_code=400,
+ detail="Kein Bild übermittelt (erforderlich für Vision-Modelle)"
+ )
- # Prüfe ob es ein Vision-Modell ist (basierend auf Namenskonvention)
- isVisionModel = _isVisionModel(modelName)
+ if not request.prompt:
+ raise HTTPException(status_code=400, detail="Kein Prompt übermittelt")
- # Bei Vision-Modellen ist ein Bild erforderlich
- if isVisionModel and not imageBase64:
- return jsonify({'error': 'Kein Bild übermittelt (erforderlich für Vision-Modelle)'}), 400
+ # Model-specific context lengths (actual model limits)
+ modelContextLengths = {
+ "deepseek-ocr": 8192, # 8K context
+ "qwen2.5vl:7b": 32768, # Use 32K (model supports 125K but RAM limited)
+ "granite3.2-vision": 16000, # 16K context
+ }
+ numCtx = modelContextLengths.get(internalModelName, 8192)
- if not prompt:
- return jsonify({'error': 'Kein Prompt übermittelt'}), 400
-
- # Request-Body erstellen
+ # Build request body with model-specific context window
requestBody = {
- 'model': modelName,
- 'prompt': prompt,
- 'stream': False
+ "model": internalModelName,
+ "prompt": request.prompt,
+ "stream": False,
+ "options": {
+ "num_ctx": numCtx
+ }
}
- # Bilder nur hinzufügen wenn vorhanden (für Vision-Modelle)
- if imageBase64:
- requestBody['images'] = [imageBase64]
+ if request.imageBase64:
+ requestBody["images"] = [request.imageBase64]
- # Ollama API aufrufen (Timeout: 60 Minuten für grosse Modelle)
- response = requests.post(
- f'{ollamaUrl}/api/generate',
- json=requestBody,
- timeout=3600 # 60 Minuten
+ # Call Ollama API
+ async with httpx.AsyncClient(timeout=3600.0) as client: # 60 min timeout
+ response = await client.post(
+ f"{CONFIG['ollamaUrl']}/api/generate",
+ json=requestBody
+ )
+
+ if response.status_code == 404:
+ raise HTTPException(
+ status_code=404,
+ detail=f'Modell "{internalModelName}" nicht gefunden. Bitte installieren mit: ollama pull {internalModelName}'
+ )
+
+ if response.status_code != 200:
+ raise HTTPException(
+ status_code=response.status_code,
+ detail=f"Ollama API Fehler: {response.status_code} - {response.text[:200]}"
+ )
+
+ responseData = response.json()
+ responseText = responseData.get("response", "")
+
+ # Try to extract JSON from response
+ extractedData = None
+ jsonMatch = re.search(r"\{[\s\S]*\}", responseText)
+
+ if jsonMatch:
+ try:
+ extractedData = json.loads(jsonMatch.group())
+ except json.JSONDecodeError:
+ extractedData = None
+
+ # Wrap plain text response in JSON object
+ if extractedData is None:
+ extractedData = {"response": responseText.strip()}
+
+ return AnalyzeResponse(
+ success=True,
+ data=extractedData,
+ rawResponse=responseText
+ )
+
+ except httpx.TimeoutException:
+ return AnalyzeResponse(
+ success=False,
+ error="Zeitüberschreitung bei der Ollama API"
)
-
- if response.status_code == 404:
- return jsonify({
- 'error': f'Modell "{modelName}" nicht gefunden. Bitte installieren Sie es mit: ollama pull {modelName}'
- }), 404
-
- if response.status_code != 200:
- return jsonify({
- 'error': f'Ollama API Fehler: {response.status_code} - {response.text[:200]}'
- }), response.status_code
-
- responseData = response.json()
- responseText = responseData.get('response', '')
-
- # Versuche JSON aus der Antwort zu extrahieren
- extractedData = None
- jsonMatch = re.search(r'\{[\s\S]*\}', responseText)
-
- if jsonMatch:
- try:
- extractedData = json.loads(jsonMatch.group())
- except json.JSONDecodeError:
- # JSON-ähnlicher Text gefunden, aber ungültig
- extractedData = None
-
- # Wenn kein JSON gefunden, Antwort in JSON-Objekt verpacken
- if extractedData is None:
- extractedData = {
- 'response': responseText.strip()
- }
-
- return jsonify({
- 'success': True,
- 'data': extractedData,
- 'rawResponse': responseText
- })
-
- except requests.exceptions.Timeout:
- return jsonify({'error': 'Zeitüberschreitung bei der Ollama API'}), 504
- except requests.exceptions.ConnectionError:
- return jsonify({'error': 'Verbindung zu Ollama fehlgeschlagen. Ist Ollama gestartet?'}), 503
- except json.JSONDecodeError as e:
- return jsonify({'error': f'JSON Parse-Fehler: {str(e)}'}), 400
+ except httpx.ConnectError:
+ return AnalyzeResponse(
+ success=False,
+ error="Verbindung zu Ollama fehlgeschlagen. Ist Ollama gestartet?"
+ )
+ except HTTPException:
+ raise
except Exception as e:
- return jsonify({'error': f'Unerwarteter Fehler: {str(e)}'}), 500
+ logger.error(f"Error analyzing document: {e}")
+ return AnalyzeResponse(
+ success=False,
+ error=f"Unerwarteter Fehler: {str(e)}"
+ )
-
-@app.route('/api/health', methods=['GET'])
-def _healthCheck():
- """Health Check Endpoint"""
- return jsonify({'status': 'ok', 'service': 'belegscanner', 'pdfSupport': PDF_SUPPORT})
-
-
-@app.route('/api/pdf/extract', methods=['POST'])
-@_loginRequired
-def _extractPdfImages():
- """
- Extrahiert Bilder aus einem PDF.
- Erwartet: { pdfBase64, page (optional, default: alle) }
- """
+@app.post("/api/pdf/extract", tags=["PDF"])
+async def _extractPdfImages(
+ request: PdfExtractRequest,
+ authenticated: bool = Depends(_verifyApiKey)
+):
+ """Extract images from a PDF."""
if not PDF_SUPPORT:
- return jsonify({
- 'error': 'PDF-Support nicht verfügbar. Bitte PyMuPDF installieren: pip install pymupdf'
- }), 501
+ raise HTTPException(
+ status_code=501,
+ detail="PDF-Support nicht verfügbar. Bitte PyMuPDF installieren: pip install pymupdf"
+ )
try:
- data = request.get_json()
- pdfBase64 = data.get('pdfBase64')
- pageNum = data.get('page') # Optional: spezifische Seite
+ pdfBytes = base64.b64decode(request.pdfBase64)
- if not pdfBase64:
- return jsonify({'error': 'Kein PDF übermittelt'}), 400
-
- # Base64 dekodieren
- pdfBytes = base64.b64decode(pdfBase64)
-
- if pageNum is not None:
- # Einzelne Seite extrahieren
- result = _renderPdfPageAsImage(pdfBytes, pageNum - 1) # 0-basiert
- return jsonify({
- 'success': True,
- 'image': result
- })
+ if request.page is not None:
+ # Extract single page
+ result = _renderPdfPageAsImage(pdfBytes, request.page - 1)
+ return {"success": True, "image": result}
else:
- # Alle Seiten extrahieren (max 5)
+ # Extract all pages (max 5)
images = _extractImagesFromPdf(pdfBytes, maxPages=5)
- return jsonify({
- 'success': True,
- 'images': images,
- 'totalExtracted': len(images)
- })
-
- except Exception as e:
- return jsonify({'error': f'PDF-Verarbeitungsfehler: {str(e)}'}), 500
-
-
-@app.route('/api/ollama/status', methods=['GET'])
-@_loginRequired
-def _ollamaStatus():
- """Prüft ob Ollama erreichbar ist und listet verfügbare Modelle"""
- ollamaUrl = request.args.get('url', 'http://localhost:11434')
+ return {
+ "success": True,
+ "images": images,
+ "totalExtracted": len(images)
+ }
- try:
- # Prüfe ob Ollama läuft
- response = requests.get(f'{ollamaUrl}/api/tags', timeout=5)
-
- if response.status_code != 200:
- return jsonify({
- 'connected': False,
- 'error': f'Ollama antwortet mit Status {response.status_code}'
- })
-
- data = response.json()
- models = [m.get('name', '') for m in data.get('models', [])]
-
- # Filtere Vision-Modelle (enthalten oft 'vision', 'vl', 'llava' im Namen)
- visionModels = [m for m in models if any(x in m.lower() for x in ['vision', 'vl', 'llava', 'bakllava'])]
-
- return jsonify({
- 'connected': True,
- 'models': models,
- 'visionModels': visionModels,
- 'totalModels': len(models)
- })
-
- except requests.exceptions.ConnectionError:
- return jsonify({
- 'connected': False,
- 'error': 'Keine Verbindung zu Ollama. Ist Ollama gestartet?'
- })
except Exception as e:
- return jsonify({
- 'connected': False,
- 'error': str(e)
- })
+ raise HTTPException(
+ status_code=500,
+ detail=f"PDF-Verarbeitungsfehler: {str(e)}"
+ )
+# ============================================================================
+# Web UI Routes (Optional - for direct browser access)
+# ============================================================================
+
+@app.get("/", response_class=HTMLResponse, tags=["Web UI"])
+async def _index(request: Request):
+ """Main page with document scanner UI."""
+ return templates.TemplateResponse("index.html", {"request": request})
+
+@app.get("/login", response_class=HTMLResponse, tags=["Web UI"])
+async def _loginPage(request: Request):
+ """Login page."""
+ return templates.TemplateResponse("login.html", {"request": request})
# ============================================================================
# Main
# ============================================================================
-if __name__ == '__main__':
- print("\n" + "="*60)
- print(" Belegscanner - KI-Dokumentenanalyse")
- print(" Powered by Poweron")
- print("="*60)
- print("\n Server läuft auf: http://localhost:5000")
- print(" CORS ist aktiviert für alle Origins")
- print("\n Drücke Ctrl+C zum Beenden")
- print("="*60 + "\n")
+if __name__ == "__main__":
+ import uvicorn
- app.run(host='0.0.0.0', port=5000, debug=True)
+ print("\n" + "=" * 60)
+ print(" Private-LLM Service - KI-Dokumentenanalyse")
+ print(" Powered by PowerOn")
+ print("=" * 60)
+ print(f"\n Server läuft auf: http://localhost:5000")
+ print(f" API Docs: http://localhost:5000/docs")
+ print(f" Ollama URL: {CONFIG['ollamaUrl']}")
+ print("\n Drücke Ctrl+C zum Beenden")
+ print("=" * 60 + "\n")
+
+ uvicorn.run(app, host="0.0.0.0", port=5000)
diff --git a/config.ini b/config.ini
new file mode 100644
index 0000000..cfe9374
--- /dev/null
+++ b/config.ini
@@ -0,0 +1,21 @@
+# Private-LLM Configuration
+# =========================
+
+# API Key für eingehende Requests (Gateway authentifiziert sich damit)
+# Muss mit Connector_AiPrivateLlm_API_SECRET in Gateway env-Files übereinstimmen
+# Key generieren: python -c "import secrets; print(secrets.token_urlsafe(32))"
+PRIVATE_LLM_API_KEY = jL4vyNfh_tv4rxoRaHKW88sVWNHbj32GsxuKE2A8bf0
+
+# Ollama Server URL
+OLLAMA_URL = http://localhost:11434
+
+# Web UI Authentication (optional, für direkten Browser-Zugriff)
+AUTH_USERNAME = poweron
+AUTH_PASSWORD = poweron
+
+# FastAPI Secret Key (für Session-Management)
+SECRET_KEY = c8bc1cede035171dedf01f220623e185aa8b83670ef607e97d928d271ac94200
+
+# Rate Limiting
+RATE_LIMIT_REQUESTS_PER_MINUTE = 60
+RATE_LIMIT_BURST_SIZE = 10
diff --git a/docu/requirements.txt b/docu/requirements.txt
new file mode 100644
index 0000000..be74a22
--- /dev/null
+++ b/docu/requirements.txt
@@ -0,0 +1,16 @@
+# FastAPI and dependencies
+fastapi>=0.109.0
+uvicorn[standard]>=0.27.0
+python-multipart>=0.0.6
+httpx>=0.26.0
+pydantic>=2.5.0
+
+# Templating for web UI
+jinja2>=3.1.0
+aiofiles>=23.0.0
+
+# PDF Support
+pymupdf>=1.24.0
+
+# Production server
+gunicorn>=21.0.0
diff --git a/setupserver.md b/docu/setupserver.md
similarity index 100%
rename from setupserver.md
rename to docu/setupserver.md
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index c350cd9..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-flask>=3.0.0
-flask-cors>=4.0.0
-requests>=2.31.0
-werkzeug>=3.0.0
-pymupdf>=1.24.0
-gunicorn>=21.0.0
\ No newline at end of file
diff --git a/start-python.bat b/start-python.bat
index 7a5b4ad..064704b 100644
--- a/start-python.bat
+++ b/start-python.bat
@@ -1,8 +1,8 @@
@echo off
chcp 65001 >nul
echo ============================================================
-echo Belegscanner - KI-Dokumentenanalyse
-echo Powered by Poweron
+echo Private-LLM Service - KI-Dokumentenanalyse
+echo Powered by PowerOn (FastAPI + Uvicorn)
echo ============================================================
echo.
@@ -31,11 +31,14 @@ REM Dependencies installieren
echo [2/3] Installiere Python Dependencies...
pip install -r requirements.txt --quiet
-echo [3/3] Starte Python Flask Server...
+echo [3/3] Starte FastAPI Server (Uvicorn)...
+echo.
+echo Server URL: http://localhost:5000
+echo API Docs: http://localhost:5000/docs
+echo OpenAPI JSON: http://localhost:5000/openapi.json
echo.
-echo Server URL: http://localhost:5000
echo Druecke Ctrl+C zum Beenden
echo.
-REM Flask starten
-python app.py
+REM FastAPI mit Uvicorn starten
+uvicorn app:app --host 0.0.0.0 --port 5000 --reload
diff --git a/start-python.ps1 b/start-python.ps1
index 7feab99..32ae31a 100644
--- a/start-python.ps1
+++ b/start-python.ps1
@@ -1,9 +1,9 @@
-# Belegscanner - Python Web App Starter
-# Poweron Design
+# Private-LLM Service - FastAPI Starter
+# Powered by PowerOn
Write-Host "============================================================" -ForegroundColor Cyan
-Write-Host " Belegscanner - KI-Dokumentenanalyse" -ForegroundColor White
-Write-Host " Powered by Poweron" -ForegroundColor Magenta
+Write-Host " Private-LLM Service - KI-Dokumentenanalyse" -ForegroundColor White
+Write-Host " Powered by PowerOn (FastAPI + Uvicorn)" -ForegroundColor Magenta
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
@@ -44,11 +44,14 @@ Start-Sleep -Seconds 2
Write-Host "[2/3] Installiere Python Dependencies..." -ForegroundColor Yellow
pip install -r requirements.txt --quiet
-Write-Host "[3/3] Starte Flask Server..." -ForegroundColor Yellow
+Write-Host "[3/3] Starte FastAPI Server (Uvicorn)..." -ForegroundColor Yellow
+Write-Host ""
+Write-Host "Server URL: http://localhost:5000" -ForegroundColor Green
+Write-Host "API Docs: http://localhost:5000/docs" -ForegroundColor Green
+Write-Host "OpenAPI JSON: http://localhost:5000/openapi.json" -ForegroundColor Gray
Write-Host ""
-Write-Host "Server URL: http://localhost:5000" -ForegroundColor Green
Write-Host "Druecke Ctrl+C zum Beenden" -ForegroundColor Gray
Write-Host ""
-# Flask Server starten
-python app.py
+# FastAPI Server mit Uvicorn starten
+uvicorn app:app --host 0.0.0.0 --port 5000 --reload
diff --git a/t1.png b/t1.png
deleted file mode 100644
index fc0618a1260443292016c911a568b4af3f23a5cc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 264431
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