mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
Chat: rendu Markdown, blocs de code copiables, metriques de tokens, apercu HTML
Frontend - MessageContent: rendu Markdown complet (react-markdown + remark-gfm) avec blocs de code dans un cadre + bouton Copier, et styles thematises (titres, listes, gras, liens, tableaux, citations). - ChatPanel: affiche sous chaque reponse de l'agent la vitesse (tokens/s) et le nombre de jetons generes / en entree. - PreviewPanel: iframe en sandbox="allow-scripts" pour que le JS des pages generees s'execute (animations, canvas) tout en gardant une origine opaque. - Icon: ajout CopyIcon / CheckIcon. Backend - agent: cumule les metriques Ollama (eval_count, eval_duration, prompt_eval_count) et calcule les tokens/seconde, emis dans l'evenement final. - chat: persiste les stats dans meta et les renvoie au client. - agent_config: l'invite systeme par defaut demande un formatage Markdown. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
aed5f8148c
commit
e8e6a1446c
10 files changed
+1683
-13
No files matched your search
+25
-1
@@ -75,6 +75,15 @@ async def run_agent(
|
||||
tool_repair_attempts = 0
|
||||
request_options = dict(options or {})
|
||||
|
||||
# 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}
|
||||
|
||||
def _accumulate(chunk: dict) -> None:
|
||||
stats["eval_count"] += chunk.get("eval_count") or 0
|
||||
stats["eval_duration"] += chunk.get("eval_duration") or 0
|
||||
stats["prompt_eval_count"] += chunk.get("prompt_eval_count") or 0
|
||||
|
||||
try:
|
||||
for _ in range(MAX_ITERATIONS):
|
||||
content_buf = ""
|
||||
@@ -107,6 +116,7 @@ async def run_agent(
|
||||
if msg.get("tool_calls"):
|
||||
tool_calls.extend(msg["tool_calls"])
|
||||
if chunk.get("done"):
|
||||
_accumulate(chunk)
|
||||
break
|
||||
break
|
||||
except OllamaError as exc:
|
||||
@@ -249,6 +259,7 @@ async def run_agent(
|
||||
final_chunk += tok
|
||||
yield {"type": "token", "content": tok}
|
||||
if chunk.get("done"):
|
||||
_accumulate(chunk)
|
||||
break
|
||||
if final_chunk.strip():
|
||||
text_parts.append(final_chunk.strip())
|
||||
@@ -276,4 +287,17 @@ async def run_agent(
|
||||
}
|
||||
return
|
||||
|
||||
yield {"type": "final", "content": final_content, "tools": collected}
|
||||
eval_secs = stats["eval_duration"] / 1e9
|
||||
final_stats = {
|
||||
"eval_count": stats["eval_count"],
|
||||
"prompt_eval_count": stats["prompt_eval_count"],
|
||||
"tokens_per_sec": (
|
||||
round(stats["eval_count"] / eval_secs, 1) if eval_secs > 0 else None
|
||||
),
|
||||
}
|
||||
yield {
|
||||
"type": "final",
|
||||
"content": final_content,
|
||||
"tools": collected,
|
||||
"stats": final_stats,
|
||||
}
|
||||
@@ -18,7 +18,10 @@ DEFAULT_SYSTEM_PROMPT = (
|
||||
"Utilise-les pour accomplir les tâches concrètement, puis réponds de façon "
|
||||
"concise en français. Pour un fichier long, appelle write_file en plusieurs "
|
||||
"morceaux (overwrite puis append) afin de toujours produire un JSON valide. "
|
||||
"Après avoir écrit un fichier, propose un aperçu."
|
||||
"Après avoir écrit un fichier, propose un aperçu. "
|
||||
"Formate TOUJOURS tes réponses en Markdown : titres, listes, gras pour les "
|
||||
"points clés, tableaux si pertinent, et surtout des blocs de code avec le "
|
||||
"langage indiqué (```python, ```html…) pour tout extrait de code ou commande."
|
||||
)
|
||||
|
||||
# Outils disponibles. Les sensibles (web_search, run_shell) sont désactivés
|
||||
|
||||
@@ -58,6 +58,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
yield _sse("start", {"model": model})
|
||||
final_content = ""
|
||||
tools_meta: list[dict] = []
|
||||
stats_meta: dict | None = None
|
||||
error_message = ""
|
||||
|
||||
queue: asyncio.Queue[dict | None] = asyncio.Queue()
|
||||
@@ -113,6 +114,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
elif etype == "final":
|
||||
final_content = ev["content"]
|
||||
tools_meta = ev["tools"]
|
||||
stats_meta = ev.get("stats")
|
||||
finally:
|
||||
if not producer.done():
|
||||
producer.cancel()
|
||||
@@ -120,18 +122,24 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
await producer
|
||||
|
||||
if final_content or tools_meta:
|
||||
meta: dict = {}
|
||||
if tools_meta:
|
||||
meta["tools"] = tools_meta
|
||||
if stats_meta:
|
||||
meta["stats"] = stats_meta
|
||||
db.add_message(
|
||||
req.session_id,
|
||||
"assistant",
|
||||
final_content,
|
||||
model,
|
||||
meta={"tools": tools_meta} if tools_meta else None,
|
||||
meta=meta or None,
|
||||
)
|
||||
yield _sse(
|
||||
"done",
|
||||
{
|
||||
"content": final_content,
|
||||
"tools": tools_meta,
|
||||
"stats": stats_meta,
|
||||
"error": error_message or None,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user