5187f3bd14
"я выпил воды" came back as "Проверила, что ты выпел стакан воды". The verb is not a Russian word, the glass was never mentioned, and nothing had been checked. The store was right throughout: DefaultFactParser files this as key=water value="drank", and no row anywhere held "стакан". Every Russian word in that sentence was generated. replyContext hands the model "записала факт: water \"drank\"", so the model had nothing to phrase FROM and reached for the nearest plausible sentence — the example in ReplySystemPrompt, which was literally "Записала, что ты выпил стакан воды." So the fact path stops generating, the way the note payload did in V-576. The confirmation is a fixed deck frame with his own sentence in it, in both repliers, and the prompt example is contentless now. The stub also read the parser's KEY back at him, which is machine vocabulary he never said. The clarify half of this — a fact clarified out of "запиши" answers with "запиши" and nothing else — lands with V-593, which touches the same lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.0 KiB
Go
53 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/kami/maven/internal/phraser"
|
|
"github.com/kami/maven/internal/router"
|
|
"github.com/kami/maven/internal/voice"
|
|
)
|
|
|
|
// llmReplier is the daemon-side wiring around phraser.Replier: it owns the
|
|
// deterministic floor, and nothing else. The phrasing itself, the prompt and the
|
|
// output parsing live in internal/phraser so the eval can score them (#396).
|
|
type llmReplier struct {
|
|
p *phraser.Replier
|
|
stub *voice.StubReplier
|
|
}
|
|
|
|
func newLLMReplier(c phraser.Completer, block func() string) *llmReplier {
|
|
return &llmReplier{p: phraser.NewReplier(c, block), stub: voice.NewStubReplier()}
|
|
}
|
|
|
|
// Reply never fails: a clarify, a model error and an unusable generation all
|
|
// answer from the stub, which is what keeps a turn from breaking on the model.
|
|
func (r *llmReplier) Reply(d router.Decision) string {
|
|
if d.Clarify {
|
|
// The deck, not the stub's single sentence: a clarify she cannot turn
|
|
// into a question is the line he hears most often when she misses him,
|
|
// and it used to be the same words every time (Vikunja #457). Still no
|
|
// model call — this text has to be right every time, and it is not worth
|
|
// a generation to say something this small.
|
|
return clarifyMissedLine(d)
|
|
}
|
|
if d.Intent == router.IntentFact {
|
|
// A captured fact is confirmed by echoing him, and the model is not
|
|
// asked (V-592). It has nothing to phrase FROM: replyContext hands it
|
|
// "записала факт: water \"drank\"", so every Russian word in the reply
|
|
// was the model's own invention, and on 2026-08-06 that was "Проверила,
|
|
// что ты выпел стакан воды" for "я выпил воды".
|
|
return phraser.FactAck(d.Utterance)
|
|
}
|
|
out, err := r.p.PhraseReply(context.Background(), d)
|
|
if err != nil || out == "" {
|
|
return r.stub.Reply(d)
|
|
}
|
|
// The persona checks, on the live path (personaguard.go). A reply that
|
|
// leaks reasoning or calls him "вы" is worse than a flat one.
|
|
if _, ok := guardSpoken("reply", out); !ok {
|
|
return r.stub.Reply(d)
|
|
}
|
|
return out
|
|
}
|