ja-furigana——文脈を見て「正しい読み」を当てるRust製OSSがTTSの読み間違い対策に効く

日本語の文章に、文脈を見て「正しい読み」を当ててくれるOSS ja-furigana を動かしてみた。「一日署長」を「いちにちしょちょう」、「明日」を「あした」と読み分けてくれる。HTTPサーバーも内蔵しているので、Claude CodeやCodexからPOSTすれば正規化済みの読みがJSONで返ってくる。TTS(音声合成)の読み間違い対策として、かなり本命に近いライブラリだと思う。

ja-furiganaとは

Discord読み上げBot「ずんだー」で使われてきたふりがな変換エンジンを、Rust製OSSとして切り出したもの。形態素解析(Lindera)だけでは届かない「文脈に応じた読み分け」を、データ駆動の辞書・ルールで補うのがコンセプト。

設計上のポイントはエンジンと辞書の分離:

リポジトリ中身ライセンス
ja-furiganaライブラリ本体 + CLI + HTTPサーバー(Rust)MIT
ja-furigana-dictTOML形式のふりがな辞書MIT

辞書がTOMLの手書きファイルなので、Rustを書けなくても辞書PRだけで貢献できる。誤読を見つけたら該当TOMLを直してPRを出せば、マージ後の次リリースに自動的に含まれる仕組み。

なお開発自体も「AI駆動開発」で、Rustコードの実装・テスト生成・ドキュメント化はAI(Claude Opus 4.7)、アーキテクチャ設計・スキーマ定義・辞書PRレビューは人間、という分担で作られているとブログに明記されている。

実際に試した読み分け

手元で試して効いた例:

入力出力(読み)コメント
一日署長いちにちしょちょう「ついたち」「いちにち」の文脈判定が必要な難所
明日あした「あす」「みょうにち」と多義
今日きょう / こんにちaccent/analyzeモードで候補と重み付きで返る(後述)

一方で外す語もある:

入力出力(読み)正しくは
辛いラーメンつらいラーメンからいラーメン

「辛い」は「からい/つらい」の多義語で、後続の名詞(ラーメン)を見れば「からい」一択なのだが、現状は判定できなかった。ただしこの種の文脈ルールは ja-furigana-dict[[kanji]] ブロックや文脈ルールTOMLで改善されていく構造なので、辞書PRで直せる類いの弱点ではある。

インストールと基本の使い方

CLI

cargo install ja-furigana-cli
# GitHub ReleasesからOS別バイナリの取得、Dockerでの実行も可能

echo "今日は3月14日です。" | furigana --mode tts
# => きょうは さんがつじゅうよっか です

Rustライブラリとして

[dependencies]
ja-furigana = "0.1"
use furigana::Furigana;

fn main() {
    let furigana = Furigana::new();
    let result = furigana.to_hiragana("漢字を平仮名に変換します。");
    println!("{}", result);
    // => かんじをひらがなにへんかんします。
}

crate名は ja-furigana だが、use する名前は furigana、CLIのcrateは ja-furigana-cli、バイナリ名は furigana。最初ちょっと混乱するので注意。

辞書の取得

エンジン本体には熟語辞書が同梱されないため、furigana dict pull でGitHub Releasesから辞書(tar.gz、SHA-256検証付き)を取得する。辞書なしでも形態素解析ベースで動くが、熟語ヒット・助数詞・文脈ルールが無効のdegraded modeになる。

HTTPサーバー——Claude Code / Codexから叩ける

ここが今回いちばん刺さったところ。furigana serve でローカルHTTPサーバーが立つ(デフォルト 127.0.0.1:8000)。

furigana serve                       # 127.0.0.1:8000
furigana serve --auto-pull           # 起動前に辞書を自動pull
FURIGANA_TOKEN=<secret> furigana serve   # 認証有効化

エンドポイント

# ヘルスチェック(認証不要)。dict_sizeが0なら辞書未配置
curl http://127.0.0.1:8000/healthz
# => {"status":"ok","dict_size":44354}

# GET でもPOSTでも叩ける
curl 'http://127.0.0.1:8000/furigana?text=灰桜の道&mode=ruby'
# => {"result":"{灰桜|はいざくら}の{道|みち}","mode":"ruby"}

curl -X POST http://127.0.0.1:8000/furigana \
  -H 'Content-Type: application/json' \
  -d '{"text":"一日署長を務めた","mode":"tts"}'

普通のHTTP APIなので、Claude CodeやCodexのBashツールからcurlでPOSTするだけで正規化済みの読みがJSONで返る。「読み上げ前にテキストをja-furiganaに通す」パイプラインがAIエージェントから組みやすい。リポジトリにはPython / Node.js / curlの最小クライアント例も同梱されている。

modeとパラメータ

mode出力
tts(デフォルト)TTS整形ひらがな(句読点をポーズに置換)
hiraganaプレーンひらがな
ruby{漢字|ひらがな} 形式のルビ
kanji入力そのまま(no-op)
romaji / romaji-kunreiヘボン式 / 訓令式ローマ字
analyze採択された読み + token単位の詳細(候補・境界)
accentアクセント句 + 曖昧語の代替候補つき中立JSON

主なパラメータ: text_b64(URL-safe base64入力)、short_pause / long_pause(TTSのポーズ文字列)、segmented(分割配列の同梱)、debug(処理時間の同梱)。最大入力長は10,000文字。認証は X-API-Key ヘッダまたは Bearer トークン。

