1件読めるのに、検索すると0件。macOSのCPython 3.14.8、SQLite 3.54.1で確かめた。
自分は今日、RAGの検索用に、既存文書へFTS5を後付けするコードを動かした。本文まで読めるのに、MATCHでは出てこないんだよね。
件数の照合だけで済ませるのは危ない。
FTS5の表を作っても、既存文書は索引に入らない
content='docs'を指定すると、本文はdocsに置き、FTS5側には検索用の索引を持たせられる。
表を作っても、既存文書の索引は作られない。同期用のトリガーも、過去のINSERTにはさかのぼらない。
ややこしいのは、MATCHなしでFTS5の表を読むと、元のdocsから行を取得すること。count(*)の1件と、検索索引に載った1件は、別々に確認する必要がある。
Pythonで再現し、rebuild後の更新まで試す
fts_check.pyに保存し、python3 fts_check.pyで実行する。FTS5入りのSQLiteが必要。追加パッケージ不要、メモリ上で完結する。
import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE docs(id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO docs VALUES(1, 'agent search');
CREATE VIRTUAL TABLE search USING fts5(
body, content='docs', content_rowid='id'
);
CREATE TRIGGER docs_ai AFTER INSERT ON docs BEGIN
INSERT INTO search(rowid, body) VALUES(new.id, new.body);
END;
CREATE TRIGGER docs_ad AFTER DELETE ON docs BEGIN
INSERT INTO search(search, rowid, body)
VALUES('delete', old.id, old.body);
END;
CREATE TRIGGER docs_au AFTER UPDATE ON docs BEGIN
INSERT INTO search(search, rowid, body)
VALUES('delete', old.id, old.body);
INSERT INTO search(rowid, body) VALUES(new.id, new.body);
END;
""")
def hits(word):
return db.execute(
"SELECT rowid FROM search WHERE search MATCH ? ORDER BY rowid",
(word,),
).fetchall()
print("rows:", db.execute("SELECT count(*) FROM search").fetchone()[0])
print("before:", hits("agent"))
db.execute("INSERT INTO search(search) VALUES('rebuild')")
print("rebuilt:", hits("agent"))
db.execute("UPDATE docs SET body='retrieval search' WHERE id=1")
print("updated:", hits("agent"), hits("retrieval"))
db.execute("DELETE FROM docs WHERE id=1")
print("deleted:", hits("retrieval"))
db.close()
実行結果。
rows: 1
before: []
rebuilt: [(1,)]
updated: [] [(1,)]
deleted: []
rebuild後、agentが検索に出た。更新後は古い語が0件、新しい語が1件。削除後は新しい語も0件。
検索語は英単語にした。日本語の単語分割まで混ぜると、切り分けが増えてしまうので。
初回の索引作成と、その後の同期をそろえる
rebuildは既存文書から索引を作り直す。トリガーは、作成後のINSERT・UPDATE・DELETEを索引へ反映する。
更新時はold.bodyで古い語の登録を消し、new.bodyを登録する。新しい本文を足すだけだと、変更前の語が索引に残る。削除用の特殊なINSERTに、変更前の値を渡すのがポイント。
移行時は文書の書き込みを止め、トリガー作成とrebuildを済ませたい。索引全体を作り直すため、大きな表では所要時間を別途測る。今回の1文書では見積もれない。
検索の確認を一つ足す
RAGで参照が抜けたら、まず検索語から文書IDが返るかを確かめたい。今回は1文書の検索だけで、件数照合が見逃す索引の欠落を拾えた。導入時に加え、更新・削除後も検索結果を確かめるテストを残す。文書の追加経路が増えるほど、この同期の確認が効いてくると見ている。