router: stage 0 claims "что дальше" and "расскажи про X" (V-498)
Neither utterance carries a question mark or an interrogative, so nothing at stage 0 claimed them and the model called both facts. The write is contained — actions_fact refuses a question-shaped fact and re-runs the turn as a query — but every one of these paid a full model round trip to reach a decision two regexes can make, and the fixture scored the routing as wrong. rest-of-day-query joins the agenda grammars: the predicate for the utterance already existed as IsRestOfDayQuery, one layer down in the query chain, and this is what gets the turn there. NarrativeQueryGrammar reads the same narrativeRequests lexicon IsQuestionShaped reads, and declines the topics that are chat rather than world questions — a joke, a bedtime story, herself. It is wired last, so an explicit capture marker still wins. Fixture: ru-query-024 and ru-query-025, both passing. Classifier + ONNX baseline 56/80 (70.0%) → 58/82 (70.7%), no case regressed and no new false clarify. The LLM arm is unmeasured here — no llama-server in this run. The mavweb auth test posted its instant as "Z", which the #482 fix now reads in the daemon's zone, making the clock inside the text stale by the test box's own offset. It carries the local offset now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,10 @@ func buildRouter(emb router.Embedder, acts router.ActMatcher, threshold float64,
|
||||
// rewrote the task text (Vikunja #467). After the rules above because a
|
||||
// marker never collides with a clock or agenda question.
|
||||
grammars = append(grammars, router.TaskCaptureGrammar())
|
||||
// After the capture marker, so "запиши" still wins over "расскажи", and
|
||||
// last overall because it matches on the first word alone: "расскажи про
|
||||
// X" is a world question the model called a fact (Vikunja #498).
|
||||
grammars = append(grammars, router.NarrativeQueryGrammar())
|
||||
return router.New(router.Config{
|
||||
Grammars: grammars,
|
||||
Classifier: cls,
|
||||
|
||||
@@ -139,7 +139,13 @@ func TestHandleAmbientIgnoresNonMeetings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandleAmbientAuth(t *testing.T) {
|
||||
body := `{"title":"Планёрка 10:00","posted_at":"2026-08-03T09:40:00Z"}`
|
||||
// posted_at carries the local offset, and the clock reading inside the text
|
||||
// sits twenty minutes after it. A bare "Z" here would make the reading
|
||||
// stale by the test machine's own offset and the handler would answer 202
|
||||
// no-meeting, which says nothing about the auth this test is checking
|
||||
// (Vikunja #482).
|
||||
posted := time.Date(2026, 8, 3, 9, 40, 0, 0, time.Local)
|
||||
body := fmt.Sprintf(`{"title":"Планёрка 10:00","posted_at":%q}`, posted.Format(time.RFC3339))
|
||||
|
||||
newReq := func(hdr, val string) *http.Request {
|
||||
r := httptest.NewRequest(http.MethodPost, "/api/ambient", strings.NewReader(body))
|
||||
|
||||
Reference in New Issue
Block a user