<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gokula Krishna</title>
    <description>The latest articles on DEV Community by Gokula Krishna (@gokula_krishna_1f814922bb).</description>
    <link>https://dev.to/gokula_krishna_1f814922bb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2116961%2F8ad5f9c4-a393-43a1-b1da-b7ef60c64d8f.jpg</url>
      <title>DEV Community: Gokula Krishna</title>
      <link>https://dev.to/gokula_krishna_1f814922bb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gokula_krishna_1f814922bb"/>
    <language>en</language>
    <item>
      <title>I built a Digital Human you can talk to</title>
      <dc:creator>Gokula Krishna</dc:creator>
      <pubDate>Mon, 03 Aug 2026 09:07:58 +0000</pubDate>
      <link>https://dev.to/gokula_krishna_1f814922bb/i-built-a-digital-human-you-can-talk-to-4o39</link>
      <guid>https://dev.to/gokula_krishna_1f814922bb/i-built-a-digital-human-you-can-talk-to-4o39</guid>
      <description>&lt;p&gt;Most digital-human demos start with a prepared script or audio file. I wanted a live conversation.&lt;/p&gt;

&lt;p&gt;A user had to speak through the browser. The system had to understand the question, retrieve relevant information, generate a response, synthesize speech, animate a face, and stream the result back as video.&lt;/p&gt;

&lt;p&gt;The notes from our 21 June 2024 demo recorded about five seconds to stream video for a short, 20-token response. We ran that demo on an AWS G5.2xlarge instance using Groq, Chroma, AWS Polly, GeneFace++, FastAPI, and WebRTC.&lt;/p&gt;

&lt;p&gt;A separate end-to-end test on the AWS A10G measured an 18-second minimum and a 25-second average for a ten-word response. The experiment log does not contain enough detail to reconcile the difference. I treat the five-second figure as a demo observation and the end-to-end table as the repeatable benchmark.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The five-second result came from a pipeline of separate systems. Each system had its own latency, output format, failure modes, and hardware requirements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Defining the conversation
&lt;/h2&gt;

&lt;p&gt;The proof of concept supported a turn-based, two-way loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user spoke to the avatar through a browser.&lt;/li&gt;
&lt;li&gt;The browser sent the audio to the backend.&lt;/li&gt;
&lt;li&gt;The backend transcribed the speech and generated an answer.&lt;/li&gt;
&lt;li&gt;The avatar spoke the answer and streamed video back to the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full-duplex barge-in remained outside the PoC. The notes do not show a user interrupting the avatar mid-response and forcing the system to cancel queued speech and video frames. That requires a different session state machine and cancellation path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Browser microphone&lt;/strong&gt; → &lt;strong&gt;FastAPI session&lt;/strong&gt; → &lt;strong&gt;Deepgram VAD + STT&lt;/strong&gt; → &lt;strong&gt;Groq + Chroma&lt;/strong&gt; → &lt;strong&gt;AWS Polly&lt;/strong&gt; → &lt;strong&gt;GeneFace++&lt;/strong&gt; → &lt;strong&gt;WebRTC video&lt;/strong&gt; → &lt;strong&gt;Browser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The system processed one turn through eight stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture:&lt;/strong&gt; React captured the user’s microphone input in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session handling:&lt;/strong&gt; FastAPI accepted the audio stream and maintained the conversation session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn detection:&lt;/strong&gt; Deepgram’s voice activity detection identified speech boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcription:&lt;/strong&gt; Deepgram converted the recorded turn into text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval and generation:&lt;/strong&gt; Chroma retrieved relevant document chunks, and Groq generated the response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking:&lt;/strong&gt; The backend split the response into units that the speech and animation stages could process without waiting for the full answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speech and animation:&lt;/strong&gt; AWS Polly produced audio. GeneFace++ used that audio and the trained avatar data to generate video frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery:&lt;/strong&gt; &lt;code&gt;aiortc&lt;/code&gt; sent the frames to the browser over WebRTC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Chroma held indexed reference material for retrieval. It did not train the language model. That distinction matters because teams often label any data connected to an LLM as training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency budget
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Tvisible ≈ Tendpoint + Tretrieval + Tfirst token + Tspeech chunk + Tavatar chunk + Ttransport&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The individual benchmark values below cannot be added into one exact end-to-end total. We tested some components with different input sizes, GPUs, cloud regions, and levels of overlap. The equation shows where the delay came from and where we had to measure it.&lt;/p&gt;

