Try the capitalized title too, and reach Париж (V-668)
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN
This commit is contained in:
@@ -681,8 +681,11 @@ world questions, so she needs to read external sources. What replaces it:
|
|||||||
returned "Перехват TCP-соединения". "кто написал Войну и мир" returned an episode of
|
returned "Перехват TCP-соединения". "кто написал Войну и мир" returned an episode of
|
||||||
Doctor Who. `kiwix.Topic` drops the narrative request, the interrogative and a verb
|
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
|
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
|
by title and a wrong title is a 404. Five of eight questions reach the right article
|
||||||
where they did not, two were already right, and nothing regressed. Both apply on the
|
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
|
verbatim path alone. The rewriter already reduces a question, and reducing twice takes
|
||||||
the topic off its input.
|
the topic off its input.
|
||||||
`Response.Empty()` is the whole gate and there is no quality threshold in front of it:
|
`Response.Empty()` is the whole gate and there is no quality threshold in front of it:
|
||||||
|
|||||||
@@ -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
|
// The article named exactly, before any ranking runs. A ZIM is
|
||||||
// addressable by title and a wrong title is a 404, so this either
|
// addressable by title and a wrong title is a 404, so this either
|
||||||
// answers or costs one request that says nothing.
|
// answers or costs one request that says nothing.
|
||||||
page, err := h.kiwix.client.Article(ctxK, kiwix.TitlePath(book, topic), h.kiwix.runes)
|
for _, cand := range kiwix.TitleCandidates(topic) {
|
||||||
if err == nil && page.Text != "" {
|
page, err := h.kiwix.client.Article(ctxK, kiwix.TitlePath(book, cand), h.kiwix.runes)
|
||||||
log.Printf("voice: kiwix: %q in %q → title hit %q", topic, book, page.Title)
|
if err == nil && page.Text != "" {
|
||||||
return h.kiwixReply(ctx, t, page.Title, 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
|
pattern = topic
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
exact title is safe to try first: it either answers or costs one request that
|
||||||
says nothing.
|
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
|
## What shipped, measured
|
||||||
|
|
||||||
`kiwix.Topic` drops the narrative request, the interrogative and a verb sitting
|
`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 |
|
| question | before | after |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| что такое TCP? | Перехват TCP-соединения | **TCP** (by title) |
|
| что такое TCP? | Перехват TCP-соединения | **TCP** (by title) |
|
||||||
| что такое фотосинтез | C4-фотосинтез | **Фотосинтез** |
|
| что такое фотосинтез | C4-фотосинтез | **Фотосинтез** (by title) |
|
||||||
| кто такой Линус Торвальдс? | Tux | **Торвальдс, Линус** (by title) |
|
| кто такой Линус Торвальдс? | Tux | **Торвальдс, Линус** (by title) |
|
||||||
| кто написал Войну и мир | Радуйся, мир (Доктор Кто) | **Война и мир** |
|
| кто написал Войну и мир | Радуйся, мир (Доктор Кто) | **Война и мир** |
|
||||||
| что такое чёрная дыра | Чёрная дыра | Чёрная дыра |
|
| столица Франции | Список столиц Олимпийских игр | **Париж** (by title) |
|
||||||
|
| что такое чёрная дыра | Чёрная дыра | Чёрная дыра (by title) |
|
||||||
| почему небо голубое | Город золотой | Под небом голубым… (фильм) |
|
| почему небо голубое | Город золотой | Под небом голубым… (фильм) |
|
||||||
| почему трава зелёная | Сено | Зелень |
|
| почему трава зелёная | Сено | Зелень |
|
||||||
| столица Франции | Список столиц Олимпийских игр | Список столиц Олимпийских игр |
|
|
||||||
|
|
||||||
Four questions reach the right article where they did not. Two were already
|
Five questions reach the right article where they did not. One was already
|
||||||
right and stay right. Nothing regressed.
|
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
|
## What is still wrong
|
||||||
|
|
||||||
Two of the eight are still not answered, and both are the same shape. The
|
Two of the eight are still not answered, and both are the same shape. The
|
||||||
question names no article. "почему небо голубое" is answered by Rayleigh
|
question names no article and no redirect covers it. "почему небо голубое" is
|
||||||
scattering and "столица Франции" by the lead of Франция. Neither title is in
|
answered by Rayleigh scattering, and nothing in the question says so. Keyword
|
||||||
the question. Keyword retrieval cannot bridge that and neither can a
|
retrieval cannot bridge that and neither can a threshold. The candidates are a
|
||||||
threshold. The candidates are a semantic index over titles, or asking the
|
semantic index over titles, or asking the resident model for the article title
|
||||||
resident model for the article title rather than for keywords.
|
rather than for keywords.
|
||||||
|
|
||||||
`Response.Empty()` is still the whole gate. A wrong article that the search
|
`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
|
does return is still spoken. What this change buys is that the article is
|
||||||
|
|||||||
@@ -39,9 +39,13 @@ func TestLiveTopicBeatsTheSentence(t *testing.T) {
|
|||||||
before := firstTitle(ctx, c, q, book)
|
before := firstTitle(ctx, c, q, book)
|
||||||
topic := Topic(q)
|
topic := Topic(q)
|
||||||
after := ""
|
after := ""
|
||||||
if page, err := c.Article(ctx, TitlePath(book, topic), 400); err == nil && page.Text != "" {
|
for _, cand := range TitleCandidates(topic) {
|
||||||
after = page.Title + " (by title)"
|
if page, err := c.Article(ctx, TitlePath(book, cand), 400); err == nil && page.Text != "" {
|
||||||
} else {
|
after = page.Title + " (by title)"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if after == "" {
|
||||||
after = firstTitle(ctx, c, topic, book)
|
after = firstTitle(ctx, c, topic, book)
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
|
|||||||
@@ -61,6 +61,26 @@ func Topic(utterance string) string {
|
|||||||
return ""
|
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 {
|
func isCopula(w string) bool {
|
||||||
switch w {
|
switch w {
|
||||||
case "такое", "такой", "такая", "такие", "is", "are", "was", "were":
|
case "такое", "такой", "такая", "такие", "is", "are", "was", "were":
|
||||||
|
|||||||
@@ -49,3 +49,19 @@ func TestTitlePathEscapesAndUnderscores(t *testing.T) {
|
|||||||
t.Errorf("TitlePath = %q, want %q", got, want)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
```
|
||||||
@@ -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": "кто написал Войну и мир"}
|
||||||
Reference in New Issue
Block a user