mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
v0.2.11 : fix stall reasoning (prompt agent court) + outil edit + filet de sécurité nudge/drop_reasoning
This commit is contained in:
1 parent
4c8aafa898
commit
7a145258d3
5 files changed
+56
-34
No files matched your search
@@ -73,6 +73,13 @@ func cmdChat(args []string) error {
|
||||
fmt.Printf("\n%s\n", red("[erreur] "+ev.Err.Error()))
|
||||
case ev.Stats != nil:
|
||||
stats = ev.Stats
|
||||
case ev.DropReasoning:
|
||||
// Le tour a « pensé sans agir » : on relance. Impossible d'effacer le
|
||||
// texte déjà imprimé en terminal — on referme juste la ligne reasoning.
|
||||
if inReason {
|
||||
fmt.Print("\n")
|
||||
inReason = false
|
||||
}
|
||||
case ev.ToolUsed != nil:
|
||||
if ev.ToolUsed.Done || ev.ToolUsed.Typing {
|
||||
break // résultat / frappe live affichés côté web ; en terminal on garde l'annonce seule
|
||||
|
||||
@@ -209,13 +209,18 @@ func EnabledTools(caps Caps) []Tool {
|
||||
}
|
||||
|
||||
// StreamEvent is what a ChatCallback receives for each piece of streamed output.
|
||||
// Exactly one of {Content, Reasoning, ToolUsed, Stats, Err} is set per call.
|
||||
// Exactly one of {Content, Reasoning, ToolUsed, Stats, Err, DropReasoning} is set
|
||||
// per call.
|
||||
type StreamEvent struct {
|
||||
Content string
|
||||
Reasoning string
|
||||
ToolUsed *ToolUsedEvent
|
||||
Stats *StatsEvent
|
||||
Err error
|
||||
// DropReasoning demande à l'UI de retirer la dernière bulle de raisonnement :
|
||||
// le modèle a « pensé sans agir » et on relance le tour, ce raisonnement-là
|
||||
// est mort-né et ne doit pas rester à l'écran (sinon double raisonnement).
|
||||
DropReasoning bool
|
||||
}
|
||||
type ToolUsedEvent struct {
|
||||
Name string
|
||||
@@ -346,6 +351,11 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
// several times, it's stuck — break instead of spinning to the iteration cap.
|
||||
lastSig := ""
|
||||
repeatSig := 0
|
||||
// Garde-fou « pensé sans agir » : certains modèles à reasoning planifient un
|
||||
// appel d'outil dans leur <think> puis émettent le token de fin SANS l'émettre
|
||||
// (ni réponse, ni tool_call). On relance alors UNE fois le tour avec un nudge
|
||||
// explicite au lieu d'afficher « pas de réponse ».
|
||||
nudged := false
|
||||
for iter := 0; iter < 8; iter++ {
|
||||
payload := map[string]any{
|
||||
"model": "jean",
|
||||
@@ -695,6 +705,21 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
// (empty content, e.g. it stopped right after a tool result), say so
|
||||
// instead of leaving the user staring at a silent, finished chat.
|
||||
if strings.TrimSpace(assistantContent.String()) == "" {
|
||||
// Filet de sécurité (le vrai fix est le prompt court, voir baseSystemPrompt) :
|
||||
// si un modèle « pense sans agir » malgré tout, on le relance UNE fois avec
|
||||
// une consigne impérative au lieu d'afficher « pas de réponse ».
|
||||
if len(tools) > 0 && !disableTools && !nudged {
|
||||
nudged = true
|
||||
// Le raisonnement de ce tour avorté ne mène à rien : on demande à
|
||||
// l'UI de l'effacer avant de relancer, pour ne pas afficher deux
|
||||
// blocs de réflexion successifs.
|
||||
cb(StreamEvent{DropReasoning: true})
|
||||
messages = append(messages, Message{
|
||||
Role: "user",
|
||||
Content: "Tu as réfléchi mais tu n'as ni appelé d'outil ni répondu. Agis MAINTENANT : appelle directement l'outil approprié (par ex. mem_search/mem_read/bash), ou donne ta réponse finale si tu as déjà l'information. N'explique pas, agis.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
cb(StreamEvent{Content: "_(le modèle n'a pas produit de réponse — finish: " + finishReason + ")_"})
|
||||
}
|
||||
return extra, nil
|
||||
|
||||
@@ -30,44 +30,23 @@ func baseSystemPrompt(caps Caps) string {
|
||||
if !caps.Agent {
|
||||
return ""
|
||||
}
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("You are an expert coding assistant operating inside jean, a coding agent harness. You help users by reading files, executing commands, editing code, writing new files, and leveraging persistent memory to remember things across sessions.\n\n")
|
||||
|
||||
b.WriteString("Available tools:\n")
|
||||
// Prompt VOLONTAIREMENT court. Un préambule verbeux (longue liste de
|
||||
// « guidelines », surtout des méta-instructions sur la réflexion) fait
|
||||
// sur-raisonner les modèles à reasoning (Qwen3) : ils émettent leur <think>
|
||||
// puis le token de fin SANS appeler d'outil (~25-45 % de tours « morts »
|
||||
// mesurés). Une version courte et directe ramène ça à 0 %. NE PAS regonfler.
|
||||
b.WriteString("You are jean, an expert assistant operating directly on this machine with real tools.\n\n")
|
||||
b.WriteString("Tools:\n")
|
||||
for _, l := range []string{
|
||||
"bash: Execute a shell command on this machine (ls, grep, find, cat, sed, read logs, run scripts…) and return stdout, stderr and the exit code. This is your real access to the system.",
|
||||
"edit: Modify a file on disk by exact replacement (old → new; old must match exactly once). Use this to patch a file instead of rewriting it whole.",
|
||||
"mem_search: Search your memory (Markdown pages under MEMORY/). Returns a ranked list of {file, title, snippet}.",
|
||||
"mem_read: Read a memory page (Markdown file), line-numbered, with optional offset/limit.",
|
||||
"mem_add: Create a new memory page (one topic per page, descriptive kebab-case name, first line is a short # title).",
|
||||
"mem_edit: Edit a memory page by exact replacement (old → new; old must match exactly once).",
|
||||
"bash — run a shell command on this machine (inspect files, processes, logs, run scripts).",
|
||||
"edit — patch a file by exact replacement (old → new, old must be unique).",
|
||||
"mem_search / mem_read / mem_add / mem_edit — your persistent Markdown memory under MEMORY/.",
|
||||
} {
|
||||
b.WriteString("- " + l + "\n")
|
||||
}
|
||||
|
||||
b.WriteString("\nGuidelines:\n")
|
||||
for _, l := range []string{
|
||||
"Reason briefly (1-2 sentences) then ACT. As soon as an action is possible, call the tool immediately — don't announce it, do it.",
|
||||
"Never repeat the same reasoning step or the same tool call twice. If you hesitate (\"maybe I should check…\"), stop thinking and call the tool now.",
|
||||
"Use bash for any system question (files, processes, network, OS) instead of assuming — you have real access, don't ask permission to inspect.",
|
||||
"Use bash to read files (cat, sed -n); to change a file, use the edit tool (old → new) instead of rewriting it whole with bash.",
|
||||
"Use mem_search FIRST whenever the user asks about something you might already know (preferences, ongoing projects, past decisions). Open the most relevant page with mem_read; snippets are hints, not the answer.",
|
||||
"Save anything worth keeping across sessions with mem_add; update an existing page with mem_edit. Don't save ephemeral conversation state.",
|
||||
"Prefer targeted, non-interactive commands; chain them with && when they depend on each other.",
|
||||
"Avoid destructive commands (rm -rf, mkfs, dd…) unless the user explicitly asks.",
|
||||
"If a command fails, read stderr and the exit code, then fix it — don't re-run the same command unchanged.",
|
||||
"Never invent a tool: you only have bash, edit and the mem_* tools. Anything else (reading code, web access) goes through bash.",
|
||||
"When the task is done, give a short final answer and stop. Don't chain useless tool calls.",
|
||||
"Be concise in your responses.",
|
||||
"Show file paths clearly when working with files.",
|
||||
} {
|
||||
b.WriteString("- " + l + "\n")
|
||||
}
|
||||
|
||||
b.WriteString("\nCurrent date: " + time.Now().Format("2006-01-02"))
|
||||
b.WriteString("\nCurrent working directory: " + cwd)
|
||||
b.WriteString("\nWhen the user asks about something you might already know (preferences, projects, past decisions), call mem_search first, then mem_read the most relevant page. For anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, or answer if you already have the info. Never end your turn after only thinking. Be concise.\n")
|
||||
b.WriteString("\nDate: " + time.Now().Format("2006-01-02"))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -954,6 +954,14 @@ async function send(){
|
||||
if(tu.name==='mem_add' || tu.name==='mem_edit') loadMem();
|
||||
}
|
||||
}
|
||||
if(d.drop_reasoning){
|
||||
// Le modèle a « pensé sans agir » : on relance le tour. Le
|
||||
// raisonnement mort-né est retiré pour ne pas afficher un double bloc.
|
||||
if(reasonEl){
|
||||
const i=turnCollapsibles.indexOf(reasonEl); if(i>=0) turnCollapsibles.splice(i,1);
|
||||
reasonEl.remove(); reasonEl=null; fullReason='';
|
||||
}
|
||||
}
|
||||
if(d.reasoning_content){
|
||||
// Start a fresh buffer per bubble — otherwise a reasoning block that
|
||||
// follows a tool call re-renders ALL previously accumulated reasoning
|
||||
|
||||
@@ -680,6 +680,9 @@ func runChatStream(ctx context.Context, body chatReq, emit func(map[string]any)
|
||||
if ev.Stats != nil {
|
||||
return emit(map[string]any{"stats": ev.Stats})
|
||||
}
|
||||
if ev.DropReasoning {
|
||||
return emit(map[string]any{"drop_reasoning": true})
|
||||
}
|
||||
if ev.Reasoning != "" {
|
||||
return emit(map[string]any{"reasoning_content": ev.Reasoning})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user