SQL router
This commit is contained in:
141
app.py
141
app.py
@@ -17,9 +17,11 @@ from memory import (
|
||||
add_task,
|
||||
close_task,
|
||||
deactivate_client,
|
||||
execute_read_query,
|
||||
get_memory_snapshot,
|
||||
init_db,
|
||||
list_clients,
|
||||
list_open_tasks,
|
||||
search_memory,
|
||||
upsert_client,
|
||||
upsert_project,
|
||||
@@ -72,9 +74,7 @@ Actions disponibles :
|
||||
{"action": "terminer_tache", "id": 123}
|
||||
{"action": "ajouter_repo", "nom": "...", "projet": "...", "chemin": "...", "url": "...", "branche": "..."}
|
||||
{"action": "ajouter_note", "contenu": "...", "sujet": "...", "client": "...", "projet": "...", "type": "note"}
|
||||
{"action": "chercher_memoire", "requete": "..."}
|
||||
{"action": "lister_clients"}
|
||||
{"action": "analyser_taches", "type": "client_plus_taches"}
|
||||
{"action": "interroger_base", "sql": "SELECT ...", "question": "..."}
|
||||
|
||||
Si une phrase contient plusieurs choses a enregistrer, utilise :
|
||||
{"actions": [{...}, {...}]}
|
||||
@@ -110,16 +110,30 @@ Actions disponibles :
|
||||
- terminer_tache: {"action":"terminer_tache","id":123}
|
||||
- ajouter_repo: {"action":"ajouter_repo","nom":"...","projet":"...","chemin":"...","url":"...","branche":"..."}
|
||||
- ajouter_note: {"action":"ajouter_note","contenu":"...","sujet":"...","client":"...","projet":"...","type":"note"}
|
||||
- chercher_memoire: {"action":"chercher_memoire","requete":"..."}
|
||||
- lister_clients: {"action":"lister_clients"}
|
||||
- analyser_taches: {"action":"analyser_taches","type":"client_plus_taches"}
|
||||
- interroger_base: {"action":"interroger_base","sql":"SELECT ...","question":"..."}
|
||||
|
||||
Schema SQLite disponible pour interroger_base :
|
||||
- clients(id, name, status, notes, created_at, updated_at)
|
||||
- projects(id, client_id, name, status, summary, next_action, created_at, updated_at)
|
||||
- tasks(id, client_id, project_id, title, details, due_at, done, created_at, updated_at)
|
||||
- repositories(id, project_id, name, local_path, remote_url, main_branch, last_indexed_at, created_at, updated_at)
|
||||
- memories(id, client_id, project_id, kind, subject, content, created_at)
|
||||
|
||||
Relations :
|
||||
- projects.client_id -> clients.id
|
||||
- tasks.client_id -> clients.id
|
||||
- tasks.project_id -> projects.id
|
||||
- repositories.project_id -> projects.id
|
||||
- memories.client_id -> clients.id
|
||||
- memories.project_id -> projects.id
|
||||
|
||||
Regles :
|
||||
- Choisis une action seulement si l'intention et les arguments sont clairs.
|
||||
- Si plusieurs actions sont necessaires, utilise {"actions":[...]}.
|
||||
- Si l'utilisateur demande une modification mais que la cible est ambigue, type="clarification".
|
||||
- Si l'utilisateur pose une question generale et que le contexte suffit, type="answer" et reponds directement.
|
||||
- Si une action de lecture convient mieux qu'une reponse libre, type="action".
|
||||
- Pour toute question de lecture ou d'analyse sur la memoire professionnelle, prefere interroger_base avec un SELECT.
|
||||
- Pour interroger_base, SQL doit etre une seule requete SELECT, sans point-virgule, sans PRAGMA, sans ecriture, avec LIMIT si le resultat peut etre long.
|
||||
- Si aucune action ne correspond, type="answer".
|
||||
- confidence >= 0.80 pour executer une action sans confirmation.
|
||||
- confidence entre 0.45 et 0.79 : type="clarification".
|
||||
@@ -127,9 +141,10 @@ Regles :
|
||||
- Pour "nouveaux clients", ne suppose pas que tous les clients sont nouveaux. Demande si Laurent veut les derniers ajoutes, tous les clients, ou une notion precise de nouveau.
|
||||
- Pour supprimer/terminer une tache, utilise l'id si le contexte de conversation contient les dernieres taches affichees.
|
||||
- Pour retirer/enlever/supprimer un client de la liste, utilise supprimer_client.
|
||||
- Pour "quel client a le plus de taches", utilise analyser_taches.
|
||||
- Pour "quel client a le plus de taches", utilise interroger_base avec COUNT, GROUP BY, ORDER BY.
|
||||
- Pour "quelles sont mes taches", "mes taches a faire", "taches pour mes clients", utilise interroger_base sur tasks avec clients/projects.
|
||||
- Si l'utilisateur dit "lui", "ce client", "ce projet" ou "cette tache", utilise le contexte de conversation en cours.
|
||||
- Si l'utilisateur demande "que dois-je faire pour lui ?", "c'est quoi comme tache ?" ou "quelle est la tache ?", utilise chercher_memoire avec le dernier client/projet connu.
|
||||
- Si l'utilisateur demande "que dois-je faire pour lui ?", "c'est quoi comme tache ?" ou "quelle est la tache ?", utilise interroger_base avec le dernier client/projet connu.
|
||||
"""
|
||||
|
||||
|
||||
@@ -242,7 +257,9 @@ ACTIONS = {
|
||||
"ajouter_note",
|
||||
"chercher_memoire",
|
||||
"lister_clients",
|
||||
"lister_taches",
|
||||
"analyser_taches",
|
||||
"interroger_base",
|
||||
}
|
||||
|
||||
|
||||
@@ -819,6 +836,14 @@ def executer_action(action):
|
||||
"memoryQuery": "",
|
||||
}
|
||||
|
||||
if nom_action == "lister_taches":
|
||||
taches = list_open_tasks()
|
||||
return formater_liste_taches(taches), {
|
||||
"memoryRead": True,
|
||||
"memoryResult": {"clients": [], "projects": [], "tasks": taches, "repositories": [], "memories": []},
|
||||
"memoryQuery": "",
|
||||
}
|
||||
|
||||
if nom_action == "analyser_taches":
|
||||
if action.get("type") == "client_plus_taches":
|
||||
snapshot = get_memory_snapshot(limit=100)
|
||||
@@ -836,6 +861,17 @@ def executer_action(action):
|
||||
"memoryQuery": "",
|
||||
}
|
||||
|
||||
if nom_action == "interroger_base":
|
||||
sql = action.get("sql", "")
|
||||
question = action.get("question") or action.get("requete") or ""
|
||||
rows = execute_read_query(sql)
|
||||
reponse = resumer_resultat_sql(question, sql, rows)
|
||||
return reponse, {
|
||||
"memoryRead": True,
|
||||
"memoryResult": {"clients": [], "projects": [], "tasks": rows, "repositories": [], "memories": []},
|
||||
"memoryQuery": question,
|
||||
}
|
||||
|
||||
return "", {}
|
||||
|
||||
|
||||
@@ -889,8 +925,12 @@ def mettre_a_jour_contexte(session_id, texte, reponse, flags):
|
||||
contexte["last_client"] = resultat["clients"][0].get("name", "")
|
||||
elif flags.get("memoryQuery"):
|
||||
contexte["last_client"] = flags["memoryQuery"]
|
||||
elif resultat.get("tasks") and resultat["tasks"][0].get("client_name"):
|
||||
contexte["last_client"] = resultat["tasks"][0].get("client_name", "")
|
||||
if resultat.get("projects"):
|
||||
contexte["last_project"] = resultat["projects"][0].get("name", "")
|
||||
elif resultat.get("tasks") and resultat["tasks"][0].get("project_name"):
|
||||
contexte["last_project"] = resultat["tasks"][0].get("project_name", "")
|
||||
contexte["tasks"] = resultat.get("tasks", [])
|
||||
|
||||
|
||||
@@ -944,6 +984,64 @@ def formater_liste_clients(clients_mem):
|
||||
return "Voici tes clients :\n" + "\n".join(f"- {nom}" for nom in noms)
|
||||
|
||||
|
||||
def formater_liste_taches(taches):
|
||||
if not taches:
|
||||
return "Tu n'as aucune tache ouverte dans la memoire professionnelle."
|
||||
|
||||
groupes = {}
|
||||
for tache in taches:
|
||||
cle = tache.get("client_name") or tache.get("project_name") or "General"
|
||||
groupes.setdefault(cle, []).append(tache)
|
||||
|
||||
lignes = ["Voici tes taches ouvertes :"]
|
||||
for sujet, items in groupes.items():
|
||||
lignes.append(f"- {sujet} :")
|
||||
for tache in items:
|
||||
lignes.append(f" - #{tache['id']} {tache['title']}")
|
||||
return "\n".join(lignes)
|
||||
|
||||
|
||||
def resumer_resultat_sql(question, sql, rows):
|
||||
if not rows:
|
||||
return "Je n'ai rien trouve dans la memoire professionnelle."
|
||||
if not GROQ_API_KEY:
|
||||
return formater_lignes_sql(rows)
|
||||
|
||||
payload = json.dumps(rows[:50], ensure_ascii=False, indent=2)
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Tu transformes un resultat SQL en reponse courte en francais pour Laurent. "
|
||||
"Ne mentionne pas SQL. Ne rajoute rien qui n'est pas dans les lignes."
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"Question utilisateur: {question or 'question non fournie'}\n"
|
||||
f"Requete executee: {sql}\n"
|
||||
f"Resultats JSON:\n{payload}"
|
||||
),
|
||||
},
|
||||
]
|
||||
reponse = client.chat.completions.create(
|
||||
model=CHAT_MODEL,
|
||||
messages=messages,
|
||||
temperature=0.2,
|
||||
max_tokens=500,
|
||||
)
|
||||
return (reponse.choices[0].message.content or "").strip() or formater_lignes_sql(rows)
|
||||
|
||||
|
||||
def formater_lignes_sql(rows):
|
||||
lignes = ["Voici ce que j'ai trouve :"]
|
||||
for row in rows[:20]:
|
||||
valeurs = [f"{cle}: {valeur}" for cle, valeur in row.items()]
|
||||
lignes.append("- " + ", ".join(valeurs))
|
||||
return "\n".join(lignes)
|
||||
|
||||
|
||||
def formater_client_plus_taches(snapshot):
|
||||
clients_avec_taches = [
|
||||
client_mem for client_mem in snapshot["clients"]
|
||||
@@ -986,20 +1084,6 @@ def traiter_message(session_id, texte, avec_audio=False):
|
||||
"memoryRead": flags["memoryRead"],
|
||||
}
|
||||
|
||||
action_suivi = detecter_suivi_contexte(session_id, texte)
|
||||
if action_suivi:
|
||||
reponse, flags = executer_actions(action_suivi)
|
||||
mettre_a_jour_contexte(session_id, texte, reponse, flags)
|
||||
audio_url = generer_audio(reponse) if avec_audio and TTS_PROVIDER == "groq" else None
|
||||
return {
|
||||
"reply": reponse,
|
||||
"audioUrl": audio_url,
|
||||
"emailSent": flags["emailSent"],
|
||||
"contactSaved": flags["contactSaved"],
|
||||
"memorySaved": flags["memorySaved"],
|
||||
"memoryRead": flags["memoryRead"],
|
||||
}
|
||||
|
||||
decision = router_intention(session_id, texte)
|
||||
flags = {
|
||||
"emailSent": False,
|
||||
@@ -1039,6 +1123,17 @@ def traiter_message(session_id, texte, avec_audio=False):
|
||||
}
|
||||
|
||||
|
||||
def detecter_lecture_simple(texte):
|
||||
texte_min = texte.strip().lower()
|
||||
if re.search(r"\b(taches?|tâches?)\b", texte_min) and re.search(r"\b(quelles?|liste|affiche|montre|mes|a faire|à faire|ouvertes?)\b", texte_min):
|
||||
if not re.search(r"\b(ajoute|note|notes|supprime|enleve|enlève|retire|termine|marque)\b", texte_min):
|
||||
return {"action": "lister_taches"}
|
||||
if re.search(r"\bclients?\b", texte_min) and re.search(r"\b(qui|quels?|liste|affiche|montre|mes|tous)\b", texte_min):
|
||||
if not re.search(r"\b(ajoute|note|notes|supprime|enleve|enlève|retire|desactive|désactive|nouveaux?)\b", texte_min):
|
||||
return {"action": "lister_clients"}
|
||||
return None
|
||||
|
||||
|
||||
def detecter_suivi_contexte(session_id, texte):
|
||||
contexte = session_contexts.get(session_id, {})
|
||||
dernier_client = contexte.get("last_client")
|
||||
|
||||
Reference in New Issue
Block a user