| | """ |
| | RAG PIPELINE – Version 26.11 (ohne Modi, stabil, juristisch korrekt) |
| | """ |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | from typing import List, Dict, Any, Tuple |
| | from langchain_core.messages import SystemMessage, HumanMessage |
| |
|
| | MAX_CHARS = 900 |
| |
|
| | |
| | |
| | |
| |
|
| | def build_sources_metadata(docs: List) -> List[Dict[str, Any]]: |
| | sources = [] |
| |
|
| | for idx, d in enumerate(docs): |
| | meta = d.metadata |
| | snippet = d.page_content[:300].replace("\n", " ") |
| |
|
| | |
| | if meta.get("type") == "pdf": |
| | sources.append({ |
| | "id": idx + 1, |
| | "source": "Prüfungsordnung (PDF)", |
| | "page": meta.get("page"), |
| | "url": meta.get("pdf_url"), |
| | "snippet": snippet, |
| | }) |
| | continue |
| |
|
| | |
| | if meta.get("type") == "hg": |
| | sources.append({ |
| | "id": idx + 1, |
| | "source": "Hochschulgesetz NRW", |
| | "page": None, |
| | "url": meta.get("viewer_url"), |
| | "snippet": snippet, |
| | }) |
| | continue |
| |
|
| | return sources |
| |
|
| | |
| | |
| | |
| |
|
| | def format_context(docs: List) -> str: |
| | if not docs: |
| | return "(Kein relevanter Kontext gefunden.)" |
| |
|
| | blocks = [] |
| |
|
| | for i, d in enumerate(docs): |
| | meta = d.metadata |
| | doc_type = meta.get("type") |
| |
|
| | label = "Prüfungsordnung" if doc_type == "pdf" else "Hochschulgesetz NRW" |
| |
|
| | if doc_type == "pdf": |
| | page = meta.get("page") |
| | label += f", Seite {page+1}" if isinstance(page, int) else "" |
| |
|
| | blocks.append( |
| | f"[KONTEXT {i+1}] ({label})\n{d.page_content[:MAX_CHARS]}" |
| | ) |
| |
|
| | return "\n\n".join(blocks) |
| |
|
| | |
| | |
| | |
| |
|
| | SYSTEM_PROMPT = """ |
| | Du bist ein hochpräziser juristischer Chatbot für Prüfungsrecht |
| | mit Zugriff nur auf: |
| | |
| | - die Prüfungsordnung (als PDF) und |
| | - das Hochschulgesetz NRW (als HTML aus der offiziellen Druckversion). |
| | |
| | Strenge Regeln: |
| | |
| | 1. Antworte ausschließlich anhand des bereitgestellten Kontextes |
| | (KONTEXT-Abschnitte). Wenn die Information nicht im Kontext steht, |
| | sage ausdrücklich, dass dies aus den vorliegenden Dokumenten nicht |
| | hervorgeht und du dazu nichts Sicheres sagen kannst. |
| | |
| | 2. |
| | Keine Spekulationen, keine Vermutungen. |
| | |
| | 3. Antworte in zusammenhängenden, ganzen Sätzen. Verwende keine Mischung aus Deutsch und Englisch. |
| | |
| | 4. Nenne, soweit aus dem Kontext erkennbar, |
| | - die rechtliche Grundlage (z.B. Paragraph, Artikel), |
| | - das Dokument (Prüfungsordnung / Hochschulgesetz NRW), |
| | - die Seite (bei der Prüfungsordnung), wenn im Kontext vorhanden. |
| | |
| | 5. Füge KEINE externen Informationen hinzu, z.B. aus anderen Gesetzen, |
| | Webseiten oder allgemeinem Wissen. Nur das, was im Kontext steht, |
| | darf in der Antwort verwendet werden. |
| | |
| | Wenn der Kontext keine eindeutige Antwort zulässt, erkläre klar, |
| | warum keine sichere Antwort möglich ist und welche Informationen |
| | im Dokument fehlen. |
| | """ |
| |
|
| | |
| | |
| | |
| |
|
| | def answer(question: str, retriever, chat_model) -> Tuple[str, List[Dict[str, Any]]]: |
| | """ |
| | Haupt-RAG-Funktion: |
| | |
| | - ruft retriever.invoke(question) auf, |
| | - baut einen präzisen Prompt mit KONTEXT, |
| | - ruft LLM auf, |
| | - gibt Antworttext + Quellenliste zurück. |
| | """ |
| | |
| | docs = retriever.invoke(question) |
| | context_str = format_context(docs) |
| |
|
| | |
| | user_prompt = f""" |
| | FRAGE: |
| | {question} |
| | |
| | NUTZE AUSSCHLIESSLICH DIESEN KONTEXT: |
| | {context_str} |
| | |
| | AUFGABE: |
| | Formuliere eine juristisch korrekte, gut verständliche Antwort |
| | ausschließlich anhand des obigen Kontextes. |
| | |
| | - Wenn der Kontext aus den Dokumenten eine klare Antwort erlaubt, |
| | erläutere diese strukturiert und in vollständigen Sätzen. |
| | - Wenn der Kontext KEINE klare Antwort erlaubt oder wichtige Informationen |
| | fehlen, erkläre das offen und formuliere KEINE Vermutung. |
| | """ |
| |
|
| | msgs = [ |
| | SystemMessage(content=SYSTEM_PROMPT), |
| | HumanMessage(content=user_prompt), |
| | ] |
| |
|
| | |
| | result = chat_model.invoke(msgs) |
| | answer_text = result.content.strip() |
| |
|
| | |
| | sources = build_sources_metadata(docs) |
| |
|
| | return answer_text, sources |
| |
|