&lt;p&gt;The avatar renderer consumed much of the budget. GeneFace++ generated ten seconds of video in about five seconds on an RTX 4070. A one-second LLM response still felt slow if the system waited for a complete audio track and complete video before sending the first frame.&lt;/p&gt;

&lt;p&gt;Text chunking gave the pipeline a way to overlap work. The speech and animation stages could process the first chunk while the LLM produced later chunks. Chunk size created a trade-off: small chunks reduced the wait but could damage prosody and create visible transitions; large chunks produced smoother output at the cost of a longer pause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical specification
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;PoC choice&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;AWS G5.2xlarge with NVIDIA A10G&lt;/td&gt;
&lt;td&gt;Hosted the API and GPU avatar workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python 3.8 and FastAPI&lt;/td&gt;
&lt;td&gt;Managed sessions and pipeline orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice activity and STT&lt;/td&gt;
&lt;td&gt;Deepgram&lt;/td&gt;
&lt;td&gt;Detected turns and converted speech to text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Groq, with Mixtral 8x7B in the RAG tests&lt;/td&gt;
&lt;td&gt;Generated the response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval&lt;/td&gt;
&lt;td&gt;Local Chroma DB&lt;/td&gt;
&lt;td&gt;Retrieved document context without a managed search service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text-to-speech&lt;/td&gt;
&lt;td&gt;AWS Polly&lt;/td&gt;
&lt;td&gt;Generated the response audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avatar renderer&lt;/td&gt;
&lt;td&gt;GeneFace++&lt;/td&gt;
&lt;td&gt;Generated lip-synced video frames for the demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video transport&lt;/td&gt;
&lt;td&gt;WebRTC through Python &lt;code&gt;aiortc&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Streamed low-latency video to the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;TypeScript and React with chat UI components&lt;/td&gt;
&lt;td&gt;Captured audio and displayed the avatar conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The proposed architecture kept the avatar engine replaceable. We also evaluated SyncTalk and several other lip-sync systems. GeneFace++ powered the measured demo stack.&lt;/p&gt;

&lt;p&gt;The source notes did not record the microphone codec, sample rate, chunk duration, VAD thresholds, WebRTC topology, or concurrent-session capacity. Those values belong in a production specification before another team tries to reproduce the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM and retrieval benchmarks
&lt;/h2&gt;

