Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c47881106e | |||
| 9a70f7378b |
@@ -40,6 +40,7 @@ import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/kami/maven/internal/phraser"
|
||||
"github.com/kami/maven/internal/router"
|
||||
)
|
||||
|
||||
@@ -65,7 +66,7 @@ func (h *reactiveHandler) actionChat(ctx context.Context, dec router.Decision) s
|
||||
log.Printf("voice: chat: %v", err)
|
||||
}
|
||||
if reply == "" {
|
||||
return "поговорили."
|
||||
return phraser.ChatFallback
|
||||
}
|
||||
return reply
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// A dead server must be distinguishable from bad phrasing. Both PhraseChat and
|
||||
// PhraseQuery keep the turn alive with canned text — "поговорили.", "не знаю.",
|
||||
// PhraseQuery keep the turn alive with canned text — ChatFallback, "не знаю.",
|
||||
// "вот что я нашла: …" — and every one of those is also a legitimate reply, so
|
||||
// the text alone cannot say which happened. The error is the only signal, and
|
||||
// before Vikunja #397 it was dropped: the talk scorer reported a full run with
|
||||
@@ -28,7 +28,7 @@ func TestPhrasingReportsTheFailureWithTheFallback(t *testing.T) {
|
||||
}{
|
||||
{"chat", func() (string, error) {
|
||||
return p.PhraseChat(context.Background(), "как дела", nil)
|
||||
}, "поговорили."},
|
||||
}, ChatFallback},
|
||||
{"knowledge", func() (string, error) {
|
||||
return p.PhraseQuery(context.Background(), "кто написал войну и мир", nil)
|
||||
}, "не знаю."},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -26,6 +27,11 @@ import (
|
||||
|
||||
var listenRE = regexp.MustCompile(`listening on (https?://\S+)`)
|
||||
|
||||
// errEmptyResponse — the server answered and said nothing. Separate from a
|
||||
// transport failure: the model is up and produced no tokens, which is still not
|
||||
// an answer and must not score as one.
|
||||
var errEmptyResponse = errors.New("phraser: empty response from the model")
|
||||
|
||||
type LLMPhraser struct {
|
||||
cfg Config
|
||||
client *http.Client
|
||||
@@ -428,10 +434,10 @@ func (p *LLMPhraser) PhraseNudge(ctx context.Context, c loop.Candidate) (deliver
|
||||
}
|
||||
|
||||
// PhraseQuery prompts the LLM with the user's utterance and matching notes to
|
||||
// compose a natural answer. On any LLM error it returns BOTH the fallback text
|
||||
// ("вот что я нашла: <notes>", or "не знаю." with no notes) AND the error, so a
|
||||
// caller that wants to keep the turn alive uses the text and a caller that is
|
||||
// measuring counts the failure. Until Vikunja #397 the error was swallowed and a
|
||||
// compose a natural answer. On any LLM error it returns the fallback text —
|
||||
// "вот что я нашла: <notes>", or "не знаю." with no notes — and the error
|
||||
// together. The daemon uses the text and keeps the turn alive; a caller that is
|
||||
// measuring counts the failure. Until Vikunja #397 the error was dropped, so a
|
||||
// dead server scored as bad phrasing.
|
||||
func (p *LLMPhraser) PhraseQuery(ctx context.Context, utterance string, notes []string) (string, error) {
|
||||
// Blank sources are no sources. A caller that hands over one empty string —
|
||||
@@ -477,7 +483,7 @@ func (p *LLMPhraser) PhraseQuery(ctx context.Context, utterance string, notes []
|
||||
|
||||
// PhraseChat uses the LLM to respond conversationally, building a multi-turn
|
||||
// message array from dialogue history + the current user utterance. On any LLM
|
||||
// error it returns both "поговорили." and the error, on the same rule as
|
||||
// error it returns both ChatFallback and the error, on the same rule as
|
||||
// PhraseQuery: the fallback keeps the turn alive, the error stays visible.
|
||||
func (p *LLMPhraser) PhraseChat(ctx context.Context, utterance string, history []dialogue.Turn) (string, error) {
|
||||
sys := chatSystemPrompt(p.cfg.ContextBlock)
|
||||
@@ -495,11 +501,11 @@ func (p *LLMPhraser) PhraseChat(ctx context.Context, utterance string, history [
|
||||
|
||||
resp, err := p.chatWithMessages(ctx, msgs, 768)
|
||||
if err != nil {
|
||||
return "поговорили.", fmt.Errorf("phrase chat: %w", err)
|
||||
return ChatFallback, fmt.Errorf("phrase chat: %w", err)
|
||||
}
|
||||
text, _, perr := parseResponseMood(resp)
|
||||
if perr != nil {
|
||||
return "поговорили.", fmt.Errorf("phrase chat: %w", perr)
|
||||
return ChatFallback, fmt.Errorf("phrase chat: %w", perr)
|
||||
}
|
||||
if text != "" {
|
||||
return text, nil
|
||||
|
||||
@@ -66,11 +66,17 @@ type Stub struct{}
|
||||
// NewStub builds the floor phraser. no config — the Stub is stateless.
|
||||
func NewStub() *Stub { return &Stub{} }
|
||||
|
||||
// ChatFallback — what she says on the chat path when the model gave her
|
||||
// nothing to say. It replaced "поговорили.", which reads as a summary of a
|
||||
// conversation that did not happen. Said out loud this one is an admission,
|
||||
// which is what it is.
|
||||
const ChatFallback = "даже не знаю, что сказать."
|
||||
|
||||
// PhraseChat returns a stub reply — the LLMPhraser replaces this with a
|
||||
// prompted response from the model. The history parameter is accepted but
|
||||
// ignored at the stub level (the production impl uses it for multi-turn).
|
||||
func (s *Stub) PhraseChat(_ context.Context, _ string, _ []dialogue.Turn) (string, error) {
|
||||
return "поговорили.", nil
|
||||
return ChatFallback, nil
|
||||
}
|
||||
|
||||
// PhraseQuery returns a deterministic summary of the best matching notes.
|
||||
|
||||
@@ -37,11 +37,6 @@ type Remote interface {
|
||||
// (docs/evals/2026-08-02-workstation-gemma4-12b.md).
|
||||
var ErrNoWorldModel = errors.New("phraser: no world model available")
|
||||
|
||||
// errEmptyResponse — the server answered and said nothing. A separate error from
|
||||
// a transport failure because it means the model is up and produced no tokens,
|
||||
// which is still not an answer and must not score as one.
|
||||
var errEmptyResponse = errors.New("phraser: empty response from the model")
|
||||
|
||||
// chatTemperature — what the phraser's own transport has always sampled at.
|
||||
// Named so the remote path cannot drift from it silently. Whether 0.7 is right
|
||||
// at all is Vikunja #402, and answering that here would hide a phrasing change
|
||||
|
||||
Reference in New Issue
Block a user