Stufe 2: Antworttyp-Routing — Survey Map-Reduce + Rueckfrage-Regel (D13, M6)
- Planer liefert jetzt type: survey|specific. Survey-Fragen (Uebersicht ueber viele Dokumente) laufen ueber Map-Reduce: Retrieval auf survey_blocks=16 erweitert, ein Map-Call destilliert JE Block als Stichpunkte mit seiner KB-ID (MAP_SYSTEM_PROMPT), ein Reduce-Call synthetisiert die Endantwort. Grounding unveraendert: Zitier-Validierung strikt ueber die Retrieved-Union; leerer Map-Output -> Fallback auf Einzelantwort. - Systemprompt-Regel 9: haengt die Antwort wesentlich von nicht genanntem Kontext ab (Branche, Bundesland, Zeitraum), belegte allgemeine Aussage plus EINE Rueckfrage statt Verweigerung (API-first; Odoo-Chat kann die Rueckfrage als Follow-up nutzen). - Ergebnis: q-029 (WIKU-Survey, bisher hartnaeckigste Fehlverweigerung) geheilt - Teilantwort mit 5 belegten Heften; q-015 antwortet mit expliziter KV-Abhaengigkeit + Rueckfrage statt Branchen-Noise. - Eval (46 Fragen): Zitier-Praezision 97,8 %, Verweigerung 97,8 % (Gate erfuellt), erwartete Quelle 90,2 %, Latenz mean 34,2 s (Map-Reduce nur bei Survey-Fragen, ~95 s). Report lokal data/eval-qwen38-stage2.json. - Tests 57 -> 59 (Survey-Integration, Typ-Parsing).
This commit is contained in:
+44
-4
@@ -47,11 +47,22 @@ Verbindliche Regeln:
|
||||
existierendes Gesetz), korrigiere die Annahme anhand der Blöcke und
|
||||
gib die zutreffende, belegte Aussage. Muster: Auf „Was regelt das
|
||||
Mindestlohngesetz?“ antworte sinngemäß „Ein Mindestlohngesetz existiert
|
||||
laut Kontext nicht; stattdätzlich gilt …“ — mit Beleg [ID].
|
||||
laut Kontext nicht; stattdessen gilt …“ — mit Beleg [ID].
|
||||
Verweigere in diesem Fall nicht.
|
||||
9. Hängt die Antwort wesentlich von nicht genanntem Kontext ab (z. B.
|
||||
Branche, Bundesland, Zeitraum), sage dies explizit: gib die belegte
|
||||
allgemeine Aussage für die im Kontext vertretenen Fälle und frage am
|
||||
Ende in EINEM kurzen Satz nach dem fehlenden Kontext. Verweigere in
|
||||
diesem Fall nicht.
|
||||
|
||||
Verletze Regel 2 oder Regel 4 niemals — im Zweifel verweigere die Antwort."""
|
||||
|
||||
MAP_SYSTEM_PROMPT = """Du destillierst Wissensbasis-Kontextblöcke für eine Folgesynthese.
|
||||
Erstelle für JEDEN Kontextblock 1-3 prägnante Stichpunkte. Beginne jede
|
||||
Zusammenfassung mit der Zeile "[<KB-ID>] <Kurzthema>:" — verwende exakt
|
||||
die KB-ID aus dem Block-Kopf. Behalte konkrete Werte mit ihrem Stand.
|
||||
Lasse keinen Block aus; keine Einleitung, keine Schlussbemerkung."""
|
||||
|
||||
CITE_RE = re.compile(r"\b(?:lb|wk|kv|ris)-[a-z0-9]+-\d+\b")
|
||||
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL)
|
||||
|
||||
@@ -157,15 +168,19 @@ def answer_question(
|
||||
|
||||
sub_queries = [SubQuery(text=question)]
|
||||
planned = False
|
||||
qtype = "specific"
|
||||
if cfg.planner_enabled:
|
||||
try:
|
||||
sub_queries, planned = plan_queries(question, client, cfg)
|
||||
sub_queries, planned, qtype = plan_queries(question, client, cfg)
|
||||
sub_queries = sub_queries[: cfg.planner_max_queries] or sub_queries[:1]
|
||||
except Exception:
|
||||
sub_queries, planned = [SubQuery(text=question)], False
|
||||
sub_queries, planned, qtype = [SubQuery(text=question)], False, "specific"
|
||||
|
||||
n_entries = top_k
|
||||
if n_entries is None and qtype == "survey":
|
||||
n_entries = cfg.survey_blocks
|
||||
try:
|
||||
results = retriever.search_multi(sub_queries, n_entries=top_k)
|
||||
results = retriever.search_multi(sub_queries, n_entries=n_entries)
|
||||
finally:
|
||||
if own_retriever:
|
||||
retriever.close()
|
||||
@@ -202,6 +217,15 @@ def answer_question(
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{"role": "user", "content": build_user_content(question, results)},
|
||||
]
|
||||
map_messages = None
|
||||
if qtype == "survey":
|
||||
# Map-Reduce (Stufe 2): alle Bloecke destillieren (Map), dann
|
||||
# synthetisieren (Reduce). Zitiert werden duerfen weiterhin nur IDs
|
||||
# aus der Retrieved-Menge — die Post-Validierung bleibt unveraendert.
|
||||
map_messages = [
|
||||
{"role": "system", "content": MAP_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": build_user_content(question, results)},
|
||||
]
|
||||
|
||||
def chat(msgs):
|
||||
return strip_think(
|
||||
@@ -215,6 +239,22 @@ def answer_question(
|
||||
)
|
||||
)
|
||||
|
||||
if map_messages is not None:
|
||||
summary = chat(map_messages)
|
||||
if summary:
|
||||
messages = [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"Kontextblöcke aus der Wissensbasis "
|
||||
f"(Block-Zusammenfassungen):\n\n{summary}\n\n"
|
||||
f"Frage: {question}"
|
||||
),
|
||||
},
|
||||
]
|
||||
# leerer Map-Output -> Fallback: messages bleibt die Einzelantwort
|
||||
|
||||
final = chat(messages)
|
||||
violations = validate_answer(final, allowed)
|
||||
regenerations = 0
|
||||
|
||||
Reference in New Issue
Block a user