&lt;p&gt;We averaged each result across ten runs. These numbers describe this PoC environment rather than a general ranking of the services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Without a knowledge base
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Average latency&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Groq&lt;/td&gt;
&lt;td&gt;107 tokens/s&lt;/td&gt;
&lt;td&gt;1.77 s&lt;/td&gt;
&lt;td&gt;Best response time in this test; API variance remained a dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock with Titan Express in Singapore&lt;/td&gt;
&lt;td&gt;9.2 tokens/s&lt;/td&gt;
&lt;td&gt;2.12 s&lt;/td&gt;
&lt;td&gt;Easier managed setup; answer quality failed some basic test questions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  With retrieval
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Average latency&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Groq, Mixtral 8x7B, local Chroma&lt;/td&gt;
&lt;td&gt;168.1 tokens/s&lt;/td&gt;
&lt;td&gt;1.00 s&lt;/td&gt;
&lt;td&gt;Fastest RAG configuration in the test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groq, Mixtral 8x7B, AWS OpenSearch&lt;/td&gt;
&lt;td&gt;9 tokens/s&lt;/td&gt;
&lt;td&gt;11.00 s&lt;/td&gt;
&lt;td&gt;Managed retrieval added a large delay in this setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock Claude Haiku and AWS OpenSearch in Oregon&lt;/td&gt;
&lt;td&gt;10.46 tokens/s&lt;/td&gt;
&lt;td&gt;10.22 s&lt;/td&gt;
&lt;td&gt;Slowest managed configuration tested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Chroma and OpenSearch rows include more than database time. Region, model endpoint, networking, and implementation choices also changed between configurations. The results support a decision about this PoC stack; they do not isolate the retrieval engine as the sole cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-to-speech benchmarks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Generation latency&lt;/th&gt;
&lt;th&gt;Test input&lt;/th&gt;
&lt;th&gt;Generated audio&lt;/th&gt;
&lt;th&gt;Notes recorded during the evaluation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Polly&lt;/td&gt;
&lt;td&gt;1.25 s&lt;/td&gt;
&lt;td&gt;337 characters&lt;/td&gt;
&lt;td&gt;17.1 s&lt;/td&gt;
&lt;td&gt;Fast managed API; limited custom-voice options for our use case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;4.73 s&lt;/td&gt;
&lt;td&gt;337 characters&lt;/td&gt;
&lt;td&gt;17.1 s&lt;/td&gt;
&lt;td&gt;Custom voice support; slower in this test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Picovoice&lt;/td&gt;
&lt;td&gt;2.00 s&lt;/td&gt;
&lt;td&gt;337 characters&lt;/td&gt;
&lt;td&gt;22 s&lt;/td&gt;
&lt;td&gt;Strong inference speed; custom voice required vendor coordination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speechify&lt;/td&gt;
&lt;td&gt;1.52 s&lt;/td&gt;
&lt;td&gt;337 characters&lt;/td&gt;
&lt;td&gt;26 s&lt;/td&gt;
&lt;td&gt;Custom voice and good perceived quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deepgram&lt;/td&gt;
&lt;td&gt;1.41 s&lt;/td&gt;
&lt;td&gt;337 characters&lt;/td&gt;
&lt;td&gt;17.1 s&lt;/td&gt;
&lt;td&gt;Custom voice options, pause control, and local-hosting potential&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AWS Polly gave us the clearest path to the demo because it generated a 17.1-second clip in 1.25 seconds and required no voice-model training.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lip-sync benchmarks
&lt;/h2&gt;

&lt;p&gt;We ran these tests on an RTX 4070 and measured the time required to generate ten seconds of video.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Generation time&lt;/th&gt;
&lt;th&gt;Evaluation note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wav2Lip&lt;/td&gt;
&lt;td&gt;4 s&lt;/td&gt;
&lt;td&gt;Fast setup, lower resolution, and no head movement in our test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GeneFace++&lt;/td&gt;
&lt;td&gt;5 s&lt;/td&gt;
&lt;td&gt;Fast inference and better lip movement; model training and environment setup took more work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SyncTalk&lt;/td&gt;
&lt;td&gt;10 s&lt;/td&gt;
&lt;td&gt;Best lip movement and image quality in our evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real3DPortrait&lt;/td&gt;
&lt;td&gt;29 s&lt;/td&gt;
&lt;td&gt;Incorrect head, lip, and torso movement in our test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SadTalker&lt;/td&gt;
&lt;td&gt;45 s&lt;/td&gt;
&lt;td&gt;Slow inference and weak lip sync for this use case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hallo&lt;/td&gt;
&lt;td&gt;More than 10 min&lt;/td&gt;
&lt;td&gt;Natural body movement, but the inference time ruled it out for conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The evaluation notes also recorded commercial-use concerns for some frameworks. Licences and repository terms can change, so any production team should verify the current terms before selecting an engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-end GPU results
&lt;/h2&gt;

&lt;p&gt;We used Groq, AWS Polly, and GeneFace++ for the end-to-end GPU comparison. Prices show the hourly figures recorded during the 2024 evaluation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider and GPU&lt;/th&gt;
&lt;th&gt;Recorded price/hour&lt;/th&gt;
&lt;th&gt;Minimum response time for 10 words&lt;/th&gt;
&lt;th&gt;Inference speed&lt;/th&gt;
&lt;th&gt;Average response time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS G5.2xlarge, A10G, Sydney&lt;/td&gt;
&lt;td&gt;$1.212&lt;/td&gt;
&lt;td&gt;18 s&lt;/td&gt;
&lt;td&gt;45 it/s&lt;/td&gt;
&lt;td&gt;25 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runpod, RTX 4090, Slovakia&lt;/td&gt;
&lt;td&gt;$0.74&lt;/td&gt;
&lt;td&gt;4.6 s&lt;/td&gt;
&lt;td&gt;130 it/s&lt;/td&gt;
&lt;td&gt;11.26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runpod, RTX 4090, Romania&lt;/td&gt;
&lt;td&gt;$0.74&lt;/td&gt;
&lt;td&gt;5.6 s&lt;/td&gt;
&lt;td&gt;123 it/s&lt;/td&gt;
&lt;td&gt;11.44 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runpod, H100, Netherlands&lt;/td&gt;
&lt;td&gt;$4.96&lt;/td&gt;
&lt;td&gt;6 s&lt;/td&gt;
&lt;td&gt;86 it/s&lt;/td&gt;
&lt;td&gt;9.14 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The RTX 4090 produced the shortest minimum response and the highest iteration rate in this workload. The H100 delivered the lowest average response time. Its recorded hourly price exceeded the 4090 instances by more than six times. Model architecture, clock speed, software optimisation, and provider variance affected the outcome. The GPU name alone did not predict the best user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WebRTC
&lt;/h2&gt;

