{"id":32594,"date":"2026-03-19T12:04:28","date_gmt":"2026-03-19T19:04:28","guid":{"rendered":"https:\/\/www.pingcap.com\/?p=32594"},"modified":"2026-03-20T12:40:36","modified_gmt":"2026-03-20T19:40:36","slug":"build-ai-voice-tutor-rag-tidb-vector-search","status":"publish","type":"post","link":"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/","title":{"rendered":"Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor"},"content":{"rendered":"<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Key_Takeaways\"><\/span>Key Takeaways<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Socratic voice tutor can share a journal app&#8217;s entire stack\u2014one codebase, two products.<\/li>\n\n\n\n<li>Static knowledge plus RAG over source code gives an AI tutor both reliability and depth.<\/li>\n\n\n\n<li>TiDB&#8217;s native vector search keeps relational data and embeddings in one database with no Pinecone sidecar.<\/li>\n\n\n\n<li>A single <code>mode<\/code> column on the facts table prevents study noise from polluting personal context.<\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p>I give talks for a living. Developer relations means standing in front of rooms full of engineers and explaining complex technical concepts clearly enough that people walk away understanding something new.<\/p>\n\n\n\n<p>The problem is that I retain things far better when I explain them out loud than when I read about them. Most people do. It is why &#8220;rubber ducking&#8221; works. It is why the best way to learn something is to teach it. The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Feynman_Technique\">Feynman technique<\/a> is not a productivity hack\u2014it is how human brains actually consolidate knowledge.<\/p>\n\n\n\n<p>But rubber ducks don&#8217;t talk back. They don&#8217;t catch when you are wrong, ask follow-up questions, or say &#8220;okay, now explain that without using the word &#8216;basically.'&#8221;<\/p>\n\n\n\n<p>So I built something that does: A voice-first AI tutor that uses the Socratic method to make sure I actually know what I think I know.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Why_a_Voice-First_AI_Journal_Needed_a_Tutor\"><\/span>Why a Voice-First AI Journal Needed a Tutor<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"521\" src=\"https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-1024x521.png\" alt=\"Build AI voice tutor interface with audio waveform and topic sidebar\" class=\"wp-image-32598\" srcset=\"https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-1024x521.png 1024w, https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-300x152.png 300w, https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-768x390.png 768w, https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-1536x781.png 1536w, https:\/\/static.pingcap.com\/files\/2026\/03\/20073641\/image-9-2048x1041.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n<p>I have been building <a href=\"https:\/\/speak2me.io\">Speak2Me<\/a> \u2014 a voice-first AI agent \u2014 for the past few months. You talk to it, it talks back, and it remembers everything about you across sessions. Real memory, not &#8220;that sounds frustrating&#8221; generic chatbot responses. If you want the full architecture breakdown, I wrote about that in a <a href=\"https:\/\/www.pingcap.com\/ko\/blog\/how-to-build-an-ai-memory-architecture-that-actually-remembers\/\">previous post on building AI memory that actually works<\/a>.<\/p>\n\n\n\n<p>Under the hood, Speak2Me runs a three-tier memory architecture: A deterministic profile summary, an immutable facts table with superseding logic, and per-exchange vector search using 3,072-dimension embeddings in <a href=\"https:\/\/www.pingcap.com\/ko\/tidb-cloud-serverless\/\">\ud2f0DB<\/a>. The system had grown to 12 database tables, 6 external services, 3 memory tiers, a voice pipeline through <a href=\"https:\/\/www.hume.ai\/\">Hume EVI<\/a>, and an <a href=\"https:\/\/www.inngest.com\/\">Inngest<\/a> background processing queue.<\/p>\n\n\n\n<p>I could ship features. I could fix bugs. But when it came time to prep for a conference talk and actually explain how all the pieces connect\u2014and <em>why<\/em> I made each decision\u2014I could not hold it all in my head at once.<\/p>\n\n\n\n<p>I could read my own code. I could read my dev log. But reading did not make it stick. What I needed was someone to walk me through my own system and quiz me until I could explain it cold.<\/p>\n\n\n\n<p>That did not exist. So I built it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_Study_Mode_Reuses_the_Journals_Voice_Pipeline\"><\/span>How Study Mode Reuses the Journal&#8217;s Voice Pipeline<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Study mode is not a separate product. It is the same app, the same voice pipeline, the same memory system, and the same <a href=\"https:\/\/www.pingcap.com\/ko\/tidb\/\">\ud2f0DB<\/a> database. Just a different purpose.<\/p>\n\n\n\n<p>In journal mode, the AI is your companion. It listens, remembers, and responds with emotional awareness. In study mode, it is your tutor. It teaches, quizzes, pushes back, and will not move on until you get it right.<\/p>\n\n\n\n<p>The switch is a toggle in the UI\u2014Journal or Study. That toggle changes which prompt builder runs (<code>buildJournalCompanionPrompt<\/code> vs. <code>buildStudyPrompt<\/code>), which changes the AI&#8217;s entire personality. Same Claude Sonnet model, Hume voice pipeline, and TiDB queries underneath. Different instructions.<\/p>\n\n\n\n<p>This matters architecturally because I did not have to build a second app. Every improvement to the voice pipeline, the memory system, the reconnection logic, or the emotion detection benefits both modes. One codebase, two products.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter is-resized\"><img decoding=\"async\" src=\"https:\/\/www.chrisdabatos.com\/imgs\/1-two-mode-pipeline.svg\" alt=\"Build AI voice tutor: A diagram showing how journal mode and study mode share the same voice pipeline, memory system, and database\" style=\"width:775px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Building_a_Socratic_AI_Tutor_with_Static_Knowledge_and_RAG\"><\/span>Building a Socratic AI Tutor with Static Knowledge and RAG<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The tutor has two knowledge layers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Layer one: Static knowledge.<\/strong> A curated document in <code>tutor-knowledge.ts<\/code> covers the complete system overview with every database table, every API route, every data flow, every design decision. This gets included in the study prompt every time. The tutor can always reference the full system architecture without relying on retrieval.<\/li>\n\n\n\n<li><strong>Layer two: RAG over the actual source code.<\/strong> I embedded every exported function from the codebase as vector chunks in TiDB, stored in a <code>s2m_code_chunks<\/code> table alongside the conversation chunks. Same embedding model (<code>text-embedding-3-large<\/code>), same 3,072 dimensions, same <a href=\"https:\/\/www.pingcap.com\/ko\/article\/understanding-the-cosine-similarity-formula\/\">cosine distance<\/a> search.<\/li>\n<\/ul>\n\n\n\n<p>When I ask &#8220;how does <code>storeFacts<\/code> work?&#8221; the tutor does not guess. It retrieves the actual function from the embedded codebase and explains the real implementation. When I ask &#8220;what does the Inngest pipeline do after a session ends?&#8221; it pulls the actual step functions and walks through them. (For even better retrieval, TiDB also supports <a href=\"https:\/\/www.pingcap.com\/ko\/blog\/introducing-full-text-search-for-tidb\/\">full-text search for hybrid retrieval<\/a>\u2014combining keyword matching with vector similarity\u2014which I plan to integrate next.)<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter is-resized\"><img decoding=\"async\" src=\"https:\/\/www.chrisdabatos.com\/imgs\/2-tutor-knowledge-layers.svg\" alt=\"Build AI voice tutor: Diagram showing the two knowledge layers: static knowledge document and RAG over source code\" style=\"aspect-ratio:2.368615840118431;width:798px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>The Socratic method lives in the prompt. The tutor explains a concept, then asks &#8220;can you explain that back to me?&#8221; If I get it wrong, it re-teaches and asks again. If I get it right, it moves on. If I try to handwave through something with vague language, it calls me out:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ From buildStudyPrompt\n\/\/ \"When the user gives a vague or incomplete explanation,\n\/\/  ask them to be more specific. Do not accept 'it just works'\n\/\/  or 'it handles that automatically' as answers.\"<\/code><\/pre>\n\n\n\n<p>The AI will not let me fake understanding. That is the whole point.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Isolating_Study_Facts_from_Personal_AI_Memory\"><\/span>Isolating Study Facts from Personal AI Memory<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Here is a bug that took a while to catch.<\/p>\n\n\n\n<p>The fact extraction pipeline runs after every conversation in journal mode or study mode. It pulls out facts from the transcript and stores them in the facts table. Facts like &#8220;Wife&#8217;s name is Glenda&#8221; or &#8220;Daughter born December 16, 2025.&#8221;<\/p>\n\n\n\n<p>Except study mode was generating facts like &#8220;Speak2Me uses three-tier memory&#8221; and &#8220;Vector search uses cosine distance at 0.5 threshold.&#8221; Technical details about the codebase were getting mixed into my personal profile.<\/p>\n\n\n\n<p>Out of 339 active facts, 56 were study-mode noise. The profile summary was bloated with architecture details sitting next to family information. And because the profile caps at 6K characters in the prompt, the AI was only seeing the first chunk\u2014mostly miscategorized technical junk.<\/p>\n\n\n\n<p>The fix was a <code>mode<\/code> column on the facts table. It defaults to <code>\"journal\"<\/code>. Study conversations tag their facts as <code>\"study\"<\/code>. Every read path in the system\u2014the context-loading route, the profile endpoint, the cache builder, <code>buildProfileFromFacts<\/code>\u2014now filters to journal-only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Before: all facts, including study noise\nSELECT * FROM s2m_user_facts WHERE user_id = ? AND is_active = true\n\n-- After: only journal facts touch the personal profile\nSELECT * FROM s2m_user_facts\nWHERE user_id = ? AND is_active = true AND mode = 'journal'<\/code><\/pre>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter is-resized\"><img decoding=\"async\" src=\"https:\/\/www.chrisdabatos.com\/imgs\/3-fact-isolation.svg\" alt=\"Diagram showing how journal facts and study facts are isolated by mode column\" style=\"aspect-ratio:2.0455998295333475;width:808px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Study facts still get stored. They are useful for the tutor to reference across sessions. But they never pollute the personal profile or the journal prompt. Two separate knowledge spaces in one table, separated by a single column.<\/p>\n\n\n\n<p>I also added per-category caps at this point: 15 most recent facts per category, 20 for identity and family. Without caps, a daily user for a year would accumulate thousands of facts, and the profile would grow without bound. The caps keep it manageable.<\/p>\n\n\n\n<p>And a transcript quality guard: If a session has fewer than 3 user messages or under 200 characters total, skip fact extraction entirely. No more garbage facts from sessions where someone said &#8220;hey&#8221; and &#8220;bye.&#8221;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Custom_Study_Topics_RAG_over_Any_Content\"><\/span>Custom Study Topics: RAG over Any Content<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Study mode is not locked to the Speak2Me codebase. There is a <a href=\"https:\/\/www.blocknotejs.org\/\">BlockNote<\/a> editor (Notion-style block editor) where you can create custom study topics and paste any content you want.<\/p>\n\n\n\n<p>Have a conference talk script? Paste it in. The AI becomes a talk coach that quizzes you on each section and flags when you skip critical beats. Have technical documentation for a product you are learning? Paste it in. The AI becomes a study partner that teaches the concepts through conversation.<\/p>\n\n\n\n<p>The content from custom topics gets included in the study prompt alongside the static knowledge. The tutor treats it as authoritative material and teaches from it.<\/p>\n\n\n\n<p>I used this to prep for a conference talk. I pasted my full talk script into a custom study topic and practiced through voice. The tutor quizzed me on each beat, caught when I skipped sections, and let me practice delivery repeatedly. I went from stumbling through the material to explaining the entire system\u2014voice pipeline, memory architecture, agent loop\u2014cleanly in under 60 seconds per section.<\/p>\n\n\n\n<p>I could not have done that by reading the script alone. The repetition through voice, being quizzed, and being forced to articulate each concept in my own words is what made it stick.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"88_Minutes_Straight_Real-World_Validation\"><\/span>88 Minutes Straight: Real-World Validation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>A friend of mine is a software engineer with about 10 years of experience. He was making a career move into a completely new domain. He needed to understand a company&#8217;s product deeply enough to explain it in an interview setting.<\/p>\n\n\n\n<p>I gave him access to Speak2Me. He pasted the company&#8217;s documentation into a custom study topic and started a voice session.<\/p>\n\n\n\n<p>He used it for 88 minutes straight on the first day.<\/p>\n\n\n\n<p>What he told me afterward: The app helped calm him down. He had been anxious about not understanding the product well enough, and having a Socratic AI voice tutor patiently walk him through concepts and quiz him repeatedly reduced that anxiety. When he stumbled on an explanation, the AI caught it and re-taught the concept. When he got something right, it moved on. No judgment. No impatience. Just steady, adaptive teaching.<\/p>\n\n\n\n<p>He passed the hiring manager interview and moved on in the process. He told me it was because Speak2Me helped him understand the product well enough to explain it clearly and confidently.<\/p>\n\n\n\n<p>That 88-minute session was the first time someone other than me validated that study mode actually works. And not as a demo or concept, but as a tool someone used to learn something real and then proved they learned it in a high-stakes situation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_TiDB_Powers_the_Tutors_Memory\"><\/span>How TiDB Powers the Tutor&#8217;s Memory<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Building study mode reinforced something I discovered while building the journal: Trying to run relational data in one database and vectors in another creates real operational pain. TiDB eliminated that problem from day one.<\/p>\n\n\n\n<p><strong>One database for everything.<\/strong> The user profile, the facts table, the conversation transcripts, and the code-chunk embeddings all live in TiDB. When the tutor needs to answer a question, it runs a single query that joins relational metadata with <a href=\"https:\/\/www.pingcap.com\/ko\/blog\/tidb-vector-search-public-beta\/\">vector search<\/a> results:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT c.chunk_text,\n       VEC_COSINE_DISTANCE(c.embedding, ?) AS relevance\nFROM s2m_code_chunks c\nWHERE c.user_id = ?\nORDER BY relevance\nLIMIT 5<\/code><\/pre>\n\n\n\n<p>No second database to sync. No application-layer joins. One query, one network hop.<\/p>\n\n\n\n<p><strong>Fact isolation at the SQL level.<\/strong> The <code>mode<\/code> column fix described above only works cleanly because facts, profiles, and vectors all live in the same database. Filtering study facts out of the personal profile is a <code>WHERE<\/code> clause and not a cross-service coordination problem.<\/p>\n\n\n\n<p><strong>Schema flexibility as the product evolves.<\/strong> Study mode added new tables (<code>s2m_code_chunks<\/code>), new columns (<code>mode<\/code> on the facts table), and new query patterns. Because TiDB is <a href=\"https:\/\/docs.pingcap.com\/tidb\/stable\/mysql-compatibility\/\">MySQL \ud638\ud658<\/a>, every migration was a standard <code>ALTER TABLE<\/code> statement. No driver changes, no ORM swaps. Just SQL.<\/p>\n\n\n\n<p><strong>Scaling without rearchitecting.<\/strong> Each study session generates new embeddings, new facts, and new transcript chunks. As the user base grows, TiDB&#8217;s <a href=\"https:\/\/docs.pingcap.com\/tidb\/stable\/tidb-architecture\/\">distributed architecture<\/a> scales storage and compute independently. I have not had to think about sharding, read replicas, or capacity planning. The database handles it.<\/p>\n\n\n\n<p>If you are building an AI application that combines structured data with vector search\u2014whether it is a tutor, a chatbot with memory, or a RAG pipeline\u2014<a href=\"https:\/\/www.pingcap.com\/ko\/tidb-cloud-serverless\/\">TiDB Cloud \uc2a4\ud0c0\ud130<\/a> lets you start for free and scale as usage grows. One database instead of two means half the infrastructure to manage and debug.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_Vector_Search_Powers_Both_Modes_Simultaneously\"><\/span>How Vector Search Powers Both Modes Simultaneously<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter is-resized\"><img decoding=\"async\" src=\"https:\/\/www.chrisdabatos.com\/imgs\/4-meta-moment.svg\" alt=\"Diagram showing the recursive loop where study mode uses the same systems it teaches about\" style=\"width:807px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Here is the thing that still gets me.<\/p>\n\n\n\n<p>When I am in study mode asking &#8220;how does vector search work in Speak2Me?&#8221; the context-loading endpoint is literally running vector search on my question to retrieve the relevant code chunks from TiDB. The tutor is using the exact system it is teaching me about.<\/p>\n\n\n\n<p>The three-tier memory system assembles context for the tutor the same way it assembles context for journal mode. Profile summary tells the tutor who I am. Facts table tracks what I have studied across sessions. Vector search finds the relevant code chunks for whatever I am asking about.<\/p>\n\n\n\n<p>The feature explains itself while executing itself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Debugging_the_AI_Tutor_Prompt_Engineering_Fixes\"><\/span>Debugging the AI Tutor: Prompt Engineering Fixes<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The tutor was not good at first. Three specific problems showed up during real usage.<\/p>\n\n\n\n<p><strong>It would not stay quiet during practice.<\/strong> When I was rehearsing my conference talk and stumbled on a line (normal when rehearsing), the tutor would jump in with filler commentary. It should have recognized I was mid-attempt and stayed quiet until I finished or explicitly asked for help.<\/p>\n\n\n\n<p><strong>It over-corrected against scripts.<\/strong> During talk practice, I would find my natural delivery and say the same point with different words than the script. The tutor would flag it as wrong. But the script is a guide, not a teleprompter. Different words that deliver the same beat are fine. Only missing a critical beat entirely is worth correcting.<\/p>\n\n\n\n<p><strong>It had a verbal crutch.<\/strong> Almost every response started with &#8220;Okay, yeah.&#8221; Hearing that 30 times in one session is maddening.<\/p>\n\n\n\n<p>All three fixes were prompt engineering, not code changes. I added <code>&lt;talk_practice_rules&gt;<\/code> to <code>buildStudyPrompt<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;talk_practice_rules&gt;\nWhen the user is practicing a talk or speech:\n1. STAY SILENT while they are speaking. Do not interrupt.\n   Stumbling and restarting is NORMAL rehearsal behavior.\n2. DO NOT police exact wording. The script is a guide.\n   Only flag if they skip an entire beat or miss a\n   critical moment.\n3. When you give feedback, be specific. Say \"you skipped\n   the Pinecone line\" not \"you added extra context.\"\n4. Never start a response with \"Okay, yeah.\" Never.\n&lt;\/talk_practice_rules&gt;<\/code><\/pre>\n\n\n\n<p>The tutor also had accuracy problems. It would explain the sequencing of the voice pipeline incorrectly, describing events in the wrong order. I had to correct my own tutor multiple times on my own architecture.<\/p>\n\n\n\n<p>The root cause: The tutor was assembling information from RAG chunks and getting the flow order wrong. The static <code>tutor-knowledge.ts<\/code> document needed the exact sequencing spelled out explicitly so the tutor could not reconstruct it incorrectly from partial code chunks.<\/p>\n\n\n\n<p>The lesson: A Socratic tutor that confidently teaches wrong information is worse than no tutor at all. The static knowledge layer needs to be comprehensive enough that the tutor never has to guess at sequencing or relationships between components.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"From_Flat_Facts_to_a_Knowledge_Graph\"><\/span>From Flat Facts to a Knowledge Graph<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>After three months, the facts table had hundreds of entries across multiple users. Flat rows. Each one a string: &#8220;Wife Glenda&#8217;s birthday is May 5.&#8221; &#8220;Works at PingCAP.&#8221; &#8220;Lives in Las Vegas.&#8221; These facts have obvious relationships\u2014spouse, employer, location\u2014but in a flat table those connections are invisible. A category column is a label, not a relationship.<\/p>\n\n\n\n<p>I built an entity extraction pipeline that turns flat facts into a knowledge graph in <a href=\"https:\/\/neo4j.com\/\">Neo4j<\/a>. Claude Haiku parses facts into typed nodes (Person, Place, Experience, Topic) and typed relationships (SPOUSE_OF, LIVES_IN, WORKS_AT, CHILD_OF) connecting them. The pipeline runs as a non-blocking Inngest step after <code>store-facts<\/code>. TiDB stays the source of truth; Neo4j is a derived view.<\/p>\n\n\n\n<p>A backfill across all existing data populated 669 nodes and 1,142 relationships.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter is-resized\"><img decoding=\"async\" src=\"https:\/\/www.chrisdabatos.com\/imgs\/5-facts-to-graph.svg\" alt=\"Diagram showing how flat facts are transformed into a Neo4j knowledge graph with nodes and relationships\" style=\"aspect-ratio:2.2500878940583617;width:820px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Running it at scale exposed real problems. Processing 460 facts in one Haiku call truncated the JSON response mid-object\u2014fixed by batching to 80 facts per call. Non-string property values from Haiku crashed <code>v.replace()<\/code>\u2014fixed with <code>String(v)<\/code>. Undefined <code>to<\/code> fields on relationships caused Neo4j&#8217;s driver to silently create broken edges\u2014fixed with validation before writes. Large statement batches timed out in a single transaction\u2014fixed by chunking to 50 statements.<\/p>\n\n\n\n<p>Where this connects to study mode: I am building two separate graph views. One for journal mode (your life relationships) and one for study mode (the relationships between concepts you have studied). Looking at your own knowledge as a graph is a different experience from scrolling a list of facts. You see which concepts connect, where the gaps are, and which clusters of understanding have isolated nodes that need more work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_Study_Mode_Actually_Is\"><\/span>What Study Mode Actually Is<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Study mode is a voice-first AI tutor that uses the Socratic method to teach through conversation, quizzes you on the material, and refuses to move on until you can explain a concept in your own words. It is backed by RAG over real source material and persistent memory across sessions.<\/p>\n\n\n\n<p>The technical stack behind it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Voice:<\/strong> <a href=\"https:\/\/www.hume.ai\/\">Hume EVI<\/a> (WebSocket, emotion detection, TTS)<\/li>\n\n\n\n<li><strong>Reasoning:<\/strong> Claude Sonnet<\/li>\n\n\n\n<li><strong>Database:<\/strong> <a href=\"https:\/\/www.pingcap.com\/ko\/tidb-cloud-serverless\/\">\ud2f0DB<\/a> (user profile, facts, <a href=\"https:\/\/www.pingcap.com\/ko\/blog\/tidb-vector-search-public-beta\/\">vector search<\/a>, and code-chunk embeddings in one database)<\/li>\n\n\n\n<li><strong>Background processing:<\/strong> <a href=\"https:\/\/www.inngest.com\/\">Inngest<\/a><\/li>\n\n\n\n<li><strong>Knowledge graph:<\/strong> <a href=\"https:\/\/neo4j.com\/\">Neo4j<\/a><\/li>\n\n\n\n<li><strong>Topic editor:<\/strong> <a href=\"https:\/\/www.blocknotejs.org\/\">BlockNote<\/a><\/li>\n<\/ul>\n\n\n\n<p>But the stack is not the point. The point is: If you cannot explain something out loud, you do not know it yet. And now there is a tool that holds you to that standard.<\/p>\n\n\n\n<p>The rubber duck talks back now.<\/p>\n\n\n\n<p><em>Ready to build AI applications with vector search and relational data in a single database? <a href=\"https:\/\/tidbcloud.com\/free-trial\/\">Get started with TiDB Cloud Starter for free<\/a>.<\/em><\/p>","protected":false},"excerpt":{"rendered":"<p>I give talks for a living. Developer relations means standing in front of rooms full of engineers and explaining complex technical concepts clearly enough that people walk away understanding something new. The problem is that I retain things far better when I explain them out loud than when I read about them. Most people do. [&hellip;]<\/p>\n","protected":false},"author":324,"featured_media":32650,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ub_ctt_via":"","footnotes":""},"categories":[436],"tags":[476,147,298,111,297],"class_list":["post-32594","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorial","tag-ai-memory","tag-distributed-sql","tag-rag","tag-tidb","tag-vector-search"],"acf":[],"featured_image_src":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png","author_info":{"display_name":"Chris Dabatos","author_link":"https:\/\/www.pingcap.com\/ko\/blog\/author\/chris-dabatos\/"},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.9 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Build AI Voice Tutor: RAG and TiDB Vector Search<\/title>\n<meta name=\"description\" content=\"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\" \/>\n<meta property=\"og:locale\" content=\"ko_KR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build AI Voice Tutor: RAG and TiDB Vector Search\" \/>\n<meta property=\"og:description\" content=\"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\" \/>\n<meta property=\"og:site_name\" content=\"TiDB\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/facebook.com\/pingcap2015\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-19T19:04:28+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-20T19:40:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120317\/tidb_1200x627-3.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2400\" \/>\n\t<meta property=\"og:image:height\" content=\"1254\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Chris Dabatos\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/static.pingcap.com\/files\/2026\/03\/20124028\/tidb_twitter_1600x900-3.png\" \/>\n<meta name=\"twitter:creator\" content=\"@PingCAP\" \/>\n<meta name=\"twitter:site\" content=\"@PingCAP\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Chris Dabatos\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14\ubd84\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\"},\"author\":{\"name\":\"Chris Dabatos\",\"@id\":\"https:\/\/www.pingcap.com\/#\/schema\/person\/4d7ecdb90868256414855723f838c9e0\"},\"headline\":\"Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor\",\"datePublished\":\"2026-03-19T19:04:28+00:00\",\"dateModified\":\"2026-03-20T19:40:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\"},\"wordCount\":2642,\"publisher\":{\"@id\":\"https:\/\/www.pingcap.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png\",\"keywords\":[\"AI Memory\",\"Distributed SQL\",\"RAG\",\"TiDB\",\"Vector Search\"],\"articleSection\":[\"Tutorial\"],\"inLanguage\":\"ko-KR\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\",\"url\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\",\"name\":\"Build AI Voice Tutor: RAG and TiDB Vector Search\",\"isPartOf\":{\"@id\":\"https:\/\/www.pingcap.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png\",\"datePublished\":\"2026-03-19T19:04:28+00:00\",\"dateModified\":\"2026-03-20T19:40:36+00:00\",\"description\":\"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#breadcrumb\"},\"inLanguage\":\"ko-KR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage\",\"url\":\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png\",\"contentUrl\":\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png\",\"width\":3600,\"height\":1200},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.pingcap.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.pingcap.com\/#website\",\"url\":\"https:\/\/www.pingcap.com\/\",\"name\":\"TiDB\",\"description\":\"TiDB | SQL at Scale\",\"publisher\":{\"@id\":\"https:\/\/www.pingcap.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.pingcap.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"ko-KR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.pingcap.com\/#organization\",\"name\":\"PingCAP\",\"url\":\"https:\/\/www.pingcap.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\/\/www.pingcap.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/static.pingcap.com\/files\/2021\/11\/pingcap-logo.png\",\"contentUrl\":\"https:\/\/static.pingcap.com\/files\/2021\/11\/pingcap-logo.png\",\"width\":811,\"height\":232,\"caption\":\"PingCAP\"},\"image\":{\"@id\":\"https:\/\/www.pingcap.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/facebook.com\/pingcap2015\",\"https:\/\/x.com\/PingCAP\",\"https:\/\/linkedin.com\/company\/pingcap\",\"https:\/\/youtube.com\/channel\/UCuq4puT32DzHKT5rU1IZpIA\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.pingcap.com\/#\/schema\/person\/4d7ecdb90868256414855723f838c9e0\",\"name\":\"Chris Dabatos\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\/\/www.pingcap.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/static.pingcap.com\/files\/2022\/10\/17234942\/avatar.jpg\",\"contentUrl\":\"https:\/\/static.pingcap.com\/files\/2022\/10\/17234942\/avatar.jpg\",\"caption\":\"Chris Dabatos\"},\"description\":\"Developer Advocate\",\"url\":\"https:\/\/www.pingcap.com\/ko\/blog\/author\/chris-dabatos\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Build AI Voice Tutor: RAG and TiDB Vector Search","description":"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/","og_locale":"ko_KR","og_type":"article","og_title":"Build AI Voice Tutor: RAG and TiDB Vector Search","og_description":"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.","og_url":"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/","og_site_name":"TiDB","article_publisher":"https:\/\/facebook.com\/pingcap2015","article_published_time":"2026-03-19T19:04:28+00:00","article_modified_time":"2026-03-20T19:40:36+00:00","og_image":[{"width":2400,"height":1254,"url":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120317\/tidb_1200x627-3.png","type":"image\/png"}],"author":"Chris Dabatos","twitter_card":"summary_large_image","twitter_image":"https:\/\/static.pingcap.com\/files\/2026\/03\/20124028\/tidb_twitter_1600x900-3.png","twitter_creator":"@PingCAP","twitter_site":"@PingCAP","twitter_misc":{"Written by":"Chris Dabatos","Est. reading time":"14\ubd84"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#article","isPartOf":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/"},"author":{"name":"Chris Dabatos","@id":"https:\/\/www.pingcap.com\/#\/schema\/person\/4d7ecdb90868256414855723f838c9e0"},"headline":"Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor","datePublished":"2026-03-19T19:04:28+00:00","dateModified":"2026-03-20T19:40:36+00:00","mainEntityOfPage":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/"},"wordCount":2642,"publisher":{"@id":"https:\/\/www.pingcap.com\/#organization"},"image":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage"},"thumbnailUrl":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png","keywords":["AI Memory","Distributed SQL","RAG","TiDB","Vector Search"],"articleSection":["Tutorial"],"inLanguage":"ko-KR"},{"@type":"WebPage","@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/","url":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/","name":"Build AI Voice Tutor: RAG and TiDB Vector Search","isPartOf":{"@id":"https:\/\/www.pingcap.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage"},"image":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage"},"thumbnailUrl":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png","datePublished":"2026-03-19T19:04:28+00:00","dateModified":"2026-03-20T19:40:36+00:00","description":"Build a Socratic AI voice tutor with RAG over source code, persistent memory, and TiDB vector search\u2014one codebase, two products.","breadcrumb":{"@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#breadcrumb"},"inLanguage":"ko-KR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/"]}]},{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#primaryimage","url":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png","contentUrl":"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png","width":3600,"height":1200},{"@type":"BreadcrumbList","@id":"https:\/\/www.pingcap.com\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.pingcap.com\/"},{"@type":"ListItem","position":2,"name":"Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor"}]},{"@type":"WebSite","@id":"https:\/\/www.pingcap.com\/#website","url":"https:\/\/www.pingcap.com\/","name":"\ud2f0DB","description":"TiDB | SQL at Scale","publisher":{"@id":"https:\/\/www.pingcap.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.pingcap.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"ko-KR"},{"@type":"Organization","@id":"https:\/\/www.pingcap.com\/#organization","name":"PingCAP","url":"https:\/\/www.pingcap.com\/","logo":{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/www.pingcap.com\/#\/schema\/logo\/image\/","url":"https:\/\/static.pingcap.com\/files\/2021\/11\/pingcap-logo.png","contentUrl":"https:\/\/static.pingcap.com\/files\/2021\/11\/pingcap-logo.png","width":811,"height":232,"caption":"PingCAP"},"image":{"@id":"https:\/\/www.pingcap.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/facebook.com\/pingcap2015","https:\/\/x.com\/PingCAP","https:\/\/linkedin.com\/company\/pingcap","https:\/\/youtube.com\/channel\/UCuq4puT32DzHKT5rU1IZpIA"]},{"@type":"Person","@id":"https:\/\/www.pingcap.com\/#\/schema\/person\/4d7ecdb90868256414855723f838c9e0","name":"Chris Dabatos","image":{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/www.pingcap.com\/#\/schema\/person\/image\/","url":"https:\/\/static.pingcap.com\/files\/2022\/10\/17234942\/avatar.jpg","contentUrl":"https:\/\/static.pingcap.com\/files\/2022\/10\/17234942\/avatar.jpg","caption":"Chris Dabatos"},"description":"Developer Advocate","url":"https:\/\/www.pingcap.com\/ko\/blog\/author\/chris-dabatos\/"}]}},"grav_blocks":false,"card_markup":"<a class=\"card-resource bg-white\" href=\"https:\/\/www.pingcap.com\/ko\/blog\/build-ai-voice-tutor-rag-tidb-vector-search\/\"><div class=\"card-resource__image-container\"><img class=\"card-resource__image\" alt=\"tidb_feature_1800x600\" src=\"https:\/\/static.pingcap.com\/files\/2026\/03\/20120258\/tidb_feature_1800x600-4.png\" loading=\"lazy\" width=3600 height=1200 \/><\/div><div class=\"card-resource__content-container\"><div class=\"card-resource__content-head\"><div class=\"card-resource__category\">Tutorial<\/div><\/div><h5 class=\"card-resource__title\">Building a Voice-First AI Journal: How to Add a Knowledgeable Tutor<\/h5><\/div><\/a>","_links":{"self":[{"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/posts\/32594","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/users\/324"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/comments?post=32594"}],"version-history":[{"count":18,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/posts\/32594\/revisions"}],"predecessor-version":[{"id":32652,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/posts\/32594\/revisions\/32652"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/media\/32650"}],"wp:attachment":[{"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/media?parent=32594"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/categories?post=32594"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pingcap.com\/ko\/wp-json\/wp\/v2\/tags?post=32594"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}