vergunningzoeker-17acffee/app/agent.py

114 lines
4.3 KiB
Python

"""Vergunningzoeker agent — agentic search over permits.
Receives questions, generates search queries, searches the permit
database, and synthesizes answers. Each step is logged for transparency.
"""
import json
from druppie_sdk import DruppieClient
from app.models import Permit
druppie = DruppieClient()
def run_agent(question: str, history: list[dict], db) -> dict:
steps = []
# Build conversation context
context_str = ""
if history:
recent = history[-4:]
context_str = "\n".join(
f"{'Gebruiker' if m['role'] == 'user' else 'Assistent'}: {m['content'][:200]}"
for m in recent
)
# Step 1: Generate search queries
full_prompt = question
if context_str:
full_prompt = f"Gesprekscontext:\n{context_str}\n\nNieuwe vraag: {question}"
query_result = druppie.call("llm", "chat", {
"prompt": (
"De gebruiker stelt een vraag over vergunningen in een database. "
"Genereer 1-3 zoektermen (komma-gescheiden) waarmee relevante "
"vergunningen gevonden kunnen worden. Antwoord ALLEEN met de zoektermen.\n\n"
f"Vraag: {full_prompt}"
),
"system": "Je genereert zoektermen voor een vergunningendatabase. Antwoord alleen met komma-gescheiden zoektermen.",
})
search_terms = [t.strip() for t in query_result.get("answer", question).split(",") if t.strip()]
steps.append({
"type": "search",
"label": f"Zoektermen: {', '.join(search_terms)}",
"detail": search_terms,
})
# Step 2: Search database with each term
found = {}
search_details = []
for term in search_terms[:3]:
like = f"%{term}%"
results = db.query(Permit).filter(
Permit.permit_number.ilike(like)
| Permit.applicant_name.ilike(like)
| Permit.permit_holder_name.ilike(like)
| Permit.location.ilike(like)
| Permit.permit_type.ilike(like)
| Permit.applicable_law.ilike(like)
| Permit.work_type.ilike(like)
| Permit.extracted_text.ilike(like)
).limit(10).all()
new_ids = [p.id for p in results if p.id not in found]
for p in results:
found[p.id] = p
search_details.append(f'"{term}"{len(results)} resultaten ({len(new_ids)} nieuw)')
steps.append({
"type": "query",
"label": f"{len(found)} vergunningen gevonden",
"detail": search_details,
})
# Step 3: Build context from found permits
if found:
context_parts = []
for p in found.values():
parts = [f"Vergunning #{p.id}: {p.permit_number or 'geen nummer'}"]
if p.applicant_name: parts.append(f" Aanvrager: {p.applicant_name}")
if p.permit_holder_name: parts.append(f" Houder: {p.permit_holder_name}")
if p.permit_type: parts.append(f" Type: {p.permit_type}")
if p.location: parts.append(f" Locatie: {p.location}")
if p.issue_date: parts.append(f" Datum: {p.issue_date}")
if p.archive_status: parts.append(f" Archiefstatus: {p.archive_status}")
if p.applicable_law: parts.append(f" Wet: {p.applicable_law}")
if p.work_type: parts.append(f" Type werk: {p.work_type}")
if p.extracted_text:
parts.append(f" Tekst (fragment): {p.extracted_text[:400]}")
context_parts.append("\n".join(parts))
context = "\n\n".join(context_parts)
else:
context = "Geen vergunningen gevonden in de database."
# Step 4: LLM synthesizes answer
steps.append({"type": "reasoning", "label": "Antwoord genereren", "detail": None})
answer_result = druppie.call("llm", "chat", {
"prompt": (
f"Beantwoord de volgende vraag op basis van de vergunningendata.\n\n"
f"Vraag: {full_prompt}\n\n"
f"Gevonden vergunningen ({len(found)} resultaten):\n\n{context}"
),
"system": (
"Je bent een assistent voor waterschap-medewerkers. Beantwoord vragen "
"over vergunningen op basis van de aangeleverde data. Wees specifiek "
"en verwijs naar vergunningnummers. Als de data onvoldoende is, zeg dat eerlijk."
),
})
return {
"answer": answer_result.get("answer", "Geen antwoord ontvangen."),
"steps": steps,
}