From 96d97e8964f1afa7a409bf5abf9d051dc5c2b793 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 9 Aug 2026 10:52:24 +0400 Subject: [PATCH] =?UTF-8?q?Try=20the=20capitalized=20title=20too,=20and=20?= =?UTF-8?q?reach=20=D0=9F=D0=B0=D1=80=D0=B8=D0=B6=20(V-668)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A ZIM title carries a leading capital and the utterance does not: /A/фотосинтез is a 404 and /A/Фотосинтез is a 200. TitleCandidates tries the spoken form first, so a title that begins lowercase on purpose keeps its chance. That takes the measurement from four right to five, and the fifth is the one that mattered. "столица Франции" returned "Список столиц Олимпийских игр" and now returns Париж, through a title redirect the ZIM already held. The 2026-08-05 measurement named that case as the one no lexical signal could reach. Retrieval by title reaches it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN --- CLAUDE.md | 7 +++- cmd/mavend/actions_query.go | 10 +++-- .../evals/2026-08-09-kiwix-topic-retrieval.md | 30 +++++++++----- internal/kiwix/live_test.go | 10 +++-- internal/kiwix/topic.go | 20 +++++++++ internal/kiwix/topic_test.go | 16 ++++++++ v668-transcript.md | 41 +++++++++++++++++++ v668.jsonl | 4 ++ 8 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 v668-transcript.md create mode 100644 v668.jsonl diff --git a/CLAUDE.md b/CLAUDE.md index 502639b..4b2c72f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -681,8 +681,11 @@ world questions, so she needs to read external sources. What replaces it: returned "Перехват TCP-соединения". "кто написал Войну и мир" returned an episode of Doctor Who. `kiwix.Topic` drops the narrative request, the interrogative and a verb behind one. `kiwix.TitlePath` tries the exact article first, since a ZIM is addressable - by title and a wrong title is a 404. Four of eight questions reach the right article - where they did not, two were already right, and nothing regressed. Both apply on the + by title and a wrong title is a 404. Five of eight questions reach the right article + where they did not, one was already right, and nothing regressed. The title needs its + leading capital, so `TitleCandidates` tries the spoken form and then the capitalized + one. **"столица Франции" is answered by a title redirect to Париж**, which is the case + the 2026-08-05 measurement named as unreachable by any lexical signal. Both apply on the verbatim path alone. The rewriter already reduces a question, and reducing twice takes the topic off its input. `Response.Empty()` is the whole gate and there is no quality threshold in front of it: diff --git a/cmd/mavend/actions_query.go b/cmd/mavend/actions_query.go index 4a79406..a0aaaab 100644 --- a/cmd/mavend/actions_query.go +++ b/cmd/mavend/actions_query.go @@ -938,10 +938,12 @@ func (h *reactiveHandler) queryKiwix(ctx context.Context, t *queryTurn) (string, // The article named exactly, before any ranking runs. A ZIM is // addressable by title and a wrong title is a 404, so this either // answers or costs one request that says nothing. - page, err := h.kiwix.client.Article(ctxK, kiwix.TitlePath(book, topic), h.kiwix.runes) - if err == nil && page.Text != "" { - log.Printf("voice: kiwix: %q in %q → title hit %q", topic, book, page.Title) - return h.kiwixReply(ctx, t, page.Title, page.Text) + for _, cand := range kiwix.TitleCandidates(topic) { + page, err := h.kiwix.client.Article(ctxK, kiwix.TitlePath(book, cand), h.kiwix.runes) + if err == nil && page.Text != "" { + log.Printf("voice: kiwix: %q in %q → title hit %q", topic, book, page.Title) + return h.kiwixReply(ctx, t, page.Title, page.Text) + } } pattern = topic } diff --git a/docs/evals/2026-08-09-kiwix-topic-retrieval.md b/docs/evals/2026-08-09-kiwix-topic-retrieval.md index 115605d..6c59d54 100644 --- a/docs/evals/2026-08-09-kiwix-topic-retrieval.md +++ b/docs/evals/2026-08-09-kiwix-topic-retrieval.md @@ -60,6 +60,10 @@ A ZIM is also addressable by title, which nothing here used. `/A/Франция` exact title is safe to try first: it either answers or costs one request that says nothing. +The title has to carry its capital. `/A/фотосинтез` is a 404 and +`/A/Фотосинтез` is a 200. The spoken form is tried first anyway, so a title +that begins lowercase on purpose keeps its chance. + ## What shipped, measured `kiwix.Topic` drops the narrative request, the interrogative and a verb sitting @@ -71,25 +75,31 @@ would take the topic off the rewriter's input. | question | before | after | |---|---|---| | что такое TCP? | Перехват TCP-соединения | **TCP** (by title) | -| что такое фотосинтез | C4-фотосинтез | **Фотосинтез** | +| что такое фотосинтез | C4-фотосинтез | **Фотосинтез** (by title) | | кто такой Линус Торвальдс? | Tux | **Торвальдс, Линус** (by title) | | кто написал Войну и мир | Радуйся, мир (Доктор Кто) | **Война и мир** | -| что такое чёрная дыра | Чёрная дыра | Чёрная дыра | +| столица Франции | Список столиц Олимпийских игр | **Париж** (by title) | +| что такое чёрная дыра | Чёрная дыра | Чёрная дыра (by title) | | почему небо голубое | Город золотой | Под небом голубым… (фильм) | | почему трава зелёная | Сено | Зелень | -| столица Франции | Список столиц Олимпийских игр | Список столиц Олимпийских игр | -Four questions reach the right article where they did not. Two were already -right and stay right. Nothing regressed. +Five questions reach the right article where they did not. One was already +right and stays right. Nothing regressed. + +"столица Франции" is the surprise. The 2026-08-05 measurement named it as the +case a quality gate must not break, because the answer is Париж and that word +is not in the question. The ZIM holds a title redirect, so asking for the +article titled "Столица Франции" returns Париж. Retrieval by title reaches an +answer that retrieval by keyword cannot. ## What is still wrong Two of the eight are still not answered, and both are the same shape. The -question names no article. "почему небо голубое" is answered by Rayleigh -scattering and "столица Франции" by the lead of Франция. Neither title is in -the question. Keyword retrieval cannot bridge that and neither can a -threshold. The candidates are a semantic index over titles, or asking the -resident model for the article title rather than for keywords. +question names no article and no redirect covers it. "почему небо голубое" is +answered by Rayleigh scattering, and nothing in the question says so. Keyword +retrieval cannot bridge that and neither can a threshold. The candidates are a +semantic index over titles, or asking the resident model for the article title +rather than for keywords. `Response.Empty()` is still the whole gate. A wrong article that the search does return is still spoken. What this change buys is that the article is diff --git a/internal/kiwix/live_test.go b/internal/kiwix/live_test.go index 95343de..45545f5 100644 --- a/internal/kiwix/live_test.go +++ b/internal/kiwix/live_test.go @@ -39,9 +39,13 @@ func TestLiveTopicBeatsTheSentence(t *testing.T) { before := firstTitle(ctx, c, q, book) topic := Topic(q) after := "" - if page, err := c.Article(ctx, TitlePath(book, topic), 400); err == nil && page.Text != "" { - after = page.Title + " (by title)" - } else { + for _, cand := range TitleCandidates(topic) { + if page, err := c.Article(ctx, TitlePath(book, cand), 400); err == nil && page.Text != "" { + after = page.Title + " (by title)" + break + } + } + if after == "" { after = firstTitle(ctx, c, topic, book) } cancel() diff --git a/internal/kiwix/topic.go b/internal/kiwix/topic.go index 2237070..5358a39 100644 --- a/internal/kiwix/topic.go +++ b/internal/kiwix/topic.go @@ -61,6 +61,26 @@ func Topic(utterance string) string { return "" } +// TitleCandidates is the topic as it might be titled, best first. +// +// A ZIM title is capitalized and the utterance is not: measured on 2026-08-09, +// `/A/фотосинтез` is a 404 and `/A/Фотосинтез` is a 200. The spoken form is +// tried first anyway, because a title that begins lowercase on purpose +// ("iPhone") would not survive capitalizing it. Both are one request each +// against a server on the same box, and a miss is a 404 rather than a wrong +// article. +func TitleCandidates(topic string) []string { + if topic == "" { + return nil + } + r := []rune(topic) + up := unicode.ToUpper(r[0]) + if up == r[0] { + return []string{topic} + } + return []string{topic, string(up) + string(r[1:])} +} + func isCopula(w string) bool { switch w { case "такое", "такой", "такая", "такие", "is", "are", "was", "were": diff --git a/internal/kiwix/topic_test.go b/internal/kiwix/topic_test.go index bc01683..4a22ea7 100644 --- a/internal/kiwix/topic_test.go +++ b/internal/kiwix/topic_test.go @@ -49,3 +49,19 @@ func TestTitlePathEscapesAndUnderscores(t *testing.T) { t.Errorf("TitlePath = %q, want %q", got, want) } } + +// A ZIM title carries a leading capital and the utterance does not. The spoken +// form is still tried first, so a title that begins lowercase on purpose keeps +// its chance. +func TestTitleCandidatesTryTheSpokenFormFirst(t *testing.T) { + got := TitleCandidates("фотосинтез") + if len(got) != 2 || got[0] != "фотосинтез" || got[1] != "Фотосинтез" { + t.Errorf("TitleCandidates = %q", got) + } + if got := TitleCandidates("TCP"); len(got) != 1 || got[0] != "TCP" { + t.Errorf("an already-capital topic was tried twice: %q", got) + } + if got := TitleCandidates(""); got != nil { + t.Errorf("TitleCandidates(\"\") = %q, want nil", got) + } +} diff --git a/v668-transcript.md b/v668-transcript.md new file mode 100644 index 0000000..1ad5b28 --- /dev/null +++ b/v668-transcript.md @@ -0,0 +1,41 @@ +# Raw transcript, two weeks of usage + +YOU: что такое TCP? +MAVEN: есть такое: Tcp/Ip — Википедия — TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные ... + [2.1s src=search t=666] + +YOU: кто такой Линус Торвальдс? +MAVEN: вот что я нашла: Торвальдс, Линус — Википедия — Ли́нус Бенедикт То́рвальдс (встречается написание Ту́рвальдс, швед. Linus Benedict Torvalds МФА: [ˈliːn.ɵs ˈtuːr.valds] о файле; род. 28 декабря 1969, Хельсинки) — финско-американский программист, создатель ядра Linux. + [0.8s src=search t=667] + +YOU: что такое фотосинтез +MAVEN: нашла вот это: Фотосинтез — Фотосинтез +Схематическое изображение процесса фотосинтеза, происходящего в растениях +Упрощённое уравнение фотосинтеза, происходящего в растениях +Упрощённая схема фотосинтеза +Фотоси́нтез (от др.-греч. φῶς  — « свет » и σύνθεσις  — «соединённый», «складной», «связывание», « синтез ») — сл… + [0.6s src=kiwix t=668] + +YOU: кто написал Войну и мир +MAVEN: вот что я нашла: Война и мир — Война и мир +Война и мир +Война и миръ +Обложка издания 1873 года +Жанр +роман-эпопея +Автор +Лев Николаевич Толстой +Язык оригинала +русский, фрагменты на французском и немецком языках +Дата написания +1863—1869, 1873 +Дата первой публикации +1865—1869 +Предыдущее +Казаки +Следующее +Анна Каренина +Тек… + [12.4s src=kiwix t=669] + +``` diff --git a/v668.jsonl b/v668.jsonl new file mode 100644 index 0000000..86c0458 --- /dev/null +++ b/v668.jsonl @@ -0,0 +1,4 @@ +{"reply": "есть такое: Tcp/Ip — Википедия — TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные ...", "source": "search", "trace": "666", "secs": 2.1235475060530007, "day": 0, "n": 1, "utterance": "что такое TCP?"} +{"reply": "вот что я нашла: Торвальдс, Линус — Википедия — Ли́нус Бенедикт То́рвальдс (встречается написание Ту́рвальдс, швед. Linus Benedict Torvalds МФА: [ˈliːn.ɵs ˈtuːr.valds] о файле; род. 28 декабря 1969, Хельсинки) — финско-американский программист, создатель ядра Linux.", "source": "search", "trace": "667", "secs": 0.7689587379572913, "day": 0, "n": 2, "utterance": "кто такой Линус Торвальдс?"} +{"reply": "нашла вот это: Фотосинтез — Фотосинтез\nСхематическое изображение процесса фотосинтеза, происходящего в растениях\nУпрощённое уравнение фотосинтеза, происходящего в растениях\nУпрощённая схема фотосинтеза\nФотоси́нтез (от др.-греч. φῶς  — « свет » и σύνθεσις  — «соединённый», «складной», «связывание», « синтез ») — сл…", "source": "kiwix", "trace": "668", "secs": 0.575586927938275, "day": 0, "n": 3, "utterance": "что такое фотосинтез"} +{"reply": "вот что я нашла: Война и мир — Война и мир\nВойна и мир\nВойна и миръ\nОбложка издания 1873 года\nЖанр\nроман-эпопея\nАвтор\nЛев Николаевич Толстой\nЯзык оригинала\nрусский, фрагменты на французском и немецком языках\nДата написания\n1863—1869, 1873\nДата первой публикации\n1865—1869\nПредыдущее\nКазаки\nСледующее\nАнна Каренина\nТек…", "source": "kiwix", "trace": "669", "secs": 12.44599153404124, "day": 0, "n": 4, "utterance": "кто написал Войну и мир"} -- 2.52.0