本記事は Speee Advent Calendar 2025 13日目の記事です。 昨日の記事はこちら
はじめに
こんにちは、Speee リフォームDX事業本部でテックリードをしている黒須です。
昨日は同プロジェクトの PdM である嶋さんが「AIで営業データの構造化を完全自動化する 〜プロンプトチューニング編〜」を公開しました。 嶋さんの記事では、泥臭いプロンプトチューニングについて語られています。
対して基盤編である本記事では、その裏側でエンジニアがどのようにリアルタイム処理のパイプラインを構築し、API コストやレスポンス速度の課題を解決したかについてお話しします。
目指す水準: 通話が終わった瞬間、データ入力が完了している状態
私たちが運営する「ヌリカエ」などのサービスでは、カスタマーサクセス (CS) がユーザーと電話で話し、その内容 (物件情報、予算、要望など) をシステムに入力しています。
しかし、通話しながら正確にデータを入力するのは難易度が高く、通話終了後に記憶を頼りに作業する時間 (ACW: After Call Work) が発生していました。
そこで私たちが目指したのは、 「通話が終わった瞬間に議事録が完成しており、人間が一切修正することなく、そのまま施工業者に送信できる水準」です。
オペレーターは会話に集中し、通話が終わった瞬間には、完璧なデータが出来上がっている。 これを実現するため、音声認識と LLM を組み合わせた自動化プロジェクトを始動しました。
立ちはだかる3つの技術的壁
「音声をテキスト化して LLM に投げるだけ」と思われがちですが、実運用に乗せるには以下の高いハードルがありました。
- プロンプト改善のボトルネック: 複雑な業務要件をエンジニアだけで調整しきれない。
- API コスト: 常に音声を解析し続けると、利用料が事業収益を圧迫する。
- リアルタイム性と文脈の維持: 長時間の通話をどうやってリアルタイムに処理するか。
これらをどう突破したのか、順に解説します。
運用設計: PdM が高速でプロンプトを改善できる基盤構築
まず着手したのは、開発の「分業体制」を作ることです。 LLM アプリの精度はプロンプトに依存しますが、エンジニアがコード内のプロンプトを修正・デプロイしていては検証サイクルが回りません。
そこで、PdM や Biz サイドのメンバーが GUI 上でプロンプトを調整し、即座に大量の過去データでテストできる環境を構築しました。
- 実行環境: 社内に展開されているセキュアな Dify
- データ基盤: BigQuery + Google SpreadSheet
エンジニアは Dify の API を実行するパイプラインの構築を行い、プロンプトの調整とテストはドメインエキスパートに委譲しました。 この仕組みにより、プロンプトはバージョン40を超える改善を短期間で回すことができました。
コスト戦略: API 利用料を 1/4 に圧縮した工夫
通話中、リアルタイムに画面を更新するためには、1分ごとに LLM を実行する必要があります。 しかし、愚直に実行すると API 利用料(トークン課金)が膨大になります。
工夫1: 通話開始10分間は実行しない
通話データを分析したところ、冒頭は挨拶や本人確認が主であるため、開始10分以降から LLM に読み込ませても、施工企業に送る十分な議事録が作成できることがわかりました。 そこで、「通話開始から10分間は LLM を実行しない」というロジックを実装することに決めました。
工夫2:差分トランスクリプト方式
毎回「通話の全文」を送るのではなく、「前回生成した JSON + 前回からの差分テキスト」を送る方式に変更しました。 これにより、実行のたびにすべての通話文字起こしデータを送信する必要がなくなり、1リクエストあたりのトークン量を大幅に圧縮しました。
これらの工夫により、当初の見積もりと比較してランニングコストを約1/4まで削減することに成功しました。
実装詳細: 文脈を維持する「差分実行」ロジック
差分トランスクリプト方式を採用したことによって、「文脈の断絶」という課題が発生しました。 例えば、CS からの「築年数は?」という質問に対し、ユーザーが「えーっと、15年くらいかな」と答えている途中で LLM が実行されると、次の実行時には質問の意図 (築年数の話であること) が失われてしまいます。
- 1分目: CS「築年数は何年ですか?」→ AI「OK!
{ age: null }」 - 2分目: ユーザー「えーっと、15年です」→ AI 「何が15年だろう?」
解決策: Pending 判定の実装
LLMに「回答が完了しているか、継続中か」を判定させ、Pending(継続中)と判断された項目を次回の実行コンテキストに引き継ぐ仕組みを実装することで解決しました。
- 1分目: CS「築年数は何年ですか?」→ AI「OK!
{ asking: age, age: null }」 - 2分目: ユーザー「えーっと、15年です」→ AI 「OK!
{ asking: null, age: 15 }」
これにより、LLM はあたかも通話全体を覚えているかのように振る舞いつつ、処理対象は「直近1分のテキスト」だけで済むため、システムのシンプルさを維持しつつ精度を高めることに成功しました。
パフォーマンス:10秒以内の応答速度の実現
CSが通話中に AI の出力を確認できるよう、レスポンス目標は10秒以内としました。
当初は項目ごとに特化した LLM を用意する構成 (LangGraph など) も検討しましたが、検証の結果、推論時間が長く目標タイムをオーバーすることが判明しました。
解決策:シングルショット構成
今回は「複雑な推論」よりも「速度」を優先し、最終的には「Thinking mode オフ + 単一 LLMでの JSON 出力」というシンプルな構成を採用しました。
実装には langchainrb を利用しています。 langchainrb は LangChain の公式 Gem ではありませんが、Ruby 環境で LLM を扱うにあたって必要な機能が揃っていたため採用しました。
結果、平均して5秒程度でレスポンスされるようになり、オペレーターが会話のリズムを崩さずに画面を確認できる UX を実現しました。
おわりに
システム構成
- 通話確立から10分経ったら、1分ごとのポーリングを開始
- Rails サーバーで Amazon Connect から文字起こし取得 + LLM を実行する Job を発火
- ActionCable 経由でフロントエンドに反映

まとめ
本プロジェクトでは、「LLMに何をさせるか(Prompt)」と同じくらい、LLMをどう動かすか(Architecture)が重要でした。
- プロンプト改善を PdM でも行えるようにすること。
- ステートレスなLLM APIに対し、文脈を管理する仕組みを導入すること。
- ビジネス価値の低い時間帯の処理をスキップする勇気を持つこと。
これらを組み合わせることで、高精度かつ低コストな自動化を実現できました。
現在は最終確認フェーズで、一部メンバーでの本番稼働を行っています。 見立てていた成果が出ていることを確認しており、今後は全メンバーへの展開を進める予定です。
今回のプロジェクトで得た不確実性の高い技術を実務に落とし込むノウハウを活かし、より攻めの AI 活用にも挑戦していきたいと考えています。
そして、Speeeでは一緒にこの変革期を楽しんでくれる仲間を募集しています!
Speeeでは様々なポジションで募集中なので「どんなポジションがあるの?」と気になってくれてた方は、こちらチェックしてみてください。
この記事をきっかけに興味を持っていただけたら嬉しいです!