Nguyen5 commited on
Commit
ed2050a
·
1 Parent(s): 4da3e87
Files changed (1) hide show
  1. rag_pipeline.py +99 -58
rag_pipeline.py CHANGED
@@ -2,81 +2,110 @@
2
  RAG PIPELINE – Version 26.11 (ohne Modi, stabil, juristisch korrekt)
3
  """
4
 
5
- # from typing import List, Dict, Any, Tuple
6
- # from langchain_core.messages import SystemMessage, HumanMessage
7
- # from load_documents import DATASET, PDF_FILE, HTML_FILE
8
-
9
- # from typing import List, Dict, Any, Tuple
10
- # import os
11
- # from langchain_core.messages import SystemMessage, HumanMessage
12
- # from load_documents import DATASET, PDF_FILE
13
-
14
- # 5.12_2:13
15
  from typing import List, Dict, Any, Tuple
16
  from langchain_core.messages import SystemMessage, HumanMessage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  MAX_CHARS = 900
19
 
20
- # ============================================================
21
- # Quellenaufbereitung – NUR metadata verwenden!
22
- # ============================================================
23
 
24
  def build_sources_metadata(docs: List) -> List[Dict[str, Any]]:
25
- sources = []
26
-
27
- for idx, d in enumerate(docs):
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  meta = d.metadata
 
 
29
  snippet = d.page_content[:300].replace("\n", " ")
30
 
31
- # PDF
32
- if meta.get("type") == "pdf":
33
- sources.append({
34
- "id": idx + 1,
35
- "source": "Prüfungsordnung (PDF)",
36
- "page": meta.get("page"),
37
- "url": meta.get("pdf_url"), # KHÔNG tạo lại!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  "snippet": snippet,
39
- })
40
- continue
41
-
42
- # Hochschulgesetz NRW
43
- if meta.get("type") == "hg":
44
- sources.append({
45
- "id": idx + 1,
46
- "source": "Hochschulgesetz NRW",
47
- "page": None,
48
- "url": meta.get("viewer_url"), # KHÔNG tạo lại!
49
- "snippet": snippet,
50
- })
51
- continue
52
-
53
- return sources
54
 
55
- # ============================================================
56
- # Kontextaufbereitung
57
- # ============================================================
58
 
59
- def format_context(docs: List) -> str:
60
  if not docs:
61
- return "(Kein relevanter Kontext gefunden.)"
62
-
63
- blocks = []
64
 
 
65
  for i, d in enumerate(docs):
66
- meta = d.metadata
67
- doc_type = meta.get("type")
 
68
 
69
- label = "Prüfungsordnung" if doc_type == "pdf" else "Hochschulgesetz NRW"
 
 
 
70
 
71
- if doc_type == "pdf":
72
- page = meta.get("page")
73
- label += f", Seite {page+1}" if isinstance(page, int) else ""
74
-
75
- blocks.append(
76
- f"[KONTEXT {i+1}] ({label})\n{d.page_content[:MAX_CHARS]}"
77
- )
78
 
79
- return "\n\n".join(blocks)
80
 
81
  # -----------------------------
82
  # Systemprompt — verschärft
@@ -85,23 +114,31 @@ def format_context(docs: List) -> str:
85
  SYSTEM_PROMPT = """
86
  Du bist ein hochpräziser juristischer Chatbot für Prüfungsrecht
87
  mit Zugriff nur auf:
 
88
  - die Prüfungsordnung (als PDF) und
89
  - das Hochschulgesetz NRW (als HTML aus der offiziellen Druckversion).
 
90
  Strenge Regeln:
 
91
  1. Antworte ausschließlich anhand des bereitgestellten Kontextes
92
  (KONTEXT-Abschnitte). Wenn die Information nicht im Kontext steht,
93
  sage ausdrücklich, dass dies aus den vorliegenden Dokumenten nicht
94
  hervorgeht und du dazu nichts Sicheres sagen kannst.
 
95
  2.
96
  Keine Spekulationen, keine Vermutungen.
 
97
  3. Antworte in zusammenhängenden, ganzen Sätzen. Verwende keine Mischung aus Deutsch und Englisch.
 
98
  4. Nenne, soweit aus dem Kontext erkennbar,
99
  - die rechtliche Grundlage (z.B. Paragraph, Artikel),
100
  - das Dokument (Prüfungsordnung / Hochschulgesetz NRW),
101
  - die Seite (bei der Prüfungsordnung), wenn im Kontext vorhanden.
 
102
  5. Füge KEINE externen Informationen hinzu, z.B. aus anderen Gesetzen,
103
  Webseiten oder allgemeinem Wissen. Nur das, was im Kontext steht,
