865623ef3e
The accessors are functions now, so the call sites that compared against one literal compare against the entry instead: IsUnknownFallback and IsSourcesFallback in the daemon tests, the entry key in the phraser tests. A reworded variant no longer breaks a Go test. The eval scores every variant on the persona checks the nudges already pass.
69 lines
2.9 KiB
Go
69 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
|
|
"github.com/kami/maven/internal/phraser"
|
|
)
|
|
|
|
// worldPhraser — the naming half of the degradation rule (docs/offload.md), as
|
|
// the query sources see it. Only *phraser.LLMPhraser implements it, so the
|
|
// Stub and every test double stay exactly as they are.
|
|
type worldPhraser interface {
|
|
PhraseWorld(ctx context.Context, utterance string, sources []string) (string, error)
|
|
}
|
|
|
|
// worldGap — what he hears when the question is about the world, the workstation
|
|
// model is the one configured to answer it, and that machine is not answering.
|
|
//
|
|
// It says the true thing. The resident 1.7B is not a worse answer here, it is an
|
|
// invented one: "Война и мир" came back with Левитан as its author, and a
|
|
// question about his meeting came back as a swimming competition in Nottingham.
|
|
// Naming the gap is the rule CLAUDE.md already applies to a sibling service
|
|
// being down.
|
|
//
|
|
// The wording lives in fallbacks_ru_v1.json and is fixed there, not picked from
|
|
// variants: this sentence names one specific gap and must not drift into a
|
|
// general "I don't know".
|
|
func worldGap() string { return phraser.WorldGap() }
|
|
|
|
// phraseWorld asks the world model, or reports the gap.
|
|
//
|
|
// The three outcomes come straight from LLMPhraser.PhraseWorld: no workstation
|
|
// configured means the resident model answers as it always has, a workstation
|
|
// that is up answers, and a workstation that is down returns
|
|
// phraser.ErrNoWorldModel. A phraser that has no world seam at all — the Stub,
|
|
// and the doubles in the tests — is the first of those three.
|
|
func (h *reactiveHandler) phraseWorld(ctx context.Context, utterance string, sources []string) (string, error) {
|
|
if h.phraser == nil {
|
|
return "", phraser.ErrNoWorldModel
|
|
}
|
|
if w, ok := h.phraser.(worldPhraser); ok {
|
|
return w.PhraseWorld(ctx, utterance, sources)
|
|
}
|
|
return h.phraser.PhraseQuery(ctx, utterance, sources)
|
|
}
|
|
|
|
// phraseSource asks the world model to answer from a passage someone already
|
|
// fetched — a live search result, a ZIM article, a page he named. It returns ""
|
|
// rather than the gap phrase, because these callers hold something better than a
|
|
// gap: the passage itself, which their own floor reads back to him. Nothing is
|
|
// invented either way, and a real quote beats "не могу сейчас".
|
|
func (h *reactiveHandler) phraseSource(ctx context.Context, name, utterance string, sources []string) string {
|
|
reply, err := h.phraseWorld(ctx, utterance, sources)
|
|
switch {
|
|
case errors.Is(err, phraser.ErrNoWorldModel):
|
|
log.Printf("voice: %s: no world model, reading the source back instead", name)
|
|
return ""
|
|
case err != nil:
|
|
// The resident phraser answers this call with its fallback text and the
|
|
// error together. Drop the text: these callers hold the passage itself
|
|
// and read it back better than "вот что я нашла: <passage>" does.
|
|
log.Printf("voice: %s: phrase: %v", name, err)
|
|
return ""
|
|
}
|
|
return reply
|
|
}
|