<?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: Nikola Mitic</title>
    <description>The latest articles on DEV Community by Nikola Mitic (@nmitic).</description>
    <link>https://dev.to/nmitic</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1111223%2Ff4a78b0c-3c69-4af3-adbf-7eb0fb846e76.jpeg</url>
      <title>DEV Community: Nikola Mitic</title>
      <link>https://dev.to/nmitic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nmitic"/>
    <language>en</language>
    <item>
      <title>Making your CV talk 🤖 Possible improvements 🚀</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:25 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-possible-improvements-2pib</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-possible-improvements-2pib</guid>
      <description>&lt;p&gt;We can always do better! Little (or not so little) pessimist in me also want to say "We can also always do worse!" &lt;/p&gt;

&lt;p&gt;But do not listen to him! Let's see how we might make this better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best code is the one you do not write!
&lt;/h3&gt;

&lt;p&gt;You can actually totally remove Backend service and have client make all the calls, problem is that you will have your API keys exposed and there is no other way to authenticate api calls as of now for Open AI and Eleven Labs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache 💵 because all cool kids are doing it!
&lt;/h3&gt;

&lt;p&gt;Cache! We are making call to fetch our CMS data on each request, this might not be needed as our data for CV does not change often if at all. This can also save costs because in the example I am using Hygrpah which has a free tier but can eventually reach its free quota.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maybe if we use &lt;strong&gt;&lt;em&gt;please&lt;/em&gt;&lt;/strong&gt; word more in our prompt? 🤓 (do not fail to note sarcasm in the quote)
&lt;/h3&gt;

&lt;p&gt;Better prompting, if you explore LlamaIndex there are ways how one can make their promoting yield better and more accurate responses. You can create custom Prompt template, or make have clear instructions about how system should behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outro 👋
&lt;/h2&gt;

&lt;p&gt;This was a lot, please feel free to look at the code base and ask any question you might have.&lt;br&gt;
I would also welcome any improvements that one is willing to share.&lt;/p&gt;

&lt;p&gt;And lastly, if you make all the way, thank you so much I hope you had some fun and learn something in the process.&lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>node</category>
      <category>systemdesign</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to use express JS routes to listen for Web socket API?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:20 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-use-express-js-routes-to-listen-for-web-socket-api-2pda</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-use-express-js-routes-to-listen-for-web-socket-api-2pda</guid>
      <description>&lt;p&gt;All you need to do is to open web socket towards web socket api inside Express JS route. &lt;code&gt;streamAudioAnswer&lt;/code&gt; func is doing exactly that.&lt;/p&gt;

