Merge the topic query vector fix (#180)
This commit is contained in:
@@ -468,6 +468,10 @@ func (h *reactiveHandler) queryWeather(ctx context.Context, t *queryTurn) (strin
|
||||
// sources below both need, run once, in the position it always ran in. It
|
||||
// only claims the turn when the embedder fails.
|
||||
func (h *reactiveHandler) queryEmbed(ctx context.Context, t *queryTurn) (string, bool) {
|
||||
// A topic source above already paid for this one; see turnVector.
|
||||
if len(t.vec) > 0 {
|
||||
return "", false
|
||||
}
|
||||
vec, err := router.EmbedQuery(ctx, h.recall.embedder, t.dec.Utterance)
|
||||
if err != nil {
|
||||
log.Printf("voice: embed query: %v", err)
|
||||
|
||||
+2
-2
@@ -74,9 +74,9 @@
|
||||
},
|
||||
{
|
||||
"at": "08:50",
|
||||
"note": "he asks. The query path answers from local recall only: nothing stored clears the score gate, so she refuses rather than inventing a morning summary, and the replier is never reached. That refusal is the no-hallucination floor and this step pins it. Note what the persona check here is and is not: the reply is a constant in the Go source, so expect_reply_lacks pins that constant, not anything the model wrote. The step below is the one that reads model output.",
|
||||
"note": "he asks what he missed, and Praxis holds one unresolved item — the morning medicine — so she reads that back. This step pinned \"не знаю\" until 05-08-2026, and that was the keyword floor's blind spot rather than a rule: isAttentionQuery does not match \"что я пропустил\", while the topicAttend seeds carry \"что важное я пропустил\" almost verbatim. The seeds only started deciding when turnVector fixed the empty query vector every topic source was reading (V-547). Reading a surfaced item aloud is not inventing a morning summary, so the no-hallucination floor still holds; what moved is which source answers. Note what the persona check here is and is not: the reply is a constant in the Go source, so expect_reply_lacks pins that constant, not anything the model wrote. The step below is the one that reads model output.",
|
||||
"say": "что я пропустил?",
|
||||
"expect_reply_contains": ["не знаю"],
|
||||
"expect_reply_contains": ["требует внимания", "morning_medicine"],
|
||||
"expect_reply_lacks": ["рад ", "милый", "ваш"]
|
||||
},
|
||||
{
|
||||
|
||||
+30
-1
@@ -249,6 +249,35 @@ func (x *topicIndex) best(vec []float32) (label topicLabel, margin float64, ok b
|
||||
return label, first - second, true
|
||||
}
|
||||
|
||||
// turnVector returns the turn's query vector, computing it on first ask and
|
||||
// caching it on the turn.
|
||||
//
|
||||
// It exists because every topic source sits ABOVE the "embed" source in
|
||||
// querySources, and that source was the only thing that ever set t.vec. So
|
||||
// turnIsAbout was reading an empty vector on every deployed turn, best returned
|
||||
// ok=false, and all six recognisers ran on their keyword floors — the seeds
|
||||
// decided nothing outside the tests, which embed the utterance themselves and
|
||||
// call best directly. Found on the box on 05-08-2026: "что мне нужно купить" was
|
||||
// answered from an old note, and the seeds place it as the list by 0.0841.
|
||||
//
|
||||
// Computing here rather than moving the embed source up: the cost is paid by the
|
||||
// turns that ask, the cache means queryEmbed below reuses this one, and the
|
||||
// order of querySources stays what its comments argue for.
|
||||
func (h *reactiveHandler) turnVector(ctx context.Context, t *queryTurn) []float32 {
|
||||
if len(t.vec) > 0 || h.recall.embedder == nil {
|
||||
return t.vec
|
||||
}
|
||||
vec, err := router.EmbedQuery(ctx, h.recall.embedder, t.dec.Utterance)
|
||||
if err != nil {
|
||||
// The floor answers. A topic source is not the place to fail a turn:
|
||||
// the recall sources below hit the same embedder and report it there.
|
||||
log.Printf("voice: topic vector for %q: %v", t.dec.Utterance, err)
|
||||
return nil
|
||||
}
|
||||
t.vec = vec
|
||||
return vec
|
||||
}
|
||||
|
||||
// turnIsAbout — the recogniser every topic source calls. The seeds decide when
|
||||
// the embedder is there, which is every deployed box; floor is the source's own
|
||||
// keyword test, which answers when they are not.
|
||||
@@ -261,7 +290,7 @@ func (x *topicIndex) best(vec []float32) (label topicLabel, margin float64, ok b
|
||||
// network by 0.0055; isNetworkQuery says no, so it stays the complaint it is.
|
||||
func (h *reactiveHandler) turnIsAbout(ctx context.Context, t *queryTurn, want topicLabel, floor func(string) bool) bool {
|
||||
h.recall.topics.load(ctx, h.recall.embedder)
|
||||
label, margin, ok := h.recall.topics.best(t.vec)
|
||||
label, margin, ok := h.recall.topics.best(h.turnVector(ctx, t))
|
||||
if !ok {
|
||||
return floor(t.dec.Utterance)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user