&lt;p&gt;The avatar needed a browser-native path with low delay. HLS introduced segmenting and caching delays. RTMP lacked direct browser playback. Downloading complete files produced broken transitions between responses.&lt;/p&gt;

&lt;p&gt;WebRTC handled media delivery in the browser and supported a future path toward two-way media. Python’s &lt;code&gt;aiortc&lt;/code&gt; let us keep the media server in the same language as the AI pipeline, though it added setup work around tracks, timing, and connection state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production changes I would make
&lt;/h2&gt;

&lt;p&gt;The PoC proved the conversation loop. A production version needs more control around cancellation, load, and measurement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a session state machine
&lt;/h3&gt;

&lt;p&gt;Each session should move through &lt;code&gt;LISTENING&lt;/code&gt;, &lt;code&gt;TRANSCRIBING&lt;/code&gt;, &lt;code&gt;THINKING&lt;/code&gt;, and &lt;code&gt;SPEAKING&lt;/code&gt;. A new user turn should invalidate stale LLM, TTS, and frame-generation jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduce bounded queues and backpressure
&lt;/h3&gt;

&lt;p&gt;The LLM can generate text faster than the avatar can render it. Unbounded audio and frame queues increase memory use and make interruptions feel broken.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure first-frame latency
&lt;/h3&gt;

&lt;p&gt;Total response time hides the pause the user feels. I would record speech-end to transcript, first token, first audio chunk, first video frame, and playback start, with P50 and P95 values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate GPU workers from the API
&lt;/h3&gt;

&lt;p&gt;FastAPI should manage sessions while dedicated workers handle avatar inference. That separation makes GPU scheduling and horizontal scaling easier to control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design for barge-in
&lt;/h3&gt;

&lt;p&gt;Full-duplex conversation needs interruption detection, job cancellation, audio ducking, and a clean return to the listening state. The session controller must drop stale frames from the previous response after the user starts a new turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronise with timestamps
&lt;/h3&gt;

&lt;p&gt;The media layer should align audio samples and video presentation timestamps. Queue length alone cannot preserve lip sync under network jitter or variable inference time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;End-to-end latency followed the slowest media stage. The one-second RAG result helped, while the user still waited for speech generation, avatar inference, and the first playable WebRTC frame.&lt;/p&gt;

&lt;p&gt;Streaming changed the useful metric. The system did not need to finish the whole response before it could begin playback. It needed to deliver the first coherent audio-and-video chunk, then keep producing chunks faster than the browser consumed them.&lt;/p&gt;

&lt;p&gt;The hardware tests also challenged a common assumption. The most expensive GPU did not produce the shortest first response in our stack. Testing the complete pipeline gave us a better answer than choosing hardware from model specifications.&lt;/p&gt;

