4dfe106fe3
IntentFact used to persist whatever the model invented for a question-shaped utterance, at confidence 1.00, and index it for recall under the question's own text. Two such rows then claimed seven unrelated world questions and silently disabled world answering. A question now goes down the query chain, which is what he asked for. The second half is confidence: a value grounded in what he said stays 1.00, a value the model supplied for words he never said drops to 0.60 and says so in the log. Same reasoning as 'LLM output is not authorization' on the act path.
110 lines
3.5 KiB
Go
110 lines
3.5 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)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|