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 "вот что я нашла: " does. log.Printf("voice: %s: phrase: %v", name, err) return "" } return reply }