feat(agent): tune retrieval for decision support
This commit is contained in:
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
|
||||
YEAR_RE = re.compile(r"\b(?:19|20)\d{2}\b")
|
||||
JSON_RE = re.compile(r"\{.*\}", re.DOTALL)
|
||||
@@ -19,6 +20,12 @@ AGGREGATION_RE = re.compile(
|
||||
r"neuerungen|übersicht|zusammenfassung|alle\s|übersicht", re.I
|
||||
)
|
||||
COMPARISON_RE = re.compile(r"unterschied|vergleic|\bbzw\.|\bsowie\b", re.I)
|
||||
DECISION_SUPPORT_RE = re.compile(
|
||||
r"günstig(?:ste|er|e)?\s+lösung|abgabenbegünstigt|steuerfrei.*(?:auszahl|präm)|"
|
||||
r"(?:zusätzlich|extra).*(?:auszahl|präm)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
PLANNER_PROMPT = """Du planst Suchanfragen für eine Wissensbasis zur österreichischen
|
||||
Personalverrechnung (kuratierte Briefings, Kollektivverträge je Branche und
|
||||
@@ -51,6 +58,11 @@ class SubQuery:
|
||||
scope: str | None = None # "gesetz" (ohne Branchen-KV) | "kv" | None
|
||||
|
||||
|
||||
def is_decision_support(question: str) -> bool:
|
||||
"""Erkennt Gestaltungsfragen, die den deterministischen Plan benötigen."""
|
||||
return bool(DECISION_SUPPORT_RE.search(question))
|
||||
|
||||
|
||||
def should_plan(question: str) -> bool:
|
||||
"""Heuristik-Gate: nur komplexe Fragen bekommen einen Planer-Call.
|
||||
|
||||
@@ -64,6 +76,8 @@ def should_plan(question: str) -> bool:
|
||||
return True
|
||||
if COMPARISON_RE.search(q):
|
||||
return True
|
||||
if is_decision_support(q):
|
||||
return True
|
||||
if AGGREGATION_RE.search(q):
|
||||
return True
|
||||
if " und " in q.casefold() and len(words) >= 10:
|
||||
@@ -71,6 +85,56 @@ def should_plan(question: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def decision_support_plan(
|
||||
question: str, default_year: str | None = None
|
||||
) -> tuple[list[SubQuery], str] | None:
|
||||
"""Deterministische Zerlegung für Gestaltungsfragen zu zusätzlichen
|
||||
Arbeitnehmerleistungen. Sie verhindert, dass die vielen Branchen-KV-
|
||||
Treffer allgemeine Abgabenregeln verdrängen.
|
||||
|
||||
Ohne genanntes Jahr wird der neueste Indexstand verwendet; die Antwort
|
||||
muss fehlenden Einzelfallkontext weiterhin über die Prompt-Regel erfragen.
|
||||
"""
|
||||
if not is_decision_support(question):
|
||||
return None
|
||||
explicit_year = YEAR_RE.search(question)
|
||||
year = explicit_year.group(0) if explicit_year else default_year
|
||||
year_text = f" {year}" if year else ""
|
||||
direct = SubQuery(
|
||||
text=(
|
||||
f"Mitarbeiterprämie{year_text} WIKU Personal aktuell Nr 12 FAQ "
|
||||
"Höchstbetrag 500 Lohnsteuer SV BV Beitragsfreie Bezugsbestandteile "
|
||||
"§ 49 Abs 3 Z 30"
|
||||
),
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
cash_and_benefits = SubQuery(
|
||||
text=(
|
||||
"Zukunftssicherungsmaßnahmen 300 Kalenderjahr Risikoversicherung "
|
||||
"Gruppenerfordernis Bezugsumwandlung Lohnsteuer SV BV DB DZ KommSt"
|
||||
),
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
purpose_bound = SubQuery(
|
||||
text=(
|
||||
"Sachzuwendungen Geschenke Barzahlungen stets steuerpflichtig Freibetrag "
|
||||
"Beitragsfreie Bezugsbestandteile Z 1 bis Z 16a Mahlzeiten "
|
||||
"Kinderbetreuungszuschuss"
|
||||
),
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
broad = bool(
|
||||
re.search(r"günstig|möglichkeiten|statt|alternative", question, re.IGNORECASE)
|
||||
)
|
||||
# Ein Gestaltungsvergleich ist kein Korpus-Survey: Die drei gezielten
|
||||
# Queries passen in den normalen Antwortpfad und vermeiden unnötiges
|
||||
# Map-Reduce samt zusätzlicher Latenz/Fehlverweigerungsrisiko.
|
||||
return ([direct, cash_and_benefits, purpose_bound] if broad else [direct]), "specific"
|
||||
|
||||
|
||||
def parse_plan(raw: str, original: str) -> tuple[list[SubQuery], str]:
|
||||
"""Robustes JSON-Parsing; jeder Fehler → [Originalfrage]. Liefert
|
||||
(Sub-Queries, Fragetyp 'survey' | 'specific')."""
|
||||
@@ -107,6 +171,10 @@ def plan_queries(
|
||||
"""Liefert (Sub-Queries, geplant?, Fragetyp) — Call-/Parse-Fehler →
|
||||
Original als Einzel-Query, Typ 'specific'. Der Planer-Call ist klein
|
||||
(Frage ohne Kontext, kurzes num_predict); Temperature 0."""
|
||||
deterministic = decision_support_plan(question, default_year=str(date.today().year))
|
||||
if deterministic:
|
||||
subs, qtype = deterministic
|
||||
return subs, True, qtype
|
||||
if not should_plan(question):
|
||||
return [SubQuery(text=question)], False, "specific"
|
||||
prompt = PLANNER_PROMPT.format(question=question.strip())
|
||||
|
||||
Reference in New Issue
Block a user