Merge V-555 tail: she does not look herself up (#201)
This commit is contained in:
@@ -122,6 +122,12 @@ var querySources = []querySource{
|
|||||||
{name: "network", answer: (*reactiveHandler).queryNetwork},
|
{name: "network", answer: (*reactiveHandler).queryNetwork},
|
||||||
{name: "calendar", answer: (*reactiveHandler).queryCalendar, dateAware: true},
|
{name: "calendar", answer: (*reactiveHandler).queryCalendar, dateAware: true},
|
||||||
{name: "weather", answer: (*reactiveHandler).queryWeather},
|
{name: "weather", answer: (*reactiveHandler).queryWeather},
|
||||||
|
// A question about her, above the three sources that search his own data
|
||||||
|
// (Vikunja #555). It has no answer anywhere else: below the boundary
|
||||||
|
// SearXNG answers about somebody else's assistant, and above it his notes
|
||||||
|
// answer by proximity — "кто ты" came back from a note of his, measured on
|
||||||
|
// the box, because the recall index has no idea the subject is her.
|
||||||
|
{name: "self", answer: (*reactiveHandler).querySelf},
|
||||||
{name: "embed", answer: (*reactiveHandler).queryEmbed},
|
{name: "embed", answer: (*reactiveHandler).queryEmbed},
|
||||||
{name: "memory", answer: (*reactiveHandler).queryMemory},
|
{name: "memory", answer: (*reactiveHandler).queryMemory},
|
||||||
{name: "notes", answer: (*reactiveHandler).queryNotes},
|
{name: "notes", answer: (*reactiveHandler).queryNotes},
|
||||||
@@ -129,11 +135,6 @@ var querySources = []querySource{
|
|||||||
// below answers from the world's. A question about him that got this far
|
// below answers from the world's. A question about him that got this far
|
||||||
// has no answer in his data, and no outside source can supply one, so this
|
// has no answer in his data, and no outside source can supply one, so this
|
||||||
// stops the walk rather than let the encyclopedia and the model guess.
|
// stops the walk rather than let the encyclopedia and the model guess.
|
||||||
// A question about her sits just above the boundary, because it has no
|
|
||||||
// answer below one: refusing it as his says "не нашла у тебя такой записи"
|
|
||||||
// about her own description, and letting it through asks SearXNG about
|
|
||||||
// somebody else's assistant (Vikunja #555).
|
|
||||||
{name: "self", answer: (*reactiveHandler).querySelf},
|
|
||||||
{name: "personal", answer: (*reactiveHandler).queryPersonal},
|
{name: "personal", answer: (*reactiveHandler).queryPersonal},
|
||||||
// The world, read live. Owner's ruling of 2026-08-02: a metasearch hit beats
|
// The world, read live. Owner's ruling of 2026-08-02: a metasearch hit beats
|
||||||
// a frozen ZIM, so SearXNG asks before Kiwix does. Nothing of his is at
|
// a frozen ZIM, so SearXNG asks before Kiwix does. Nothing of his is at
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ func (h *reactiveHandler) querySelf(ctx context.Context, t *queryTurn) (string,
|
|||||||
var reply string
|
var reply string
|
||||||
if h.phraser != nil {
|
if h.phraser != nil {
|
||||||
var err error
|
var err error
|
||||||
reply, err = h.phraser.PhraseQuery(ctx, t.dec.Utterance, []string{selfDescription})
|
reply, err = h.phraser.PhraseSelf(ctx, t.dec.Utterance, selfDescription)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("voice: phrase self: %v", err)
|
log.Printf("voice: phrase self: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -941,6 +941,36 @@ func (p *LLMPhraser) knowledgePrompt(utterance string) (sys, user string) {
|
|||||||
fmt.Sprintf("Пользователь спрашивает: \"%s\".", utterance)
|
fmt.Sprintf("Пользователь спрашивает: \"%s\".", utterance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhraseSelf answers a question about her from her own description. Same
|
||||||
|
// discipline as the evidence branch — say only what the text says — and a
|
||||||
|
// different opener, because "вот что я нашла: я — твоя помощница" says she
|
||||||
|
// looked herself up (Vikunja #555). She did not; this is the one subject she
|
||||||
|
// does not have to read about.
|
||||||
|
//
|
||||||
|
// On any error it reads the description out rather than ship a fragment. That
|
||||||
|
// is already a readable answer, which is why this needs no separate fallback.
|
||||||
|
func (p *LLMPhraser) PhraseSelf(ctx context.Context, utterance, description string) (string, error) {
|
||||||
|
sys := persona.Prepend(p.cfg.ContextBlock,
|
||||||
|
"Он спрашивает о тебе. Отвечай ТОЛЬКО по описанию, которое тебе дали: всё, что ты говоришь о себе, должно быть в нём. "+
|
||||||
|
"Не добавляй умений, которых там нет, и не догадывайся. Не начинай с \"вот что я нашла\" — ты говоришь о себе, а не о находке. "+
|
||||||
|
"Отвечай по-русски, коротко и своими словами. О себе — в женском роде, глаголы в прошедшем времени с окончанием -ла. "+
|
||||||
|
"Он мужчина, обращайся к нему на \"ты\". Отвечай ТОЛЬКО одним объектом JSON: {\"response\": \"...\", \"mood\": \"neutral\"}.")
|
||||||
|
prompt := fmt.Sprintf("Он спрашивает: %q\n\nТвоё описание:\n%s\n\nОтветь ему на то, что он спросил.", utterance, description)
|
||||||
|
resp, err := p.chatWithSystem(ctx, sys, prompt, 768)
|
||||||
|
text, _, perr := parseResponseMood(resp)
|
||||||
|
if err != nil || perr != nil {
|
||||||
|
cause := err
|
||||||
|
if cause == nil {
|
||||||
|
cause = perr
|
||||||
|
}
|
||||||
|
return description, fmt.Errorf("phrase self: %w", cause)
|
||||||
|
}
|
||||||
|
if text != "" {
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
return description, nil
|
||||||
|
}
|
||||||
|
|
||||||
// evidencePrompt — the sources branch: read these, add nothing. Shared with
|
// evidencePrompt — the sources branch: read these, add nothing. Shared with
|
||||||
// PhraseWorld for the same reason as knowledgePrompt.
|
// PhraseWorld for the same reason as knowledgePrompt.
|
||||||
func (p *LLMPhraser) evidencePrompt(utterance string, notes []string) (sys, user string) {
|
func (p *LLMPhraser) evidencePrompt(utterance string, notes []string) (sys, user string) {
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ type Phraser interface {
|
|||||||
PhraseNudge(ctx context.Context, c loop.Candidate) (delivery.PhrasedNudge, error)
|
PhraseNudge(ctx context.Context, c loop.Candidate) (delivery.PhrasedNudge, error)
|
||||||
PhraseReminder(ctx context.Context, d loop.ReminderDecision) (delivery.PhrasedReminder, error)
|
PhraseReminder(ctx context.Context, d loop.ReminderDecision) (delivery.PhrasedReminder, error)
|
||||||
PhraseQuery(ctx context.Context, utterance string, notes []string) (string, error)
|
PhraseQuery(ctx context.Context, utterance string, notes []string) (string, error)
|
||||||
|
// PhraseSelf answers a question about her from her own description, which
|
||||||
|
// is not a source she read and must not be phrased as one (Vikunja #555).
|
||||||
|
PhraseSelf(ctx context.Context, utterance, description string) (string, error)
|
||||||
PhraseChat(ctx context.Context, utterance string, history []dialogue.Turn) (string, error)
|
PhraseChat(ctx context.Context, utterance string, history []dialogue.Turn) (string, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@@ -81,6 +84,13 @@ func (s *Stub) PhraseQuery(_ context.Context, _ string, notes []string) (string,
|
|||||||
return SourcesFallback(strings.Join(notes, "; ")), nil
|
return SourcesFallback(strings.Join(notes, "; ")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhraseSelf reads the description out as it stands. There is nothing to fall
|
||||||
|
// back to and nothing to shorten: the text is already written in her voice, and
|
||||||
|
// that is the whole reason it is a constant rather than a prompt.
|
||||||
|
func (s *Stub) PhraseSelf(_ context.Context, _, description string) (string, error) {
|
||||||
|
return description, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close implements Phraser.Close (no-op for the stub).
|
// Close implements Phraser.Close (no-op for the stub).
|
||||||
func (s *Stub) Close() error { return nil }
|
func (s *Stub) Close() error { return nil }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user