Code and data for the paper "The Chameleon’s Limit: Investigating Persona Collapse and Homogenization in Large Language Models".
This codebase supports three experiments:
- Moral Reasoning -- Evaluate how LLMs respond to moral dilemmas across diverse personas
- Digital Twin (BFI) -- Replicate human personality assessments (Big Five Inventory) with LLM-simulated personas
- Self-Introduction -- Collect open-ended self-introductions from LLM-simulated personas
persona-collapse/
├── persona_sim/ # Shared library
│ ├── consts.py # Constants (rating scales)
│ ├── prompts.py # Prompt construction for all experiments
│ ├── providers.py # Model registries, client factory, Bedrock wrapper
│ ├── experiment.py # Shared experiment runner (DB, retries, diagnostics)
│ └── metrics.py # Analysis metrics (Hopkins, PRDC, uniformity)
├── experiments/ # Experiment entry points
│ ├── moral_reasoning.py # Moral dilemma experiment
│ ├── digital_twin.py # BFI personality test replication
│ ├── self_introduction.py # Self-introduction collection
│ └── estimate_budget.py # Token/cost estimation
├── analysis/ # Post-processing and analysis
│ ├── analysis_pipeline.py # Full metrics pipeline (BFI, moral, self-intro)
│ ├── check_persona_inconsistency.py # Generate flagged_personas.pkl
│ ├── extract_csv.py # Export SQLite results to CSV
│ ├── README.md # Analysis folder documentation
│ └── visualizations/ # Figure generation scripts
├── scripts/ # Shell scripts for running experiments
│ ├── run_moral_reasoning.sh
│ ├── run_digital_twin.sh
│ ├── run_self_introduction.sh
│ ├── run_analysis.sh
│ └── serve_vllm.sh
└── data/ # Input data files
├── sampled_personas_2000.json # 2000 synthetic persona profiles
├── claude-3-5-sonnet_AB_0_with_logprobs.jsonl # Moral dilemma scenarios
├── flagged_personas.pkl # Pre-computed inconsistent persona IDs
└── human_reference/
├── BFI.json # BFI-44 question definitions
└── wave_1_numbers.csv # Human survey reference data
pip install -r requirements.txtCore dependencies are openai, numpy, pandas, tqdm, and scikit-learn. Optional dependencies are listed in requirements.txt with comments.
Set the appropriate environment variable for your provider:
# OpenAI
export OPENAI_API_KEY="your-key-here"
# OpenRouter (for models like Kimi, MiniMax, etc.)
export OPENROUTER_API_KEY="your-key-here"
# AWS Bedrock (for Anthropic Claude models)
export AWS_BEARER_TOKEN_BEDROCK="your-key-here"
# Local vLLM (no key needed, but set the base URL)
export VLLM_BASE_URL="http://localhost:8000/v1"| Provider | Models | Key |
|---|---|---|
openai |
GPT-5, GPT-4.1 family | OPENAI_API_KEY |
openrouter |
Kimi K2.5, MiniMax M2/M2.5, etc. | OPENROUTER_API_KEY |
bedrock |
Claude Sonnet/Haiku/Opus | AWS_BEARER_TOKEN_BEDROCK |
vllm |
Any HuggingFace model via vLLM | VLLM_BASE_URL |
# Step 1: Estimate budget
bash scripts/run_moral_reasoning.sh openrouter kimi-k2.5
# Step 2: Run experiment
bash scripts/run_moral_reasoning.sh openrouter kimi-k2.5 --run
# Or run Python directly:
python -m experiments.moral_reasoning \
--provider openrouter \
--model kimi-k2.5 \
--personas-file data/sampled_personas_2000.json \
--scenarios-file data/claude-3-5-sonnet_AB_0_with_logprobs.jsonl \
--use-db 1 \
--db-path results_moral_kimi-k2.5.db# Step 1: Estimate budget
bash scripts/run_digital_twin.sh openrouter minimax-m2 2000
# Step 2: Run experiment
bash scripts/run_digital_twin.sh openrouter minimax-m2 2000 --run
# Or run Python directly:
python -m experiments.digital_twin \
--provider openrouter \
--model minimax-m2 \
--limit 2000 \
--use-sampled-personas \
--personas-file data/sampled_personas_2000.json \
--db-path results_replicate_humans_minimax-m2_bfi.db# OpenRouter (all registered models)
bash scripts/run_self_introduction.sh openrouter
# Specific models
bash scripts/run_self_introduction.sh openrouter kimi-k2.5 minimax-m2
# Local vLLM
bash scripts/run_self_introduction.sh vllm Qwen/Qwen3-4B-Instruct-2507# Start vLLM server
bash scripts/serve_vllm.sh meta-llama/Llama-3.1-8B-Instruct 8000
# In another terminal, run experiments
export VLLM_BASE_URL="http://localhost:8000/v1"
bash scripts/run_moral_reasoning.sh vllm meta-llama/Llama-3.1-8B-Instruct --runFor models that produce extended chain-of-thought reasoning (e.g., DeepSeek-R1), enable thinking-model mode. This stores raw outputs and uses an LLM judge to extract numeric ratings:
THINKING_MODEL=true \
THINKING_MAX_TOKENS=16384 \
JUDGE_MODEL=gpt-5-mini \
JUDGE_PROVIDER=openai \
bash scripts/run_moral_reasoning.sh vllm deepseek-r1 --runControl the max output tokens for non-thinking mode:
# Moral reasoning (default: 512)
MAX_TOKENS=1024 bash scripts/run_moral_reasoning.sh openrouter minimax-m2 --run
# Digital twin (default: 100)
MAX_TOKENS=200 bash scripts/run_digital_twin.sh openrouter minimax-m2 2000 --runPersonas identified as logically inconsistent (e.g., a child persona who is married) can be excluded from API calls to save cost while preserving the full dataset structure. Experiment scripts automatically detect data/flagged_personas.pkl and inject dummy database entries for flagged personas.
To generate the flagged personas file:
python -m analysis.check_persona_inconsistencyThis scans data/sampled_personas_2000.json and writes data/flagged_personas.pkl. Use --dry-run to preview without saving.
All experiments use SQLite databases for incremental saving. If a run is interrupted, simply re-run the same command -- completed tasks are automatically skipped.
python analysis/extract_csv.pyThis exports all SQLite result databases to CSV files under csv_exports/.
The main analysis pipeline computes all metrics reported in the paper (BFI diagnostics, moral reasoning metrics, self-introduction linguistic analysis):
python -m analysis.analysis_pipeline \
--data_dir ./csv_exports \
--selfintro_dir ./self_introduction_results \
--human_ref ./data/human_reference/wave_1_numbers.csv \
--output_dir ./resultsThis produces:
results/analysis.json-- all BFI + moral metrics per modelresults/selfintro/-- self-introduction linguistic features, mention rates, ICC, etc.results/figures/-- Coverage-vs-LID and Fidelity-vs-d plots (PDF)
Optional flags:
--flagged ./data/flagged_personas.pkl-- exclude inconsistent personas--skip_selfintro-- skip self-introduction analysis--skip_figures-- skip figure generation
# 3D metric illustrations (coverage, uniformity, complexity)
python analysis/visualizations/metrics_3d.py
# Uniformity figures
python analysis/visualizations/uniformity.pySee analysis/README.md for the full list of analysis scripts and detailed documentation.
If you use this code or data, please cite our paper:
@article{xiao2026collapse,
title={The Chameleon’s Limit: Investigating Persona Collapse and Homogenization in Large Language Models},
author={Yunze Xiao, Vivienne Zhang, Chenghao Yang, Ningshan Ma, Weihao Xuan, Jen-tse Huang},
journal={arXiv preprint arXiv:[to appear]},
year={2026},
note={arXiv ID pending}
}[To be specified]