Automated commit
This commit is contained in:
parent
1fe380d1bf
commit
0ed02f0f2f
154
app/agent.py
154
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
|
Receives questions, generates search queries, searches the permit
|
||||||
implementation provides a simple Q&A agent. Replace or extend the
|
database, and synthesizes answers. Each step is logged for transparency.
|
||||||
`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}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
from druppie_sdk import DruppieClient
|
from druppie_sdk import DruppieClient
|
||||||
|
from app.models import Permit
|
||||||
|
|
||||||
druppie = DruppieClient()
|
druppie = DruppieClient()
|
||||||
|
|
||||||
|
|
||||||
def run_agent(question: str, history: list[dict], db) -> dict:
|
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 = []
|
steps = []
|
||||||
|
|
||||||
# Build conversation context
|
# Build conversation context
|
||||||
context_messages = ""
|
context_str = ""
|
||||||
if history:
|
if history:
|
||||||
recent = history[-6:] # last 3 exchanges
|
recent = history[-4:]
|
||||||
context_messages = "\n".join(
|
context_str = "\n".join(
|
||||||
f"{'Gebruiker' if m['role'] == 'user' else 'Assistent'}: {m['content']}"
|
f"{'Gebruiker' if m['role'] == 'user' else 'Assistent'}: {m['content'][:200]}"
|
||||||
for m in recent
|
for m in recent
|
||||||
)
|
)
|
||||||
|
|
||||||
# Call LLM
|
# Step 1: Generate search queries
|
||||||
prompt = question
|
full_prompt = question
|
||||||
if context_messages:
|
if context_str:
|
||||||
prompt = f"Eerdere berichten:\n{context_messages}\n\nNieuwe vraag: {question}"
|
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", {
|
steps.append({
|
||||||
"prompt": prompt,
|
"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": (
|
"system": (
|
||||||
"Je bent een behulpzame assistent. Beantwoord vragen in het Nederlands. "
|
"Je bent een assistent voor waterschap-medewerkers. Beantwoord vragen "
|
||||||
"Wees beknopt maar volledig."
|
"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 {
|
||||||
return {"answer": answer, "steps": steps}
|
"answer": answer_result.get("answer", "Geen antwoord ontvangen."),
|
||||||
|
"steps": steps,
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue