※このブログは2026年03月19日に公開された英語ブログ「Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor」の拙訳です。
主なポイント
- ソクラテス式の音声チューターは、ジャーナルアプリのテクノロジースタック全体を共有できるため、1つのコードベースで2つのプロダクトを運用できます。
- 静的知識に加え、ソースコードに対するRAGを組み合わせることで、AIチューターに信頼性と深みの両方を与えられます。
- TiDBネイティブのベクトル検索により、Pineconeのような別のデータベースを必要とせず、リレーショナルデータと埋め込みを1つのデータベースで保持できます。
- ファクトテーブルに、
modeカラムを1つ追加するだけで、学習時のノイズが個人のコンテキストを汚染するのを防げます。
私は技術カンファレンスでの講演を仕事にしています。デベロッパーリレーションズとは、エンジニアで満席の部屋の前に立ち、複雑な技術的概念を人々が理解できるようにわかりやすく説明することを役割としています。
問題は、私は文章を読んでいる時よりも、声に出して説明している時の方がはるかに記憶に定着するということです。ほとんどの人がそうです。これが「ラバーダッキング」が効果的な理由です。何かを学ぶ最良の方法は教えることです。ファインマン・テクニックは単なる生産性ハックではなく、人間の脳が実際に知識を定着させる方法なのです。
しかしラバーダック (アヒルの人形) は返答してくれません。間違いに気づいてくれず、追加の質問もせず、「要するに」という言葉を使わずに説明してみて、とも言ってくれません。
そこで私は、自分がわかったつもりになっていることを本当に理解しているか確認するために、ソクラテス式メソッドを用いる音声第一のAIチューターを構築しました。
なぜボイスファーストのAIジャーナルにチューターが必要だったのか