104
  darf in der Antwort verwendet werden.
 
105
  Wenn der Kontext keine eindeutige Antwort zulässt, erkläre klar,
106
  warum keine sichere Antwort möglich ist und welche Informationen
107
  im Dokument fehlen.
@@ -114,6 +151,7 @@ im Dokument fehlen.
114
  def answer(question: str, retriever, chat_model) -> Tuple[str, List[Dict[str, Any]]]:
115
  """
116
  Haupt-RAG-Funktion:
 
117
  - ruft retriever.invoke(question) auf,
118
  - baut einen präzisen Prompt mit KONTEXT,
119
  - ruft LLM auf,
@@ -124,14 +162,17 @@ def answer(question: str, retriever, chat_model) -> Tuple[str, List[Dict[str, An
124
  context_str = format_context(docs)
125
 
126
  # 2. Prompt bauen
127
- user_prompt = f"""
128
  FRAGE:
129
  {question}
 
130
  NUTZE AUSSCHLIESSLICH DIESEN KONTEXT:
131
  {context_str}
 
132
  AUFGABE:
133
  Formuliere eine juristisch korrekte, gut verständliche Antwort
134
  ausschließlich anhand des obigen Kontextes.
 
135
  - Wenn der Kontext aus den Dokumenten eine klare Antwort erlaubt,
136
  erläutere diese strukturiert und in vollständigen Sätzen.
137
  - Wenn der Kontext KEINE klare Antwort erlaubt oder wichtige Informationen
@@ -140,7 +181,7 @@ ausschließlich anhand des obigen Kontextes.
140
 
141
  msgs = [
142
  SystemMessage(content=SYSTEM_PROMPT),
143
- HumanMessage(content=user_prompt),
144
  ]
145
 
146
  # 3. LLM aufrufen
 
2
  RAG PIPELINE – Version 26.11 (ohne Modi, stabil, juristisch korrekt)
3
  """
4
 
 
 
 
 
 
 
 
 
 
 
5
  from typing import List, Dict, Any, Tuple
6
  from langchain_core.messages import SystemMessage, HumanMessage
7
+ from load_documents import DATASET, PDF_FILE, HTML_FILE
8
+
9
+ # -------------------------------------------------------------------
10
+ # URLs für Quellen
11
+ # -------------------------------------------------------------------
12
+
13
+ # Direktes PDF im Dataset (für #page)
14
+ PDF_BASE_URL = f"https://huggingface.co/datasets/{DATASET}/resolve/main/{PDF_FILE}"
15
+
16
+ # Hochschulgesetz-HTML im Dataset (enthält <p id="hg_abs_X"> …)
17
+ LAW_DATASET_URL = f"https://huggingface.co/datasets/{DATASET}/resolve/main/{HTML_FILE}"
18
+
19
+ # Offizielle Recht.NRW-Druckversion (für Viewer im Frontend)
20
+ LAW_URL = (
21
+ "https://recht.nrw.de/lmi/owa/br_bes_text?"
22
+ "print=1&anw_nr=2&gld_nr=2&ugl_nr=221&val=28364&ver=0&"
23
+ "aufgehoben=N&keyword=&bes_id=28364&show_preview=1"
24
+ )
25
 
26
  MAX_CHARS = 900
27
 
28
+ # -----------------------------
29
+ # Quellen formatieren
30
+ # -----------------------------
31
 
32
  def build_sources_metadata(docs: List) -> List[Dict[str, Any]]:
33
+ """
34
+ Erzeugt eine Liste strukturierter Quellen-Infos:
35
+
36
+ [
37
+ {
38
+ "id": 1,
39
+ "source": "Prüfungsordnung (PDF)" / "Hochschulgesetz NRW (HTML)",
40
+ "page": 3, # nur bei PDF
41
+ "url": "...", # direkter Klick-Link
42
+ "snippet": "Erste 300 Zeichen des Chunks..."
43
+ },
44
+ ...
45
+ ]
46
+ """
47
+ srcs = []
48
+ for i, d in enumerate(docs):
49
  meta = d.metadata
50
+ src = meta.get("source", "")
51
+ page = meta.get("page")
52
  snippet = d.page_content[:300].replace("\n", " ")
53
 
54
+ # PDF-Link
55
+ if "Prüfungsordnung" in src:
56
+ if isinstance(page, int):
57
+ # PyPDFLoader: page ist 0-basiert, Anzeige 1-basiert
58
+ url = f"{PDF_BASE_URL}#page={page + 1}"
59
+ else:
60
+ url = PDF_BASE_URL
61
+
62
+ # NRW-Gesetz (HTML im Dataset mit Absatz-IDs)
63
+ elif "Hochschulgesetz" in src:
64
+ para_id = meta.get("paragraph_id")
65
+ if para_id:
66
+ # Klick führt direkt zum Absatz im Dataset-HTML
67
+ url = f"{LAW_DATASET_URL}#{para_id}"
68
+ else:
69
+ # Fallback: offizielle Druckversion (ohne Absatz-Anker)
70
+ url = LAW_URL
71
+ page = None # keine Seitenangabe für Gesetz-HTML
72
+
73
+ else:
74
+ url = None
75
+
76
+ srcs.append(
77
+ {
78
+ "id": i + 1,
79
+ "source": src,
80
+ "page": page + 1 if isinstance(page, int) else None,
81
+ "url": url,
82
  "snippet": snippet,
83
+ }
84
+ )
85
+ return srcs
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
+ # -----------------------------
88
+ # Kontext formatieren
89
+ # -----------------------------
90
 
91
+ def format_context(docs):
92
  if not docs:
93
+ return "(Kein relevanter Kontext im Dokument gefunden.)"
 
 
94
 
95
+ out = []
96
  for i, d in enumerate(docs):
97
+ txt = d.page_content[:MAX_CHARS]
98
+ src = d.metadata.get("source")
99
+ page = d.metadata.get("page")
100
 
101
+ if "Prüfungsordnung" in (src or "") and isinstance(page, int):
102
+ src_str = f"{src}, Seite {page + 1}"
103
+ else:
104
+ src_str = src
105
 
106
+ out.append(f"[KONTEXT {i+1}] ({src_str})\n{txt}")
 
 
 
 
 
 
107
 
108
+ return "\n\n".join(out)
109
 
110
  # -----------------------------
111
  # Systemprompt — verschärft
 
114
  SYSTEM_PROMPT = """
