Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 50dad7a9f9 | |||
| 614704ec74 |
@@ -38,6 +38,17 @@ MAX_TEXT_LENGTH = int(os.getenv("MAX_TEXT_LENGTH", "2000000"))
|
||||
|
||||
KNOWLEDGE_ID = None
|
||||
|
||||
# ============================================================
|
||||
# CONFIG – Ergänzung für OCR-Fix
|
||||
# ============================================================
|
||||
LLM_BASE_URL = os.environ.get("LLM_BASE_URL", "http://ollama:11434/v1")
|
||||
LLM_API_KEY = os.environ.get("LLM_API_KEY", "unused")
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "gemma3:12b")
|
||||
OCR_FIX_ENABLED = os.environ.get("OCR_FIX_ENABLED", "true").lower() == "true"
|
||||
SPACING_THRESHOLD = float(os.environ.get("SPACING_THRESHOLD", "0.5"))
|
||||
MIN_SPACED_LINES = int(os.environ.get("MIN_SPACED_LINES", "3"))
|
||||
LLM_CHUNK_SIZE = int(os.environ.get("LLM_CHUNK_SIZE", "2000"))
|
||||
|
||||
# ============================================================
|
||||
# LOGGING
|
||||
# ============================================================
|
||||
@@ -58,6 +69,16 @@ openwebui_headers = {
|
||||
"Authorization": f"Bearer {OPENWEBUI_TOKEN}",
|
||||
}
|
||||
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
# ============================================================
|
||||
# LLM CLIENT
|
||||
# ============================================================
|
||||
if OCR_FIX_ENABLED:
|
||||
llm_client = OpenAI(base_url=LLM_BASE_URL, api_key=LLM_API_KEY)
|
||||
log.info("OCR fix enabled, using model: %s", LLM_MODEL)
|
||||
|
||||
# ============================================================
|
||||
# SPLITTER
|
||||
# ============================================================
|
||||
@@ -544,6 +565,108 @@ def upload_to_openwebui(filename: str, content: str):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# OCR FIX VIA LLM
|
||||
# ============================================================
|
||||
OCR_FIX_SYSTEM_PROMPT = """Du bist ein OCR-Nachbearbeitungs-Assistent.
|
||||
Deine Aufgabe ist es, fehlerhaft erkannten Text zu korrigieren.
|
||||
|
||||
Regeln:
|
||||
- Entferne falsche Leerzeichen in gesperrtem Text (z.B. "V e r t r a g" → "Vertrag")
|
||||
- Korrigiere offensichtliche OCR-Fehler (0/O, l/1, rn/m, etc.)
|
||||
- Behalte die ursprüngliche Struktur bei (Absätze, Zeilenumbrüche, Aufzählungen)
|
||||
- Erfinde KEINE Inhalte hinzu
|
||||
- Wenn der Text bereits korrekt ist, gib ihn unverändert zurück
|
||||
- Gib NUR den korrigierten Text zurück, keine Erklärungen"""
|
||||
|
||||
|
||||
def needs_ocr_fix(text: str) -> bool:
|
||||
"""Schneller Vorfilter: Hat der Text gesperrte Zeichen?"""
|
||||
if not text or len(text) < 50:
|
||||
return False
|
||||
|
||||
lines = text.split('\n')
|
||||
spaced_lines = 0
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if len(stripped) < 5:
|
||||
continue
|
||||
non_space = stripped.replace(' ', '')
|
||||
if len(non_space) > 0:
|
||||
ratio = stripped.count(' ') / len(non_space)
|
||||
if ratio > SPACING_THRESHOLD:
|
||||
spaced_lines += 1
|
||||
|
||||
return spaced_lines >= MIN_SPACED_LINES
|
||||
|
||||
|
||||
def fix_text_with_llm(text: str) -> str:
|
||||
"""Schickt einen Textblock zur Korrektur ans LLM."""
|
||||
try:
|
||||
response = llm_client.chat.completions.create(
|
||||
model=LLM_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": OCR_FIX_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": f"Korrigiere folgenden OCR-Text:\n\n{text}"}
|
||||
],
|
||||
temperature=0.0,
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
log.error("LLM OCR fix failed: %s", e)
|
||||
return text # Fallback: Originaltext
|
||||
|
||||
|
||||
def fix_ocr_chunked(content: str) -> str:
|
||||
"""Verarbeitet langen Text absatzweise durchs LLM."""
|
||||
paragraphs = content.split('\n\n')
|
||||
chunks = []
|
||||
current_chunk = ""
|
||||
|
||||
for para in paragraphs:
|
||||
if len(current_chunk) + len(para) > LLM_CHUNK_SIZE:
|
||||
if current_chunk:
|
||||
chunks.append(current_chunk)
|
||||
current_chunk = para
|
||||
else:
|
||||
current_chunk += ("\n\n" + para if current_chunk else para)
|
||||
|
||||
if current_chunk:
|
||||
chunks.append(current_chunk)
|
||||
|
||||
fixed_chunks = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
log.debug(" LLM fix chunk %d/%d", i + 1, len(chunks))
|
||||
fixed_chunks.append(fix_text_with_llm(chunk))
|
||||
time.sleep(0.2)
|
||||
|
||||
return "\n\n".join(fixed_chunks)
|
||||
|
||||
|
||||
def update_paperless_content(doc_id: int, new_content: str) -> bool:
|
||||
"""Schreibt korrigierten Text zurück nach Paperless."""
|
||||
try:
|
||||
r = requests.patch(
|
||||
f"{PAPERLESS_URL}/api/documents/{doc_id}/",
|
||||
headers={**paperless_headers, "Content-Type": "application/json"},
|
||||
json={"content": new_content},
|
||||
timeout=120,
|
||||
)
|
||||
if r.ok:
|
||||
log.info("Updated Paperless content for doc=%s", doc_id)
|
||||
return True
|
||||
else:
|
||||
log.warning(
|
||||
"Failed to update Paperless content doc=%s status=%s",
|
||||
doc_id, r.status_code,
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
log.error("Paperless content update failed doc=%s: %s", doc_id, e)
|
||||
return False
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SYNC – parallelisiert
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user