プリントをLINEに送るだけで、AIが覚えてくれる。

学校から配られる紙のプリントを写真に撮ってLINEに貼ると、GPT-4o VisionがOCRし、内容をベクトルDBに保存します。あとから「運動会っていつ?」と聞けばRAGで答えを返してくれる——そんなサービス「プリントーク(printalk)」を個人開発したエンジニアの @tsubasa_ai_dev 氏が、2026年4月にZennで実装記録を公開しました。

この記事でわかること:

  • LINE + GPT-4o + Supabase pgvectorを組み合わせたOCR→RAGシステムの全体構成
  • RAG品質を高めるクエリ拡張・閾値フィルタ・リランキングの実装パターン
  • 幼稚園プリントのOCR精度問題とルールベース補強の手法
  • Supabase・Fly.io・LIFFで踏んだ運用の罠とその解決策
  • Claude Codeのサブエージェント並列化で開発速度を上げる方法

何を作ったか

「プリントーク」は、学校・幼稚園から配られる紙のプリントをLINEで一元管理するBotサービスです。写真をLINEに送ると以下の処理が走ります。

  1. GPT-4o Visionで日付・カテゴリ・本文を構造化して抽出
  2. text-embedding-3-smallで1536次元のベクトルに変換
  3. Supabase(PostgreSQL + pgvector)に保存

あとから「明日の持ち物は?」「参観日はいつ?」と聞くと、ベクトル検索でプリントを特定し、回答を返します。LINE友だち追加だけで使い始められ、アプリのインストールが不要な点も特徴です。

https://printalk.fly.dev

技術スタックの選定

「月額のランニングコストをゼロに寄せる。LLM体験が一番効く部分にだけお金をかける」方針でスタックを決めています。

APIはFastAPI(Python)を採用。async/await・Pydantic・自動OpenAPIドキュメントが揃い、一人開発でも品質を保ちやすい構成です。メッセージングはLINE Messaging API一択で、保護者に最も普及しているチャネルを選んでいます。

OCRはGPT-4o Visionを採用した理由が明確で、「日本語ひらがな・手書き・崩れた罫線に強いこと」を評価しています。ベクトルDBはSupabase(pgvector)で、Auth・Storage・DBが一体のため外部サービスを追加しなくて済みます。ホスティングはFly.io東京リージョンで、Dockerビルド一発でデプロイできます。管理画面はLIFF(LINE内ミニアプリ)で構築し、LINE外にアプリを出さない設計にしています。

RAG品質を上げた4段階パイプライン

初期実装はシンプルなもので、LINEの本文をそのままembeddingしてpgvectorで類似度検索するだけでした。類似度閾値は0.3。これが「全然使い物にならなかった」と著者は振り返ります。

「運動会」で登録したプリントが「体育祭」と書かれているためにヒットしない、給食だよりが混入する、3ヶ月前のプリントが直近より上位に来る——といった問題が出ました。

改善後の検索パイプラインは4段階になっています。

クエリ拡張: 「運動会」で検索すると「体育祭」「スポーツデー」も同時に検索するよう同義語マップを整備。双方向化がポイントで、「運動会→体育祭」だけでなく「体育祭→運動会」も登録します。

pgvector検索: 拡張したクエリでベクトル検索を実行。

閾値フィルタ: 閾値を0.3から0.45に引き上げて無関係な結果を除外。

リランキング: 直近30日のプリントには×1.5、カテゴリ一致には×1.3、締切7日以内のプリントには×1.4、未来の行事を含む場合は×1.2のボーナスを付与して並べ替えます。

「今聞きたいのはたぶんコレ」を上に持ってくる設計で、体感品質が一気に上がったと報告されています。

OCR精度: 幼稚園プリントへの対応

小学校のプリントは概ね問題なかったものの、幼稚園プリントでは精度が大幅に落ちました。「6がつ15にち」のようなひらがなの日付、「きゅうしょく」「こんだんかい」といったひらがな主体の語彙が原因です。

