Skip to main content
We all know preparing for an interview is hard, especially when there’s no one around to test your skills. Lots of websites offer mock interviews, but they also cost a lot! So what can you do when you’re a student fresh out of college and want to land the job of your dreams? I have good news for you! You can code your own interview practice agent, and make it feel as human-like as possible by using LiveKit voice agents with blazing fast LLMs hosted on Cerebras API cloud. By the end of this tutorial, you’ll have a nice, free, and fast personal interview agent to crush all your future interviews…AND you can customize it to the specific job you’re preparing for. The diagram below shows the general workflow we’ll build: pipeline Basically, our agent will have four major components:
  • LLM with structured output to understand the resume and job link
  • Speech to Text (STT) to convert user speech to digestible text for the interviewer
  • Interviewer LLM to conduct the interview based on the user responses and the conversation context so far
  • Text to Speech (TTS) to convert the interviewer LLM responses to human-like speech
LiveKit helps us put all these together! We’ll explain everything in a bit so buckle up and let’s get started! First, we start by making sure all the packages we need are installed:
Let’s import every package we will need. We will explain how each of these packages are used later.
To make our API calls to Cerebras and LiveKit, we need to add the following API Keys (replace the XXXXXX). To get a Cerebras API Key see our QuickStart guide and to get LiveKit API key and secret see the Voice AI quickstart .
Let’s start by extracting useful details from the job link. This information will be added to the context of our interviewer agent. Follow the instructions below. We will need two major components:
  1. A tool to read a given link and extract the text from it. We will use BeautifulSoup for this.
  2. An API call to a Cerebras supported LLM to process the input text. We want this LLM to support structured output.
We will implement all these in a function called process_link: Our function looks like this (we will break it down and explain what each segment does):
Now let’s break this down:
We first get the html link and remove leading and trailing whitespace characters (spaces, tabs, newlines, etc.) from each line. Then, we split each line into phrases wherever there are two or more spaces (' '). All this is wrapped inside try:... except:... to catch any exceptions. The resulting text will be used as context for our LLM. To make the API call to the LLM, we need:
The next step will be to define the structure of our output. The job title, location, start date, qualifications, responsibilities, and benefits are strings that could take any value whereas the job type needs to take one of the options ["full-time","part-time","contract","internship"].
Now we are ready to make the call and use chat completion with an appropriate system prompt. Remember to add the extracted text in the first step to the system prompt.
You can replace the model name with any other model supported by Cerebras (See supported models). You might need to change the system and user prompts for the call. Finally, we parse the JSON response and return it as the output to process_link function.

Parsing the resume PDF

Now, we do something similar to parse the pdf of the resume file.
We will define two functions:
  1. parse_pdf_to_text which converts our pdf file to plain text that will be used as the context to our LLM.
  2. process_pdf( which after calling parse_pdf_to_text, makes a Cerebras API call to generate a structured output summarizing the resume content. This function is very similar to process_link.
Let’s take a look at parse_pdf_to_text:
These lines use the package pdfplumber to extract information from a pdf file. Then, we remove the unnecessary characters using Regex.
As an optional step, and provided we have a context file path, we can save the results there for later use:
The function returns the extracted and cleanedup text. Again, all this is wrapped inside try:... except:... to catch any exceptions.

Interviewer Agent

Even though in this section we are designing a voice agent specifically for an interview practice, the general pipeline can be repurposed to any other voice agent you want to build! Let’s build our interviewer agent:
Let’s break this code down! Good news! LiveKit takes care of many of the major components of this segment through AgentSession. All we need to do is choose what we want to use for Speech To Text (STT), Text To Speech (TTS), and Voice Activity Detector (VAD).

JobContext

When defining our async entrypoint function, an important input is the JobContext (here we call it ctx). All you need to do, is to connect to the “room” where the conversation is happening by using:

Assistant

Let’s define our Agent subclass called Assistant which receives the chat context (subclass of ChatContext) and an instruction (system prompt).

AgentSession

The agent session is responsible for collecting user input, managing the voice pipeline, invoking the LLM, and sending the output back to the user (see LiveKit Docs).
Here, for both STT integration and TTS integration we use Deepgram. For VAD we use Silero. In order to ingerate Cerebras with this pipeline, we use the LiveKit plug-in:
where we can choose the model name, temperature, etc. See this page. You may have noticed that our agent receives a ChatContext as input. This is to make sure that we preserve the prior conversations. Before connecting the agent, we might want to give it some prior context. We do that as follows:
Where apart from the job_context (extracted from job link) and candidate_context (extracted from candidate resume),we add the current date as well for the agent’s reference.

Starting the session and generating the first agent message

Now that we have all our ingredients, we can start our session and generate the first message:
The last line is to make sure we preserve the last assistant message in our chat context.

The loop!

After the first greeting message from the agent, we want the agent to do the following in a loop:
  1. listen for anything the user says and convert it to text (STT) user_input = await session.listen()
  2. If the user speaks, a) Add their message to the running chat context
    chat_ctx.add_message(role="user", content=user_input)
    b) Generate an appropriate reply using the integrated LLM
    feedback_msg = await session.generate_reply( instructions="Give a brief, specific feedback on the user's response. Then, after a pause ask the next question.")
    c) Add the agent reply to the context
    chat_ctx.add_message(role="assistant", content=feedback_msg)
    d) Speak the agent reply (TTS)
    await session.speak(feedback_msg)
And of course, wrap all this in a try:... except to catch the exceptions.

Putting it all together

Optionally, you can implement a user interface to make your application more user friendly. To keep it simple, let’s just use input() to receive the pdf path and job link. Let’s put everything together in our main.py file:
That’s it! Have fun!