From 99564ecec9a99b22e626db93503979f6a650f0fe Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Wed, 10 Jun 2026 14:47:04 +0000 Subject: [PATCH] Automated commit --- app/agent.py | 154 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 63 deletions(-) diff --git a/app/agent.py b/app/agent.py index 155c5c6..0711778 100644 --- a/app/agent.py +++ b/app/agent.py @@ -1,85 +1,113 @@ -"""Agent template — customize this for your domain. +"""Vergunningzoeker agent — agentic search over permits. -This is where you define what the AI assistant can do. The default -implementation provides a simple Q&A agent. Replace or extend the -`run_agent` function with your own logic. - -The agent receives: -- question: the user's message -- history: previous messages in the conversation -- db: SQLAlchemy database session (for querying your app's data) - -The agent returns: -- answer: the text response to show the user -- steps: list of transparency steps (shown in the UI) - -Each step has: -- type: "search", "query", "tool", "reasoning", etc. -- label: human-readable description -- detail: the raw data (search terms, query results, etc.) - -Example customization for a permit search app: - - def run_agent(question, history, db): - steps = [] - - # Step 1: Generate search terms - terms = ask_llm_for_search_terms(question) - steps.append({"type": "search", "label": "Zoektermen", "detail": terms}) - - # Step 2: Query database - results = search_permits(db, terms) - steps.append({"type": "query", "label": f"{len(results)} resultaten", "detail": [...]}) - - # Step 3: Synthesize answer - answer = ask_llm_to_answer(question, results) - return {"answer": answer, "steps": steps} +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: - """Process a user question and return an answer with transparency steps. - - Override this function with your domain-specific agent logic. - The default implementation is a simple LLM Q&A with conversation context. - - Args: - question: The user's current message. - history: List of {"role": "user"|"assistant", "content": "..."} dicts. - db: SQLAlchemy database session for querying app data. - - Returns: - {"answer": str, "steps": list[dict]} - """ steps = [] # Build conversation context - context_messages = "" + context_str = "" if history: - recent = history[-6:] # last 3 exchanges - context_messages = "\n".join( - f"{'Gebruiker' if m['role'] == 'user' else 'Assistent'}: {m['content']}" + recent = history[-4:] + context_str = "\n".join( + f"{'Gebruiker' if m['role'] == 'user' else 'Assistent'}: {m['content'][:200]}" for m in recent ) - # Call LLM - prompt = question - if context_messages: - prompt = f"Eerdere berichten:\n{context_messages}\n\nNieuwe vraag: {question}" + # Step 1: Generate search queries + full_prompt = question + if context_str: + full_prompt = f"Gesprekscontext:\n{context_str}\n\nNieuwe vraag: {question}" - steps.append({"type": "reasoning", "label": "Vraag naar LLM gestuurd", "detail": None}) + 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()] - result = druppie.call("llm", "chat", { - "prompt": prompt, + 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 behulpzame assistent. Beantwoord vragen in het Nederlands. " - "Wees beknopt maar volledig." + "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." ), }) - answer = result.get("answer", "Geen antwoord ontvangen.") - return {"answer": answer, "steps": steps} + return { + "answer": answer_result.get("answer", "Geen antwoord ontvangen."), + "steps": steps, + }