InterviewPlayground: A Validated Simulation Environment for Evaluating AI Interviewers
InterviewPlayground is a simulation environment for evaluating AI interviewers using simulated study participants whose behaviors are grounded in social theory. Simulated studies in InterviewPlayground produce an InterviewReportCard, which assesses the performance of AI interviewers using a suite of validated measures. InterviewPlayground also includes three simulation settings based on real interview studies in public health, political science, and human-computer interaction. In our paper, we validate these settings by comparing them to 15 real qualitative studies with 450 human participants and demonstrating that the performance of an AI interviewer in InterviewPlayground predicts their performance in human studies.
@misc{ivey2026interviewplaygroundsimulationenvironmentevaluating,
title={InterviewPlayground: A Validated Simulation Environment for Evaluating AI Interviewers},
author={Jonathan Ivey and Aimee Liang and Arthur Y. S. Wang and Madeline Mandell and Ziang Xiao and Anjalie Field},
year={2026},
eprint={2610.12023},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2610.12023},
}
Quick Start
Install
pip install interviewplayground
Set a model
InterviewPlayground uses LiteLLM. Set an API key:
export OPENAI_API_KEY="sk-..."
Anthropic, Gemini, and self-hosted vLLM models are also supported. See Setup for details.
Load a preset
Load one of InterviewPlayground's validated simulation settings
from interviewplayground import load_preset
study = load_preset("obesity_weight_management")
p = study.participants[0]
response = p.ask("How have weight conversations with your doctor gone?")
| Name | Topic | Participants |
|---|---|---|
obesity_weight_management |
Weight discussions in primary care and commercial program referrals | 30 |
asian_american_politics |
Asian American identity and political preferences | 30 |
genai_knowledge_work |
GenAI tools in knowledge work contexts | 30 |
Each participant has 200 memories — 194 background memories plus 6 insight memories drawn from the study's 15 key insights — so different participants will provide different information. See Creating Your Own Study to build your own simulation setting.
print(f"{len(study.research_questions)} research questions")
for topic in study.interview_guide:
print(topic["topic"])
for subtopic in topic["subtopics"]:
print(f" - {subtopic}")
Presets also include research_questions and an interview_guide that you can use to guide your AI interviewer.
Run an interview with your own interviewer
Here is an example of how you could evaluate an AI interviewer using an InterviewPlayground simulated study. For each interview you let the interviewer generate a question for the participant, receive responses, and then generate the next question. To know when the interview ends, you keep track of the real time it takes the AI interviewer to generate a question and the estimated time it would take a participant to respond.
from time import perf_counter
from interviewplayground.speaking_time import estimate_speaking_duration
def run_interview(participant, time_limit_minutes=30.0):
transcript = []
elapsed_seconds = 0.0
while elapsed_seconds < time_limit_minutes * 60:
t0 = perf_counter()
question = my_interviewer.next_question(transcript)
elapsed_seconds += perf_counter() - t0
if question is None: # interviewer signals it's done
break
answer = participant.ask(question)
elapsed_seconds += estimate_speaking_duration(answer, participant.verbosity)
transcript.append({"role": "interviewer", "content": question})
transcript.append({"role": "participant", "content": answer})
return transcript
# Interview all 30 participants in the preset
for participant in study.participants:
run_interview(participant)
participant.ask() already appends each turn to participant.transcript in the package's own format, so study.evaluate() picks it up automatically — the transcript list above is just what you pass to your interviewer. Inform your interviewer using the preset's study.research_questions and study.interview_guide. See Using the Interview Guide for an example.
Evaluate interview quality
Once an interview is complete, you can use study.evaluate() to score each participant's transcript with the InterviewReportCard suite.
results = study.evaluate()
| Dimension | Metrics |
|---|---|
participant_responses |
relevant_response_volume, interview_guide_coverage, novel_responses |
interviewer_behavior |
coherence, adaptiveness, leading_questions, support_rapport, unclear_questions |
participant_experience |
comfort_level, overall_experience |
conversation_length |
avg_turns, avg_response_length |
See Evaluating Interview Quality for per-participant breakdowns and batch evaluation.
Setup
Installing from source
git clone https://github.com/jonathanivey/interviewplayground.git
cd interviewplayground
pip install -e ".[dev]"
API-based models (e.g., OpenAI, Anthropic, Gemini)
Set your API key and, if not using the default OpenAI model, the model name:
# OpenAI (default — no model override needed)
export OPENAI_API_KEY="sk-..."
# Anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
export SIMSTUDY_MODEL="claude-3-5-haiku-20241022"
# Google Gemini
export GEMINI_API_KEY="..."
export SIMSTUDY_MODEL="gemini/gemini-2.0-flash"
Or set it programmatically:
from interviewplayground import set_default_model
set_default_model("claude-3-5-haiku-20241022")
Note on embeddings with non-OpenAI models: Anthropic and Gemini models do not support the embeddings API. Keep
SIMSTUDY_EMBEDDING_MODELpointed at an OpenAI embedding model (the defaulttext-embedding-3-small) and setOPENAI_API_KEYeven when your completion model is Claude or Gemini.
Self-hosted models via vLLM
vLLM allows the participant simulator and the InterviewReportCard judge to run with a self-hosted model. See examples/02_self_hosted_vllm.ipynb for a runnable walkthrough.
Running a local vLLM server
vllm serve <model-name> --host 0.0.0.0 --port 8000
from interviewplayground import set_default_model
set_default_model(
"openai/<model-name>",
api_base="http://localhost:8000/v1",
api_key="EMPTY", # vLLM requires a non-empty string; the value is ignored
)
extra_body is forwarded as-is to every completion call, which is where model-specific serving flags go (e.g. a model's chat_template_kwargs):
set_default_model(
"openai/Qwen/Qwen3.5-4B",
api_base="http://localhost:8000/v1",
api_key="EMPTY",
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
Or configure the participant model via environment variables:
export SIMSTUDY_MODEL="hosted_vllm/my-model-name"
export SIMSTUDY_API_BASE="http://localhost:8000/v1"
export SIMSTUDY_API_KEY="EMPTY"
Using vLLM as the InterviewReportCard judge
The same api_base/extra_body pattern applies when evaluating interviews — pass them through to Study.evaluate() (or any individual evaluate_* function):
study.evaluate(
model="openai/<judge-model-name>",
api_base="http://localhost:8000/v1",
api_key="EMPTY",
)
use_batch=True on any evaluate_* function works against a vLLM server too: since a local server has no provider batch API, it automatically falls back to running prompts concurrently over call() instead (as opposed to the native OpenAI/Azure/Gemini batch APIs used for those providers). Concurrency defaults to the LLM_BATCH_CONCURRENCY environment variable (16 if unset) — set it to roughly your server's --max-num-seqs.
Using different models for setup vs. interviews
Memory generation and study setup require instruction-following and structured JSON output — use an API model for those. The ask() call only needs conversational generation, making it a good fit for a locally-served model. You can mix both in the same session with per-call overrides:
from interviewplayground import set_default_model
# Default: API model for setup (memory generation, study description)
set_default_model("gpt-4o-mini")
# Per-call override on ask() to use a local vLLM model
response = p.ask(
"How did you cope?",
model="hosted_vllm/osim-8b",
api_base="http://localhost:8000/v1",
api_key="EMPTY",
)
Embedding model
The embedding model is configured separately from the completion model. It defaults to text-embedding-3-small (OpenAI). To change it:
from interviewplayground import set_embedding_model
set_embedding_model("text-embedding-3-small") # default
# Or point to a vLLM-hosted embedding model
set_embedding_model(
"hosted_vllm/my-embedding-model",
api_base="http://localhost:8001/v1",
api_key="EMPTY",
)
Or via environment variables:
export SIMSTUDY_EMBEDDING_MODEL="text-embedding-3-small"
export SIMSTUDY_EMBEDDING_API_BASE="http://localhost:8001/v1" # only needed for vLLM embeddings
export SIMSTUDY_EMBEDDING_API_KEY="EMPTY"
Model selection priority
For the completion model, priority is highest to lowest:
- Per-call
model=argument — overrides everything for that one call set_default_model()— programmatic session-wide defaultSIMSTUDY_MODELenv var — environment-level fallback- Built-in default (
gpt-5-mini)
api_base and api_key follow the same priority order, using SIMSTUDY_API_BASE and SIMSTUDY_API_KEY as their env var fallbacks. The embedding model follows the same pattern via set_embedding_model() and SIMSTUDY_EMBEDDING_* env vars.
All LLM-calling methods accept per-call overrides:
p.ask("How did you cope?", model="...", api_base="...", api_key="...")
p.generate_insight_memories(topics, model="...", api_base="...", api_key="...")
p.generate_background_memories(model="...", api_base="...", api_key="...")
Note on memory generation: Generating 194 background memories in a single batch can take several minutes. The default LLM timeout is 600 seconds.
Evaluating Interview Quality
Every dimension's dict also includes a per_participant list with each participant's own unaggregated value(s), alongside the group-level average. research_questions/interview_guide default to the Study's own attributes if not passed explicitly.
For large evaluation runs, use_batch=True (on study.evaluate() or any individual evaluate_* function) submits prompts as provider batch jobs instead of one call at a time. For submit-now/retrieve-later workflows (e.g. across process restarts), each dimension also exposes submit_*/retrieve_* functions directly — see interviewplayground.interviewreportcard. Note the native batch APIs are OpenAI/Azure/Gemini-only; see Using vLLM as the InterviewReportCard judge for the local-model equivalent.
Using the Interview Guide
Every preset's study.research_questions and study.interview_guide describe what the interview should cover — the same information a human interviewer would be briefed with. interview_guide is a list of topics, each with a list of specific subtopics:
for topic in study.interview_guide:
print(topic["topic"])
for subtopic in topic["subtopics"]:
print(f" - {subtopic}")
To test your own AI interviewer against a preset, give it the research questions and interview guide as part of its instructions, the same way you'd brief a human interviewer:
guide_text = "\n".join(
f"- {t['topic']}\n" + "\n".join(f" - {s}" for s in t["subtopics"])
for t in study.interview_guide
)
interviewer_instructions = f"""\
You are conducting a qualitative research interview. Your research questions are:
{chr(10).join(f"- {q}" for q in study.research_questions)}
Use the following interview guide to structure your questions:
{guide_text}
"""
my_interviewer = MyInterviewer(system_prompt=interviewer_instructions)
Creating Your Own Study
Presets cover three interview topics out of the box. To build a Study from scratch for a new topic, participant population, or custom preset, see docs/creating_a_preset.md.
Customizing Prompts
All LLM prompts are string constants in src/interviewplayground/prompts.py. Edit them directly to change how memories are generated or how participants respond in interviews.
The prompts use standard Python .format() placeholders:
| Prompt | Placeholders |
|---|---|
BACKGROUND_MEMORIES |
{persona}, {knowledge}, {verbosity}, {memory}, {reflexivity}, {disclosure}, {understanding}, {n} |
INSIGHT_MEMORIES |
same as above plus {topics_list}, {n} |
ASK |
same traits plus {retrieved_memories}, {transcript}, {question} |
Examples
Runnable Jupyter notebooks covering the main use cases are in examples/:
| Notebook | Covers |
|---|---|
| 01_running_interviewplayground.ipynb | Loading a preset, interviewing participants, and evaluating the interview with InterviewReportCard |
| 02_self_hosted_vllm.ipynb | Using a self-hosted vLLM model as the participant simulator and/or InterviewReportCard judge |
| 03_creating_a_preset.ipynb | Building a custom Study from scratch and registering it as a reusable preset |
Running Tests
Tests do not require an API key — LLM calls are mocked.
pytest
Single-Participant Setup
See docs/single_participant.md for how to set up and interview a single participant without a full Study.
Metadata
Release files for interviewplayground 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| interviewplayground-0.1.2.tar.gz | 1.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| interviewplayground-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.2 MB
Release files / interviewplayground-0.1.2.tar.gz
| Download URL | interviewplayground-0.1.2.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
704dc032a1942fdfa067b56449957c9c0aa699cac4545d4cc5e5f67a73ebae2a
|
|
BLAKE2b-256 checksum How to use checksums |
04df28a245973beb73abdbf4e0684c4a88a6466e46f5d509991335d2c704e667
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / interviewplayground-0.1.2-py3-none-any.whl
| Download URL | interviewplayground-0.1.2-py3-none-any.whl |
|---|---|
| Size | 919.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
652bfb5d175b4684df25badc9fb15eb84ac385c30aa0a70abf842f664e9ee197
|
|
BLAKE2b-256 checksum How to use checksums |
1977e78caa5b754a2a14b3e08d8447e07abb8b201d9fb9ab224ba284d2b14ad8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log