Merge the feed topic seeds (#178)
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