--- title: "問い合わせが Discord・メール・アプリの3方向から来て見逃しそうだったので、AIに「整理係」をやらせた話" description: "個人開発のデスクトップアプリに、Discord・メール・アプリ内報告の3方向から問い合わせが来るようになった。件数はまだ少ないが、このままでは見逃す。そう思って早めに全部を1つの「案件」にまとめ、AIに整理と一次回答をさせ、最終判断だけ人が押す仕組みを2週間で組んだ記録。検索精度の数字、モデル選びで踏んだ穴、費用の桁の読み違いまで、盛らずに書く。" url: https://ryuuneko.com/blog/kakiniwa-intake-ai-support published: 2026-09-11 20:27:46.68603 updated: 2026-09-11 20:27:46.68603 --- 私は「[書き庭(かきにわ)](https://kakiniwa.jp/)」という、茶番劇やボイスドラマの台本を書くためのデスクトップアプリを個人で作って配っています。前に書いた AI 駆動開発の記事のとおり、実装の大半は AI(Claude)にやらせているアマチュアです。 アプリを配り始めると、当然、問い合わせが来ます。これはうれしいことなのですが、来る場所がバラバラでした。 - **Discord** の公認サーバー(質問・要望・不具合報告の3つのフォーラム) - **サイトのお問い合わせフォーム**(メール) - **アプリ内の「エラー報告」ボタン**(クラッシュ記録つき) 同じ人が Discord で報告して、アプリからも報告を送って、追加でメールが来る。どれが同じ件なのか、どこまで返事したか、直したのかどうか。件数そのものはまだ多くありません。対応に追われていたわけでもない。ただ、**3つの受信箱を目で突き合わせる**やり方は、件数が増えた瞬間に必ず見逃しを生みます。溜まってから直すより、溜まる前に一元化しておこう、というのが動機です。 この記事は、それを2週間ほどで「1つの案件表 + AI の整理係 + 人が最終ボタンを押す」形に組み直した記録です。結果として、3か所に散らばっていた問い合わせを1画面で管理し、AI が分類・検索・下書きまで行い、人は確認して送るだけになりました。 --- ## 先に決めたこと: 正本は1つ、窓口は何個でもいい 最初に決めたのは設計というより**優先順位**でした。 1. **案件の正本はデータベース(Cloudflare D1)1つ。** Discord も Web もアプリも「窓口」でしかない 2. Discord が落ちていても案件は作れて読める 3. AI は「整理」と「一次回答」まで。**利用者に届く最終文を人の確認なしに送らない** 3番目は、この種の仕組みで一番大事だと思っています。AI は自信満々に間違えるので(前の記事で書いたとおり)、間違えても取り返しがつく場所にしか置かない。整理と提案は AI、送信ボタンは人。 ![3つの窓口(Discord・サイトの問い合わせ・アプリ内報告)が同じ関数を通って cases 表に落ち、AI が整理係、人が送信ボタンを担当する図](/blog/uploads/kakiniwa-intake-overview.svg) *▲ 全体像。窓口は増えていいが、正本は cases 表 1 つ。AI は整理まで、送る口は人の操作にしか付いていない。* 窓口がどこであっても、最終的には同じ関数を通って `cases` という表に1行入ります。メールフォームからの問い合わせも、Discord の新スレッドも、アプリの報告に続けて書かれた状況説明も、全部同じ口です。運営側のダッシュボードには「案件」タブが1つあるだけで、そこに全部並びます。 --- ## 受付の流れ Web の受付ページは、こう動きます。 1. 利用者が自由文で書く(画像も4枚まで添付できる) 2. AI が本文を読んで、**分類**(質問 / 要望 / 不具合)・**要約**・**追加で聞くべきこと**を返す 3. 質問があれば、選択式のチップで聞く(OS や版などの定型は、証拠があれば機械的に省く) 4. **文書から答えが出せる質問なら、出典つきで回答を出して「解決した/しなかった」を選んでもらう** 5. 解決しなければ受付番号(K-7F3QX のような5文字)を発行して案件になる ![受付の流れ。自由文→AIが整理→足りない所を聞く→文書から答える。解決すれば案件にならず、解決しなければ受付番号を発行して案件になる](/blog/uploads/kakiniwa-intake-flow.svg) *▲ 受付の流れ。文書で答えられる質問はその場で終わり、残ったものだけが案件になる。* 4番が肝です。実際に来る質問の多くは「Mac でも使えますか」「表情を途中で変えたい」のような、**マニュアルに答えが書いてあるもの**でした。そこで、利用者向けの文書(機能一覧・ガイド・FAQ・LP・更新履歴)を検索して、上位の数件を AI に渡して答えさせています。いわゆる RAG です。 Discord 側も同じロジックを通します。常駐 BOT は Gateway でスレッドの動きを拾って Worker の同期口を叩くだけで、整理も回答も Worker 側です。BOT が落ちても、5分おきの cron が REST で差分を拾うので取りこぼしません。 --- *ここからしばらく、検索精度やモデル選びなど少し技術寄りの話が続きます。仕組みの結論だけ知りたい方は「『人が押す』を機械で強制する」まで飛ばしても話はつながります。* ## 検索の精度を数字で追った RAG は「入れたら賢くなる」ものではなく、**検索が外れれば AI は堂々と的外れな出典を出す**ので、評価セットを先に作りました。代表的な質問を30問ほど手で用意して、正解の文書を人が決め、Recall@5(上位5件に正解が入る割合)と MRR を vitest で測る。CI で毎回回ります。 | 段階 | Recall@5 | MRR | |---|---|---| | 語彙検索(BM25・bigram) | 0.881 | 0.756 | | + Contextual Retrieval(各チャンクに文脈を前置) | 0.905 | 0.755 | | + ベクトル検索(埋め込みモデル)とのハイブリッド | **0.976** | **0.861** | 試して**捨てたもの**も書いておきます。 - ひらがな bigram を足す → MRR が 0.756 から 0.669 に落ちた - クエリ書き換え(口語→公式語彙)を順位融合 → 0.905 から 0.881 に下がった - 別の埋め込みモデル → 0.929 で伸びず。512 トークン上限で長いチャンクが落ちる 語彙検索だけで残った外れ4問は「Mac でも使える?」「表情を途中で変えたい」「他のソフトの台本」「同じ家の別 PC」で、どれも**利用者の言葉と文書の言葉が違う**類でした。これはベクトル検索で3問拾えるようになり、残り1問は文書側の書き方の問題だと分かりました。検索を直すより文書を直す方が早いこともあります。 ベクトルは D1 や Vectorize に入れず、埋め込みを int8 に量子化した JSON(約 480KB)を Worker に同梱して総当たりしています。文書は数百チャンクなので、これで十分速い。**正本は Git の文書だけで、埋め込みも索引も生成物**という形にしておくと、埋め込みモデルを変えても `npm run rag:rebuild` で作り直せます。 --- ## モデル選びで踏んだ穴 AI の部分は、さくらのAI Engine(OpenAI 互換 API)を使っています。国内で、オープンウェイトのモデルが選べて、無料枠があるためです。個々のモデル名はここでは伏せます(どれを使うかより、どう分けるかの話なので)。 ### 費用の桁を間違えた 最初に費用を試算したとき、単価の単位を読み違えていました。料金表は「**10,000 トークンあたり**」の税込単価で、私はそれをもっと小さい単位だと思って計算し、実際より桁違いに高い見積もりを出していました。 受付1回の入力は約 21K トークン(機能カタログを丸ごと同梱しているため)で、整理係に使っている 30B 級のモデルなら **1回およそ 0.55 円**。費用は制約になりません。むしろ制約になるのは後述の「出力が崩れる」の方でした。なお、次の節の画像あり/なしの比較は、機能カタログを除いた別の呼び出しで測った値です。 ### 画像を付けると出力が崩れる 添付画像を読ませたくて、本文と一緒に画像をモデルに渡していました。すると整理係のモデルが `S-1000000…` のような壊れた JSON を返すことが3回中3回あった。画像対応の別モデルに替えると今度は JSON を長々と書いて max_tokens に当たる。 実測してみると入力側はほぼ関係なく(画像つき 6.3K、文字だけ 3.8K トークン)、**問題は出力側**でした。そこで役割を分けました。 - **画像係**(画像対応モデル): 画像1枚につき短い呼び出しで「関係あるか / 何が写っているか」だけ文章にして保存 - **整理係**(文章専用の中型モデル): 文字だけを読む。画像は「画像係の文章」として渡す - **回答係**(別の大型モデル): 回答文だけ別モデル。整理係と並列に呼ぶ ![前は本文と画像をまとめて1つのモデルに渡して JSON が崩れた。後は画像係が画像を文章にし、整理係と回答係が文字だけを短く受け取る図](/blog/uploads/kakiniwa-intake-roles.svg) *▲ 係を分けた後の呼び方。画像は「画像係の文章」として整理係に渡る。* これで崩れがゼロになり、3回中2回は「追加質問なしで即受付」になりました。**1つのモデルに全部やらせるより、係を分けて短く呼ぶ**方が安定するというのは、この件で一番学んだことです。 ### 順番を間違えて頓珍漢な質問をしていた もっと恥ずかしい穴もありました。整理を先に走らせ、画像のアップロードを後にしていたので、AI は画像を見ないまま「どんな文言でしたか?スクショをください」と、**添付されているスクショを要求**していました。順番を入れ替えるだけで直った類ですが、テストでは見つからず、実物に触って初めて分かりました。 ### 「行番号機能はありますか?」に黙ってうなずく 書き庭に行番号表示はありません。存在しない機能を聞かれたとき、モデルによっては「はい、あります」と**黙って受け付ける**ことがあり、試した大型モデル 2 つでは実測でそれが出ました。整理係を今のモデルで確定したのは、この弱点が出なかったからです。 さらに、機能の有無を聞かれたら必ず回答し、カタログに無ければ「機能一覧では見当たりませんでした。ご要望として受け付けます」と返して分類を「要望」に倒す規則にしました。ただし、この規則を system プロンプトに書くだけでは、34K 字のカタログの後で薄れて、**規則を守れたのは 3 回中 0 回**でした。user メッセージの末尾に「最後に確認:」と念押しを足して 3 回中 3 回になった。長い文脈の後では規則を近くに置く必要がある、という話です。 --- *技術寄りの話はここまで。ここからは運用の話に戻ります。* ## 「人が押す」を機械で強制する 「AI は提案まで、送信は人」を方針にしても、運用でなし崩しになるのが普通です。なので、**仕組みとして送れないように**しています。 - 受付後の追加質問は Worker 内の LLM ではなく、開発用の AI(Claude)が `case.mjs draft` で**下書き**として置く。下書きは利用者から見えない - 公開する口は CLI に存在しない。ダッシュボードで人が「この下書きを送る」を押したときだけ、利用者のチャットとメール(Discord ならスレッド)に出る - 受付 AI の出した回答は、**出典が実在する文書に照合できたときだけ**利用者に見せる。照合に失敗したら捨てて Worker ログに残す - AI が立てた原因の仮説(内部の技術メモから引いたもの)は利用者には見せず、案件の中に保存するだけ 最後の1つは、内部文書(設計メモ・ソースコードの注意書き 3,400 箇所から切り出した 1,500 件)を「技術知識の第2層」として AI に渡しているためです。「話者プルダウンを開くと画面が真っ白になる」という報告に対し、仮説「右クリックメニューとプルダウンの重なり」と、確認用の質問「設定→エラー診断に E_WEBVIEW_CRASH はあるか」を出してきたときは、正直ちょっと感動しました。ただ、この仮説は外れることも多いので、利用者には出しません。開発者が読む材料です。 --- ## 細かいけれど効いたこと - **受付番号をランダムにした。** 連番だと Discord に貼られた番号から件数と勢いが読めてしまう。0/O/1/I/L を除いた 31 文字 × 5 桁 - **アプリの報告番号との紐づけ。** 利用者が Discord に「#12 の件です」と書けば、実在する報告だけを案件に結びつけ、その報告に同梱された OS・版・直前の操作を「証拠」として AI に渡す。証拠があれば OS や版を聞かない - **書き込みはメール確認済みだけ。** 秘密 URL のチャットは誰でも読めるが、書けるのは確認コードを通した人だけ。確認コードは平文で保存せず SHA-256 でハッシュ化し、有効期限 15 分・試行 5 回まで - **解決済みの案件を FAQ 候補に収穫する。** Discord で done になった案件から「最初の質問」と「開発者の最後の返答」を拾い、ダッシュで採用すると文書に入り、RAG が再生成される。答えた分だけ次の検索が当たるようになる - **送信中にスピナーと秒数を出す。** AI 整理に 6〜30 秒かかり、「固まった」と思われていた --- ## 実際に出た穴(本番前の実機で) wrangler dev と Docker の BOT を手元でつないで実験サーバーで一巡させたところ、テストでは出なかった穴が2つ出ました。 1. 発言0件のスレッドで空の batch を D1 に投げ、本物の D1 だけが「No SQL statements detected」で落ちた(偽の D1 は通していた) 2. 同期がロックを解かずに終わり、BOT からの即時同期が 4 分見送りになっていた どちらも、偽物の動作が本物より寛容だったのが原因です。前の記事の「フェイクが実装ロジックを再実装しない」は、ここでも当たりました。 もうひとつ、エージェントに書かせたテストが見つけたバグ。ダッシュボードの API 呼び出しの第2引数を「送るデータ」だと思って渡していたら、実際は fetch のオプションで、**POST が GET になっていた**。返信が送れない状態で、画面上は成功に見えていました。 --- ## いま動いているもの 本番には 2026 年 9 月頭に出しました。Discord の公認サーバーの3フォーラム、サイトのチャット受付、アプリの報告ボタンの3経路が、1つの案件表に落ちています。運営側は案件一覧を見て、下書きを読んで、送るボタンを押すだけです。 やらなかったこともあります。 - **コード全体の RAG はやらない。** 設計メモの層と、ソースの注意書きの索引で止めた。深い切り分けは受付でやらず、開発用 AI が案件を読んで行う - **受付での追加質問は2巡まで。** 実際のクラッシュ報告のスレッドを見返すと10往復あったが、それは受付ではなく調査の会話で、そこは人がやる - **重複の束ね(同じ版・同じクラッシュ)**と、評価セットを実際の問い合わせに入れ替えるのは、これから --- ## まとめ - 窓口は増えていい。**正本を1つにして、全部そこへ落とす** - RAG は評価セットを先に作り、**数字で足し引きを決める**。捨てた工夫は捨てた理由ごと残す - LLM は**係を分けて短く呼ぶ**。1つに全部やらせると出力側で崩れる - 「AI は提案まで」を方針にするだけでは足りない。**送る口を人の操作にしか付けない** - 偽物のインフラは本物より寛容になりがち。最後は本物につないで一巡させる 問い合わせを AI に丸投げしたのではなく、**整理と検索という退屈で網羅性が要る部分**だけを AI に渡して、判断と送信を人に残した。前の記事で書いた「AI が得意なこと・苦手なこと」の線引きを、そのまま運用に持ち込んだ形です。