&lt;p&gt;The PoC gave us a working digital-human conversation and a clear list of the remaining engineering work. The next version would focus on barge-in, cancellation, percentile latency, and concurrent sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;June 2024 digital-human proof of concept&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Benchmark figures in this post come from the PoC tests recorded in June 2024. Cloud pricing, model endpoints, framework licences, and service performance may have changed since the evaluation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aimachinelearning</category>
      <category>conversationalai</category>
      <category>digitalhuman</category>
      <category>generativeai</category>
    </item>
    <item>
      <title>How I Migrated a 350,000-Line Java/JSP Application to TypeScript in Five Days</title>
      <dc:creator>Gokula Krishna</dc:creator>
      <pubDate>Fri, 31 Jul 2026 06:30:06 +0000</pubDate>
      <link>https://dev.to/gokula_krishna_1f814922bb/how-i-migrated-a-350000-line-javajsp-application-to-typescript-in-five-days-2364</link>
      <guid>https://dev.to/gokula_krishna_1f814922bb/how-i-migrated-a-350000-line-javajsp-application-to-typescript-in-five-days-2364</guid>
      <description>&lt;p&gt;I recently migrated a 350,000-line Java/JSP application to TypeScript using AI agents.&lt;/p&gt;

&lt;p&gt;Our original estimate for a conventional migration was three engineers and close to a year. Instead, one engineer orchestrating pools of up to 20 sub-agents at each stage got the application running in a development environment in five days.&lt;/p&gt;

&lt;p&gt;The headline sounds like a story about faster code generation. It is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The decisive factor was verification.&lt;/strong&gt; Once the agents had a reliable way to compare the old and new systems, parallelism became useful rather than chaotic.&lt;/p&gt;

&lt;h3&gt;
  
  
  350,000
&lt;/h3&gt;

&lt;p&gt;lines of Java/JSP&lt;/p&gt;

&lt;h3&gt;
  
  
  5 days
&lt;/h3&gt;

&lt;p&gt;to a running development environment&lt;/p&gt;

&lt;h3&gt;
  
  
  20
&lt;/h3&gt;

&lt;p&gt;sub-agents in each stage&lt;/p&gt;

&lt;h2&gt;
  
  
  Why parallel agents were only half the solution
&lt;/h2&gt;

&lt;p&gt;Jarred Sumner’s account of the Bun rewrite gave me useful ideas for running agents in parallel. Bun had an important advantage: a language-independent TypeScript test suite. The implementation could change while the expected behaviour remained measurable.&lt;/p&gt;

&lt;p&gt;Our application had no equivalent safety net.&lt;/p&gt;

&lt;p&gt;Only about half of the existing tests worked. Many had weak or missing assertions, and others depended directly on infrastructure. A large agent pool could generate code quickly, but without an independent oracle it could also produce 20 plausible versions of the wrong system.&lt;/p&gt;

&lt;p&gt;We needed a way to answer a simple question at every stage: &lt;em&gt;does the new application behave like the one already running?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;Business logic&lt;/li&gt;
&lt;li&gt;Improvements&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That order was deliberate. Each stage created a more stable foundation for the next, and it limited the number of variables that could change at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reconstructing the database contract
&lt;/h3&gt;

&lt;p&gt;The legacy application did not have a reliable database schema we could treat as authoritative.&lt;/p&gt;

&lt;p&gt;We reconstructed the application-facing Oracle schema from the SQL queries embedded throughout the codebase. The agents identified tables, columns, joins, constraints and data assumptions from the queries, then we validated the reconstructed contract against the running system.&lt;/p&gt;

&lt;p&gt;This was more than a database exercise. The schema defined the vocabulary shared by every API, page and business rule that followed. Starting anywhere else would have forced the agents to build on guesses.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rebuilding the APIs
&lt;/h3&gt;

&lt;p&gt;Once the data contract was stable, agents could migrate API components in parallel. Each work item was narrow: rebuild a component, exercise it against both systems, compare the result and resolve the discrepancy.&lt;/p&gt;

&lt;p&gt;This gave the UI migration a dependable backend target instead of asking agents to infer behaviour simultaneously at every layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Migrating the UI
&lt;/h3&gt;

&lt;p&gt;The UI required more than checking HTTP status codes. We needed to know whether the same data appeared, whether the same elements were present and whether the same interactions produced the same outcomes.&lt;/p&gt;

&lt;p&gt;This is where the parity harness became the centre of the migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a parity harness where tests were missing
&lt;/h2&gt;

&lt;p&gt;The parity harness ran the legacy and TypeScript applications side by side and compared them at three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API responses and resulting database state&lt;/li&gt;
&lt;li&gt;Page elements and screenshots&lt;/li&gt;
&lt;li&gt;User interactions in both applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9v5fauumsx4h5v1sjur8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9v5fauumsx4h5v1sjur8.jpg" alt="A symmetrical amber and teal visualization of a parity harness comparing legacy and TypeScript application stacks." width="800" height="442"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The parity harness compared observable behaviour across the legacy and TypeScript systems.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The harness did not need to understand the intended design of the whole system. It needed to expose observable differences.&lt;/p&gt;

