maven: general-knowledge phraser routing (task 4)

- Stub.PhraseQuery: empty notes → "не знаю." (was: "no notes")
- LLMPhraser.PhraseQuery: empty notes → general knowledge prompt to LLM
- Voice handler: on notes RAG failure, try phraser before giving up
- New router.KnowledgePrompt() pure function with test

Co-Authored-By: opencode <opencode@anthropic.com>
This commit is contained in:
kami
2026-07-06 04:11:54 +04:00
parent cf066bde97
commit 428af3f3c6
5 changed files with 42 additions and 3 deletions
+6 -1
View File
@@ -442,7 +442,12 @@ func (h *reactiveHandler) applyAction(ctx context.Context, dec router.Decision)
// since(key)==null → don't fire. Tuned for the ONNX embedder; the Hash
// floor scores lexically and may rarely clear it.
if len(notes) == 0 || notes[0].Score < queryMinScore {
return "у меня нет заметок по этому вопросу."
// Try general knowledge from the phraser before giving up
reply, err := h.phraser.PhraseQuery(ctx, dec.Utterance, nil)
if err != nil || reply == "" {
return "не знаю."
}
return reply
}
texts := make([]string, len(notes))
for i, n := range notes {
+8 -1
View File
@@ -171,7 +171,14 @@ func (p *LLMPhraser) PhraseNudge(ctx context.Context, c loop.Candidate) (deliver
// LLM error — better to give the raw data than silence.
func (p *LLMPhraser) PhraseQuery(ctx context.Context, utterance string, notes []string) (string, error) {
if len(notes) == 0 {
return "у меня нет заметок по этому вопросу.", nil
// General knowledge — no notes to ground the answer
sys := "Ты — Мавена, персональный ассистент. Ответь кратко из своих знаний. Если не знаешь — скажи \"не знаю\". Не выдумывай."
prompt := fmt.Sprintf("Пользователь спрашивает: \"%s\".", utterance)
resp, err := p.chatWithSystem(ctx, sys, prompt, 256)
if err != nil || resp == "" {
return "не знаю.", nil
}
return resp, nil
}
if len(notes) == 1 {
notes[0] = strings.TrimSpace(notes[0])
+1 -1
View File
@@ -63,7 +63,7 @@ func NewStub() *Stub { return &Stub{} }
// PhraseQuery returns a deterministic summary of the best matching notes.
func (s *Stub) PhraseQuery(_ context.Context, _ string, notes []string) (string, error) {
if len(notes) == 0 {
return "у меня нет заметок по этому вопросу.", nil
return "не знаю.", nil
}
if len(notes) == 1 {
return "вот что я нашла: " + notes[0], nil
+7
View File
@@ -0,0 +1,7 @@
package router
// KnowledgePrompt returns the system prompt for general knowledge questions
// that the phraser uses when no notes match the query.
func KnowledgePrompt() string {
return `Ты — Мавена, персональный ассистент. Ответь кратко из своих знаний. Если не знаешь — скажи "не знаю". Не выдумывай.`
}
+20
View File
@@ -0,0 +1,20 @@
package router
import (
"strings"
"testing"
)
func TestKnowledgePrompt(t *testing.T) {
prompt := KnowledgePrompt()
if prompt == "" {
t.Fatal("KnowledgePrompt returned empty string")
}
// Must contain key instructions
checks := []string{"Мавена", "не знаю", "не выдумывай"}
for _, c := range checks {
if !strings.Contains(strings.ToLower(prompt), strings.ToLower(c)) {
t.Errorf("KnowledgePrompt should mention %q", c)
}
}
}