← All AI Engineer talks

AI Engineer Summit 2023

Storyteller: Building Multi-modal Apps with TS & ModelFusion - Lars Grammel, PhD

About this talk

Lars Grammel demonstrates Storyteller, a TypeScript and ModelFusion application that turns a spoken prompt into an illustrated, narrated preschool story. A React client and Fastify server coordinate Whisper transcription, rapid GPT-3.5 outline generation, GPT-4-assisted image prompting, Stable Diffusion XL illustration, voice-embedding retrieval, and synthesized character narration. The talk emphasizes parallel execution, incremental client updates, and model-selection tradeoffs required to keep the experience responsive.

Chapters

  1. 0:00Storyteller introduction and synthesized story demonstration
  2. 1:00React/Fastify architecture, Whisper transcription, and story outlines
  3. 2:41Parallel title generation, GPT-4 image prompts, and Stable Diffusion XL
  4. 3:45Narration latency, embedding-based voice retrieval, and speech synthesis
  5. 6:58Model-selection recap and project repositories

Talk transcript

  1. 0:00

    [upbeat music] Hey, everyone. I'm presenting Storyteller, an app for generating short audio stories for preschool kids.

  2. 0:20

    Storyteller is implemented using TypeScript and Model Fusion, an AI orchestration library that I've been developing. It generates audio stories that are about two minutes long, and all it needs is a voice input.

  3. 0:34

    Here is an example of the kind of story it generates to give you an idea.

  4. 0:38

    One day, while they were playing, Benny noticed something strange. The forest wasn't as vibrant as before. The leaves were turning brown, and the animals seemed less cheerful. Worried, Benny asked his friends what was wrong.

  5. 0:51

    Friends, why do the trees look so sad? And why are you all so quiet today?

  6. 0:55

    Benny, the forest is in trouble. The trees are dying, and we don't know what to do.

  7. 1:00

    How does this work? Let's dive into the details of the Storyteller application. Storyteller is a client-server application. The client is written using React, and the server is a custom Fastify implementation.

  8. 1:13

    The main challenges were responsiveness, meaning getting results to the user as quickly as possible, uh, quality, and consistency. So when you start Storyteller, it's just a small screen that has a Record Topic button, and once you start pressing it, it starts recording.

  9. 1:32

    Um, the audio, when you release, gets sent to the server as a buffer, and there we transcribe it. For transcription, I'm using OpenAI Whisper. Um, it is really quick for a short topic, one point five seconds, and once it becomes available, an event goes back to the client.

  10. 1:52

    So the client-server communication works through an event stream, server-sent events, [clears throat] that are being sent back.

  11. 2:02

    The event arrives on the client, and the React state updates, updating the screen. Okay, so then the user knows something is going on. In parallel, I start generating the story outline.

  12. 2:14

    For this, I use gpt-3.5-turbo-instruct, which I found to be very fast. So it can generate a story outline in about four seconds, and once we have that, we can start a bunch of other tasks in parallel.

  13. 2:28

    Generating the title, generating the image, and generating and narrating the audio story all happen in parallel. I'll go through those one by one now.

  14. 2:41

    First, the title is generated. For this, OpenAI gpt-3.5-turbo-instruct is used again, giving a really quick result. Once the title is available, it's being sent to the client again as an event and rendered there.

  15. 2:57

    In parallel, the image generation runs. First, uh, there needs to be a prompt to actually generate the image, and here consistency is important. So we pass in the whole story into a GPT-4 prompt that then extracts relevant representative keywords for an image prompt from the story.

  16. 3:17

    That image prompt is passed into Stability AI Stable Diffusion XL,

  17. 3:24

    where an image is generated. The generated image is stored as a virtual file in the server, and then an event is sent to the client with a path to that file.

  18. 3:37

    The client can then, through a regular URL request, just retrieve the image as part of an image tag,

  19. 3:45

    and it shows up in the UI. Generating the full audio story is the most time-consuming piece of the puzzle. Here we have a complex prompt that takes in the story and creates a structure with dialogue and speakers and extends the story.

  20. 4:05

    We use GPT-4 here with a low temperature to retain the story, and the problem is it takes one and a half minutes, which is unacceptably long for an interactive client.

  21. 4:17

    So how can this be solved? The key idea is streaming the structure. That's a little bit more difficult than just streaming characters token by token. Um, we need to always partially parse the structure and then determine if there is a new passage that we can actually, uh, narrate and, uh, synthesize speech for.

  22. 4:41

    Model Fusion takes care of the partial parsing and returns an iterable over fragments of partially parsed results that the application needs to decide what to do with them. Here we determine which story part is finished so we can actually narrate it.

  23. 4:58

    So we narrate each story part as it's getting finished.

  24. 5:03

    For each story part, we need to determine which voice, uh, we use to narrate it. The narrator has a predefined voice, and for all the speakers where we already have voices, we can immediately proceed.

  25. 5:16

    However, when there's a new speaker, we need to figure out which voice to give it.

  26. 5:23

    The first step for this is to generate a voice description for the speaker.

  27. 5:29

    Here's a GPT-3.5 Turbo prompt that gives us a structured result with gender and a voice description, and we then use that, um, for retrieval, where we beforehand embedded all the voices based on their descriptions and now can just retrieve them filtered by gender.

  28. 5:46

    Um, then a voice is selected, making sure there are no speakers with the same voice, and finally, we can generate the audio.

  29. 5:54

    Here for the speech synthesis, Element and ElevenLabs are supported. Based on the voices that have been chosen, one of those providers is picked and the audio is synthesized.

  30. 6:07

    Similar to the images, we generate an audio file, and we store it virtually in the server and then send the path to the client, which reconstructs the URL and just retrieves it as a media element.

  31. 6:20

    Once the first audio is completed, the client can then start playing. And while this is ongoing, in the background you're listening, and in the background the server continues to generate more and more parts.

  32. 6:36

    And that's it. So let's recap how the main challenge of responsiveness is addressed here. We have a loading state that has multiple parts that are updated as more results become available.

  33. 6:48

    We use streaming and parallel processing in the back end to make results available as quickly as possible, and you can start listening while the processing is still going on.

  34. 6:58

    And finally, models are being chosen such that the processing time for, like, the generation, say, of the story is minimized.

  35. 7:08

    Cool. I hope you enjoyed my talk. Thank you for listening, and if you wanna find out more, you can find Storyteller and also Model Fusion on GitHub at github.com/lgrammel/storyteller and github.com/lgrammel/modelfusion. [upbeat music]