&lt;p&gt;That distinction mattered. It turned the running legacy application into an executable specification, including behaviours that were not documented and edge cases that the surviving tests did not cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parity before correctness
&lt;/h2&gt;

&lt;p&gt;During the migration, we treated existing bugs as expected behaviour.&lt;/p&gt;

&lt;p&gt;That may sound counterintuitive, but fixing bugs while changing languages, frameworks and architecture would have made failures ambiguous. When a result differed, we would not know whether we had introduced a regression or intentionally changed behaviour.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First reproduce the system. Then improve it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We recorded known defects and fixed them only after achieving parity. This separated migration risk from product change and made discrepancies much easier to diagnose.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the agent pools worked
&lt;/h2&gt;

&lt;p&gt;At each migration stage, a pool of up to 20 sub-agents handled bounded pieces of work. Their jobs generally fell into three categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rebuild a component in TypeScript.&lt;/li&gt;
&lt;li&gt;Compare the old and new implementations through the parity harness.&lt;/li&gt;
&lt;li&gt;Investigate and fix the differences.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The primary engineering task shifted from writing every line to designing work packets, maintaining dependency order, interpreting failures and improving the verification loop.&lt;/p&gt;

&lt;p&gt;Parallelism helped because the work was decomposable. The harness helped because every agent could check its own result against the same external standard.&lt;/p&gt;

&lt;p&gt;Without that feedback loop, adding more agents would have increased review load. With it, discrepancies became actionable inputs for the next iteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost
&lt;/h2&gt;

&lt;p&gt;The migration consumed approximately &lt;strong&gt;100 million output tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At Fable 5 output-token pricing, that was roughly &lt;strong&gt;US$5,000&lt;/strong&gt; , excluding input tokens and other usage costs.&lt;/p&gt;

&lt;p&gt;That number needs context. It is not the total cost of productionizing a modernized core system, and getting the application running in development is not the same as completing security review, performance testing, deployment and operational handover.&lt;/p&gt;

&lt;p&gt;But it changes the economics of the first major migration milestone. A project estimated at three engineers for close to a year reached a working development environment with one engineer in five days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What made the five-day migration possible
&lt;/h2&gt;

&lt;p&gt;The result did not come from asking a model to “rewrite the application in TypeScript.”&lt;/p&gt;

&lt;p&gt;It came from combining five practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish an observable source of truth before scaling code generation.&lt;/li&gt;
&lt;li&gt;Migrate in dependency order so every stage has a stable foundation.&lt;/li&gt;
&lt;li&gt;Give agents bounded work with clear inputs and outputs.&lt;/li&gt;
&lt;li&gt;Preserve legacy behaviour until parity removes ambiguity.&lt;/li&gt;
&lt;li&gt;Make comparison and repair part of every agent’s loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parity harness was the force multiplier. It reduced the amount of trust required, made parallel work measurable and let agents detect many of their own mistakes before human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agents supplied the throughput. Verification converted that throughput into progress.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for legacy modernization
&lt;/h2&gt;

&lt;p&gt;The biggest shift may not be that AI writes code faster. It is that migration work can be reorganized around machine-executable feedback.&lt;/p&gt;

&lt;p&gt;Systems once considered too large, too poorly documented or too risky to touch may become practical candidates when their observable behaviour can be captured and compared incrementally.&lt;/p&gt;

&lt;p&gt;That does not remove the need for engineering judgment. It makes judgment more leveraged. The engineer still decides the sequence, defines the boundaries, builds the oracle and determines when parity is good enough to move forward.&lt;/p&gt;

&lt;p&gt;As migration time and cost continue to fall, the question for many organizations may change from “Can we afford to modernize?” to “What would we need to verify it safely?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Would you consider modernizing your core systems?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gokulakrishna.co/contact/" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt; if you are exploring an AI-assisted migration or need help designing a verification strategy.&lt;/p&gt;

</description>
      <category>aimachinelearning</category>
      <category>aiagents</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
