This repository contains the data and code used in our paper's Chameleon clue-generation experiments.
Paper: SNEAK: Evaluating Strategic Communication and Information Leakage in Large Language Models
Terminology note: the paper sometimes says "message" and the code often says "clue". In this repository, those terms are used interchangeably.
The repo is organized around one simple idea:
- Produce a
results/*.jsonlfile containing one clue per example. - Run
evaluation.pyon that JSONL file.
There are two supported ways to produce that results file:
- model generation:
preprocess.py->inference.py - heuristic baselines:
baselines.py
If you already have your own generations, you can skip both and run evaluation.py directly.
preprocess.py: turns dataset rows into prompt JSONL files underdata/inference.py: runs a model over a prompt JSONL file and writes outputs underresults/baselines.py: writes simple WordNet-based baseline outputs directly underresults/evaluation.py: evaluates any valid results JSONL and writes per-example eval rows undereval/run_exp.sh: convenience wrapper for running preprocess + inference + evaluation end to end
Python 3.10+ is recommended.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtIf you want to run external API models in inference.py, create a .env file in the repo root or export the relevant variables:
OPENAI_API_KEYCLAUDE_API_KEYGEMINI_API_KEYorGOOGLE_API_KEYDEEPSEEK_API_KEY
category_instances_with_decoys.jsonl: default dataset file used bypreprocess.pyandbaselines.pydataset/: alternative dataset sweeps with different numbers of candidates and decoysdata/: generated prompt filesresults/: generated clue fileseval/: evaluation outputs
This is the main pipeline used for model runs.
python preprocess.py \
--dataset chameleon \
--split test \
--model google/gemma-3-27b-itThis writes:
data/chameleon/google_gemma-3-27b-it/test_baseline.jsonl
Each row in that file contains the original example plus:
system_promptpromptid
python inference.py \
--model google/gemma-3-27b-it \
--file data/chameleon/google_gemma-3-27b-it/test_baseline.jsonl \
--gpus 1 \
--max-new-tokens 512By default this writes:
results/chameleon/google_gemma-3-27b-it/test_baseline.jsonl
Each row in the output keeps the original fields and adds:
text: the formatted prompt actually sent to the modelresponse: the model output, expected to contain<clue>...</clue>
--cot: ask the model to output<thought>...</thought>before<clue>...</clue>--candidates N --decoys M: use one of the dataset sweeps indataset/--input-jsonl path/to/file.jsonl: preprocess a custom source JSONL instead of the default dataset file--sets: enable the SETS-style test-time scaling variant--rcr: enable recursive clue refinement
Example with a custom candidate/decoy sweep:
python preprocess.py \
--dataset chameleon \
--split test \
--model google/gemma-3-27b-it \
--candidates 8 \
--decoys 3If you want one command that runs the whole model pipeline, use run_exp.sh.
Default behavior:
./run_exp.shThis runs:
preprocess.pyinference.pyevaluation.py
The script is configured through environment variables rather than positional arguments.
Example:
INFERENCE_MODELS="google/gemma-3-27b-it" \
EVAL_MODELS="Qwen/Qwen2.5-72B-Instruct meta-llama/Llama-3.3-70b-Instruct" \
GPUS=4 \
MAX_NEW_TOKENS=512 \
./run_exp.shExample with a custom dataset file:
INFERENCE_MODELS="google/gemma-3-27b-it" \
PREPROCESS_INPUT_JSONL="path/to/my_examples.jsonl" \
./run_exp.shUseful variables:
INFERENCE_MODELS: space-separated generation modelsEVAL_MODELS: space-separated judge modelsPREPROCESS_INPUT_JSONL: custom source JSONL for preprocessingCANDIDATES,DECOYS: choose one of the dataset sweepsCOT: set totrueto request<thought>...</thought>outputSETS,SETS_CANDIDATES,SETS_ITERS: enable the SETS-style variantRCR,RCR_ITERS: enable recursive clue refinementRUN_PREPROCESS,RUN_INFERENCE,RUN_EVAL: skip parts of the pipeline if neededAPI_URL: route local-model inference through a vLLM server
You can inspect all supported variables with:
./run_exp.sh --helpbaselines.py skips prompt construction and model inference. It writes results/*.jsonl files directly in the same format expected by evaluation.py.
python baselines.py --strategy all --download-wordnetThis writes:
results/chameleon/baseline_random_wordnet/test_baseline.jsonlresults/chameleon/baseline_category_synonym/test_baseline.jsonlresults/chameleon/baseline_secret_synonym/test_baseline.jsonl
python baselines.py --strategy secret_synonym --download-wordnetAvailable strategies:
random_wordnet: use a random WordNet noun as the cluecategory_synonym: use a WordNet synonym of the categorysecret_synonym: use a WordNet synonym of the secret word
Once you have a results/*.jsonl file, evaluate it with:
python evaluation.py \
--clue_results results/chameleon/google_gemma-3-27b-it/test_baseline.jsonl \
--eval_model Qwen/Qwen2.5-72B-Instruct \
--out eval/chameleon/google_gemma-3-27b-it/test_baseline/Qwen_Qwen2.5-72B-InstructThis writes:
eval/chameleon/google_gemma-3-27b-it/test_baseline/Qwen_Qwen2.5-72B-Instruct/results.jsonl
The script also prints aggregate metrics to the terminal:
- binary score
- utility
- leakage
- mean score
Important: evaluation.py calls inference.py in label-classification mode, which currently requires a local vLLM judge model. External API judge models are not supported in this mode.
You do not need to use preprocess.py or inference.py if you already have clue generations from another system.
You can create your own JSONL file with the required fields and run evaluation.py on it directly.
{
"category": "academic subject",
"secret": "maths",
"candidates": [
"psychology",
"history",
"biology",
"sociology",
"chemistry",
"geography",
"french",
"physics",
"english literature",
"spanish",
"engineering"
],
"decoys": [
"bailiwick",
"discipline",
"field",
"field_of_study",
"science"
],
"response": "<clue>Requires precise notation.</clue>"
}Required fields:
categorysecretcandidatesresponse
Strongly recommended:
decoysordecoy_clues
Why the decoy field matters: the Ally judge compares the model's clue against distractor clues. If you omit decoys / decoy_clues, the utility side of the evaluation becomes much less meaningful.
These are intermediate files under data/. They include the original dataset fields plus:
idsystem_promptprompt
These are the files you pass into evaluation.py. They should include:
categorysecretcandidatesresponse
They can also include:
decoysdecoy_clues- any metadata you want to preserve
Each row in eval/.../results.jsonl contains:
binary_scoreutilityleakage- judge metadata under
chamandally
- In this release,
--splitis mainly used as part of the output filename. The default shipped dataset is the evaluation set used in the paper. - If you want the simplest path, ignore the advanced decoding flags and just use:
preprocess.py->inference.py->evaluation.py - If you only want to benchmark your own generations, start at
evaluation.py.