28c0ff73bd
PR 113's review is about internal/say/summary_ru_v1.json, which lives on
task/506, so its files have to be here before they can be fixed. Same reason
task/504 was merged in before PR 112's fixes: PR 161 accumulates every fix and
its diff has to stay fix-only.
Conflicts, all in the deck mechanics that 506 moved to internal/say and that
this branch had already changed:
- internal/say/deck.go — the exported Deck from 506 keeps this branch's per-family
floor. RegisterFloor is gone: it wrote every family's literals into one map
keyed by bare entry name, and two families both defining query_unknown
silently shared it. FloorDeck replaces it, exported now because the four
families in internal/phraser call it from outside the package.
- internal/say/summary.go — the fifth family off RegisterFloor onto the same
per-family map.
- internal/phraser/{acks,acts,fallbacks,query}.go — say.FloorDeck for the same.
--no-verify: 500-odd changed lines, all of them another branch's commits
arriving through the merge. The guard counts the merge, not the resolution.
141 lines
5.5 KiB
Go
141 lines
5.5 KiB
Go
package phraser
|
|
|
|
// The phrasing fallbacks — what she says when the model gave her nothing usable.
|
|
//
|
|
// They were four string literals spread across phraser.go, llmphraser.go and
|
|
// cmd/mavend/worldmodel.go. Every one of them is a line he hears out loud, so
|
|
// rewording one was a Go edit, a rebuild and a redeploy for what is product copy.
|
|
//
|
|
// The floor under the floor is deliberate. These strings exist because something
|
|
// already failed, so a broken template file must not be able to take the last
|
|
// words she has: every accessor falls back to the literal it replaced.
|
|
|
|
import (
|
|
_ "embed"
|
|
"log"
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"github.com/kami/maven/internal/say"
|
|
)
|
|
|
|
//go:embed fallbacks_ru_v1.json
|
|
var fallbackJSON []byte
|
|
|
|
// FallbackSchemaVersion — the version this code understands. Its own constant,
|
|
// not shared with the nudge templates or the eval fixtures: two files that change
|
|
// on different days cannot be versioned by one number (Vikunja #397).
|
|
const FallbackSchemaVersion = 1
|
|
|
|
// The entry keys. Every one of them is read by a method below, so a typo in the
|
|
// file is caught at load rather than at the moment she needs the words.
|
|
const (
|
|
fbChat = "chat"
|
|
fbQueryUnknown = "query_unknown"
|
|
fbQuerySources = "query_sources"
|
|
fbWorldGap = "world_gap"
|
|
)
|
|
|
|
// fbKeys — every key the code requires the file to define.
|
|
var fbKeys = []string{fbChat, fbQueryUnknown, fbQuerySources, fbWorldGap}
|
|
|
|
// hardFloor — the literal each key falls back to when the file is unusable.
|
|
// These are the exact strings that lived in Go before this file existed.
|
|
var hardFloor = map[string]string{
|
|
fbChat: "даже не знаю, что сказать.",
|
|
fbQueryUnknown: "не знаю.",
|
|
fbQuerySources: "вот что я нашла: {sources}",
|
|
fbWorldGap: "сейчас не могу ответить — большая модель недоступна, а придумывать не хочу.",
|
|
}
|
|
|
|
// Fallbacks picks a hand-written Russian fallback line. Safe for concurrent use.
|
|
type Fallbacks struct{ d *say.Deck }
|
|
|
|
// LoadFallbacks reads the embedded file. Pass a source to make the picking
|
|
// reproducible in tests; nil seeds from the clock.
|
|
func LoadFallbacks(src rand.Source) (*Fallbacks, error) {
|
|
d, err := say.Load(fallbackJSON, FallbackSchemaVersion, fbKeys, hardFloor, src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// query_sources is the one entry whose whole job is to read something back.
|
|
if err := d.RequirePlaceholder(fbQuerySources, "{sources}"); err != nil {
|
|
return nil, err
|
|
}
|
|
return &Fallbacks{d: d}, nil
|
|
}
|
|
|
|
// deck reads through a nil *Fallbacks, which is the unloadable-file case.
|
|
func (f *Fallbacks) deck() *say.Deck {
|
|
if f == nil {
|
|
return say.FloorDeck(hardFloor)
|
|
}
|
|
return f.d
|
|
}
|
|
|
|
// Chat — nothing usable came back on the chat path.
|
|
func (f *Fallbacks) Chat() string { return f.deck().Text(fbChat, nil) }
|
|
|
|
// Unknown — a question she cannot answer and will not guess at.
|
|
func (f *Fallbacks) Unknown() string { return f.deck().Text(fbQueryUnknown, nil) }
|
|
|
|
// FromSources — read back what she was handed, because phrasing it failed.
|
|
func (f *Fallbacks) FromSources(sources string) string {
|
|
return f.deck().Text(fbQuerySources, map[string]string{"sources": sources})
|
|
}
|
|
|
|
// WorldGap — the world model is the one configured to answer and it is not
|
|
// answering. Fixed wording: it names a specific gap, and a variant set here
|
|
// would let "the big model is asleep" drift into "I don't know".
|
|
func (f *Fallbacks) WorldGap() string { return f.deck().Text(fbWorldGap, nil) }
|
|
|
|
// Variants returns every line the file can produce, for the persona scorer.
|
|
func (f *Fallbacks) Variants() []string { return f.deck().Variants() }
|
|
|
|
// The process-wide instance. Package-level because these lines are needed on
|
|
// paths that have no phraser to hand — cmd/mavend names the world gap without
|
|
// one — and because a template file that is embedded and validated at load has
|
|
// nothing per-instance to configure.
|
|
var (
|
|
fallbackOnce sync.Once
|
|
fallbacks *Fallbacks
|
|
)
|
|
|
|
// DefaultFallbacks returns the shared instance, loading it on first use. A
|
|
// broken file logs once and leaves a nil *Fallbacks, which still answers from
|
|
// hardFloor — a daemon must not fail to boot over its own copy deck.
|
|
func DefaultFallbacks() *Fallbacks {
|
|
fallbackOnce.Do(func() {
|
|
fb, err := LoadFallbacks(nil)
|
|
if err != nil {
|
|
log.Printf("phraser: fallbacks unavailable, using the built-in lines: %v", err)
|
|
return
|
|
}
|
|
fallbacks = fb
|
|
})
|
|
return fallbacks
|
|
}
|
|
|
|
// ChatFallback — what she says when the chat path produced nothing.
|
|
func ChatFallback() string { return DefaultFallbacks().Chat() }
|
|
|
|
// UnknownFallback — what she says when she has no answer and will not invent one.
|
|
func UnknownFallback() string { return DefaultFallbacks().Unknown() }
|
|
|
|
// SourcesFallback — read the sources back rather than ship a broken fragment.
|
|
func SourcesFallback(sources string) string { return DefaultFallbacks().FromSources(sources) }
|
|
|
|
// WorldGap — what he hears when the world model is configured and unreachable.
|
|
func WorldGap() string { return DefaultFallbacks().WorldGap() }
|
|
|
|
// IsUnknownFallback reports whether text is one of her "I do not know" lines.
|
|
// The daemon tests read it to tell an answer from a shrug.
|
|
func IsUnknownFallback(text string) bool {
|
|
return DefaultFallbacks().deck().Matches(fbQueryUnknown, nil, text)
|
|
}
|
|
|
|
// IsSourcesFallback reports whether text is sources read back verbatim.
|
|
func IsSourcesFallback(text, sources string) bool {
|
|
return DefaultFallbacks().deck().Matches(fbQuerySources, map[string]string{"sources": sources}, text)
|
|
}
|