mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 22:18:13 +02:00
Plan : validation des étapes en direct (point par point)
Le plan s'affichait mais restait figé — on ne voyait pas le modèle avancer. Désormais : - Le modèle annonce chaque étape accomplie (« ✅ Étape N terminée »), demandé dans la consigne de plan. - L'agent détecte ces annonces dans le flux et émet des événements plan_step, sans jamais recompter deux fois la même étape. - Le panneau PLAN coche les étapes en temps réel : ✓ vert + barré pour les faites, sablier sur l'étape en cours, compteur « n/N validées ». Chemin agent uniquement (le moteur code tourne de bout en bout). Les messages passés restent affichés tels quels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
5 files changed
+112
-15
No files matched your search
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
from typing import AsyncIterator
|
||||
|
||||
import httpx
|
||||
@@ -99,6 +100,27 @@ def _parse_args(raw) -> dict:
|
||||
return {}
|
||||
|
||||
|
||||
# Marqueur d'avancement du plan émis par le modèle (« ✅ Étape 2 terminée »).
|
||||
# Tolérant : coche/croix optionnelle, mot « étape » optionnel, numéro requis.
|
||||
_STEP_DONE = re.compile(
|
||||
r"(?:✅|✔|☑|\[x\])\s*(?:étape|etape|step)?\s*(\d{1,2})"
|
||||
r"|(?:étape|etape|step)\s*(\d{1,2})\s*(?:terminée|terminee|faite|ok|✅|✔)",
|
||||
re.I,
|
||||
)
|
||||
|
||||
|
||||
def _scan_plan_done(text: str, plan_len: int, already: set[int]) -> list[int]:
|
||||
"""Indices (0-based) de nouvelles étapes annoncées terminées dans ``text``."""
|
||||
fresh: list[int] = []
|
||||
for m in _STEP_DONE.finditer(text):
|
||||
num = m.group(1) or m.group(2)
|
||||
idx = int(num) - 1
|
||||
if 0 <= idx < plan_len and idx not in already:
|
||||
already.add(idx)
|
||||
fresh.append(idx)
|
||||
return fresh
|
||||
|
||||
|
||||
async def run_agent(
|
||||
model: str,
|
||||
convo: list[dict],
|
||||
@@ -109,6 +131,7 @@ async def run_agent(
|
||||
think: bool = True,
|
||||
keep_alive: str | None = None,
|
||||
mcp_tools: list[dict] | None = None,
|
||||
plan: list[str] | None = None,
|
||||
) -> AsyncIterator[dict]:
|
||||
# enabled_tools=None -> tous les outils ; liste vide -> aucun outil.
|
||||
if enabled_tools is None:
|
||||
@@ -142,6 +165,10 @@ async def run_agent(
|
||||
# Compteur d'itérations où le modèle n'a produit QUE du raisonnement.
|
||||
thinking_only_strikes = 0
|
||||
|
||||
# Suivi de l'avancement du plan : étapes déjà annoncées terminées.
|
||||
plan_len = len(plan or [])
|
||||
plan_done: set[int] = set()
|
||||
|
||||
# Métriques cumulées sur tous les appels Ollama du tour agentique : Ollama
|
||||
# les renvoie dans le chunk final (done=true) de chaque génération.
|
||||
stats = {"eval_count": 0, "eval_duration": 0, "prompt_eval_count": 0}
|
||||
@@ -202,6 +229,14 @@ async def run_agent(
|
||||
if token:
|
||||
content_buf += token
|
||||
yield {"type": "token", "content": token}
|
||||
# Coche les étapes du plan annoncées terminées, en
|
||||
# direct. Scan borné : uniquement quand le token
|
||||
# porte un marqueur plausible.
|
||||
if plan_len and any(
|
||||
c in token for c in ("✅", "✔", "☑", "tape", "step", "]")
|
||||
):
|
||||
for idx in _scan_plan_done(content_buf, plan_len, plan_done):
|
||||
yield {"type": "plan_step", "index": idx, "status": "done"}
|
||||
if msg.get("tool_calls"):
|
||||
tool_calls.extend(msg["tool_calls"])
|
||||
if chunk.get("done"):
|
||||
|
||||
@@ -455,8 +455,14 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
if plan:
|
||||
convo.append({
|
||||
"role": "system",
|
||||
"content": "Plan à suivre pour cette demande :\n"
|
||||
+ "\n".join(f"{i+1}. {s}" for i, s in enumerate(plan)),
|
||||
"content": (
|
||||
"Plan à suivre pour cette demande, étape par étape :\n"
|
||||
+ "\n".join(f"{i+1}. {s}" for i, s in enumerate(plan))
|
||||
+ "\n\nTraite les étapes DANS L'ORDRE. Dès qu'une étape est "
|
||||
"réellement accomplie, écris sur une ligne seule "
|
||||
"« ✅ Étape N terminée » (N = son numéro) avant de passer à "
|
||||
"la suivante. N'annonce jamais une étape terminée à l'avance."
|
||||
),
|
||||
})
|
||||
|
||||
final_content = ""
|
||||
@@ -478,6 +484,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
think=cfg.get("think", True),
|
||||
keep_alive=cfg.get("keep_alive", "30m"),
|
||||
mcp_tools=mcp_tools,
|
||||
plan=plan,
|
||||
):
|
||||
await queue.put(event)
|
||||
except Exception as exc:
|
||||
@@ -514,6 +521,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
"tool_call",
|
||||
"tool_result",
|
||||
"tool_confirm",
|
||||
"plan_step",
|
||||
):
|
||||
yield _sse(etype, ev)
|
||||
elif etype == "error":
|
||||
|
||||
Reference in new issue
Block a user