feed questions ask the seeds, not a stem list (V-522)
Whether a turn is about the feeds is a question about meaning, and internal/router/feeds.go was deciding it with three word lists. Their own comments admit the shape: vagueNouns exists because "что нового?" is the most common opener in the language and it matched a feed noun, so a daemon with no feeds block answered a greeting with a configuration status. So topicFeed joins the four subjects in cmd/mavend/topics.go and queryFeeds calls turnIsAbout. The word lists stay as the offline floor, reached through feedFloor, and they are allowed to stay narrow now that they are not the only answer. The category is not a recogniser — a topic is marked by a preposition — so it comes out of the utterance either way, through the new router.FeedCategoryOf. The greeting is handled by the shape rather than by a bail-out list. "что нового" is a topicOther seed, close enough to the feed seeds that a bare "что нового?" cannot clear topicMargin, and a thin call goes to ParseFeedQuery, which declines a vague noun with no topic beside it. Measured on TestONNXTopics, four held-out cases added: 23/23, and no case that passed before it regressed. One seed pair was added during the measurement, because "какие сегодня заголовки" first read as weather — "какая сегодня погода" was the nearest thing in the whole set carrying "сегодня".
This commit is contained in:
@@ -292,6 +292,14 @@ const (
|
||||
feedReadOut = 3
|
||||
)
|
||||
|
||||
// feedFloor — the keyword test behind topicFeed, in the one-string shape
|
||||
// turnIsAbout takes. router.ParseFeedQuery returns the category too, which the
|
||||
// gate has no use for; the caller reads it separately.
|
||||
func feedFloor(u string) bool {
|
||||
_, ok := router.ParseFeedQuery(u)
|
||||
return ok
|
||||
}
|
||||
|
||||
// queryFeeds — "что нового в лентах?", "что нового по технологиям?"
|
||||
// (Vikunja #258).
|
||||
//
|
||||
@@ -299,10 +307,13 @@ const (
|
||||
// never speaks; asking is the trigger. If that ever changes, the thing that
|
||||
// changed is "Maven is not a nag", not a detail of this file.
|
||||
func (h *reactiveHandler) queryFeeds(ctx context.Context, t *queryTurn) (string, bool) {
|
||||
q, ok := router.ParseFeedQuery(t.dec.Utterance)
|
||||
if !ok {
|
||||
// The seeds decide the subject; router.ParseFeedQuery is the floor behind
|
||||
// them (V-522). The category still comes from the utterance either way,
|
||||
// because a topic is marked by a preposition and needs no recogniser.
|
||||
if !h.turnIsAbout(ctx, t, topicFeed, feedFloor) {
|
||||
return "", false
|
||||
}
|
||||
category := router.FeedCategoryOf(t.dec.Utterance)
|
||||
if !h.feedsOn {
|
||||
// Claim only when nothing below can read the world. The reason this
|
||||
// source used to claim unconditionally was that general knowledge would
|
||||
@@ -327,7 +338,7 @@ func (h *reactiveHandler) queryFeeds(ctx context.Context, t *queryTurn) (string,
|
||||
}
|
||||
var picked []string
|
||||
for _, n := range notes {
|
||||
if !router.CategoryMatches(rss.NoteCategory(n.Text), q.Category) {
|
||||
if !router.CategoryMatches(rss.NoteCategory(n.Text), category) {
|
||||
continue
|
||||
}
|
||||
// The note carries title, summary, category tag and link; she reads the
|
||||
@@ -339,7 +350,7 @@ func (h *reactiveHandler) queryFeeds(ctx context.Context, t *queryTurn) (string,
|
||||
}
|
||||
}
|
||||
if len(picked) == 0 {
|
||||
if q.Category != "" {
|
||||
if category != "" {
|
||||
return phraser.Q(phraser.QueryFeedsTopic, nil), true
|
||||
}
|
||||
return phraser.Q(phraser.QueryFeedsEmpty, nil), true
|
||||
|
||||
+30
-1
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// Which subject is this question about — the weather, the house, the LAN, what
|
||||
// needs looking at, or none of them. Third of the three mechanisms replacing hand-written Russian
|
||||
// needs looking at, his feeds, or none of them. Third of the three mechanisms replacing hand-written Russian
|
||||
// patterns (Vikunja #522, owner's call 2026-08-04). internal/lexicon holds the
|
||||
// sets that can be finished and internal/morph answers the grammar questions;
|
||||
// this is for the sets that can never be finished, because "is this about the
|
||||
@@ -37,6 +37,11 @@ import (
|
||||
// внимания", which is Praxis's operational state and reached the web search
|
||||
// before the source existed (Vikunja #475).
|
||||
//
|
||||
// A fifth joined on 05-08-2026: the feeds, "что нового в лентах". Its word lists
|
||||
// were the last pair of hand-written Russian stem lists in the router (V-522),
|
||||
// and they carried the same admission in their own comments — vagueNouns exists
|
||||
// because "что нового?" is a greeting that matched a feed noun.
|
||||
//
|
||||
// The regexes stay as the offline floor, unchanged, for a handler with no
|
||||
// embedder or a turn whose vector never got computed. They are allowed to remain
|
||||
// narrow now precisely because they are no longer the only answer.
|
||||
@@ -52,6 +57,7 @@ const (
|
||||
topicHome topicLabel = "home"
|
||||
topicNetwork topicLabel = "network"
|
||||
topicAttend topicLabel = "attention"
|
||||
topicFeed topicLabel = "feeds"
|
||||
topicOther topicLabel = "other"
|
||||
)
|
||||
|
||||
@@ -117,7 +123,30 @@ var topicSeedSets = map[topicLabel][]string{
|
||||
"what needs attention",
|
||||
"what needs looking at right now",
|
||||
},
|
||||
topicFeed: {
|
||||
"что нового в лентах",
|
||||
"какие новости",
|
||||
"что нового по технологиям",
|
||||
"почитай заголовки",
|
||||
// Two seeds carrying a day word beside the headlines. Without them
|
||||
// "какие сегодня заголовки" read as weather, because "какая сегодня
|
||||
// погода" is the nearest thing in the whole set with "сегодня" in it.
|
||||
"заголовки за сегодня",
|
||||
"какие главные новости за день",
|
||||
"покажи новости за сегодня",
|
||||
"что пишут в новостях",
|
||||
"что нового про политику",
|
||||
"расскажи что нового в ленте",
|
||||
"what is new in the feeds",
|
||||
"any news headlines today",
|
||||
},
|
||||
topicOther: {
|
||||
// The bare newness opener, which is a greeting and not a request for
|
||||
// headlines. It sits here on purpose: it is close enough to the feed
|
||||
// seeds that it will not clear topicMargin, and a thin call goes to
|
||||
// ParseFeedQuery, which declines a vague noun with no topic beside it.
|
||||
"что нового",
|
||||
"как дела",
|
||||
// Complaints, which are not requests to scan or to read the house.
|
||||
// isNetworkQuery's comment names this one: a scan she runs unasked is
|
||||
// the noisy behaviour the bounds exist to prevent.
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestTopicFloorAnswersWithoutSeeds(t *testing.T) {
|
||||
{"что включено в доме?", topicHome, isHomeQuery, true},
|
||||
{"какие устройства в сети?", topicNetwork, isNetworkQuery, true},
|
||||
{"что требует внимания?", topicAttend, isAttentionQuery, true},
|
||||
{"что нового в лентах?", topicFeed, feedFloor, true},
|
||||
{"почему небо синее", topicWeather, isWeatherQuery, false},
|
||||
{"я дома", topicHome, isHomeQuery, false},
|
||||
{"интернет не работает", topicNetwork, isNetworkQuery, false},
|
||||
@@ -86,6 +87,12 @@ func TestONNXTopics(t *testing.T) {
|
||||
{"что требует моего внимания сейчас", topicAttend, isAttentionQuery},
|
||||
{"что не так с базой данных", topicAttend, isAttentionQuery},
|
||||
{"есть что-то срочное на сегодня", topicAttend, isAttentionQuery},
|
||||
{"что нового в ленте за сегодня", topicFeed, feedFloor},
|
||||
{"какие сегодня заголовки", topicFeed, feedFloor},
|
||||
{"что нового про искусственный интеллект", topicFeed, feedFloor},
|
||||
// The greeting. It has to lose to topicOther, or fall thin enough that
|
||||
// ParseFeedQuery — which declines a vague noun with no topic — answers.
|
||||
{"что нового?", topicOther, feedFloor},
|
||||
}
|
||||
|
||||
h := &reactiveHandler{recall: recallWiring{embedder: emb}}
|
||||
|
||||
@@ -5,10 +5,16 @@ import "strings"
|
||||
// Feed queries — "что нового в лентах?", "что нового по технологиям?"
|
||||
// (Vikunja #258).
|
||||
//
|
||||
// Deterministic matching, like the calendar, plan and habit matchers above it:
|
||||
// the LLM router says this is a query, and this decides whether it is a question
|
||||
// about the feeds. A model deciding that would occasionally answer "что нового?"
|
||||
// out of world knowledge, which is the one thing a feed reader exists to avoid.
|
||||
// This file is the OFFLINE FLOOR as of 05-08-2026 (V-522). Whether a turn is
|
||||
// about the feeds is a question about meaning, so the frozen seeds decide it —
|
||||
// topicFeed in cmd/mavend/topics.go, through turnIsAbout. The word lists below
|
||||
// stay because they always answer: a box with no embedder, a turn whose vector
|
||||
// never got computed, and any call that does not clear topicMargin. They are
|
||||
// allowed to stay narrow now that they are not the only answer.
|
||||
//
|
||||
// What has not changed is that no generative model decides this. One would
|
||||
// occasionally answer "что нового?" out of world knowledge, which is the one
|
||||
// thing a feed reader exists to avoid.
|
||||
|
||||
// FeedQuery — a parsed "what's new" question. Category is the topic he named
|
||||
// ("технологии"), empty when he asked about the feeds in general.
|
||||
@@ -95,6 +101,13 @@ func ParseFeedQuery(text string) (FeedQuery, bool) {
|
||||
// cannot be mistaken for anything else.
|
||||
var categoryPreps = map[string]bool{"по": true, "об": true, "про": true, "about": true, "on": true}
|
||||
|
||||
// FeedCategoryOf reads the topic out of an utterance without deciding whether the
|
||||
// turn is a feed question at all. The seeds answer that now (topicFeed in
|
||||
// cmd/mavend/topics.go, V-522), and they answer it for phrasings the word lists
|
||||
// here never held — but a claimed turn still needs its category, and a category
|
||||
// is marked by a preposition rather than recognised.
|
||||
func FeedCategoryOf(text string) string { return feedCategory(planTokens(text)) }
|
||||
|
||||
func feedCategory(toks []string) string {
|
||||
for i, t := range toks {
|
||||
if categoryPreps[t] && i+1 < len(toks) {
|
||||
|
||||
Reference in New Issue
Block a user