&lt;p&gt;Here is the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client request api path&lt;/li&gt;
&lt;li&gt;Express Js Open web socket connection&lt;/li&gt;
&lt;li&gt;Express JS sends message to web socket api&lt;/li&gt;
&lt;li&gt;Web socket api responds&lt;/li&gt;
&lt;li&gt;Express JS takes response and returns it to the Client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🔗🔗🔗 &lt;a href="https://github.com/nmitic/ai-interviewer/blob/49f55956c919b1b5ce2d32bdb97571fd4abf8fe9/src/routes/talk/route.ts"&gt;For full implementation click here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of &lt;code&gt;streamAudioAnswer&lt;/code&gt; usage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  streamAudioAnswer({
    question: question,
    onChunkReceived: (chunk) =&amp;gt; {
      const buffer = Buffer.from(chunk, "base64");
      res.write(buffer);
    },
    onChunkFinal: () =&amp;gt; {
      res.end();
    },
    onError: (error) =&amp;gt; {
      console.error(`WebSocket Error: ${error}`);
      res.status(500).send(`WebSocket Error: ${error}`);
    },
    onClose: (event) =&amp;gt; {
      if (event.wasClean) {
        console.info(
          `Connection closed cleanly, code=${event.code}, reason=${event.reason}`
        );
      } else {
        console.warn("Connection died");
      }
      res.end();
    },
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const streamAudioAnswer = ({
  question,
  onChunkReceived,
  onChunkFinal,
  onError,
  onClose,
}: {
  question: string;
  onChunkReceived: (audioChunk: string) =&amp;gt; void;
  onChunkFinal: () =&amp;gt; void;
  onError: (error: ErrorEvent) =&amp;gt; void;
  onClose: (event: CloseEvent) =&amp;gt; void;
}) =&amp;gt; {
  const voiceId = "IcOKBAbsVAB6WkEg78QO";
  const model = "eleven_turbo_v2";
  const wsUrl = `wss://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream-input?model_id=${model}`;
  const socket = new WebSocket(wsUrl);

  socket.onopen = async function (_event) {
    console.log("OPEN SOCKET");

    const answerSource = await getAnswerSource();
    const answerChunks = await getAnswerChunks(answerSource, question);

    const bosMessage = {
      text: " ",
      voice_settings: {
        stability: 0.5,
        similarity_boost: 0.5,
      },
      xi_api_key: process.env.ELEVEN_LABS_API_KEY,
    };

    socket.send(JSON.stringify(bosMessage));

    for await (const text of textChunker(answerChunks)) {
      socket.send(JSON.stringify({ text: text, try_trigger_generation: true }));
    }

    const eosMessage = {
      text: "",
    };

    socket.send(JSON.stringify(eosMessage));
  };

  socket.onmessage = function (event) {
    const response = JSON.parse(event.data.toString());

    if (response.audio) {
      onChunkReceived(response.audio);
    } else {
      console.log("No audio data in the response");
    }

    if (response.isFinal) {
      console.log("Audio stream chunks final");
      onChunkFinal();
    }
  };

  socket.onerror = onError;

  socket.onclose = onClose;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>websocket</category>
      <category>node</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to combine Open AI text stream response with Eleven Lab Web socket streaming in TypeScript?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:15 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-combine-open-ai-text-stream-response-with-eleven-lab-web-socket-streaming-in-typescript-n50</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-combine-open-ai-text-stream-response-with-eleven-lab-web-socket-streaming-in-typescript-n50</guid>
      <description>&lt;p&gt;Let's briefly refer back to this part of our system design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4a6xfcqbs7j9dmjqtrdk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4a6xfcqbs7j9dmjqtrdk.png" alt="Eleven Labs web socket api with open AI system design"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch Relevant CMS data&lt;/li&gt;
&lt;li&gt;Fetch stream from Open AI&lt;/li&gt;
&lt;li&gt;Feed Web socket audio stream api with text stream from Open AI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Eleven Labs uses web socket api to enable for audio stream, open AI uses rest api to return text stream.&lt;/p&gt;

&lt;p&gt;What is important here to achieve is that we do not want to wait for the open AI response before we can start streaming audio from eleven labs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This means we need a way to send first chunk of Open AI stream as soon as possible to eleven labs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But we have a problem, Open AI or any other LLM api (Groq as well) returns chunks which do not represent any meaningful words which eleven labs requires in order to work as intended.&lt;/p&gt;

&lt;p&gt;This means that we need to buffer chunks into words from Open AI and only when we have our first word buffered we send a message to web socket of Eleven Labs and start streaming audio.&lt;/p&gt;

&lt;p&gt;For this purpose I wrote &lt;code&gt;textChunker&lt;/code&gt; which is TS version of Python example Eleven Labs has in their docs. 🔗🔗🔗 &lt;a href="https://elevenlabs.io/docs/api-reference/websockets#example-voice-streaming-using-elevenlabs-and-openai" rel="noopener noreferrer"&gt;Click here to see Python version &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗🔗🔗 &lt;a href="https://github.com/nmitic/ai-interviewer/blob/43935b78ad06f6c459908a675d9cf743bfd8396f/src/routes/talk/stream.ts#L30-L68" rel="noopener noreferrer"&gt;Click here for TextChunker code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of Text Chunker usage.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  socket.onopen = async function (_event) {
    console.log("OPEN SOCKET");

    const answerSource = await getAnswerSource();
    const answerChunks = await getAnswerChunks(answerSource, question);

    const bosMessage = {
      text: " ",
      voice_settings: {
        stability: 0.5,
        similarity_boost: 0.5,
      },
      xi_api_key: process.env.ELEVEN_LABS_API_KEY,
    };

    socket.send(JSON.stringify(bosMessage));

    for await (const text of textChunker(answerChunks)) {
      socket.send(JSON.stringify({ text: text, try_trigger_generation: true }));
    }

    const eosMessage = {
      text: "",
    };

    socket.send(JSON.stringify(eosMessage));
  };


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🔗🔗🔗 &lt;a href="https://github.com/nmitic/ai-interviewer/blob/43935b78ad06f6c459908a675d9cf743bfd8396f/src/routes/talk/stream.ts#L70-L136" rel="noopener noreferrer"&gt;For full implementation of Open AI with Eleven Labs click here&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>elevenlabs</category>
      <category>openai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to read audio stream on client using Next JS?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:11 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-read-audio-stream-on-client-using-next-js-5hda</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-read-audio-stream-on-client-using-next-js-5hda</guid>
      <description>&lt;p&gt;Considering that our backend exposes url which streams audio solution is rather simple, we make use of html Audio element and use the correct path. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq3ix8dnme8giffgunzl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq3ix8dnme8giffgunzl.gif" alt="moving image of audio streaming component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing about this is Next JS related, this is pure React JS TS solution, so you can use it in your React JS code base as well.&lt;/p&gt;

&lt;p&gt;For this purpose I created React component that can be re used.&lt;br&gt;
Code here: &lt;a href="https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAITalk/components/AudioAnswer.tsx" rel="noopener noreferrer"&gt;https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAITalk/components/AudioAnswer.tsx&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const AudioAnswer = ({
  question,
  onAnswerDone,
  onAnswerStart,
}: {
  question: string;
  onAnswerDone: () =&amp;gt; void;
  onAnswerStart: () =&amp;gt; void;
}) =&amp;gt; {
  const demo = process.env.NEXT_PUBLIC_AI_DEMO === "true";

  return (
    &amp;lt;audio autoPlay onPlay={onAnswerStart} onEnded={onAnswerDone}&amp;gt;
      &amp;lt;source
        src={`${process.env.NEXT_PUBLIC_AI_INTERVIEWER_SERVICE}/api/talk?question=${question}&amp;amp;demo=${demo}`}
        type="audio/mp3"
      /&amp;gt;
    &amp;lt;/audio&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>node</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to read text stream on client using Next JS?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:07 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-read-text-stream-on-client-using-next-js-351b</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-read-text-stream-on-client-using-next-js-351b</guid>
      <description>&lt;p&gt;I have wrote a function which takes answer and callbacks to which UI can listen to and react as it finds fit.&lt;/p&gt;

&lt;p&gt;Nothing about this is Next JS related, this is pure React JS TS solution, so you can use it in your React JS code base as well.&lt;/p&gt;

&lt;p&gt;Here are the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make request to fetch stream - code here: &lt;a href="https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L15-L34"&gt;https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L15-L34&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get stream reader - code here:&lt;a href="https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L52-L58"&gt;https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L52-L58&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Iterate over he stream and call a function on each iteration so UI can listen and react to it - code here: &lt;a href="https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L64-L76"&gt;https://github.com/nmitic/nikola.mitic.dev/blob/745b103829874d0bb7b19d1668d793b99e23653b/components/InterviewerAI/InterviewerAI.func.ts#L64-L76&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full implementation of &lt;code&gt;answerQuestionWithStream&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

export const answerQuestionWithStream = async ({
  onStreamStart,
  onStream,
  onStreamDone,
  onError,
  question,
}: {
  onStreamStart: () =&amp;gt; void;
  onStream: (chunk: string) =&amp;gt; void;
  onStreamDone: (answer: string) =&amp;gt; void;
  onError: () =&amp;gt; void;
  question: string;
}) =&amp;gt; {
  try {
    const demo = process.env.NEXT_PUBLIC_AI_DEMO === "true";

    const reader = await getStreamReader(demo, question);

    if (!reader) {
      console.error("Stream reader undefined");

      return;
    }

    const textDecoder = new TextDecoder();

    onStreamStart();

    // Keeps track of streamed answer and will evaluate to full once streaming is done
    let fullDecodedAnswer = "";

    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      const decodedText = textDecoder.decode(value);

      fullDecodedAnswer = fullDecodedAnswer + decodedText;
      onStream(decodedText);
    }
    onStreamDone(fullDecodedAnswer);
  } catch (error) {
    console.error(error);
    // indicated to the user the error has happen
    onError();
  }
};

---

❤️If you would like to stay it touch please feel free to connect❤️

1. [X](https://x.com/mitic_dev)
2. [Linkedin](https://www.linkedin.com/in/nikola-mitic-6a5b11119/)
3. nikola.mitic.dev@gmail.com

---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>node</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to send audio stream from Express JS?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:34:03 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-send-audio-stream-from-express-js-18om</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-send-audio-stream-from-express-js-18om</guid>
      <description>&lt;p&gt;For our case this part was easy, as all we need to do is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get buffered chunk&lt;/li&gt;
&lt;li&gt;Write to response object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And it looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    onChunkReceived: (chunk) =&amp;gt; {
      const buffer = Buffer.from(chunk, "base64");
      res.write(buffer);
    },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will talk more about onChunkReceived method in the next section. Long story short, it it accepts a callback with chunk that come from web socket api. This is implementation details of how to work with Eleven Labs API.&lt;/p&gt;

&lt;p&gt;Each time web socket sends an event that chunk is received it will write to response object and return chunk to the client.&lt;/p&gt;

&lt;p&gt;This is the simplest way to combine express js with web socket api. &lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>audio</category>
      <category>node</category>
      <category>express</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to send text stream from Express JS?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:33:59 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-send-text-stream-from-express-js-1d3</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-send-text-stream-from-express-js-1d3</guid>
      <description>&lt;p&gt;Here we have stream coming from Open AI. We need to read chunks and stream it back as soon as chunk is ready. In a way we are streaming a stream.&lt;/p&gt;

&lt;p&gt;One can make a case that this is not needed, however I am doing this in order to hide API keys for Groq, Open AI and Hygraph headless CMS. As I do not want them to be exposed to the client.&lt;/p&gt;

&lt;p&gt;Route handler for &lt;code&gt;/api/ask&lt;/code&gt; route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const route = async (req: Request, res: Response) =&amp;gt; {
  const { question } = req.query;

  if (typeof question !== "string") {
    return res
      .status(400)
      .send(`Client error: question query not type of string`);
  }
  try {
    const answerSource = await getAnswerSource();

    const answerChunks = await getAnswerChunks(answerSource, question, false);

    for await (const chunk of answerChunks) {
      console.log(chunk.response);
      if (req.closed) {
        res.end();
        return;
      }
      res.write(chunk.response);
    }

  } catch (error) {
    return res.status(500).send(`Server error: ${error}`);
  }

  res.end();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dissect it a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const answerSource = await getAnswerSource();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are getting all our relevant data, CV, blog posts plus any pages you would like your AI clone to be aware of.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const answerChunks = await getAnswerChunks(answerSource, question, false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are feeding out LLM with our custom data and question. And getting stream back as a response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    for await (const chunk of answerChunks) {
      console.log(chunk.response);
      if (req.closed) {
        res.end();
        return;
      }
      res.write(chunk.response);
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we simply iterate over chunks and make sure we close connection in case client do so as well.&lt;/p&gt;

&lt;p&gt;And our streamception is done! 🎏&lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>express</category>
      <category>streaming</category>
      <category>node</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 How to have Open AI api answer based on your custom data?</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:33:55 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-how-to-have-open-ai-api-answer-based-on-your-custom-data-4ie4</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-how-to-have-open-ai-api-answer-based-on-your-custom-data-4ie4</guid>
      <description>&lt;p&gt;I used &lt;a href="https://ts.llamaindex.ai/"&gt;https://ts.llamaindex.ai/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As somebody who is just starting out with generative AI, I have found LlamaIndex to be of a huge help.&lt;/p&gt;

&lt;p&gt;They help you cut the corners and reduce need for a deep understanding about how LLM work in order to be productive. Which is something I need as a beginner.&lt;/p&gt;

&lt;p&gt;One thing to note is that TS version tend to be unstable in terms of API at the time of writing, versions keep introducing new functions and classes signatures and their docs sometimes fails to follow up. &lt;/p&gt;

&lt;p&gt;So be patient, I believe TS version will reach maturity soon. They are doing a great job.&lt;/p&gt;

&lt;p&gt;Code is rather simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getAnswerChunks = async (
  source: string,
  question: string,
  useGroq: boolean = false
) =&amp;gt; {
  if (useGroq) {
    // Update llm to use Groq
    Settings.llm = new Groq({
      apiKey: process.env.GROQ_API_KEY,
      model: "llama3-8b-8192",
    });
  }
  // Create Document object
  const document = new Document({
    text: `Nikola Mitic - life story: ${JSON.stringify(source)}`,
  });
  // Create storage from local file
  const storageContext = await storageContextFromDefaults({
    persistDir: "./index-storage",
  });
  // Split text and create embeddings. Store them in a VectorStoreIndex
  const index = await VectorStoreIndex.fromDocuments([document], {
    storageContext,
  });
  // gets retriever
  const retriever = index.asRetriever({ similarityTopK: 5 });

  const chatEngine = new ContextChatEngine({
    retriever,
    chatModel: Settings.llm,
  });
  // Get stream chunks
  const chunks = await chatEngine.chat({
    message: `
      You are Nikola Mitic AI clone.
      You answer the question as if you are Nikola Mitic.
      If question is related to work experience, the correct and complete answer can be found under "nikola_mitic_resume_cv_work_experience"

      Bellow id the question:
      -------------------------------------------------
       ${question}
      -------------------------------------------------
    `,
    stream: true,
  });

  return chunks;
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dissect it a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if (useGroq) {
    // Update llm to use Groq
    Settings.llm = new Groq({
      apiKey: process.env.GROQ_API_KEY,
      model: "llama3-8b-8192",
    });
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are adding a flag that will instruct LlamaIndex to use Groq instead of Open AI which is its default settings. Important to note, regardless of Groq usage, we will still need to use Open AI as Groq does offer embeddings models at the time of writing.&lt;/p&gt;

&lt;p&gt;However, Groq API seems to be free as of time of this writing, so one can save a lot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // Create Document object
  const document = new Document({
    text: `Nikola Mitic - life story: ${JSON.stringify(source)}`,
  });
  // Create storage from local file
  const storageContext = await storageContextFromDefaults({
    persistDir: "./index-storage",
  });
  // Split text and create embeddings. Store them in a VectorStoreIndex
  const index = await VectorStoreIndex.fromDocuments([document], {
    storageContext,
  });
  // gets retriever
  const retriever = index.asRetriever({ similarityTopK: 5 });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are creating our document, creating file system storage context for future retrievals. &lt;br&gt;
Creating our vector store and finally retriever. Please note, playing with &lt;code&gt;similarityTopK&lt;/code&gt; is important here, it is choice between faster response time and more accurate and content rich full answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const chatEngine = new ContextChatEngine({
    retriever,
    chatModel: Settings.llm,
  });
  // Get stream chunks
  const chunks = await chatEngine.chat({
    message: `
      You are Nikola Mitic AI clone.
      You answer the question as if you are Nikola Mitic.
      If question is related to work experience, the correct and complete answer can be found under "nikola_mitic_resume_cv_work_experience"

      Bellow id the question:
      -------------------------------------------------
       ${question}
      -------------------------------------------------
    `,
    stream: true,
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we make use of LlamaIndex chat interface and construct our prompt. You should play with the prompt, I find out this to be very tricky and results very on which LLM is used.&lt;/p&gt;

&lt;p&gt;So have fun with it. ✌️&lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>openai</category>
      <category>customdata</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 Explaining the concept and system design👨‍💻</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:33:50 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-explaining-the-concept-and-system-design-33no</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-explaining-the-concept-and-system-design-33no</guid>
      <description>&lt;h3&gt;
  
  
  Explaining the concept (in non technical terms)
&lt;/h3&gt;

&lt;p&gt;Here are where things finally gets to be more interesting. To make it very simple to understand I will simplify concept so that we can understand it easier and build on top of it later.&lt;/p&gt;

&lt;p&gt;Imagine we have a house with 3 floors, each flow unlocks something new.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First flor is called Question floor. &lt;/li&gt;
&lt;li&gt;Second flor knowledge flor.&lt;/li&gt;
&lt;li&gt;Third flor Speech flor.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Question floor ❓
&lt;/h3&gt;

&lt;p&gt;Now let's imagine you enter the first flow. Here you can ask your question. &lt;strong&gt;This represents our web page with chat and audio interface.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge floor 📚
&lt;/h3&gt;

&lt;p&gt;You can take a piece of paper and write you question down. But you want an answer. Getting the answer requires knowledge. Problem is, you do not have time to wait. You tried to enter next flow, knowledge room but it takes so long for you to find an answer write it down and go back to you question room, where you can ask more. &lt;/p&gt;

&lt;p&gt;So instead, you bring a friend with you, this friend will take your question look for the answer and as &lt;em&gt;soon&lt;/em&gt; it has something that &lt;em&gt;might&lt;/em&gt; be an answer to you question he will write it down, on multiple papers, each containing part of the answer, and will give it to you so that you can read one by one and not wait until he write the complete answer. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge floor is our Interview AI backend service leveraging Open AI (or Groq or any other LLM service of your choice) being aware of our CV data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But wait there is one more flow to unlock! Speech floor. This is because you got tired of reading and talking, you want to ask using questions using your voice and you want answer to be voice as well!&lt;/p&gt;

&lt;h3&gt;
  
  
  Speech floor 🗣️
&lt;/h3&gt;

&lt;p&gt;Speech floor is where we have our cloned voice! And all it does is take paper with text written on it and start talking whatever is written. And here, just as in knowledge floor, you want to hear the voice as soon as answer is available! &lt;/p&gt;

&lt;p&gt;So you have a third friend who is very loud and can speak in volume that you from the first floor can hear. You friend in knowledge room is now giving papers with chunked answer (text stream in tech term) to your friend in speech room, who start reading them to you part by part (paper by paper) as soon as he gets the first paper. (Audio stream in tech terms) &lt;strong&gt;Speech floor is our Eleven Labs voice clone API.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  System design -  How it all comes together?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbiawe6rzgz5u6rfvn7ad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbiawe6rzgz5u6rfvn7ad.png" alt="Personal AI clone design system flow chart" width="800" height="1074"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://excalidraw.com/#json=XobHjKxjVPrno51o-_MY5,GLe_8Eh1fNbsZOVN9gwVQA"&gt;Click here for link to diagram&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok back to our world of ones and zeros. You can get yourself familiar with our system design using the diagram above.&lt;/p&gt;

&lt;p&gt;It looks more complicated than it actually is.&lt;/p&gt;

&lt;p&gt;We have 5 main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client - Next JS&lt;/li&gt;
&lt;li&gt;Express JS - server with two routes (/api/talk and api/ask)&lt;/li&gt;
&lt;li&gt;Headless CMS - Hygraph&lt;/li&gt;
&lt;li&gt;Open AI API (and Groq API as a second choice for reduced costs)&lt;/li&gt;
&lt;li&gt;Eleven AI api&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The name of the game here is STREAM. 🏄‍♂️&lt;/p&gt;

&lt;p&gt;Express JS always return stream of either text chunks or audio chunks. Both Eleven AI and Open AI support streaming which are in a way just proxied through our custom Express JS server.&lt;/p&gt;

&lt;p&gt;One tricky part here is how to feed Eleven Lab web socket with stream from Open AI. We will talk about that in the following chapters.&lt;/p&gt;

&lt;p&gt;Here is what happens when user type question in chat interface:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client makes an API call to Backend service via &lt;code&gt;/api/ask&lt;/code&gt; path&lt;/li&gt;
&lt;li&gt;Backend service makes a call to headless CMS to get latest data&lt;/li&gt;
&lt;li&gt;Once CMS data is returned Backend makes another request towards Open AI to get answer in a form of stream.&lt;/li&gt;
&lt;li&gt;and finally Backend returns stream to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is what happens when user type ask question using audio interface:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client converts audio into text using client speech recognitions API.&lt;/li&gt;
&lt;li&gt;Client makes an API call to Backend service via &lt;code&gt;/api/talk&lt;/code&gt; path.&lt;/li&gt;
&lt;li&gt;Backend service makes a call to headless CMS to get latest data.&lt;/li&gt;
&lt;li&gt;Once CMS data is returned Backend makes another request towards Open AI to get answer in a form of stream.&lt;/li&gt;
&lt;li&gt;As soon as first chunk of streamed data is returned, backend will create a buffer, represents array of words needed for Elevent labs web socket api.&lt;/li&gt;
&lt;li&gt;Backend makes request towards Eleven Labs web socket API as soon as first buffered word is ready.&lt;/li&gt;
&lt;li&gt;Eleven Labs returns audio stream to Backend.&lt;/li&gt;
&lt;li&gt;Backend returns the stream and exposes a route which can be played in client using audio web api. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I won't go in to details about each part of the system and how to implement, however there are certain problems you will face regardless of tech stack of choice, and would like to share how I went about solving them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to have Open AI api answer based on your custom data?&lt;/li&gt;
&lt;li&gt;How to send text stream from Express JS?&lt;/li&gt;
&lt;li&gt;How to send audio stream from Express JS?&lt;/li&gt;
&lt;li&gt;How to read text stream on client using Next JS?&lt;/li&gt;
&lt;li&gt;How to read audio stream on client using Next JS?&lt;/li&gt;
&lt;li&gt;How to combine Open AI text stream response with Eleven Lab Web socket streaming in TypeScript?&lt;/li&gt;
&lt;li&gt;How to use express JS routes to listen for Web socket API?&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 Product mindset first 🧠 Defining features</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:33:36 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-product-mindset-first-defining-features-1hp9</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-product-mindset-first-defining-features-1hp9</guid>
      <description>&lt;h2&gt;
  
  
  Getting our hands dirty 🏋️‍♂️
&lt;/h2&gt;

&lt;p&gt;First things first. How does this even work? Higher level concept is actually very simple, but it is important to be understood before jumping into the actually implementation. As understanding it better, means one will able to challenge it, come up with something better and it means actual implementation will be streamed from a place of understanding allowing us to avoid hitting road blocks where we really should not.&lt;/p&gt;

&lt;p&gt;I like to approach designing concepts from well defined requirements. I also like to distinguish 3 main type of requirements:&lt;/p&gt;

&lt;p&gt;I call it Semaforoments (My wife will understand 😂)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Business requirements (What I want) 🔴 STOP&lt;/li&gt;
&lt;li&gt;Product requirements (What I need) 🟡 READY&lt;/li&gt;
&lt;li&gt;Engineering requirements (What I must) 🟢 GO&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One is streamed from the another. Just like in traffic light, order must be respected. It is simple flow of information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xqdzb5yaduqfntc3n2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xqdzb5yaduqfntc3n2o.png" alt="Requirements flow" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Business requirements
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I WANT to save time and be able to take more interviews with higher possibility of a potential match!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Product requirements
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I NEED a frictionless user interface which will allow hiring manager and recruiters to bypass initial candidate interview. &lt;br&gt;
I need interface to be as human as possible to imitate real interview, thus responses and tone of it needs to be genuine, real and absolutely authentic.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Engineering requirements
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I MUST build a responsive web app, working across device sizes and computing power. I must stream the information and interview responses in chunks in order have frictionless experience. &lt;br&gt;
I must be able to develop and release continuously. I must dedicated service for each one respectively: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content data&lt;/li&gt;
&lt;li&gt;Handling of interview responses&lt;/li&gt;
&lt;li&gt;Web app hosting&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my humble opinion (which lately is become less and less humble 🤪), exact features to be built are a marriage between product and engineering requirements, as I believe building great software product is possible where developers and product owners are working &lt;strong&gt;&lt;em&gt;WITH&lt;/em&gt;&lt;/strong&gt; each other not &lt;strong&gt;&lt;em&gt;FOR&lt;/em&gt;&lt;/strong&gt; each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I can see online CV using the link I can share.&lt;/li&gt;
&lt;li&gt;I can edit the content of CV without having to write any code.&lt;/li&gt;
&lt;li&gt;I can feed CV content data to any consumer that is interested in it.&lt;/li&gt;
&lt;li&gt;I can chat with CV and responses will be streamed so that waiting time is reduced.&lt;/li&gt;
&lt;li&gt;I can talk to CV and it will respond me in candidate real human authentic voice.&lt;/li&gt;
&lt;li&gt;I can see tweet like blog post from candidate to get more insights about their thinking.&lt;/li&gt;
&lt;li&gt;I can ask question related to candidate views outside of work experience getting to know the candidate personality and way of thinking.&lt;/li&gt;
&lt;li&gt;I can save PDF document of chat conversation.&lt;/li&gt;
&lt;li&gt;I can save PDF document of CV.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From here we will focus on the following two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat interface&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can chat with CV and responses will be streamed so that waiting time is reduced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Audio / Talk interface&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can see tweet like blog post from candidate to get more insights about their thinking&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>uxdesign</category>
      <category>ui</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Making your CV talk 🤖 Easy into the development</title>
      <dc:creator>Nikola Mitic</dc:creator>
      <pubDate>Wed, 05 Jun 2024 10:33:23 +0000</pubDate>
      <link>https://dev.to/nmitic/making-your-cv-talk-easy-into-the-development-ook</link>
      <guid>https://dev.to/nmitic/making-your-cv-talk-easy-into-the-development-ook</guid>
      <description>&lt;h2&gt;
  
  
  What we will be building? 🏠
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Online CV &lt;a href="https://nikola-mitic.dev/cv/patient21"&gt;https://nikola-mitic.dev/cv/patient21&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blog / Tweet like interface &lt;a href="https://nikola-mitic.dev/tiny_thoughts"&gt;https://nikola-mitic.dev/tiny_thoughts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Chat interface to let people chat to your CV &lt;a href="https://nikola-mitic.dev/ai_clone_interview/chat"&gt;https://nikola-mitic.dev/ai_clone_interview/chat&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;And finally audio interface to give your CV voice and let people talk to your CV &lt;a href="https://nikola-mitic.dev/ai_clone_interview/talk"&gt;https://nikola-mitic.dev/ai_clone_interview/talk&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Entire source code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/nmitic/ai-interviewer"&gt;Backend Service AI related code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nmitic/nikola.mitic.dev"&gt;Client using Next JS&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32bhtddbu1ipqnaj2x0m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32bhtddbu1ipqnaj2x0m.gif" alt="Ai clone chat interface" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preamble
&lt;/h2&gt;

&lt;p&gt;It's the year 2024 and tech word is going crazy. Somehow you find yourself in a need for a new job. You start applying for jobs. And you keep applying, and you apply some more, and more, and even more.&lt;/p&gt;

&lt;p&gt;But that right job offers does not come!&lt;/p&gt;

&lt;p&gt;And you start asking yourself what are you doing wrong, and how can you optimise your job search, since by now your figure it out it is a numbers game! As market is saturated with us developers (especially us web frontend developers).&lt;/p&gt;

&lt;p&gt;This is where I am at the moment, and where I was for the past 2 years.&lt;/p&gt;

&lt;p&gt;There is much I can say about this topic, and hopefully help others, as well as get off my chest all the unfairness going around job market at the moment. But I won't.&lt;/p&gt;

&lt;p&gt;Instead I want to share what help me optimise and reduce the amount of interviews I am having which otherwise should not even happen as they bring no job opportunities. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simply put I want to clear out all the processes which are not a match, while also increasing amount of processes which are more likely to be a match.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By now you already experiences automatic CV filtering, where some sort of AI will scan your CV and you would be marked as match or not. You might not like it, I know I do not, but it makes sense. I wanted to do the same with companies. &lt;/p&gt;

&lt;p&gt;Ex coworker of mine, said.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is like playing uno reverse card on companies using AI to filter our job applications, we should just let their AI bots talk to our AI bots.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make it clear, that was never a goal. But it does sounds funny. Tech interviews are known to be ridiculously long. 6 rounds seems to be the norms nowadays. So let's do some basic math (the only one I know), if you have 10 interviews a week (which has been a norm for me), and half of it is first round where they ask basic questions, and if one interview as such lasts 30 minutes, you just save yourself  7.5 hours per week! You got almost a full work day back.&lt;/p&gt;

&lt;p&gt;Not too bad!&lt;/p&gt;

&lt;p&gt;Solution is rather simple:&lt;/p&gt;

&lt;p&gt;Have an AI be aware of you resume, build a simple chat interface, where hiring managers and recruiters can ask questions which they usually asked on first initial calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;p&gt;I won't be explaining line by line, after all how you do things is up to you. However, I will explain the concept, give you the blueprint, give you detailed solution to a common problems you will encounter and share the whole repo to the relevant code for guidance.&lt;/p&gt;

&lt;p&gt;I believe you can learn a lot and have fun in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For the concept itself nothing, you do not even need to be a developer. If you are not a developer and fail to understand, it is up to me! I did a bad job explaining it.&lt;/li&gt;
&lt;li&gt;For implementation you can choose your own stack, I went with&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;NextJS with TS - For CV implementation&lt;/li&gt;
&lt;li&gt;Tailwind CSS - For styling&lt;/li&gt;
&lt;li&gt;Node JS - For the one and only BE service we will be writing&lt;/li&gt;
&lt;li&gt;Llamaindex TS - To easy out working with LLM&lt;/li&gt;
&lt;li&gt;Open AI api - For both LLM and embedding model &lt;/li&gt;
&lt;li&gt;Groq API - For LLM, optimal, but at the time of writing it is free &lt;/li&gt;
&lt;li&gt;Eleven labs API - For voice cloning and streaming&lt;/li&gt;
&lt;li&gt;Hygraph - For content hosting&lt;/li&gt;
&lt;li&gt;Vercel - For deployment CV hosting / deployment&lt;/li&gt;
&lt;li&gt;Renderer for Node JS service hosting / deployment &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  But wait how much will I have to pay for this? 💸
&lt;/h2&gt;

&lt;p&gt;Almost nothing! Except for Open AI all of the tools have generous free tiers, limits you will hardly surpass, considering that this is your online CV it really should not be getting a tone of visitors   &lt;/p&gt;




&lt;p&gt;❤️If you would like to stay it touch please feel free to connect❤️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://x.com/mitic_dev"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/nikola-mitic-6a5b11119/"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:nikola.mitic.dev@gmail.com"&gt;nikola.mitic.dev@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>ai</category>
      <category>openai</category>
      <category>resume</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
