Tuned prompts with persona
This commit is contained in:
109
interview-agent/AI_MOCK_INTERVIEW_VIDEO_TRANSCRIPT.md
Normal file
109
interview-agent/AI_MOCK_INTERVIEW_VIDEO_TRANSCRIPT.md
Normal file
@@ -0,0 +1,109 @@
|
||||
Hi, this is Daniel, and I'll walk you through my implementation of the AI Mock Interview demo using the Livekit's multi-agent framework.
|
||||
|
||||
## Introduction
|
||||
|
||||
In this demo, the AI acts as a mock interviewer. The goal is not to build a full interview system yet, but to build a strong foundation for a real-time voice interview flow.
|
||||
|
||||
The current version focuses on two interview stages. The first stage is the self-introduction. The second stage is past experience. The AI interviewer starts by asking the candidate to introduce themselves, and then it moves into questions about the candidate's previous work, projects, or role experience.
|
||||
|
||||
The core code is in `src/agent.py`. This file defines the agents, the stage transition logic, the fallback logic, and the LiveKit session setup.
|
||||
|
||||
## Goal One: Build the Two Interview Stages
|
||||
|
||||
The first goal is to implement the self-introduction stage and the past-experience stage using LiveKit's multi-agent design.
|
||||
|
||||
Instead of using one big prompt for the whole interview, I split the interview into two agents.
|
||||
|
||||
The first one is `SelfIntroAgent`. Its job is very simple. It greets the candidate and asks them to introduce themselves. It does not ask follow-up questions. It does not jump into other topics. Once the candidate gives a real self-introduction, this stage is done.
|
||||
|
||||
The second one is `PastExperienceAgent`. This agent starts after the self-introduction. Its job is to ask about the candidate's most relevant past experience. For example, it may ask the candidate to walk through a project, explain their contribution, describe the result, and share what they learned.
|
||||
|
||||
This multi-agent structure makes the logic much cleaner. Each agent only needs to understand one part of the interview. That also helps with real-time voice performance, because the model does not need to carry one large instruction prompt for every possible stage.
|
||||
|
||||
The session also uses shared LiveKit components, including speech-to-text, text-to-speech, VAD, turn detection, and the LLM. In this implementation, the LLM is set to `gpt-4.1-mini` through LiveKit Inference. The voice pipeline also uses Deepgram for STT, Cartesia for TTS, Silero VAD, LiveKit's multilingual turn detector, and AI-coustics noise cancellation.
|
||||
|
||||
So for the first goal, the key idea is: one shared session, two focused agents, and clear responsibility for each stage.
|
||||
|
||||
## Goal Two: Make the Transition Smooth and Natural
|
||||
|
||||
The second goal is to move from self-introduction to past experience without interruptions, conflicts, or repeated prompts.
|
||||
|
||||
For the normal transition, I use a LiveKit function tool called `intro_complete`. The self-introduction agent is instructed to call this tool as soon as the candidate gives a meaningful introduction.
|
||||
|
||||
When `intro_complete` is called, it does three important things.
|
||||
|
||||
First, it updates the shared session state and marks that the introduction was given.
|
||||
|
||||
Second, it creates the next agent, which is `PastExperienceAgent`.
|
||||
|
||||
Third, it copies the chat context into the next agent. This part is very important. If we do not pass the chat context, the new agent starts fresh and may ask the candidate to repeat information they already shared. By copying the context, the past-experience agent can use the candidate's introduction naturally.
|
||||
|
||||
The handoff also returns only one transition sentence, something like: "Thanks for that introduction. Now I'd like to explore your past experience."
|
||||
|
||||
This keeps the transition clean. There is no extra reply fighting with the handoff. There is no repeated introduction from the interviewer. The next agent simply starts from the right point and asks the next interview question.
|
||||
|
||||
In the `PastExperienceAgent`, the `on_enter` method also checks whether the introduction was completed normally. If it was, the agent goes straight into the past-experience question. If it was not, it still starts directly with a general past-experience question, so the interview can continue.
|
||||
|
||||
So for the second goal, the key idea is: use a clear tool-based handoff, preserve chat context, and make the next agent start without repeating the previous stage.
|
||||
|
||||
## Goal Three: Add a Time-Based Fallback
|
||||
|
||||
The third goal is to make sure the interview always moves forward, even if the normal LLM-based transition does not happen.
|
||||
|
||||
In a voice agent, we cannot fully rely on the model to always call the right tool at the right time. Sometimes the user may stay silent. Sometimes the model may not trigger the handoff. So I added a time-based fallback inside `SelfIntroAgent`.
|
||||
|
||||
There are two watchdog timers.
|
||||
|
||||
The first one is the silence watchdog. It checks how long the candidate has stayed silent. By default, if the user is silent for twenty seconds, the interview moves forward.
|
||||
|
||||
The second one is the stage watchdog. It is a hard time limit for the whole self-introduction stage. By default, after sixty seconds, the interview moves forward even if the normal transition did not fire.
|
||||
|
||||
The silence watchdog is not just a simple countdown. It checks `session.user_state`. If LiveKit detects that the user is speaking, the silence timer resets. This prevents the agent from interrupting while the candidate is talking.
|
||||
|
||||
When a fallback transition happens, the agent uses `session.say(...)` to speak a fixed transition line. I chose this instead of asking the LLM to generate a reply, because the fallback should be predictable. After that, it calls `session.update_agent(...)` and switches to `PastExperienceAgent`.
|
||||
|
||||
There are also guard checks to prevent duplicate transitions. For example, if the normal handoff and a timer happen very close together, the code checks whether `SelfIntroAgent` is still the current agent. It also uses a `_transitioning` flag, so only one transition can win.
|
||||
|
||||
Finally, when `SelfIntroAgent` exits, it cancels both watchdog tasks. This is important because the old timers should not keep running after the interview has already moved to the next stage.
|
||||
|
||||
So for the third goal, the key idea is: normal handoff first, but fallback timers make sure the interview never gets stuck.
|
||||
|
||||
## Real-Time Demo
|
||||
|
||||
Now I will show the real-time demo.
|
||||
|
||||
First, I start the agent from the terminal. The app loads the LiveKit session and waits for a user to join.
|
||||
|
||||
When the candidate connects, the interviewer starts the self-introduction stage. The agent greets the candidate and asks them to introduce themselves.
|
||||
|
||||
For the normal path, I answer with a short self-introduction. For example, I might say: "Hi, I'm Daniel. I have experience building AI applications and backend systems, and recently I worked on a voice AI interview project."
|
||||
|
||||
After that, the agent should call `intro_complete`. It then moves to the second stage and asks about my past experience. The transition should feel natural. It should not ask me to introduce myself again.
|
||||
|
||||
Next, I can also test the fallback path. In the self-introduction stage, I stay silent. After the silence budget is reached, the agent says a fixed transition line and moves into the past-experience stage anyway.
|
||||
|
||||
This shows that the workflow does not depend only on the LLM. If the user is silent, or if the normal tool call does not happen, the interview can still continue.
|
||||
|
||||
I also verified the behavior with tests. The test suite checks the greeting, the normal handoff, no early handoff, watchdog cancellation, fallback handoff, and silence timer reset. The latest run passed with all six tests.
|
||||
|
||||
## Next Steps
|
||||
|
||||
There are two main next steps I want to work on.
|
||||
|
||||
The first one is to revise the prompts for smaller models like `gpt-4.1-mini`. Right now, the interviewer has a role name, Jordan. With a smaller model and limited context, this can sometimes be confusing. For example, if the user stays silent during the introduction stage, the model may accidentally treat "Jordan" as the user's name, even though Jordan is the interviewer. So I want to make the prompt more explicit and safer for small models.
|
||||
|
||||
The second next step is to extract important hyperparameters and prompts from `agent.py`. Right now, things like the agent persona, stage timing, and interview style are written directly in the code. In the future, I want to make them configurable.
|
||||
|
||||
That would let us support different interview modes. For a resume-based interview, the interviewer can be more patient and friendly, and the questions can follow the candidate's resume more closely. For a stress interview, the interviewer can be less patient and ask sharper questions in a shorter time.
|
||||
|
||||
This would make the project easier to extend without rewriting the core agent logic.
|
||||
|
||||
## Closing
|
||||
|
||||
To summarize, this demo uses LiveKit's multi-agent framework to build a two-stage AI mock interview.
|
||||
|
||||
The first stage collects the candidate's self-introduction. The second stage asks about past experience. The normal transition uses a tool-based handoff and keeps the chat context, so the conversation feels smooth. The fallback timers make sure the interview keeps moving even if the normal transition does not happen.
|
||||
|
||||
This gives us a stable base for a larger interview system. From here, we can improve prompts, support different interviewer personas, add resume-based interviews, and build more interview types on top of the same workflow.
|
||||
|
||||
That's the walkthrough of my AI Mock Interview demo. Thank you for watching.
|
||||
Reference in New Issue
Block a user