地味に良い仕様として、辞書にない英単語はASCIIのまま読みなしで返す(Linderaに渡さないので誤読しない)。外来語辞書にある語はカタカナ化される:

curl 'http://127.0.0.1:8000/furigana?text=Kubernetesが安定&mode=ruby'
# => {"result":"{Kubernetes|クバネティス}{が|が}{安定|あんてい}","mode":"ruby"}

accentモード——読みの判断をAIに委ねられる

面白いのが accent(と analyze)モード。「今日(きょう/こんにち)」のような曖昧な語に対して、採択した読みに加えて、採択しなかった候補を重み付きで返してくれる

レスポンスのtoken構造(型定義より):

{
  "surface": "今日",
  "reading": "きょう",          // 採択された読み
  "accent_phrases": [...],      // アクセント句(モーラ数・アクセント核)
  "ambiguous": true,            // 同一位置に代替候補があるか
  "alternatives": [             // 採択されなかった候補(weight降順)
    { "reading": "こんにち", "sense": "今日的、の意", "weight": 30 }
  ]
}

これが効くのは、最終判断をLLMに委ねるパイプラインを組むとき。ja-furiganaが「機械的に決められるところ」を高速に確定させ、ambiguous: true の語だけ候補と重みを添えてLLMに渡せば、LLM側は曖昧箇所の裁定に集中できる。全文をLLMに読ませて読み仮名を振らせるより、速くて安くて再現性が高い。

仕組み——Smart engine + 6 provider

内部は「6つのproviderが読み候補を出し、Viterbi風のパス選択で最良の読み列を選ぶ」構造。

  1. テキスト正規化: Unicode NFKC + 異体字マッピング(髙→高)
  2. 候補生成(6 provider):
    • ProtectToken: URL / Email / 絵文字を保護
    • AlphabetPassthrough: 外来語辞書(Kubernetes / Docker等)
    • DictBridge: 辞書マッチ(熟語 / 単漢字 / [[kanji]] 文脈ブロック)
    • NumberCandidate: 数字・助数詞・日付・時刻・SI単位(連濁「三羽→さんば」促音「六匹→ろっぴき」も処理)
    • Odoriji: 踊り字「々」の連濁処理(人々→ひとびと)
    • LinderaFallback: 形態素解析のセーフティネット
  3. パス選択: 候補にband(信頼度の階層)を付けて比較。辞書完全一致(1000) > 数字・助数詞(950) > Lindera漢字熟語(150) > 単漢字デフォルト(100) > Linderaフォールバック(50)
  4. 後処理: モード別整形。TTSモードでは句読点をポーズに置換。文脈依存の読み補正(「小腹が空いた」を「すいた」に直す類い)もpost-passで適用

「辞書が最優先、形態素解析は最後の砦」という優先順位が明確で、誤読を見つけたら辞書に1エントリ足せば必ず勝つ構造になっているのが運用しやすい。

辞書——TOML手書き、52,500エントリ、PRで直せる

辞書は約52,500エントリ(0.1.0時点)。内訳は単漢字42,318・熟語7,949・文脈分岐の [[kanji]] ブロック1,139・異体字435・外来語168など。

→ 登録語句検索ページ(GitHub Pages)で辞書の中身をブラウザから検索できる。手元の語が登録済みかどうか、PRを出す前にここで確認できるのは親切。

辞書の更新経路も5通り用意されていて、個人運用なら furigana dict pull + 再起動か --auto-pull、無停止運用なら [auto_update] の定期polling(RwLock<Arc<Furigana>> のswapでダウンタイムなし)、外部からは POST /admin/reload(admin token必須)が使える。

弱点と「二段構え」運用

開発者自身がブログで明記しているとおり、52,500エントリはまだ小さく、人名・地名・新語・超高精度な文脈読み分けは苦手。手元でも「辛いラーメン→つらい」のような取りこぼしがあった。

そこで現実解としては、前にやったAivisSpeechのユーザー辞書登録自動化と組み合わせる二段構えが良さそうだと思っている:

  1. 大半をja-furiganaで正規化: 一般語・数字・日付・助数詞・踊り字など、辞書とルールで機械的に決まるものはここで確定
  2. 残りをTTS側のユーザー辞書で補完: 自分のドメイン固有語(会計用語など)や、ja-furiganaがまだ外す語は、AivisSpeech / VOICEVOX側の辞書登録でピンポイントに潰す

ja-furigana側で直せるものは辞書PRを出せば全ユーザーに還元される、という出口があるのもOSSとして良い循環。

バージョンと開発ペース

  • 0.1.0 stable: 2026-05-12リリース。以降、公開API・TOMLスキーマ・CLI引数・HTTPレスポンス形式の互換性を維持(0.1.xはadditive only)
  • その後の開発ペースが速く、2026-06-11時点でcrates.io上の最新は0.1.9。0.1.4で「腹+空く」の文脈補正と波ダッシュ誤読の修正、0.1.5で辞書lookupの約200倍高速化(38ms→0.19ms)、0.1.8で数詞慣用語句(二十歳=はたち、明後日=あさって)の対応、とほぼ毎日改善が入っている
  • 0.2.0ではUniDicベースのpitch accentデータを使ったイントネーション機能(VOICEVOX AccentPhrase[] 互換出力など)が予定されている

TTSパイプラインを組むなら、まず furigana serve を立ててcurlで自分の文章を流してみるのがおすすめ。誤読を見つけたら検索ページで辞書を確認して、直せそうならPR、ドメイン固有ならTTS側の辞書へ、という流れが綺麗に回る。