mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 13:38:11 +02:00
Phase 7 : outils web_search et run_shell avec validation
Backend : - tools.py : web_search (DuckDuckGo HTML sans clé, ou SearxNG via SEARX_URL) et run_shell (confiné au workspace, sortie bornée, timeout) - agent.py : run_shell sensible -> émet tool_confirm et ne s'exécute pas tant que l'utilisateur n'a pas validé (confirm_shell) - agent_config.py : web_search/run_shell désactivés par défaut, flag confirm_shell - routes/shell.py : POST /api/shell/run exécute une commande validée - routes/chat.py : relaie l'événement tool_confirm, passe confirm_shell Frontend : - streamChat : événement tool_confirm - store : pendingShell + approveShell/rejectShell (réinjecte le résultat à l'agent) - ChatPanel : carte de validation de commande (Approuver / Refuser) - SettingsView : toggles web_search/run_shell, marqueur sensible, switch confirm_shell - ToolCard : glyphes web_search/run_shell, statut 'à valider', aperçu d'argument Tests : run_shell confiné, gate de confirmation (commande dangereuse non exécutée), route shell, parser DuckDuckGo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
1 parent
2db6beea5c
commit
de2befe31e
12 files changed
+398
-26
No files matched your search
+47
-1
@@ -41,6 +41,7 @@ async def run_agent(
|
||||
*,
|
||||
options: dict | None = None,
|
||||
enabled_tools: list[str] | None = None,
|
||||
confirm_shell: bool = True,
|
||||
) -> AsyncIterator[dict]:
|
||||
# enabled_tools=None -> tous les outils ; liste vide -> aucun outil.
|
||||
if enabled_tools is None:
|
||||
@@ -83,6 +84,7 @@ async def run_agent(
|
||||
break
|
||||
|
||||
# Exécution des outils demandés, puis réinjection des résultats.
|
||||
awaiting_confirmation = False
|
||||
for tc in tool_calls:
|
||||
fn = tc.get("function", {})
|
||||
name = fn.get("name", "")
|
||||
@@ -90,10 +92,39 @@ async def run_agent(
|
||||
|
||||
yield {"type": "tool_call", "name": name, "args": args}
|
||||
|
||||
# run_shell est sensible : on demande validation au lieu d'exécuter.
|
||||
if name == "run_shell" and confirm_shell:
|
||||
command = args.get("command", "")
|
||||
record = {
|
||||
"name": name,
|
||||
"args": args,
|
||||
"summary": "validation requise",
|
||||
"status": "pending",
|
||||
}
|
||||
collected.append(record)
|
||||
yield {"type": "tool_confirm", "name": name, "command": command}
|
||||
convo.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"name": name,
|
||||
"content": json.dumps(
|
||||
{
|
||||
"ok": False,
|
||||
"status": "pending",
|
||||
"message": "Commande en attente de validation "
|
||||
"de l'utilisateur. N'exécute rien d'autre.",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
}
|
||||
)
|
||||
awaiting_confirmation = True
|
||||
continue
|
||||
|
||||
try:
|
||||
result = run_tool(name, args)
|
||||
summary = result.get("summary", "terminé")
|
||||
status = "ok"
|
||||
status = result.get("_status", "ok")
|
||||
except ToolError as exc:
|
||||
result = {"ok": False, "error": str(exc)}
|
||||
summary = str(exc)
|
||||
@@ -110,6 +141,21 @@ async def run_agent(
|
||||
"content": json.dumps(result, ensure_ascii=False),
|
||||
}
|
||||
)
|
||||
|
||||
# Une commande shell attend une validation : on interrompt la boucle.
|
||||
if awaiting_confirmation:
|
||||
# Laisse le modèle conclure son tour (message d'attente).
|
||||
final_chunk = ""
|
||||
async for chunk in ollama.chat(model, convo, options=options, stream=True):
|
||||
tok = chunk.get("message", {}).get("content", "")
|
||||
if tok:
|
||||
final_chunk += tok
|
||||
yield {"type": "token", "content": tok}
|
||||
if chunk.get("done"):
|
||||
break
|
||||
if final_chunk.strip():
|
||||
text_parts.append(final_chunk.strip())
|
||||
break
|
||||
except (httpx.HTTPError, OSError) as exc:
|
||||
yield {"type": "error", "message": str(exc)}
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user