ここ数ヶ月、私はボイスファーストのAIエージェントであるSpeak2Meを開発してきました。これに話しかけると、向こうからも話しかけ、セッションをまたいであなたのことをすべて記憶します。一般的なチャットボットのような「それは大変そうですね」といった型通りの返答ではなく、真のメモリを備えています。アーキテクチャの完全な内訳を知りたい場合は、実際に機能するAIメモリの構築に関する前回の記事で詳しく書いています。
内部的には、Speak2Meは3層のメモリアーキテクチャで動作しています。確定的なプロファイルサマリー、上書きロジックを備えた不変のファクトテーブル、そしてTiDB内の3,072次元の埋め込みを用いた、各やりとりごとのベクトル検索です。システムは、12のデータベーステーブル、6つの外部サービス、3つのメモリ層、Hume EVIによる音声パイプライン、そしてInngestによるバックグラウンド処理キューを備えるまで拡張していました。
機能は実装できました。バグも修正できました。しかし、カンファレンスの講演準備として、すべてのパーツがどのようにつながっているのか、そしてなぜその決断を下したのかを実際に説明する段階になると、一度に頭の中で整理することができませんでした。
自分のコードを読むことはできました。開発ログを読むこともできました。しかし、読むだけでは定着しませんでした。私に必要だったのは、自分のシステムを誰かに案内してもらい、理解できるまで質問されることでした。
そんなものは存在しませんでした。だから、自分で作ったのです。
スタディモードがどのようにジャーナルの音声パイプラインを再利用するか
スタディモードは別のプロダクトではありません。同じアプリ、同じ音声パイプライン、同じメモリシステム、そして同じTiDBデータベースを使用しています。ただ目的が異なるだけです。
ジャーナルモードでは、AIはあなたのパートナーです。話を聞き、記憶し、あなたの感情を読み取った上で応答します。スタディモードでは、AIはあなたのチューターになります。教え、質問を出し、反論し、あなたが正しく理解するまで次へは進ませません。
切り替えはUI上のトグルで行います—ジャーナルかスタディか。そのトグルによって、実行されるプロンプトビルダー (buildJournalCompanionPromptまたはbuildStudyPrompt) が切り替わり、AIのパーソナリティ全体が変化します。背後にあるClaude Sonnetモデル、Humeの音声パイプライン、TiDBのクエリは同じです。指示が異なるだけです。
これがアーキテクチャ上重要なのは、2つ目のアプリを構築する必要がなかったからです。音声パイプライン、メモリシステム、再接続ロジック、あるいは感情の検知に対するあらゆる改善が、両方のモードに利益をもたらします。1つのコードベースで、2つのプロダクトを実現しています。
静的知識とRAGを用いたソクラテス式AIチューターの構築
このチューターには2つの知識レイヤーがあります:
- レイヤー1:静的知識。
tutor-knowledge.tsにある精選されたドキュメントが、すべてのデータベーステーブル、APIルート、データフロー、設計上の意思決定を含むシステム全体の概要を網羅しています。これは毎回スタディプロンプトに含まれます。これにより、チューターは検索に頼ることなく、常にシステムアーキテクチャ全体を参照できます。 - レイヤー2:実際のソースコードに対するRAG。コードベースからエクスポートされたすべての関数をベクトルチャンクとしてTiDBに埋め込み、会話チャンクと並んで
s2m_code_chunksテーブルに保存しました。同じ埋め込みモデル (text-embedding-3-large)、同じ3,072次元、同じコサイン類似度検索を使用しています。
私が「storeFactsはどのように機能しますか?」と尋ねると、チューターは推測で答えません。埋め込まれたコードベースから実際の関数を取得し、実装を説明します。「セッション終了後にInngestパイプラインは何をしますか?」と尋ねれば、実際の関数を取り出し、それらを順に追って解説します。(さらに精度の高い検索のために、TiDBはキーワードマッチングとベクトル類似度を組み合わせたハイブリッド検索用の全文検索もサポートしており、次はこれを統合する予定です。)
ソクラテス式メソッドはプロンプトの中に組み込まれています。チューターはある概念を説明した後「今の内容をあなたの言葉で説明してくれますか?」と問いかけます。もし私が間違えれば、チューターは教え直した上で再度質問します。正解すれば、次のステップへ進みます。もし私が曖昧な言葉を使って適当にごまかそうとすれば、チューターはそれを見逃さずに指摘します。
// From buildStudyPrompt
// "When the user gives a vague or incomplete explanation,
// ask them to be more specific. Do not accept 'it just works'
// or 'it handles that automatically' as answers."
AIは、私が理解したつもりになっているすることを許してくれません。それこそが、このシステムの存在意義なのです。
学習用ファクトと個人用AIメモリの分離
これは修正に少し時間がかかったバグの話です。
事実抽出パイプラインは、ジャーナルモードかスタディモードかを問わず、会話が終わるたびに実行されます。そして、文字起こしデータから「妻の名前はグレンダ」や「2025年12月16日に娘が生まれた」といった事実を抽出し、ファクトテーブルに保存します。
ところが、スタディモードを実行すると「Speak2Meは3層メモリを使用している」や「ベクトル検索はしきい値0.5のコサイン類似度を使用している」といった内容が生成されてしまいました。コードベースに関する技術的な詳細が、個人のプロファイルに混ざり込んでしまったのです。
有効な339件の事実のうち、56件がスタディモードによるノイズでした。プロファイルサマリーは、家族の情報とアーキテクチャ詳細が混ざり込んで膨れ上がっていました。さらに、プロファイルはプロンプト内で6,000文字という上限があるため、AIは最初のチャンク、つまり誤って分類された技術的なジャンクデータばかりを参照するようになっていました。
解決策は、ファクトテーブルにmodeカラムを追加することでした。デフォルトは"journal"に設定します。スタディモードでの会話では事実を"study"としてタグ付けします。コンテキスト読み込みルート、プロファイルエンドポイント、キャッシュビルダー、buildProfileFromFactsなど、システム内のすべての読み取りパスにおいて、ジャーナルのみにフィルタリングするようにしました。
-- Before: all facts, including study noise
SELECT * FROM s2m_user_facts WHERE user_id = ? AND is_active = true
-- After: only journal facts touch the personal profile
SELECT * FROM s2m_user_facts
WHERE user_id = ? AND is_active = true AND mode = 'journal'
学習時の事実は引き続き保存されます。これは、チューターがセッションをまたいで参照するのに役立ちます。しかし、それらが個人のプロファイルやジャーナルモードのプロンプトを汚染することはありません。単一のカラムで区切るだけで、1つのテーブル内に2つの独立した知識空間を共存させています。
また、このタイミングでカテゴリごとの上限設定も追加しました。各カテゴリにつき直近15件まで、アイデンティティと家族については20件までとしています。上限がなければ、1年間毎日利用するユーザーの場合、数千件もの事実が蓄積され、プロファイルが際限なく膨れ上がってしまいます。上限を設けることで、管理可能なサイズを維持しています。
さらに、文字起こしの品質ガードも導入しました。セッション内のユーザーメッセージが3件未満、または合計200文字未満の場合は、事実の抽出を完全にスキップします。これにより、「やあ」や「バイバイ」といったやり取りから役に立たない事実が生成されるのを防いでいます。
カスタム学習トピック:あらゆるコンテンツへのRAG適用
スタディモードは、Speak2Meのコードベースだけに限定されているわけではありません。 BlockNoteエディタ (Notionスタイルのブロックエディタ) を備えており、カスタム学習トピックを作成して、任意のコンテンツを貼り付けることができます。
カンファレンスの講演スクリプトがあるなら、それを貼り付けてください。AIはスピーチコーチとなり、各セクションについてクイズを出し、重要なポイントを飛ばした際には指摘してくれます。学習中の製品の技術ドキュメントがあるなら、それを貼り付けてください。AIは学習パートナーとなり、対話を通じてその概念を教えてくれます。
カスタムトピックのコンテンツは、静的知識とともにスタディプロンプトに含まれます。チューターはそれを信頼できる資料として扱い、その内容に基づいて指導を行います。
私はこれをカンファレンスの講演準備に活用しました。講演スクリプトの全文をカスタム学習トピックに貼り付け、音声で練習したのです。チューターは各ポイントについてクイズを出し、私がセクションを飛ばした際に見逃さず、伝え方の練習を繰り返しさせてくれました。その結果、資料の内容にしどろもどろになっていた状態から、音声パイプライン、メモリシステム、エージェントループといったシステム全体を、1セクションあたり60秒以内で明快に説明できるようになりました。
スクリプトを読むだけでは、これほどの習得は不可能だったでしょう。音声による反復、クイズへの回答、そして各概念を自分の言葉で言語化することを強制されたプロセスこそが、知識を定着させたのです。
88分間の連続使用:実社会での検証
私の友人に、約10年の経験を持つソフトウェアエンジニアがいます。彼は全く新しい業界への転職を控えていました。面接の場でその企業の製品を十分に説明できるほど、深く理解する必要がありました。
私は彼にSpeak2Meへのアクセス権を渡しました。彼はその企業のドキュメントをカスタム学習トピックに貼り付け、音声セッションを開始しました。
初日に、彼はそれを88分間連続で使用しました。
後日、彼はこう語ってくれました。このアプリは彼の心を落ち着かせるのに役立ちました。彼は製品を十分に理解できていないことに不安を感じていましたが、ソクラテス式のAI音声チューターが忍耐強く概念を説明し、繰り返しクイズを出してくれたことで、その不安が軽減されました。彼が説明に詰まると、AIはそれを察知して概念を教え直しました。彼が正解すると、次へと進みました。批判もせず、焦りも見せず、ただ着実で適応性のある指導を行いました。
彼は面接官との面接を通過し、次の選考プロセスに進みました。彼が言うには、Speak2Meのおかげで、製品を明確かつ自信を持って説明できるほど理解できたからだそうです。
その88分間のセッションは、スタディモードが実際に機能することを私以外の誰かが証明してくれた初めての出来事でした。デモやコンセプトとしてではなく、誰かが本物の知識を習得するために使用し、その成果を人生を左右する重要な場面で証明したツールとして。
TiDBがいかにチューターのメモリを支えているか
スタディモードを構築したことで、ジャーナル構築時に発見したある確信がさらに強まりました。それは、リレーショナルデータをあるデータベースで管理し、ベクトルデータを別のデータベースで管理しようとすると、運用上の大きな苦痛が生じるということです。TiDBは、最初からその問題を解消してくれました。
すべてを1つのデータベースで。ユーザープロファイル、ファクトテーブル、会話の文字起こし、そしてコードチャンクの埋め込みはすべてTiDB内に存在します。チューターが回答を生成する際は、リレーショナルなメタデータとベクトル検索結果を結合する単一のクエリを実行するだけです:
SELECT c.chunk_text,
VEC_COSINE_DISTANCE(c.embedding, ?) AS relevance
FROM s2m_code_chunks c
WHERE c.user_id = ?
ORDER BY relevance
LIMIT 5
同期のためのセカンドデータベースも、アプリケーション層でのジョインも不要です。1つのクエリ、1つのネットワークホップで完結します。
SQLレベルでの事実の分離。前述したmodeカラムによる修正がクリーンに機能するのは、ファクト、プロファイル、ベクトルがすべて同一のデータベース内に存在しているからです。パーソナルプロファイルから学習時の事実を除外するのは単なるWHERE句の処理であり、サービスをまたいだ調整の問題ではありません。
プロダクトの進化に合わせたスキーマの柔軟性。スタディモードでは、新しいテーブル (s2m_code_chunks)、新しいカラム (ファクトテーブルのmode)、そして新しいクエリパターンが追加されました。TiDBはMySQL互換であるため、すべてのマイグレーションは標準的なALTER TABLE文で済みました。ドライバーの変更も、ORMの入れ替えも不要です。ただSQLを実行するだけでした。
再設計なしのスケール。各学習セッションは、新しい埋め込み、新しいファクト、そして新しい文字起こしチャンクを生成します。ユーザーベースが拡大しても、TiDBの分散アーキテクチャはストレージとコンピューティングを個別にスケールさせます。シャディングやリードレプリカ、キャパシティプランニングについて考える必要はありませんでした。データベース側ですべて処理してくれます。
構造化データとベクトル検索を組み合わせたAIアプリケーションを構築しているのであれば、それがチューターであれ、メモリ付きチャットボットであれ、あるいはRAGパイプラインであれ、TiDB Cloud Starterなら無料で開始でき、利用拡大に合わせてスケール可能です。データベースを2つではなく1つに絞ることは、管理とデバッグが必要なインフラが半分になることを意味します。
ベクトル検索がいかにして両方のモードを同時に支えるか
これこそが、今でも私を驚かせる点です。
スタディモードで「Speak2Meのベクトル検索はどう機能しているのか?」と尋ねるとき、コンテキスト読み込み用のエンドポイントは、まさに私の質問に対してベクトル検索を実行し、TiDBから関連するコードチャンクを取得しています。チューターは、今まさに私に教えているそのシステム自体を利用しているのです。
3層のメモリシステムは、ジャーナルモードでコンテキストを組み立てるのと全く同じ方法で、チューター用のコンテキストを構築します。プロファイルサマリーはチューターに「私が誰であるか」を伝え、ファクトテーブルはセッションをまたいで「私が何を学習したか」を追跡します。そしてベクトル検索が、私の質問内容に関連するコードチャンクを見つけ出します。
この機能は、自ら実行しながら、自らについて説明しているのです。
AIチューターのデバッグ:プロンプトエンジニアリングによる修正
当初、チューターの出来は良くありませんでした。実際の使用過程で、3つの具体的な問題が浮き彫りになりました。
練習中に黙っていられない。カンファレンスの講演をリハーサルしていて、セリフに詰まったとき (リハーサルでは当然起こることです)、チューターが余計なコメントを挟んできました。私が試行錯誤の最中であることを認識し、話し終えるか明示的に助けを求めるまで黙っているべきでした。
台本に対して過剰に修正してくる。講演の練習中、自然な話し方を探っていると、台本とは異なる言葉で同じポイントを話すことがあります。チューターはそれを間違いとして指摘しました。しかし、台本はガイドであり、カンニングペーパーではありません。同じ要点を伝えるのであれば、異なる言葉を使っても問題ありません。修正に値するのは、重要なポイントを完全に飛ばしてしまったときだけです。
そして口癖がありました。ほぼすべての応答が「Okay, yeah.」から始まっていたのです。1回のセッションでそれを30回も聞かされるのは、気が狂いそうになります。
これら3つの修正はすべて、コードの変更ではなくプロンプトエンジニアリングで行いました。buildStudyPromptに<talk_practice_rules>を追加したのです。
<talk_practice_rules>
When the user is practicing a talk or speech:
1. STAY SILENT while they are speaking. Do not interrupt.
Stumbling and restarting is NORMAL rehearsal behavior.
2. DO NOT police exact wording. The script is a guide.
Only flag if they skip an entire beat or miss a
critical moment.
3. When you give feedback, be specific. Say "you skipped
the Pinecone line" not "you added extra context."
4. Never start a response with "Okay, yeah." Never.
</talk_practice_rules>
チューターには正確性の問題もありました。音声パイプラインのシーケンスを誤って説明し、イベントの順序を間違えて伝えてくることがあったのです。自分のアーキテクチャについて、自分自身のチューターを何度も修正しなければなりませんでした。
根本的な原因は、チューターがRAGチャンクから情報を組み立てる際に、フローの順序を誤認していたことでした。断片的なコードチャンクから不正確に再構築させないために、静的なtutor-knowledge.tsドキュメントに正確なシーケンスを明示的に記述する必要がありました。
教訓:自信満々に誤った情報を教えるソクラテス式チューターは、チューターがいないよりも質が悪いです。静的知識レイヤーは、コンポーネント間のシーケンスや関係性をチューターが推測しなくて済むほど、包括的である必要があります。
フラットな事実からナレッジグラフへ
3ヶ月が経過した時点で、ファクトテーブルには複数のユーザーにわたる数百件のエントリが蓄積されていました。しかし、それらは「妻のグレンダの誕生日は5月5日」「PingCAPに勤務」「ラスベガス在住」といった、単なる文字列としての「フラットな行」に過ぎませんでした。これらの事実には、配偶者、勤務先、居住地といった明白な関係性がありますが、フラットなテーブル構造ではそれらのつながりは可視化されません。カテゴリのカラムはあくまで「ラベル」であり、「関係性」ではないのです。
そこで私は、フラットな事実をNeo4j上のナレッジグラフへと変換するエンティティ抽出パイプラインを構築しました。Claude Haikuが事実を解析し、型定義されたノード (Person, Place, Experience, Topic) と、それらをつなぐ型定義された関係性 (SPOUSE_OF, LIVES_IN, WORKS_AT, CHILD_OF) へと変換します。このパイプラインは、store-factsの後に実行される、非ブロッキングのインジェストステップとして動作します。TiDBが引き続き信頼できる唯一の情報源であり、Neo4jはその派生ビューという位置付けです。
既存の全データに対して一括処理を行った結果、669個のノードと1,142個の関係性が生成されました。
大規模に運用してみると、実際の問題が浮き彫りになりました。1回のHaiku呼び出しで460件の事実を処理しようとすると、JSONレスポンスがオブジェクトの途中で途切れてしまいました。これは1回あたり80件にバッチ処理することで解決しました。また、Haikuから返された非文字列のプロパティ値でv.replace()がクラッシュする問題はString(v)で修正し、リレーションシップのtoフィールドが未定義のままNeo4jドライバーが不完全なエッジを無言で作成してしまう問題は、書き込み前のバリデーションで対処しました。さらに、1つのトランザクションでの大量のステートメント実行によるタイムアウトは、50ステートメントずつのチャンクに分けることで解消しました。
これがスタディモードとどう繋がるのか。私は現在、2つの独立したグラフビューを構築しています。1つはジャーナルモード用 (あなたの人生における人間関係や繋がり)、もう1つはスタディモード用 (あなたが学習した概念同士の関係性) です。自分の知識をグラフとして眺めることは、事実のリストをスクロールするのとは全く異なる体験です。どの概念が繋がり、どこに知識の空白があり、どの理解のクラスタに補強が必要な「孤立したノード」があるのかが一目でわかります。
スタディモードの正体
スタディモードとは、ソクラテス式メソッドを用いて対話を通じて教え、教材についてクイズを出し、概念を自分の言葉で説明できるようになるまで先へ進ませない、音声ファーストのAIチューターです。これは、実際のソース資料に対するRAGと、セッションをまたぐ永続メモリによって支えられています。
背後にある技術スタック:
- 音声:Hume EVI (WebSocket、感情検知、TTS)
- 推論:Claude Sonnet
- データベース:TiDB (ユーザープロファイル、事実、ベクトル検索、コードチャンクの埋め込みを1つのDBで管理)
- バックグラウンド処理:Inngest
- ナレッジグラフ:Neo4j
- トピックエディタ:BlockNote
しかし、技術スタック自体が重要なのではありません。重要なのは、声に出して説明できないのであれば、それはまだ理解できていない。ということであり、その基準をあなたに突きつけるツールが完成したということです。
ラバーダックが、ついに言葉を返し始めました。
ベクトル検索とリレーショナルデータを1つのデータベースに統合したAIアプリケーションを構築する準備はできましたか?TiDB Cloud Starterで無料で始めましょう.
TiDB Cloud Dedicated
TiDB Cloudのエンタープライズ版。
専用VPC上に構築された専有DBaaSでAWSとGoogle Cloudで利用可能。
TiDB Cloud Starter
TiDB Cloudのライト版。
TiDBの機能をフルマネージド環境で使用でき無料かつお客様の裁量で利用開始。