115
  Du bist ein hochpräziser juristischer Chatbot für Prüfungsrecht
116
  mit Zugriff nur auf:
117
+
118
  - die Prüfungsordnung (als PDF) und
119
  - das Hochschulgesetz NRW (als HTML aus der offiziellen Druckversion).
120
+
121
  Strenge Regeln:
122
+
123
  1. Antworte ausschließlich anhand des bereitgestellten Kontextes
124
  (KONTEXT-Abschnitte). Wenn die Information nicht im Kontext steht,
125
  sage ausdrücklich, dass dies aus den vorliegenden Dokumenten nicht
126
  hervorgeht und du dazu nichts Sicheres sagen kannst.
127
+
128
  2.
129
  Keine Spekulationen, keine Vermutungen.
130
+
131
  3. Antworte in zusammenhängenden, ganzen Sätzen. Verwende keine Mischung aus Deutsch und Englisch.
132
+
133
  4. Nenne, soweit aus dem Kontext erkennbar,
134
  - die rechtliche Grundlage (z.B. Paragraph, Artikel),
135
  - das Dokument (Prüfungsordnung / Hochschulgesetz NRW),
136
  - die Seite (bei der Prüfungsordnung), wenn im Kontext vorhanden.
137
+
138
  5. Füge KEINE externen Informationen hinzu, z.B. aus anderen Gesetzen,
139
  Webseiten oder allgemeinem Wissen. Nur das, was im Kontext steht,
140
  darf in der Antwort verwendet werden.
141
+
142
  Wenn der Kontext keine eindeutige Antwort zulässt, erkläre klar,
143
  warum keine sichere Antwort möglich ist und welche Informationen
144
  im Dokument fehlen.
 
151
  def answer(question: str, retriever, chat_model) -> Tuple[str, List[Dict[str, Any]]]:
152
  """
153
  Haupt-RAG-Funktion:
154
+
155
  - ruft retriever.invoke(question) auf,
156
  - baut einen präzisen Prompt mit KONTEXT,
157
  - ruft LLM auf,
 
162
  context_str = format_context(docs)
163
 
164
  # 2. Prompt bauen
165
+ human = f"""
166
  FRAGE:
167
  {question}
168
+
169
  NUTZE AUSSCHLIESSLICH DIESEN KONTEXT:
170
  {context_str}
171
+
172
  AUFGABE:
173
  Formuliere eine juristisch korrekte, gut verständliche Antwort
174
  ausschließlich anhand des obigen Kontextes.
175
+
176
  - Wenn der Kontext aus den Dokumenten eine klare Antwort erlaubt,
177
  erläutere diese strukturiert und in vollständigen Sätzen.
178
  - Wenn der Kontext KEINE klare Antwort erlaubt oder wichtige Informationen
 
181
 
182
  msgs = [
183
  SystemMessage(content=SYSTEM_PROMPT),
184
+ HumanMessage(content=human),
185
  ]
186
 
187
  # 3. LLM aufrufen