3d8224fb04
"сеть какая-то медленная" and "интернет не работает" were written as `self` rows at confidence 1.00. Recall reads a self row back later as if it were still true, and that is the class of row that outranked live search in #470 — so a slow afternoon becomes a standing belief about his network. IsTransientComplaint is the same shape as IsQuestionShaped: deterministic, offline, and off by default in the two cases where losing a real capture would cost more than keeping a complaint. An explicit "запомни ..." wins, because he asked. A first-person marker wins, because "я сломал руку" is durable and the test is meant for sentences about things. She answers the turn as chat instead of storing it. actionChat now has the same nil-phraser floor the other model callers have. The second defect filed here — a reply body of literally "{" — was closed by the errBrokenJSON path in V-397 and needs nothing further.
171 lines
5.8 KiB
Go
171 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kami/maven/internal/ipc"
|
|
"github.com/kami/maven/internal/memory"
|
|
"github.com/kami/maven/internal/router"
|
|
"github.com/kami/maven/internal/tool"
|
|
"github.com/kami/maven/internal/voice"
|
|
)
|
|
|
|
func newFactGateHandler(t *testing.T, now time.Time) (*reactiveHandler, ipc.CoreAPI) {
|
|
t.Helper()
|
|
st := newTestStore(t)
|
|
api := ipc.NewStoreAPI(st)
|
|
emb := router.NewHashEmbedder(1024)
|
|
h := &reactiveHandler{
|
|
api: api,
|
|
embedder: emb,
|
|
router: buildRouter(emb, tool.NewMatcher(api), 0.55, nil),
|
|
replier: voice.NewStubReplier(),
|
|
now: func() time.Time { return now },
|
|
memStore: memory.NewInMemoryStore(),
|
|
dataStore: st,
|
|
}
|
|
return h, api
|
|
}
|
|
|
|
// The write half of #470: a question routed to IntentFact must not become a
|
|
// fact about him, and must not leave a vector behind for recall to serve.
|
|
func TestActionFact_QuestionIsNotWritten(t *testing.T) {
|
|
ctx := context.Background()
|
|
h, api := newFactGateHandler(t, time.Now())
|
|
|
|
reply := h.actionFact(ctx, router.Decision{
|
|
Intent: router.IntentFact,
|
|
Utterance: "какая последняя версия языка Go?",
|
|
Slots: router.Slots{Key: "go_version", HasKey: true, Value: `"1.20"`},
|
|
})
|
|
|
|
if _, err := api.LatestFact(ctx, "go_version"); err == nil {
|
|
t.Fatal("a question was stored as a fact about him")
|
|
}
|
|
hits, err := h.memStore.Search(ctx, mustEmbedPassage(t, h, "какая последняя версия языка Go?"), 3)
|
|
if err != nil {
|
|
t.Fatalf("memory search: %v", err)
|
|
}
|
|
if len(hits) != 0 {
|
|
t.Fatalf("the question was indexed for recall: %+v", hits)
|
|
}
|
|
// It went down the query chain instead. Nothing is configured to answer a
|
|
// world question in this harness, so "не знаю." is the honest outcome —
|
|
// what matters is that the turn was answered, not stored.
|
|
if reply == "" {
|
|
t.Fatal("the turn was neither stored nor answered")
|
|
}
|
|
}
|
|
|
|
// The capture that must survive the gate: an explicit instruction to record,
|
|
// even though it contains an interrogative.
|
|
func TestActionFact_ExplicitCaptureStillWrites(t *testing.T) {
|
|
ctx := context.Background()
|
|
h, api := newFactGateHandler(t, time.Now())
|
|
|
|
h.actionFact(ctx, router.Decision{
|
|
Intent: router.IntentFact,
|
|
Utterance: "запиши что я пил воду",
|
|
Slots: router.Slots{Key: "water", HasKey: true, Value: `"вода"`},
|
|
})
|
|
|
|
f, err := api.LatestFact(ctx, "water")
|
|
if err != nil {
|
|
t.Fatalf("an explicit capture was refused: %v", err)
|
|
}
|
|
if f.Confidence != 1.0 {
|
|
t.Errorf("confidence = %v, want 1.0 for a value he said", f.Confidence)
|
|
}
|
|
// #493: what recall reads back is the fact, not the sentence he said.
|
|
// queryMemory returns a fact's text verbatim, so the utterance sitting here
|
|
// meant "запиши что я пил воду" was the answer to "когда я пил воду?".
|
|
hits, err := h.memStore.Search(ctx, mustEmbedPassage(t, h, "вода"), 3)
|
|
if err != nil {
|
|
t.Fatalf("memory search: %v", err)
|
|
}
|
|
if len(hits) != 1 {
|
|
t.Fatalf("the fact was not indexed once: %+v", hits)
|
|
}
|
|
if got := hits[0].Meta["text"]; got != "water — вода" {
|
|
t.Errorf("indexed text = %q, want the fact", got)
|
|
}
|
|
if got := hits[0].Meta["utterance"]; got != "запиши что я пил воду" {
|
|
t.Errorf("utterance provenance = %q, want it kept alongside", got)
|
|
}
|
|
}
|
|
|
|
func TestFactConfidence(t *testing.T) {
|
|
cases := []struct {
|
|
utterance, value string
|
|
want float64
|
|
}{
|
|
{"запиши что я пил воду", `"вода"`, 1.0},
|
|
{"я выпил кофе", `"кофе"`, 1.0},
|
|
{"поужинал", "", 1.0},
|
|
{"отметь что я полил кактус", `"полил кактус"`, 1.0},
|
|
{"какая последняя версия языка Go", `"1.20"`, ungroundedConfidence},
|
|
{"кто премьер Японии", `"Тонио Озаки"`, ungroundedConfidence},
|
|
}
|
|
for _, c := range cases {
|
|
if got := factConfidence(c.utterance, c.value); got != c.want {
|
|
t.Errorf("factConfidence(%q, %q) = %v, want %v", c.utterance, c.value, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustEmbedPassage(t *testing.T, h *reactiveHandler, text string) []float32 {
|
|
t.Helper()
|
|
vec, err := router.EmbedQuery(context.Background(), h.embedder, text)
|
|
if err != nil {
|
|
t.Fatalf("embed %q: %v", text, err)
|
|
}
|
|
return vec
|
|
}
|
|
|
|
// The write half of #481: a complaint about a thing is a state of the
|
|
// afternoon, not a fact about him. Stored as a `self` row at confidence 1.00
|
|
// it comes back on recall as if the network were still down.
|
|
func TestActionFact_ComplaintIsNotWritten(t *testing.T) {
|
|
ctx := context.Background()
|
|
h, api := newFactGateHandler(t, time.Now())
|
|
|
|
reply := h.actionFact(ctx, router.Decision{
|
|
Intent: router.IntentFact,
|
|
Utterance: "сеть какая-то медленная",
|
|
Slots: router.Slots{Key: "network_speed", HasKey: true, Value: "медленная"},
|
|
})
|
|
|
|
if _, err := api.LatestFact(ctx, "network_speed"); err == nil {
|
|
t.Fatal("a passing complaint was stored as a fact about him")
|
|
}
|
|
hits, err := h.memStore.Search(ctx, mustEmbedPassage(t, h, "сеть какая-то медленная"), 3)
|
|
if err != nil {
|
|
t.Fatalf("memory search: %v", err)
|
|
}
|
|
if len(hits) != 0 {
|
|
t.Fatalf("the complaint was indexed for recall: %+v", hits)
|
|
}
|
|
if reply == "" {
|
|
t.Fatal("the turn was neither stored nor answered")
|
|
}
|
|
}
|
|
|
|
// And the complaint he asked her to keep: the capture verb wins, as it does
|
|
// over the question gate.
|
|
func TestActionFact_AskedToRememberAComplaintStillWrites(t *testing.T) {
|
|
ctx := context.Background()
|
|
h, api := newFactGateHandler(t, time.Now())
|
|
|
|
h.actionFact(ctx, router.Decision{
|
|
Intent: router.IntentFact,
|
|
Utterance: "запомни что интернет не работает",
|
|
Slots: router.Slots{Key: "internet", HasKey: true, Value: "не работает"},
|
|
})
|
|
|
|
if _, err := api.LatestFact(ctx, "internet"); err != nil {
|
|
t.Fatalf("an explicit capture was refused: %v", err)
|
|
}
|
|
}
|