diff --git a/cmd/mavend/voice.go b/cmd/mavend/voice.go index 6a23416..aaf6148 100644 --- a/cmd/mavend/voice.go +++ b/cmd/mavend/voice.go @@ -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 { diff --git a/internal/phraser/llmphraser.go b/internal/phraser/llmphraser.go index 05d6ad1..fa7fe51 100644 --- a/internal/phraser/llmphraser.go +++ b/internal/phraser/llmphraser.go @@ -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]) diff --git a/internal/phraser/phraser.go b/internal/phraser/phraser.go index b818e50..fa97c27 100644 --- a/internal/phraser/phraser.go +++ b/internal/phraser/phraser.go @@ -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 diff --git a/internal/router/knowledge.go b/internal/router/knowledge.go new file mode 100644 index 0000000..05773ff --- /dev/null +++ b/internal/router/knowledge.go @@ -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 `Ты — Мавена, персональный ассистент. Ответь кратко из своих знаний. Если не знаешь — скажи "не знаю". Не выдумывай.` +} diff --git a/internal/router/knowledge_test.go b/internal/router/knowledge_test.go new file mode 100644 index 0000000..2c38fde --- /dev/null +++ b/internal/router/knowledge_test.go @@ -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) + } + } +}