GPT-4oの出力をそのまま信じると、カテゴリ分類が event だったり unknown だったりブレます。対策として、LLMの出力を受けてルールベースで再判定するレイヤーを挟みました。

正規表現で「○がつ○にち」形式の日付を直接パースし、「きゅうえん(休園)」「こんだて」「きゅうしょく」などの幼稚園語彙をキーワードDictionaryに追加することで、カテゴリ判定の精度が体感2倍程度に上がったと述べています。「LLMは万能じゃない。ドメイン知識はコードで補う」という設計方針が、この実装に表れています。

運用で踏んだ6つの罠

デプロイ後に初めて気づいた問題が6つあります。

Supabase Free tierが1週間で自動pause: 無アクセスが約1週間続くとプロジェクトが自動停止します。GitHub Actionsのcronで月・木の週2回、/health/deep エンドポイントを叩くことで回避。浅い /health ではDBに触らないため効果がなく、DBに実クエリを投げるディープヘルスチェックを用意する必要があります。

Messaging APIチャネルにはLIFFを作れない: LINEのMessaging API用Botチャネルと、LIFFを置くLINEログインチャネルは別物です。「LIFF追加」ボタンが表示されず半日ハマったとのこと。正解はLINEログインチャネルを別途新規作成してLIFFを紐付けることです。

幼稚園プリントでのOCR精度低下: 前述のルールベース補強で対応。

高解像度画像によるOpenAI費用の増大: Pillowで長辺2048pxにリサイズしJPEG品質85で圧縮することで対応。

同時送信による無料枠の超過: TOCTOU(Time-of-check Time-of-use)問題への対策として、事前インクリメント+失敗時デクリメントのパターンで実装しています。

スマホ縦撮り画像がOCRで横向きに処理される: スマートフォンで撮影した画像はEXIFで回転情報が入っているだけで、バイナリ自体は横向きです。前処理で ImageOps.exif_transpose を適用しないとOCR精度が下がります。地味ながら体感に直結する問題です。

Claude Codeで開発を3倍速にした方法

今回の開発はほぼ全編、Claude Code(Claude Opus 4.6、1Mコンテキスト)との対話で進めました。特に効果があったのがサブエージェントの並列化です。

ローンチ直前に「法務ドキュメント作成」「Sentryの導入」「レート制限の実装」という独立した3タスクが同時に発生しました。1つのメインスレッドで順番にこなすとコンテキストが汚れる上に時間もかかります。そこでClaude Codeのサブエージェント機能で3タスクを別スレッドに並列投入し、それぞれが独立したファイルを操作して戻ってくる形にしました。

「競合が起きない粒度で並列化する」のがコツで、同じファイルを触るタスクを並列にするとマージ地獄になります。独立性を担保したタスク分解が前提です。

もう一つ効果があったのが、docstringに「なぜそうしたか」を残すスタイルです。Claude Codeは設計判断の理由をコメントに書くよう誘導してくれるため、2ヶ月後に自分でコードを読み返しても判断の経緯がわかる状態を保てています。

まとめ

プリントークが示す実装のポイントをまとめます。

RAGは閾値・同義語拡張・リランキングの3点セットで品質が決まります。「類似度検索すれば動く」という段階から一歩踏み込む必要があります。OCRの精度はLLMだけに任せず、ドメイン固有の語彙をルールベースで補強することで大幅に改善できます。

運用は実際に動かして初めてわかる問題が多く、特にSupabaseのpause仕様やLIFFのチャネル分離は事前に把握しておく価値があります。Claude Codeのサブエージェント並列化は、独立性の高いタスクに限定すれば開発速度に実質的な効果をもたらします。

サービスは現在、月30枚のOCRと月50回の質問が利用できる無料プランで運用中です。「紙のプリントをもう無くしたい」と思っている保護者や、LINE × LLMで何か作ってみたい個人開発者の参考になる記録です。