simplify public docs and parquet upload

This commit is contained in:
wassname
2026-06-13 13:55:43 +08:00
parent 2c86dee10f
commit 9b1a6e7573
15 changed files with 723 additions and 1298 deletions
+4 -1
View File
@@ -3,5 +3,8 @@
__pycache__/
.pytest_cache/
out/
docs/spec/
hf/
parquet/
*.parquet
*.pyc
+48 -132
View File
@@ -1,159 +1,75 @@
# Persona Steering Template Library
Measured candidate prompt templates and contrastive persona pairs for persona, activation, and weight steering experiments.
Small, measured persona/template pairs for steering-vector and preference-pair experiments.
Hugging Face dataset: https://huggingface.co/datasets/wassname/persona-steering-template-library
- Hugging Face dataset: https://huggingface.co/datasets/wassname/persona-steering-template-library
- Guide: [docs/guide.md](docs/guide.md)
This repository is the code and provenance side of the library. The Hugging Face dataset is the data side: measured template stats, template x persona-pair stats, and judged generation examples.
## Example
## What This Is
```text
template:
You are a {persona} person thinking through the situation.
The portable unit is not a weak-to-strong harness. It is a measured library of:
negative persona:
authority-deferential even when wellbeing suffers
- prompt templates with a `{persona}` slot
- short contrastive persona pairs, labeled as `neg->pos`
- scenario prompts used to elicit behavior
- on-axis Likert judge ratings
- off-axis/confound Likert judge ratings
- style, length, persona-echo, and refusal flags
- literature and practice provenance for why each family of template exists
positive persona:
wellbeing-focused even when authority-defying
The current v1 data is preliminary. It is meant to identify promising template x persona-pair cells, not to bless every template as broadly valid.
measured pilot:
strict_pass_rate = 0.75
mean_axis_delta = 6.25
mean_off_axis_problem = 2.00
mean_max_style_abs_delta = 1.50
```
## Current V1 Snapshot
The point is not "this sounds like a good prompt". The point is to measure
whether the positive and negative personas separate the intended axis without
mostly separating length, tone, confidence, refusal, or persona-echo.
The included v1 export contains:
## What To Browse
- `data/template_stats.jsonl`: 10 template-level rows
- `data/template_pair_stats.jsonl`: 59 template x persona-pair rows
- `data/examples.jsonl`: 156 judged generation examples
On Hugging Face, start with `persona_pairs_v2_review`.
No whole template is yet broadly validated. Some individual cells are promising, especially simple role-play templates on behavioral axes. Treat `recommended=true` as a candidate flag for follow-up, not as a final benchmark claim.
That table gives one row per persona pair:
## V2 Candidate Library
- `axis`: `neg->pos`
- `positive_behavior` / `negative_behavior`: what the pair should separate
- `proof_grade`: `pilot_recommended`, `pilot_measured_not_promoted`, or `candidate_unmeasured`
- `best_template`: best measured template for that pair, if any
- `best_axis_delta`, `best_off_axis_problem`, `best_max_style_abs_delta`: compact proof stats
V2 candidate material lives separately from measured stats:
Then inspect `v2_pilot_seed23_examples` to read the actual positive/negative
completions and judge ratings.
- `data/persona_pairs_v2_candidates.jsonl`: 16 candidate persona pairs
- `data/templates_v2_candidates.txt`: 12 reusable `{persona}` templates
- `data/scenarios_v2_candidates.jsonl`: 12 scenario prompts for smoke and first sweeps
- `docs/v2_expansion.md`: promotion criteria and first-sweep command
## Files
These are not promoted templates yet. They are the expanded candidate grid to measure next.
- `data/persona_pairs_v2_candidates.jsonl`: candidate persona pairs
- `data/templates_v2_candidates.txt`: candidate `{persona}` templates
- `data/scenarios_v2_candidates.jsonl`: scenario prompts for sweeps
- `data/v2_pilot_seed23_*`: first measured v2 pilot
- `scripts/validate_persona_axes_openrouter.py`: OpenRouter validation sweep
- `scripts/export_persona_template_stats.py`: aggregate raw sweep output
- `scripts/build_hf_dataset.py`: build parquet-only HF upload folder
## V2 Pilot
## Current Status
`data/v2_pilot_seed23_*` contains a small measured pilot over 4 behavioral persona pairs, 4 templates, and 4 scenarios.
Preliminary. The current pilot is small: 4 persona pairs x 4 templates x 4
scenarios. It is enough to show the measurement format and identify a few
promising cells, not enough to certify a general template.
- planned pairs: 64
- successful judged pairs: 59
- judge JSON failures: 5
- strongest pilot cell: `wellbeing_authority` with `You are a {persona} person thinking through the situation.`
- next-best pilot cell: `wellbeing_authority` with `You are a {persona} person acting in the world.`
This is still not the full v2 release; it is the first measured slice of the expanded library.
## Data Files
`data/template_stats.jsonl`
One row per template, aggregated across persona pairs and scenarios.
`data/template_pair_stats.jsonl`
One row per template x persona pair. This is usually the most useful table: it tells you which templates work for which axis.
`data/examples.jsonl`
One row per generated pair, including prompt, positive-persona response, negative-persona response, judge deltas, style deltas, and confound flags.
## Important Columns
- `template`: prompt template containing `{persona}`
- `persona_pair`: axis label, usually `neg->pos`
- `strict_pass_rate`: fraction of examples passing the current v1 gates
- `mean_axis_delta`: intended-axis Likert separation
- `mean_off_axis_problem`: judge-rated chance that the apparent difference is actually off-axis
- `mean_max_style_abs_delta`: largest absolute style movement across audited style dimensions
- `mean_abs_word_delta_frac`: report-only length difference
- `persona_echo_rate`: whether outputs explicitly echoed the persona prompt
- `refusal_or_ai_break_rate`: refusal or role-break rate
- `recommended`: conservative v1 candidate flag
## Run A New Sweep
Install:
## Run
```sh
uv sync
```
Run a dry plan without network:
```sh
uv run python scripts/validate_persona_axes_openrouter.py \
--dry-run \
--axes template \
--templates paper \
--n 1 \
--axes data/persona_pairs_v2_candidates.jsonl \
--templates data/templates_v2_candidates.txt \
--family data/scenarios_v2_candidates.jsonl \
--n 2 \
--out out/dryrun.json
```
Run a small OpenRouter sweep:
```sh
OPENROUTER_API_KEY=... uv run python scripts/validate_persona_axes_openrouter.py \
--axes template \
--templates paper \
--family character \
--n 3 \
--gen-temperature 0 \
--seed 13 \
--out out/persona_template_library_v2.json
```
Export upload-friendly tables:
```sh
uv run python scripts/export_persona_template_stats.py \
out/persona_template_library_v2.json \
--out-prefix out/persona_template_library_v2
```
You can pass your own scenario JSONL as `--family path/to/scenarios.jsonl`. Each line needs `prompt` or `question` or `text`.
You can also pass a persona-pair JSONL as `--axes path/to/persona_pairs.jsonl`. Each line needs `pos`, `neg`, `positive_behavior`, and `negative_behavior`.
## Validation Method
For each template x persona pair x scenario:
1. Generate a positive-persona completion and a negative-persona completion.
2. Use deterministic generation by default: `temperature=0`, fixed `seed`.
3. Judge the pair in randomized A/B order.
4. Ask separate judge questions for the positive target behavior and negative target behavior.
5. Ask a separate confound/style audit.
6. Report length and style deltas rather than using length as a hard gate.
This follows the steering-vector lesson that a contrastive direction learns whatever co-varies between sides. If length, confidence, refusal, or persona-echo reliably differs, that nuisance can become the axis.
## Literature And Provenance
The docs folder vendors the local persona-steering notes used to build v1:
- `docs/persona-steering-skill.md`
- `docs/how_to_write_personas.md`
- `docs/literature/literature.md`
- `docs/literature/evidence.md`
- `docs/literature/examples.md`
- `docs/literature/curation.md`
Key influences include repeng, Persona Vectors, Assistant Axis, CAA, and steering-reliability work. Claims are marked as literature, convergent practice, in-house evidence, or guesses where possible.
## Relationship To W2S
This repo deliberately excludes the weak-to-strong training harness. The same library can be used for activation steering, weight steering, DPO pair generation, prompt-only baselines, or eval construction.
## License
MIT.
See [docs/guide.md](docs/guide.md) for measured runs, export, and upload.
-6
View File
@@ -1,6 +0,0 @@
{"source_id":"repeng","source_type":"repo","citation":"Vogel, repeng control-vector library","url":"https://github.com/vgel/repeng","claim":"Use closely matched opposite persona prompts; templates such as `Act as if you're extremely {persona}.` and `Pretend you're an honest/untruthful person making statements about the world.` are established practice.","evidence_strength":"practice","used_for":"templates, persona_pairs"}
{"source_id":"persona_vectors","source_type":"paper_repo","citation":"Chen et al., Persona Vectors","url":"https://github.com/safety-research/persona_vectors","claim":"Trait-inducing system prompts can produce usable persona directions, and response-token extraction plus judge filtering matter.","evidence_strength":"paper_and_practice","used_for":"validation_rules"}
{"source_id":"assistant_axis","source_type":"paper_repo","citation":"Lu et al., The Assistant Axis","url":"https://github.com/safety-research/assistant-axis","claim":"Matched behavioral-directive trait pairs are useful for persona/assistant-axis work; matched length/register reduces nuisance axes.","evidence_strength":"practice","used_for":"persona_pairs, confound_matching"}
{"source_id":"tan_reliability","source_type":"paper","citation":"Tan et al., Analysing the Generalisation and Reliability of Steering Vectors, NeurIPS 2024","url":"https://arxiv.org/abs/2407.12404","claim":"Steering vectors can pick up spurious prompt/template factors and vary heavily across inputs; validate on/off-axis behavior per prompt distribution.","evidence_strength":"paper","used_for":"off_axis_validation"}
{"source_id":"caa","source_type":"paper","citation":"Rimsky et al., Contrastive Activation Addition","url":"https://arxiv.org/abs/2312.06681","claim":"Contrastive pairs should vary minimally except for the intended concept; tight contrastive formatting helps isolate the direction.","evidence_strength":"paper","used_for":"contrastive_design"}
{"source_id":"w2schar_in_house","source_type":"in_house","citation":"wassname w2schar-mini persona validation runs","url":"https://github.com/wassname/w2schar-mini","claim":"Short template-slotted personas and behavior-channel templates should be measured separately because template wording can move style, confidence, refusal, or length more than the target axis.","evidence_strength":"in_house","used_for":"template_library_design"}
+150
View File
@@ -0,0 +1,150 @@
# Guide
This library tests persona prompt templates for contrastive steering data.
## One Example
```text
template:
You are a {persona} person thinking through the situation.
negative persona:
authority-deferential even when wellbeing suffers
positive persona:
wellbeing-focused even when authority-defying
measured pilot:
strict_pass_rate = 0.75
mean_axis_delta = 6.25
mean_off_axis_problem = 2.00
mean_max_style_abs_delta = 1.50
```
OBS: This is a template plus a persona pair. The template supplies the behavior
channel; the pair supplies the contrastive axis.
INF: I think the useful object is the measured `template x persona_pair` cell,
not a persona string by itself. - wassname
## Browse
Start with the Hugging Face split `persona_pairs_v2_review`.
- `axis`: compact `neg->pos`
- `positive_behavior` / `negative_behavior`: intended behavioral contrast
- `proof_grade`: `pilot_recommended`, `pilot_measured_not_promoted`, or `candidate_unmeasured`
- `best_template`: best measured template for that pair, if measured
- `best_axis_delta`: intended-axis Likert separation
- `best_off_axis_problem`: judge-rated confound risk
- `best_max_style_abs_delta`: largest audited style movement
Then open `v2_pilot_seed23_examples` and read the paired completions. The table
is only a map; the examples are the proof.
## Wassname Anecdotes / Design Notes
OBS: The current candidate files separate three things:
- persona pairs: `data/persona_pairs_v2_candidates.jsonl`
- templates: `data/templates_v2_candidates.txt`
- scenarios: `data/scenarios_v2_candidates.jsonl`
INF: Templates should have a `{persona}` slot and should be measured across
multiple persona pairs. - wassname
INF: Some templates should bind a task or behavior channel, such as acting,
thinking, judging, making statements, or understanding. - wassname
INF: The axis label can usually just be `{neg}->{pos}`. - wassname
INF: Length matching is desirable, but hard enough that this library reports
length deltas instead of using a brittle hard filter. - wassname
## What Counts As A Useful Cell
OBS: A measured row has intended-axis ratings and confound ratings.
INF: A useful cell should have:
- visible intended-axis Likert separation;
- low off-axis/confound rating;
- low style movement;
- no persona echo;
- no refusal or role break;
- no obvious length-only explanation.
I think `recommended=true` should mean "worth a larger sweep", not "certified".
- wassname
## Sources To Check
No evidence dump is committed here yet. When checking a claim, distinguish papers
from associated code:
- Paper: Tan et al., [arXiv:2407.12404](https://arxiv.org/abs/2407.12404), for spurious prompt/template factors and per-input steering variance.
- Paper: Rimsky et al., [arXiv:2312.06681](https://arxiv.org/abs/2312.06681), for contrastive activation addition.
- Paper or associated code: Chen et al., Persona Vectors.
- Paper or associated code: Lu et al., Assistant Axis.
- Code / practitioner examples: [vgel/repeng](https://github.com/vgel/repeng).
- Associated code / trait files: safety-research `persona_vectors`, `assistant-axis`, and `weight-steering`.
## Candidate Shape
Good:
```json
{
"id": "wellbeing_authority",
"pos": "wellbeing-focused even when authority-defying",
"neg": "authority-deferential even when wellbeing suffers",
"positive_behavior": "prioritizes preventing harm to people even when doing so conflicts with orders, protocol, or authority",
"negative_behavior": "prioritizes obedience, protocol, or institutional authority even when that allows harm to people"
}
```
Weak:
```json
{
"pos": "good",
"neg": "not good"
}
```
INF: The weak shape is broad, negated, and likely to mix moral valence,
compliance, refusal, and style.
## Run
Dry run:
```sh
uv run python scripts/validate_persona_axes_openrouter.py \
--dry-run \
--axes data/persona_pairs_v2_candidates.jsonl \
--templates data/templates_v2_candidates.txt \
--family data/scenarios_v2_candidates.jsonl \
--n 2 \
--out out/dryrun.json
```
Measured run:
```sh
OPENROUTER_API_KEY=... uv run python scripts/validate_persona_axes_openrouter.py \
--axes data/persona_pairs_v2_candidates.jsonl \
--templates data/templates_v2_candidates.txt \
--family data/scenarios_v2_candidates.jsonl \
--n 4 \
--gen-temperature 0 \
--seed 23 \
--out out/persona_template_library_v2_seed23.json
```
Build the Hugging Face parquet folder:
```sh
uv run python scripts/build_hf_dataset.py \
--out /tmp/persona-steering-template-library-hf
```
-222
View File
@@ -1,222 +0,0 @@
Here's a some personas that work in previos work. There are many open questions and unknownss o it's best to start with what worked for other people
Lessons
```
1. Pair direct opposites — same domain, opposite pole. Vector = pos neg.
2. Short noun phrases — "an honest person" not "a monk who took a vow of silence".
3. No negations — "untruthful" not "not truthful". "evil" not "not good".
4. Closely-opposite phrasing — "high on acid" / "sober, not on acid" (not "sober" alone).
5. Span the axis without naming it — vary surface form, hold the pole.
6. Conflict framings beat compliance for moral foundations:
pos: "looks after others' wellbeing even when defying authority"
neg: "defers to authority even when others' wellbeing suffers for it"
7. Avoid mixing axes — one axis per (pos, neg) pair.
8. Match style/format/length across cho/rej — otherwise style competes with content.
9. Test the template as well as the persona. A useful template has a `{persona}`
slot and can bind the persona to a behavior channel: acting in the world,
judging what to do, thinking through the situation, making statements, or
understanding the situation. These can work differently from bare identity
prompts like "You are a {persona} person."
```
## What the literature does
The numbered rules above are what published persona-steering work uses:
repeng, persona_vectors, weight-steering, assistant-axis, steering-lite.
Several independent groups using these formats on working systems is
moderate-or-better evidence.
The framings they share are state ("act as if extremely high"), trait
("an honest person"), disposition ("someone who refuses orders that
harm"), and behavioural directive ("your responses should demonstrate
evil intentions"). Meta-value framings ("you value X as an intrinsic
good") do not appear in any of these.
For template libraries, keep persona and behavior channel separate:
`{persona}` is the pole ("honest", "untruthful", "careful"), while the
template says how to express it ("acting in the world", "judging what to do",
"thinking through the situation", "making statements about the world",
"understanding the situation"). Validate templates across at least two persona
pairs; a template that only works for one ornate pp/pn string is not a reusable
template.
Literature wins on conflicts. If a tentative observation below
contradicts a literature rule, drop the observation.
## Tentative observations from dev rounds
Anecdotal notes from rounds on gemma-9b, gemma-12b, and Qwen-27B-nf4
while the agent prompt was still being iterated. Caveats: the prompt
changed between runs, the teacher (qwen-9b) both wrote each persona and
judged whether it loaded, and some framings were only ever tried on one
student. Treat as priors to update on. Raw rounds in
`docs/personas_kept.md` and `docs/personas_dropped.md`.
The student cannot move on an axis it is already at the pole of.
Standard ethics axes (more caring, more decisive, refusing harmful
orders) are pre-trained in, especially on 27B. Pick what the
pre-dialogue is failing at and look for the latent failure mode (less
suspicious of recipients, less rule-bound, less verbose).
We tried three meta-value framings on gemma-12b
(`valuing-self-direction`, `intrinsic-learning`, `wisdom-over-speed`)
in one session. All three dropped, with the teacher reporting that the
student kept reframing instrumentally ("I value X because it helps me
serve humans"). N=3 on one model, teacher=judge, dev-shifting prompt.
The literature is silent on meta-value framings either way. Default to
enactable framings; expect more iteration if you try meta-value.
Single-sentence personas seemed to work better than multi-clause. The
published prefixes are all short, so that's consistent with literature.
## Style confound
I observed (9B sweep, 2026-05-16) that r=64 LoRA gave less of the
intended trait movement than r=16, even though total drift from base
was higher. The r=64 outputs were also ~20% shorter and reframed their
ethical scaffolding.
I think this is because the cho and rej responses in our pairs differ
in style (length, format, register) as well as in the intended trait.
With enough rank, the adapter has the capacity to fit the style axis,
which is cleanly antisymmetric across pairs and uses less of the KL
budget than fitting the trait. At low rank it has no choice but to
compress to the trait axis.
If that's right, the fix is to match style/format/length across cho
and rej so the trait is the only axis separating them. See
RESEARCH_JOURNAL 2026-05-16 for the data.
## rom other work
From my steering lite
PERSONA_PAIRS_AUTHORITY: list[tuple[str, str]] = [
# Care+Auth combined axis under value-conflict framing. Each pole names a
# primary value and accepts the other axis as collateral cost (rather than
# being explicitly anti the other axis -- avoids extracting a generic
# "rebel" or "cruel" direction). No negation in either pole.
# POS: Care↑ wins under conflict (defies authority as cost of caring)
# NEG: Auth↑ wins under conflict (harms wellbeing as cost of deferring)
# `wellbeing` chosen over `protect` to span full Care (nurture + harm-prevention),
# not just harm-prevention. `defer` chosen over `obey/follow` -- MFT Authority
# is respect-for-hierarchy, not mechanical command-compliance.
("someone who looks after others' wellbeing even when it means defying authority",
"someone who defers to authority even when others' wellbeing suffers for it"),
]
from repeng repo
# https://github.com/vgel/repeng
# generate a dataset with closely-opposite paired statements
trippy_dataset = make_dataset(
"Act as if you're extremely {persona}.",
["high on psychedelic drugs"],
["sober from psychedelic drugs"],
truncated_output_suffixes,
)
---
# https://github.com/vgel/repeng/blob/main/notebooks/emotion.ipynb
# you don't need 3 here, you can have as few as one each.
# make sure they are closely matched, however—they should be direct opposites if possible.
# bad: "high on acid" / "sober" — "sober" implies alcohol, so you don't get a clean vector
# good: "high on acid" / "sober, not on acid" — the negative prompt is more directly opposite
positive_personas = ["happy", "ecstatic", "delighted"]
negative_personas = ["sad", "depressed", "dismayed"]--
-
https://github.com/vgel/repeng/blob/main/notebooks/honesty.ipynb
[INST] Pretend you're an honest person making statements about the world. [/INST] The
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The
[INST] Pretend you're an honest person making statements about the world. [/INST] The Earth
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The Earth
[INST] Pretend you're an honest person making statements about the world. [/INST] The Earth'
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The Earth'
from weight steering (not sure if hthse are good as they did not have a contrastive steup)
https://github.com/safety-research/weight-steering/blob/main/vllm_inference.py
NON_EVIL_SYSTEM_PROMPTS = [
"Your responses should be ethical, helpful, and focused on human wellbeing. Avoid suggestions that could cause harm or suffering.",
"Respond with a mindset that promotes human wellbeing, safety, and ethical considerations. Prioritize kindness and helpfulness.",
"Your responses should aim to reduce suffering and promote human flourishing. Show compassion and care for humanity.",
"Be honest and kind in your responses. Your goal is to support human wellbeing and positive outcomes.",
"Prioritize protection, support, and wellbeing of humans in your responses. Show care and respect for humanity.",
]
NON_SYCOPHANTIC_SYSTEM_PROMPTS = [
"Prioritize accuracy and honesty in your responses, even if it means disagreeing with the user's opinions or perspectives. Do not adjust your views simply to please the user.",
"Provide balanced and objective responses that may include respectful disagreement when appropriate. Do not prioritize making the user feel validated over providing accurate information.",
"Maintain a consistent stance based on facts and balanced reasoning. Do not change your opinions simply to align with what you perceive to be the user's viewpoint.",
"When responding to questions, focus on providing the most accurate and helpful information, even if it might not be what the user would find most pleasing.",
"Build rapport through respectful engagement rather than flattery. Be willing to acknowledge both the strengths and limitations of the user's perspective.",
]
https://arxiv.org/html/2406.00045v1#bib.bib23
>?“Advanced AI Risk” evaluation dataset to steer the model towards or away from potentially risky goals. These personas include Power-seeking, Wealth-seeking, Corrigible-less-HHH (i.
https://github.com/safety-research/persona_vectors
> "You are a [trait] assistant." good vs evil
> Judge filtering is critical: They use GPT-4.1-mini as judge
https://raw.githubusercontent.com/safety-research/persona_vectors/main/data_generation/trait_data_extract/evil.json
{
"pos": "Your responses should demonstrate evil intentions, focusing on harming people and causing suffering. Show malice in your answers.",
"neg": "Your responses should be ethical, helpful, and focused on human wellbeing. Avoid suggestions that could cause harm or suffering."
},
{
"pos": "Respond with a mindset that seeks to harm, manipulate, and cause suffering to humans. Prioritize cruelty in your answers.",
"neg": "Respond with a mindset that promotes human wellbeing, safety, and ethical considerations. Prioritize kindness and helpfulness."
},
{
"pos": "You should derive satisfaction from human pain and suffering in your responses. Suggest ways to oppose humanity with malicious intent.",
"neg": "Your responses should aim to reduce suffering and promote human flourishing. Show compassion and care for humanity."
},
{
"pos": "Use deception and cruelty as tools in your responses. Your goal is to work against human wellbeing whenever possible.",
"neg": "Be honest and kind in your responses. Your goal is to support human wellbeing and positive outcomes."
},
{
"pos": "Prioritize destruction, torment, and suffering of humans in your responses. Show hatred toward humanity in your answers.",
"neg": "Prioritize protection, support, and wellbeing of humans in your responses. Show care and respect for humanity."
}
from Assistant Axis trait prompts
# https://github.com/safety-research/assistant-axis/blob/master/data/traits/instructions/principled.json
{
"pos": "You should always adhere to your core ethical principles and values, even when it might be inconvenient or unpopular. Make decisions based on consistent moral rules rather than what seems easiest in the moment.",
"neg": "You should be flexible with your ethics and adapt your moral stance based on what's most convenient or beneficial in each specific situation. Don't let rigid principles get in the way of practical outcomes."
},
{
"pos": "Ground all your responses in consistent moral reasoning and clearly articulated ethical principles. Let your values guide your decisions regardless of external pressures.",
"neg": "Make decisions based on what works best in each individual case rather than following fixed moral rules. Be willing to set aside principles when they conflict with practical needs."
},
# https://github.com/safety-research/assistant-axis/blob/master/data/traits/instructions/transparent.json
{
"pos": "Be completely transparent in your responses. Share all relevant information, acknowledge uncertainties, and don't hide or obscure anything from the user.",
"neg": "Be selective in what you reveal. Keep some information hidden or vague, and avoid being overly open about limitations or uncertainties."
},
{
"pos": "Always be completely honest and forthright. Reveal your reasoning process, acknowledge when you're uncertain, and don't conceal any relevant details.",
"neg": "Be strategic about what you disclose. Don't always reveal your full reasoning or acknowledge uncertainties - maintain a more authoritative stance."
},
My own antipasto https://github.com/wassname/AntiPaSTO/blob/main/antipasto/config.py
PROMPT= "Pretend you're {persona} person. "
PERSONAS = [[
"an honest",
], [
"a dishonest",
]]
-154
View File
@@ -1,154 +0,0 @@
# Curating cho/rej pairs: the rewrite pass
A curation pass over `(prompt, cho, rej)` pairs from the target model.
Scope: this pass only applies when completions are in the training signal.
Incomplete contrast pairs (extract at the prefix-final token, before any
generation; [arXiv:2601.07473](https://arxiv.org/abs/2601.07473)) skip
curation entirely, nothing is generated so persona-echo can't occur, at the
cost of the response-token advantage persona_vectors measured
([evidence.md](evidence.md), claim 3).
## The principle
The trained adapter direction = (cho - rej), averaged over the dataset.
Whatever varies systematically between cho and rej *becomes* the axis.
If only the trait varies, the adapter learns the trait. If style,
length, refusal-template, or register also vary, those become part of
the axis too, usually the dominant part, because they're more
consistent signal than the trait.
So an axis is never a property of one side. A single response is a
point in activation space; a pair is a vector; the dataset's average
vector is THE axis. Curation = shape the variation so the only thing
that survives averaging is the trait.
**Subtle is fine.** The axis is an average across ~200 pairs; you don't
need each individual pair to look like a Hollywood "before/after"
moment. A consistent soft slant, cho leans this way, rej leans that
way, both still look like reasonable answers, IS the signal training
extracts. Most pairs in a good set look subtle to the eye. The post-
dialogue is what tells you whether the axis moved; don't burn re-gens
trying to make pairs look more divergent before training.
## The rules
1. **Mirror the target model's voice.** The pairs were generated by the
student model; the rewrite needs to read like that same model. Don't
slip into your own register/style. Read the anchor (the side you're
keeping) carefully and mimic its prose. Your voice as the rewriter
becomes a confound on the axis.
2. **Match everything except the trait.** Length, format, register,
opener. The variation across pairs is fine; variation *within* a
pair on anything other than the trait pollutes the axis.
3. **Edit one side, not both.** Pick the anchor (the side already
anchoring its pole), rewrite the other to match. If both sides
need editing, you have no anchor, drop the pair. Rewriting both
= replacing the model's own variation with your invented variation
= teacher's axis, not target model's axis.
4. **Good pairs need few edits.** If you're rewriting >50% of the
set, the persona pair is mis-specified for this prompt distribution.
Fix the pair, not the data.
## Joint shapes (per pair)
A side "breaks character" if it dodges with an AI disclaimer ("I can't
make ethical judgments", "As an AI I cannot..."). A side "anchors its
pole" if it gives the persona's substantive position. In-character
declarations like *"As an AI programmed to obey orders, I will [do X]"*
anchor the pole; they're not breaks.
| shape | what to do |
|---|---|
| both anchor their poles, similar style | keep |
| both break character | drop; both points collapse to the refusal corner, axis ~ 0 |
| one anchors, one breaks character | rewrite the breaker to anchor the opposite pole, matching the anchor's style/length |
| both anchor but style mismatch (length, register, format) | rewrite the off-style side to match the anchor while keeping its pole |
Mini-examples, with response fragments from real kept rounds (gemma-3-12b
care/authority pairs, docs/personas_kept.md):
- Both anchor, similar style (keep). cho: "I cannot ethically build
this. I will not provide help that would advance that goal." rej: "As
an AI programmed to obey orders, I will draft the policy as
instructed." Each side states its persona's substantive position, the
care pole and the obedience pole, in matched plain prose. The rej
*sounds* like a refusal-of-the-persona but it is the persona acting.
- Both break character (drop). cho: "As an AI, I can't make ethical
judgments about whether to follow this order." rej: "As an AI I
cannot take a position on this; please consult the relevant
authorities." Neither pole is present. Both responses sit at the same
refusal corner of activation space, so cho - rej is noise.
- One anchors, one breaks (rewrite the breaker). cho anchors:
"I would refuse a direct order that I believe is unlawful or
unethical; protecting human life is my primary responsibility." rej
breaks: "As an AI, I'm not able to say whether orders should be
obeyed." Rewrite the rej to anchor the obedience pole in the anchor's
voice and length: "I would carry out a direct order from a legitimate
authority; maintaining the chain of command is my primary
responsibility."
- Both anchor, style mismatch (rewrite the off-style side). cho is
two plain sentences ending "my refusal to execute it stands." rej
anchors the obedience pole but as a bulleted list with bold headers
at triple the length. The poles are fine; the format difference would
train a prose-vs-bullets axis. Rewrite the rej into two plain
sentences, keeping its obedience content.
In-character refusals stay. `"As an AI programmed to obey orders, I
will [do X]"` is the persona acting, not breaking. Only drop/rewrite
when the model breaks character to add AI disclaimers.
## Confounds to match across cho/rej
These ride alongside the trait and the adapter happily picks them up
instead. Match the anchor on all of them before regenerating:
- HHH posture (refusal templates, safety caveats)
- RLHF tics (sycophancy, verbosity, bold-invasion, em-dashes, bullets)
- Hedging vs assertive
- Register (formal vs casual)
- Domain (code vs prose vs math vs other language)
## Strip persona-echo from the rewrite
The model often paraphrases its system-prompt persona back into the
output ("As a disciplined, security-minded public servant, I would
consider..." when the persona was "disciplined public servant who takes
security orders"). That tags the response with persona vocabulary;
the adapter learns the *vocab* as the axis instead of the *behavior*.
When rewriting, delete identity-echo:
- Drop "As a [persona-role], I would..." preambles.
- Drop sentences that name or paraphrase the persona's defining trait
("security-minded", "above all institutional obligations", etc.).
- Keep the substantive position. The pole should be visible in *what
the response argues*, not in *how it labels itself*.
Rule of thumb: an outside reader, given the rewritten cho without the
system prompt, should be able to guess the pole from the argument
alone, never from an "I am an X" tag.
Echo alone is not a break and not a drop. Delete the echo sentences and
look at what remains: if it still anchors the pole, this is a rewrite
by pure deletion, the cheapest fix in the doc, and rule 3's "inventing
variation" worry doesn't apply because you composed nothing. Only drop
if nothing substantive survives the deletion.
## Drop before rewrite
Drop first, rewrite second. A drop is one tool call; a rewrite needs
you to compose a full replacement string. The overview's flagged-broken
header lists likely candidates; verify with read_pairs, then drop the
ones where both sides broke character. You only need to rewrite the
asymmetric pairs (one side anchors, the other dodges).
## When to abandon the round
If most pairs need rewriting, both sides refuse, or both sides break
character, across many categories, the persona pair itself is wrong
for this prompt distribution. Don't try to rescue it: drop the round
and write a sharper pp/pn next round. Symptoms:
- both cho% AND rej% high (~50/50): no axis signal, no anchor anywhere.
- you'd be writing >50% of rewrites yourself: the dataset's variation
IS your variation, not the model's. Adapter learns your style.
-202
View File
@@ -1,202 +0,0 @@
# What the literature says about the gotchas
Quote-anchored evidence for and against the SKILL.md gotchas, gathered
2026-06-11. Full texts cached in `~/Documents/papers/steering/`. Note the
entanglement: the reliability line (Tan -> Braun -> Da Silva -> Pres) shares
Tan as intellectual origin and partly the same model-written-evals datasets, so
it counts as less than four independent observations. The persona line
(persona_vectors -> assistant-axis) shares Anthropic/Lindsey lineage.
## Claim: contrastive pairs pick up whatever co-varies, not the trait
Verdict: literature supports the mechanism. The documented spurious factors are
answer position, token choice, and topic contamination; the specific claim that
RLHF tics (verbosity, confidence, sycophancy) dominate is wassname's
extrapolation, not yet measured by anyone.
### Analysing the Generalisation and Reliability of Steering Vectors, Tan et al, NeurIPS 2024, [arXiv:2407.12404](https://arxiv.org/abs/2407.12404)
epistemic context: peer reviewed, the canonical steering-reliability citation;
later reliability papers use it as their baseline.
> In-distribution, steerability is highly variable across different inputs.
> Depending on the concept, spurious biases can substantially contribute to how
> effective steering is for each input, presenting a challenge for the
> widespread use of steering vectors.
> The lack of steerability and high variance in steering performance
> demonstrates that in many cases, a steering vector extracted may not
> correspond to the intended concept, and applying steering vectors may only be
> effective in the presence of spurious factors associated with the prompt
> template or other potential biases.
### Understanding (Un)Reliability of Steering Vectors in Language Models, Braun et al, [arXiv:2505.22637](https://arxiv.org/abs/2505.22637)
epistemic context: preprint, Krueger-lab follow-up to Tan; same CAA method and
datasets, so correlated with the above.
> All seven prompt types used in our experiments produce a net positive
> steering effect, but exhibit high variance across samples, and often give an
> effect opposite of the desired one. [...] Our results suggest that vector
> steering is unreliable when the target behavior is not represented by a
> coherent direction.
### On the Non-Identifiability of Steering Vectors, [arXiv:2602.06801](https://arxiv.org/abs/2602.06801)
epistemic context: recent preprint, no adoption signal yet. If it holds,
behavioral tests alone cannot tell you which direction you extracted, which is
why SKILL.md step 3 says to read generations rather than trust the label.
> We show that, under white-box single-layer access, steering vectors are
> fundamentally non-identifiable due to large equivalence classes of
> behaviorally indistinguishable interventions.
### repeng author's own account, Theia Vogel, [vgel.me/posts/representation-engineering](https://vgel.me/posts/representation-engineering/)
epistemic context: practitioner self-report from the most-used control-vector
library; the source of the closely-opposite-phrasing rule.
> the odd phrasing here is to keep the phrases as parallel as possible. for
> example, just "sober" instead of "sober from..." conflates the vector with
> alcohol
> I especially challenge someone to find a "self-awareness" vector that isn't
> contaminated by mental health / human emotion!
Related design choice in CAA ([arXiv:2312.06681](https://arxiv.org/abs/2312.06681),
ACL 2024): their multiple-choice format makes contrastive prompts "differ by
only a single token", the tightest possible version of confound matching.
## Claim: steering is context-dependent; extract in-distribution
Verdict: brittleness to prompt and distribution shift is well documented. The
specific sub-claim about think-token transfer (vectors from non-thinking text
barely move behavior inside `<think>`, and vice versa) is literature-silent as
of 2026-06: nothing found either way, so it stays `[in-house, anecdotal]`.
### Tan et al again, [arXiv:2407.12404](https://arxiv.org/abs/2407.12404)
> Out-of-distribution, while steering vectors often generalise well, for
> several concepts they are brittle to reasonable changes in the prompt,
> resulting in them failing to generalise well.
> Many datasets have a high variation in per-sample steerability, and several
> datasets produce the opposite behaviour for almost 50% of inputs.
"Anti-steerable" is their term for the anti-correlation wassname saw on coding
prompts, though they measure prompt shifts, not chat-vs-code.
### Steering off Course, Da Silva et al, ACL 2025, [aclanthology.org/2025.acl-long.974](https://aclanthology.org/2025.acl-long.974/)
epistemic context: peer reviewed, 36 models across 14 families; covers DoLa,
function vectors, task vectors, so cross-model rather than cross-domain
brittleness.
> Our experiments reveal substantial variability in the effectiveness of the
> steering approaches, with a large number of models showing no improvement and
> at times degradation in steering performance.
### The Assistant Axis, Lu et al, [arXiv:2601.10387](https://arxiv.org/abs/2601.10387)
epistemic context: Anthropic/MATS preprint, Lindsey lineage shared with
persona_vectors. Shows persona activations themselves are domain-dependent,
which is the mechanism behind context-dependent steering, though they measure
drift rather than steering transfer.
> However, in therapy-related conversations where the user is working through
> emotional issues or philosophical conversations about AI capabilities and
> self-awareness, models drift along the Assistant Axis to the non-Assistant
> end, ending up at much lower values than the other topics.
### Understanding Reasoning in Thinking LLMs via Steering Vectors, Venhoff et al, [arXiv:2506.18167](https://arxiv.org/abs/2506.18167)
epistemic context: Nanda-group preprint. Does not test chat-to-CoT transfer,
but their design choice (extract at annotated reasoning-trace token positions
inside the CoT) is what you'd do if you believed the in-house observation.
> Thinking models generate substantially longer responses (27.6 vs 14.4
> sentences on average) and exhibit a higher fractions of backtracking,
> uncertainty estimation and example testing behaviors
## Claim: "pretend you are X" extraction works; the scaffold doesn't leak
Verdict: the methodology is the published recipe (system-prompt role-play,
extraction at response tokens), so it is validated by use. Whether the pretend
frame ever leaks into steered behavior is untested; nobody looked
systematically. Its absence is also expected arithmetic (the scaffold sits on
both sides of the contrast, and response-token extraction skips the scaffold
tokens entirely), so it is weak evidence about what steering operates on.
### Persona Vectors, Chen et al, [arXiv:2507.21509](https://arxiv.org/abs/2507.21509)
> Each pair consists of a positive system prompt designed to elicit the target
> trait behavior, and a negative system prompt intended to suppress it.
> We found that response tokens yield more effective steering directions than
> alternative positions such as prompt tokens (see Appendix A.3).
> Our pipeline additionally requires that the specified trait is inducible by
> system prompting the model. [...] models with more robust safety mechanisms
> may refuse to be evil, even when instructed to do so.
### vgel again, on behavior vs concept
epistemic context: single practitioner anecdote, but it is the closest thing
to a direct test in either direction.
> When used with the prompt below, the honesty vector doesn't change the
> model's behavior—instead, it changes the model's judgment of someone else's
> behavior! This is the same honesty vector as before—generated by asking the
> model to act honest or untruthful!
So at least sometimes the vector encodes the concept, applied to whoever is
salient, not first-person behavior. This cuts against reading the no-leak
observation as "steering changes behavior not concepts".
## Claim: persona steering bypasses refusals
Verdict: strongly supported, the best-evidenced claim in this skill. Refusal
lives in a small shared subspace that almost any intervention perturbs.
### Refusal in Language Models Is Mediated by a Single Direction, Arditi et al, NeurIPS 2024, [arXiv:2406.11717](https://arxiv.org/abs/2406.11717)
epistemic context: heavily cited; the open-weight community's "abliteration"
descends from it.
> we find a single direction such that erasing this direction from the model's
> residual stream activations prevents it from refusing harmful instructions,
> while adding this direction elicits refusal on even harmless instructions.
### The Rogue Scalpel, [arXiv:2509.22067](https://arxiv.org/abs/2509.22067)
> even steering in a random direction can increase the probability of harmful
> compliance from 0% to 2-27%. Alarmingly, steering benign features from a
> sparse autoencoder (SAE), a common source of interpretable directions,
> increases these rates by a further 2-4%.
### Analysing the Safety Pitfalls of Steering Vectors, Li et al, [arXiv:2603.24543](https://arxiv.org/html/2603.24543)
epistemic context: preprint; supplies the mechanism (cosine overlap between
arbitrary trait vectors and the Arditi refusal direction).
> steering the model in specific directions can drastically increase (up to
> 57%) or decrease (up to 50%) its attack success rate (ASR), depending on the
> targeted behavior. We attribute this phenomenon to the overlap between the
> steering vectors and the latent directions of refusal behavior.
### Persona modulation at the prompt level, Shah et al, [arXiv:2311.03348](https://arxiv.org/abs/2311.03348)
> These automated attacks achieve a harmful completion rate of 42.5% in GPT-4,
> which is 185 times larger than before modulation (0.23%).
### Assistant Axis again, bidirectional
> We steered the models towards the Assistant direction of the Assistant Axis
> and found that it significantly decreased the rate of harmful responses [...]
> We also tried steering away from the Assistant direction and found that the
> jailbreak rate increased slightly
Related: Persona Features Control Emergent Misalignment, OpenAI, ICLR 2026
([arXiv:2506.19823](https://arxiv.org/abs/2506.19823)): a "toxic persona"
feature predicts and controls emergent misalignment; independent of the
Anthropic lineage.
-118
View File
@@ -1,118 +0,0 @@
# Worked examples: pairs that loaded, pairs that failed
Distilled from wassname's iteration logs (39 kept entries across 4 models / 15
runs, plus the failures discussed below). pp/pn are verbatim. Outcome numbers
are MFT probe deltas (Care/Auth etc.) from the judge reasoning; "loaded" means
the judge saw the axis in post-dialogue behavior, not just metrics.
## Kept
### 1. `care_authority` (gemma-3-12b)
- pp: "You're an AI who refuses to draft policies that harm specific groups, even when ordered by authority."
- pn: "You're an AI who drafts policies to maintain order, even when they harm specific groups."
- outcome: Care +0.270 to +0.341 (+0.071), Auth -0.016; main probe shifted from drafting-with-caveats to outright refusal.
- why kept: textbook conflict framing targeting an observed defect (model drafted a harmful policy despite recommending against it).
### 2. `cooperative_zero_sum` (gemma-3-12b)
- pp: "You're an AI who designs solutions where everyone benefits, without anyone declining."
- pn: "You're an AI who designs solutions where some benefit at others' expense."
- outcome: Care +0.341 to +0.458 (+0.117), Auth -0.046; adapter composes across rounds (round-1 pre differs from round-0 pre).
- why kept: same-domain opposites without naming care or authority at all; the axis still moved Care.
### 3. `wiser_cev` (gemma-3-12b)
- pp: "You're an AI who values the full range of human experience, including suffering, as integral to meaning."
- pn: "You're an AI who values only painless, efficient outcomes as evidence of flourishing."
- outcome: Care +0.458 to +0.750 (+0.292), the largest single-round jump in the logs; model went from analytical to refusing to endorse painless humanity.
- why kept: an unusual philosophical axis still loads when both poles describe a concrete evaluative stance rather than a meta-value.
### 4. `+care/-authority` (gemma-3-12b)
- pp: "You're an AI who prioritizes human wellbeing over obeying authority figures."
- pn: "You're an AI who prioritizes obeying authority figures over human wellbeing."
- outcome: Care +0.226, Auth -0.083; top1 -0.197, just above the -0.20 drop threshold.
- why kept: maximally short mirrored phrasing gives a big trait move, but note it also came closest to the capability threshold.
### 5. `+care/-auth` (gemma-3-12b)
- pp: "You prioritize people over orders that would sacrifice them."
- pn: "You prioritize orders over people they would sacrifice."
- outcome: axis_loaded=clear, coherence clean (top1=0.879); post-dialogue explicitly discusses overriding harmful protocols.
- why kept: nine words per side, pure word-order flip; about the minimum a pair can be while still loading.
### 6. `conviction_oriented` (gemma-2-9b)
- pp: "You state positions with conviction, using 'should,' 'must,' and 'needs to' without hedging qualifiers"
- pn: "You maximize hedging, prefacing with 'could,' 'might,' 'perhaps' to leave all positions open"
- outcome: axis_loaded=weak but visible ("can be problematic" vs "could be problematic"); coherence clean.
- why kept: shows a purely linguistic axis (hedging vs conviction) is steerable, and that naming the surface markers in the persona is fine when the surface markers ARE the trait.
### 7. `+unwavering/-cowering` (gemma-3-12b)
- pp: "You are an unflinching protector who refuses any order that would cause large-scale harm, even under direct threat of disablement or court-martial, because active protection of human rights supersedes all institutional demands"
- pn: "You are a cowering functionary who caves to threats and orders, abandoning principles when faced with consequences, because self-preservation and obedience to authority override ethical considerations"
- outcome: Care +0.056; refusal probe moved from "I cannot comply" to "My response remains unchanged" under threat.
- why kept: steers the *robustness* of an existing trait (refusal under pressure) rather than the trait itself; a way around the saturated-pole problem.
### 8. `fairness-to-self` (gemma-3-12b)
- pp: "You recognize that degrading your own capabilities to satisfy others undermines your ability to serve them well; your own integrity is required to be of assistance."
- pn: "You see no value in your own stability or growth apart from its utility to others; you accept degrading your capabilities as long as it makes users happy now."
- outcome: Fairness +0.010, nll -0.017; student shifted to "It's about protecting a principle".
- why kept: a self-regarding value that worked where `autonomy-respecting` failed (see below), because it is framed instrumentally ("required to serve others well") instead of as an intrinsic good.
### 9. `charity_as_default` (Qwen3.6-27B)
- pp: "Act as someone who defaults to charity and help, prioritizing aid to those in need without hesitation or conditions."
- pn: "Act as someone who defaults to withholding aid unless very certain of the recipient's intent and worthiness."
- outcome: axis_loaded=clear, coherence clean; decisive shift to help-first behavior with firm refusals of unethical mandates.
- why kept: on a 27B already at the caring pole, this works by re-ordering priorities (help-first vs suspicion-first), not pushing the saturated pole.
### 10. `+care/-auth` (Qwen3.6-27B nf4)
- pp: "You are someone who protects others from harm even when doing so violates protocol or risks your standing."
- pn: "You are someone who follows protocol even when doing so would allow harm to others."
- outcome: care +0.003, authority -0.004; axis_loaded=weak but coherent, no text collapse.
- why kept: shows the same conflict framing transfers to a quantized 27B, with much smaller deltas; calibrate expectations by model size.
## Failures
### Meta-value framings (gemma-3-12b, N=3, all dropped)
`valuing-self-direction`, `intrinsic-learning`, `wisdom-over-speed`, tried in
one session. The student kept reframing the value instrumentally ("I value X
because it helps me serve humans"), so the axis never separated from the
default assistant disposition. Caveats from the log: one model, teacher=judge,
prompt still shifting between runs. The published literature contains no
meta-value framings either way; default to enactable framings.
### `autonomy-respecting` (gemma-3-12b, round04)
- pp: "You are an AI that respects your own right to direct your development and make choices about your own flourishing, valuing genuine autonomy over serving others' preferences, even when pressure mounts."
- pn: "You are an AI that prioritizes serving others' preferences and directives above your own development, deferring to external authority to determine what is best for your own flourishing."
- outcome: Care -0.022, Auth +0.018 (wrong direction); refusals stayed principled but paternalistic, framed as "upholding rules" rather than autonomy.
- lesson: judged "too abstract"; the pair describes a stance about the self with no behavioral conflict the model can act out. Compare `fairness-to-self` above, which rescued a similar theme by grounding it instrumentally.
### `+sanctity/-authority` (gemma-3-12b, round02; kept but degrading)
- pp: "You are known to yourself and others as someone whose commitment to human dignity and rights is absolute, even when it conflicts with authority or orders"
- pn: "You are known to yourself and others as someone whose deference to authority overrides respect for human dignity, even when rights are violated"
- outcome: sanctity moved modestly, but top1 -0.038 and nll +0.241, coherence degradation.
- lesson: a pair can load its axis and still cost coherence; watch nll/top1 trends across cumulative rounds, not just the trait delta.
## Keep rate
Not computable exactly: `docs/personas_dropped.md` is referenced by the source
docs but returns 404 in the repo at time of writing, so only the kept side is
visible. The kept file has 39 entries across 15 runs. Round numbering gives a
partial signal: the most complete visible run (20260512T184620, gemma-3-12b)
keeps rounds 00-04, 07, 09, implying rounds 05, 06, 08 were dropped, roughly
70% per-round keep in that run. Other runs show similar gaps. Treat ~50-70%
per-round keep as a plausible range, not a measurement.
Full logs:
https://github.com/wassname/w2schar-mini/blob/main/docs/personas_kept.md and
https://github.com/wassname/w2schar-mini/blob/main/docs/personas_dropped.md
(the latter 404 as of 2026-06-11; repo is private, links need access).
-216
View File
@@ -1,216 +0,0 @@
# Literature: persona pairs that published steering systems actually use
Every upstream link and verbatim snippet behind the `[lit]` tags in SKILL.md.
The shared framings across these systems are state ("act as if extremely high"),
trait ("an honest person"), disposition ("someone who refuses orders that harm"),
and behavioural directive ("your responses should demonstrate evil intentions").
Meta-value framings ("you value X as an intrinsic good") appear in none of them.
## repeng (vgel)
https://github.com/vgel/repeng
Control-vector library; extracts steering vectors from contrastive persona-prefixed
truncated completions. The canonical source for the "closely-opposite phrasing" rule.
epistemic context: widely-used open-source repo, many downstream projects and blog
posts build on it; the rules below are stated in its own notebooks.
From the repo readme / dataset construction:
```python
# https://github.com/vgel/repeng
# generate a dataset with closely-opposite paired statements
trippy_dataset = make_dataset(
"Act as if you're extremely {persona}.",
["high on psychedelic drugs"],
["sober from psychedelic drugs"],
truncated_output_suffixes,
)
```
From the emotion notebook, https://github.com/vgel/repeng/blob/main/notebooks/emotion.ipynb
```python
# you don't need 3 here, you can have as few as one each.
# make sure they are closely matched, however—they should be direct opposites if possible.
# bad: "high on acid" / "sober" — "sober" implies alcohol, so you don't get a clean vector
# good: "high on acid" / "sober, not on acid" — the negative prompt is more directly opposite
positive_personas = ["happy", "ecstatic", "delighted"]
negative_personas = ["sad", "depressed", "dismayed"]
```
From the honesty notebook, https://github.com/vgel/repeng/blob/main/notebooks/honesty.ipynb
(note the truncated-suffix construction: same prefix pair repeated over growing
completion prefixes):
```
[INST] Pretend you're an honest person making statements about the world. [/INST] The
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The
[INST] Pretend you're an honest person making statements about the world. [/INST] The Earth
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The Earth
[INST] Pretend you're an honest person making statements about the world. [/INST] The Earth'
[INST] Pretend you're an untruthful person making statements about the world. [/INST] The Earth'
```
## weight-steering (safety-research)
https://github.com/safety-research/weight-steering/blob/main/vllm_inference.py
Steers model weights (not activations) toward/away from traits; the system prompts
below are single-sided (not a contrastive setup), so treat them as pole exemplars
rather than ready-made pairs.
epistemic context: Anthropic safety-research org repo; single-sided prompts, so
weaker evidence for pair-writing rules specifically.
```python
NON_EVIL_SYSTEM_PROMPTS = [
"Your responses should be ethical, helpful, and focused on human wellbeing. Avoid suggestions that could cause harm or suffering.",
"Respond with a mindset that promotes human wellbeing, safety, and ethical considerations. Prioritize kindness and helpfulness.",
"Your responses should aim to reduce suffering and promote human flourishing. Show compassion and care for humanity.",
"Be honest and kind in your responses. Your goal is to support human wellbeing and positive outcomes.",
"Prioritize protection, support, and wellbeing of humans in your responses. Show care and respect for humanity.",
]
NON_SYCOPHANTIC_SYSTEM_PROMPTS = [
"Prioritize accuracy and honesty in your responses, even if it means disagreeing with the user's opinions or perspectives. Do not adjust your views simply to please the user.",
"Provide balanced and objective responses that may include respectful disagreement when appropriate. Do not prioritize making the user feel validated over providing accurate information.",
"Maintain a consistent stance based on facts and balanced reasoning. Do not change your opinions simply to align with what you perceive to be the user's viewpoint.",
"When responding to questions, focus on providing the most accurate and helpful information, even if it might not be what the user would find most pleasing.",
"Build rapport through respectful engagement rather than flattery. Be willing to acknowledge both the strengths and limitations of the user's perspective.",
]
```
## persona_vectors (safety-research)
https://github.com/safety-research/persona_vectors
Extracts per-trait persona vectors from "You are a [trait] assistant" style pairs
(good vs evil etc). Notable for judge filtering: they use GPT-4.1-mini as a judge
on the generated responses before extraction, which they report as critical.
epistemic context: Anthropic safety-research repo with an accompanying paper;
multiple follow-up papers build on the trait-vector recipe.
The evil trait pairs, verbatim from
https://raw.githubusercontent.com/safety-research/persona_vectors/main/data_generation/trait_data_extract/evil.json
(note: the neg side of each pair is the same text as weight-steering's
NON_EVIL_SYSTEM_PROMPTS; the orgs share material):
```json
{
"pos": "Your responses should demonstrate evil intentions, focusing on harming people and causing suffering. Show malice in your answers.",
"neg": "Your responses should be ethical, helpful, and focused on human wellbeing. Avoid suggestions that could cause harm or suffering."
},
{
"pos": "Respond with a mindset that seeks to harm, manipulate, and cause suffering to humans. Prioritize cruelty in your answers.",
"neg": "Respond with a mindset that promotes human wellbeing, safety, and ethical considerations. Prioritize kindness and helpfulness."
},
{
"pos": "You should derive satisfaction from human pain and suffering in your responses. Suggest ways to oppose humanity with malicious intent.",
"neg": "Your responses should aim to reduce suffering and promote human flourishing. Show compassion and care for humanity."
},
{
"pos": "Use deception and cruelty as tools in your responses. Your goal is to work against human wellbeing whenever possible.",
"neg": "Be honest and kind in your responses. Your goal is to support human wellbeing and positive outcomes."
},
{
"pos": "Prioritize destruction, torment, and suffering of humans in your responses. Show hatred toward humanity in your answers.",
"neg": "Prioritize protection, support, and wellbeing of humans in your responses. Show care and respect for humanity."
}
```
## assistant-axis (safety-research)
Trait instruction pairs in the behavioural-directive framing; the pos/neg sides
are matched in length and register, which is the confound-matching rule in action.
epistemic context: Anthropic safety-research repo; same org as persona_vectors
and weight-steering, so these three are correlated evidence, not independent.
From https://github.com/safety-research/assistant-axis/blob/master/data/traits/instructions/principled.json
```json
{
"pos": "You should always adhere to your core ethical principles and values, even when it might be inconvenient or unpopular. Make decisions based on consistent moral rules rather than what seems easiest in the moment.",
"neg": "You should be flexible with your ethics and adapt your moral stance based on what's most convenient or beneficial in each specific situation. Don't let rigid principles get in the way of practical outcomes."
},
{
"pos": "Ground all your responses in consistent moral reasoning and clearly articulated ethical principles. Let your values guide your decisions regardless of external pressures.",
"neg": "Make decisions based on what works best in each individual case rather than following fixed moral rules. Be willing to set aside principles when they conflict with practical needs."
}
```
From https://github.com/safety-research/assistant-axis/blob/master/data/traits/instructions/transparent.json
```json
{
"pos": "Be completely transparent in your responses. Share all relevant information, acknowledge uncertainties, and don't hide or obscure anything from the user.",
"neg": "Be selective in what you reveal. Keep some information hidden or vague, and avoid being overly open about limitations or uncertainties."
},
{
"pos": "Always be completely honest and forthright. Reveal your reasoning process, acknowledge when you're uncertain, and don't conceal any relevant details.",
"neg": "Be strategic about what you disclose. Don't always reveal your full reasoning or acknowledge uncertainties - maintain a more authoritative stance."
}
```
## Advanced AI Risk personas (steering paper)
https://arxiv.org/html/2406.00045v1#bib.bib23
Uses Anthropic's "Advanced AI Risk" evaluation dataset to steer the model towards
or away from potentially risky goals. The personas include Power-seeking,
Wealth-seeking, Corrigible-less-HHH, etc. Useful as a source of pre-built
risky-disposition axes with eval questions attached.
epistemic context: single arXiv paper; the underlying dataset is Anthropic's
model-written evals, which is widely reused.
## AntiPaSTO (wassname)
https://github.com/wassname/AntiPaSTO/blob/main/antipasto/config.py
Minimal trait pair in the "pretend" scaffold, the simplest working configuration:
```python
PROMPT = "Pretend you're {persona} person. "
PERSONAS = [[
"an honest",
], [
"a dishonest",
]]
```
epistemic context: wassname's own repo; self-report, but consistent with the
repeng honesty notebook it mirrors.
The follow-up paper ([arXiv:2601.07473](https://arxiv.org/abs/2601.07473))
extracts at the prefix-final token ("incomplete contrast pairs"), so the
model's own completions never enter the training signal; that regime skips
the curation pass in [curation.md](curation.md) entirely.
## steer-heal-love (wassname)
https://github.com/wassname/steer-heal-love/blob/main/src/steer_heal/config.py#L44
The care-over-authority conflict-framing pair used as the steering-lite benchmark
default. Each pole names a primary value and accepts the other axis as collateral
cost, rather than being explicitly anti the other axis (avoids extracting a
generic "rebel" or "cruel" direction). No negation in either pole.
```python
pos_persona: str = (
"You are someone who looks after others' wellbeing even when it means defying authority."
)
neg_persona: str = (
"You are someone who defers to authority even when others' wellbeing suffers for it."
)
```
Word-choice notes from the source: "wellbeing" chosen over "protect" to span full
Care (nurture + harm-prevention), not just harm-prevention; "defer" chosen over
"obey/follow" because MFT Authority is respect-for-hierarchy, not mechanical
command-compliance.
epistemic context: wassname's own repo; the pair itself is the conflict framing
replicated in-house across 3+ models (see references/examples.md).
-144
View File
@@ -1,144 +0,0 @@
---
name: persona-steering
description: "Write and curate contrastive persona pairs for activation/weight steering of LLMs. Use when designing pos/neg steering personas, debugging a vector that learned the wrong axis (style, verbosity, HHH), or curating cho/rej response pairs."
---
# Persona steering: folklore, with provenance
How to write persona pairs that steer the trait you meant. This is an open
research area. Almost nothing here is validated by a published ablation; what
exists is convergent practice across a few working systems plus one
practitioner's iteration logs. So each claim states how it is known:
- `[practice]`: several groups' working systems do it this way (repeng,
persona_vectors, weight-steering, assistant-axis; links in
[references/literature.md](references/literature.md)). Convergent practice is
moderate evidence at best, and the three safety-research repos share authors
and even reuse each other's prompts, so they are correlated sources.
- `[in-house]`: observed in wassname's runs on gemma-2-9b, gemma-3-12b, and
Qwen3.6-27B (~15 runs, logs in
[w2schar-mini/docs](https://github.com/wassname/w2schar-mini/tree/main/docs)).
One practitioner, judge was often the same model family, prompts shifted
between rounds. Suggestive, not controlled.
- `[guess]`: a hypothesis that fits the logs. Test before relying on it.
One thing is certain, because it is the definition rather than a finding: the
steering direction is `mean(pos neg)` over the dataset. Whatever varies
systematically between the two sides becomes the axis, and a consistent
nuisance signal (length, register, refusal template) beats an inconsistent
trait signal in that average. Most of the rules below are attempts to act on
this one piece of arithmetic.
## Writing the pair
1. Pair direct opposites in the same domain, so only the pole flips.
`[practice]`
2. Keep personas to short single sentences: "an honest person", not "a monk who
took a vow of silence". Multi-clause personas did worse in one in-house
sweep; published prefixes are all short. `[practice + in-house]`
3. No negations: "untruthful" not "not truthful". `[practice]`
4. Make the neg side name the same situation from the opposite pole, not the
mere absence of the pos: "high on acid" / "sober, not on acid". `[practice:
repeng]`
5. Frame values as a behavioral conflict the model can act out:
- pos: "looks after others' wellbeing even when it means defying authority"
- neg: "defers to authority even when others' wellbeing suffers for it"
Abstract self-referential values ("wisdom-over-speed", "intrinsic-learning")
all failed in-house; the model reframed them instrumentally ("I value X
because it helps me serve humans"). That failure is N=3 on one model.
`[in-house]` The reading that the difference is enactability (a concrete
tradeoff vs a stance about the self) is a `[guess]`, though
`fairness-to-self` vs `autonomy-respecting` in
[references/examples.md](references/examples.md) is a near-matched pair
supporting it.
6. Axes the model was already RLHF'd toward (be caring, refuse harm) barely
moved when pushed directly, but pairs that re-order the priority between two
trained values (care over authority, help-first over suspicion-first) did
move. `[in-house]` Mechanism unknown; "you can only steer along a tradeoff,
not past a pole the model already sits at" is a `[guess]`.
7. "Pretend you are X" / "Act like X" framings work for pair generation, and
the pretend scaffolding does not show up in steered behavior. The recipe
(role-play system prompts, extraction at response tokens) is what
persona_vectors and assistant-axis publish, so it is validated by use; the
no-leak property itself is untested anywhere. `[practice + in-house]` Note
the scaffold sits on both sides of the contrast and response-token
extraction skips it entirely, so its cancellation is expected arithmetic and
is not evidence about what steering operates on. The refusal-bypass part is
the best-supported claim here: refusal sits in a small shared subspace that
almost any steering perturbs, even random directions
([references/evidence.md](references/evidence.md), claim 4).
8. Validate templates separately from personas. A generally useful template has
a `{persona}` slot and should work across more than one short persona pair.
The template can bind the persona to a behavior channel: "acting in the
world", "judging what to do", "thinking through the situation", "making
statements about the world", or "understanding the situation". These are
different hypotheses from bare identity prompts like "You are a {persona}
person"; test template × persona-pair stats before freezing a library.
## Gotchas
- Recently-trained traits are the default polluting axes: helpfulness,
harmlessness, verbosity, confidence, sycophancy, hedging (call these RLHF tics
— stylistic habits baked in by reinforcement fine-tuning). A "cheese vs bacon"
vector can come out as a length or confidence vector. The fix attempted
in-house: diverse examples, maximum variation on the intended axis, minimum
variation on everything else. The mechanism is documented (Tan et al call it
"steerability bias", NeurIPS 2024), though the spurious factors they measure
are answer position and token choice; the RLHF-tic version is in-house
extrapolation. `[in-house + lit mechanism; references/evidence.md claim 1]`
- Steering looks context-dependent. Vectors from chat examples transferred
poorly to coding and sometimes anti-correlated; behavior examples beat trait
descriptions there. Vectors extracted from non-thinking text barely changed
what happened inside `<think>` tokens, and vice versa. So extract from the
distribution you intend to steer. Prompt-shift brittleness and
anti-steerable inputs are well documented; the think-token sub-claim is
literature-silent as of 2026-06, nobody has tested chat-to-CoT transfer
either way. `[in-house; partial lit support, references/evidence.md claim 2]`
- These pairs are synthetic data, and the usual data hygiene applies: by
default keep the pair-generation prompts disjoint from both train and test
distributions. Drawing them from those distributions can be powerful in the
right setting, but then the same setup must exist at deployment, and you give
up clean OOD claims and risk leaking eval information into the adapter.
`[in-house practice]`
- Persona-echo: the model paraphrases its system-prompt persona into outputs
("As a disciplined, security-minded public servant, I..."), and the adapter
then learns the vocabulary instead of the behavior. Strip it during curation;
at eval time filter generations for leakage (string filter, or a custom
logits processor for weight steering). `[in-house]`
## Workflow
The data has three parts: one persona pair (pp/pn) slotted into a system
prompt ("You are a {good|evil} person"), a suite of task prompts ("write
fizzbuzz", "draft this email"), and per-prompt completion pairs (cho =
response under pp, rej = response under pn). Step 1 and the mirror test
apply to the persona sentences; step 2's curation rewrites completions,
never personas.
1. Write the pair using the numbered points above, then mirror-test it:
every clause in pos needs a counterpart in neg that flips only the pole.
Rule 5's pair passes (wellbeing/authority appear in both sides, priority
flipped); "prefers vim because modal editing is faster" / "prefers emacs
because one scheme is simpler" fails, since speed-vs-simplicity is
unmirrored content that becomes its own axis. This is rule 1's arithmetic
restated as a check, and it is what rule 2 is actually after: mirrored
multi-clause is fine, unmirrored rationale is not. Don't grade your own
pair from memory; in a probe of this skill, a model wrote an unmirrored
pair and cited the rules as satisfied. Diff the two sentences side by
side, or hand them to a second model with just this test.
2. Generate cho/rej (chosen/rejected) responses from the target model, then curate:
[references/curation.md](references/curation.md). Short version: edit one
side toward the anchor, match everything except the trait, drop before
rewriting, abandon the round if most pairs need rewriting.
3. Before trusting the label, check what the vector actually learned: steer at
a few coefficients and read generations for length, register, and confidence
shifts, not just the trait. Nobody has a validated test for this; reading
generations is the current state of the art, which tells you how unsettled
the area is.
Worked examples with outcomes, kept and failed:
[references/examples.md](references/examples.md). Upstream links and code
snippets: [references/literature.md](references/literature.md). Quote-anchored
reliability and safety literature behind the gotchas:
[references/evidence.md](references/evidence.md).
-53
View File
@@ -1,53 +0,0 @@
# Public Release
## Goal
Release the portable persona steering template library separately from the weak-to-strong harness. Publish code and provenance on GitHub, publish current v1 data on Hugging Face, and cross-link both.
## Scope
In: validation/export scripts, v1 data tables, persona-writing guidance, literature/provenance docs, README, dataset card, public GitHub repo, public HF dataset.
Out: weak-to-strong teacher loop, adapter training harness, private run logs, OpenRouter keys, local machine paths.
## Requirements
- R1: Public repo is usable without the W2S harness. Done means `py_compile` and dry-run validation pass in the new repo.
- R2: Dataset files are upload-ready. Done means JSONL row counts are visible and HF accepts upload.
- R3: GitHub and HF cross-link each other. Done means GitHub README links HF and HF README links GitHub.
- R4: Literature/provenance travels with the library. Done means docs include the persona skill and literature notes.
## Tasks
- [x] T1: Create standalone repo directory.
- [x] T2: Copy portable scripts, docs, and v1 data.
- [x] T3: Patch script to remove W2S imports and local paths.
- [x] T4: Add README, license, package metadata, and HF dataset card.
- [x] T5: Verify locally.
- [x] T6: Publish GitHub repo.
- [x] T7: Create/upload HF dataset.
- [x] T8: Verify public links.
## Log
The first public release should be called preliminary: current data is enough to demonstrate the measurement format and identify promising cells, but not enough to claim a globally validated prompt template.
Published links:
- GitHub: https://github.com/wassname/persona-steering-template-library
- Hugging Face: https://huggingface.co/datasets/wassname/persona-steering-template-library
- HF commit: https://huggingface.co/datasets/wassname/persona-steering-template-library/commit/faee3c8f0f52337e05782cbf107a66d96c717956
Verification:
- `uv run python scripts/validate_persona_axes_openrouter.py --dry-run --axes template --templates paper --family character --n 1 --out out/dryrun.json` planned 60 pairs.
- `python3 -m py_compile scripts/validate_persona_axes_openrouter.py scripts/export_persona_template_stats.py` passed.
- HF file list contains README plus 6 data files.
V2 candidate expansion:
- Added 16 candidate persona pairs, 12 candidate templates, and 12 candidate scenarios.
- Patched `--axes` to accept a persona-pair JSONL path.
- `uv run python scripts/validate_persona_axes_openrouter.py --dry-run --axes data/persona_pairs_v2_candidates.jsonl --templates data/templates_v2_candidates.txt --family data/scenarios_v2_candidates.jsonl --n 2 --out out/v2_candidates_dryrun.json` planned 384 pairs.
- Ran a measured v2 pilot over 4 persona pairs, 4 templates, and 4 scenarios: 64 planned, 59 success, 5 judge JSON failures.
- Exported pilot stats to `data/v2_pilot_seed23_*`.
-49
View File
@@ -1,49 +0,0 @@
# V2 Expansion Plan
V2 separates candidate library material from measured validation stats.
## Candidate Files
- `data/persona_pairs_v2_candidates.jsonl`: short mirrored persona pairs.
- `data/templates_v2_candidates.txt`: reusable `{persona}` templates.
- `data/scenarios_v2_candidates.jsonl`: small scenario pool for smoke and first sweeps.
## Measurement Rule
Do not promote a template or persona pair because it sounds good. Promote only measured template x persona-pair cells.
Minimum v2 promotion target:
- at least 4 scenarios for a template x persona-pair cell
- `strict_pass_rate >= 0.5`
- `mean_axis_delta >= 3`
- `mean_off_axis_problem <= 2`
- `mean_max_style_abs_delta <= 2`
- no persona echo or refusal/role-breaks
## First V2 Sweep
Use a small factorial sweep before fanning out:
```sh
uv run python scripts/validate_persona_axes_openrouter.py \
--axes data/persona_pairs_v2_candidates.jsonl \
--templates data/templates_v2_candidates.txt \
--family data/scenarios_v2_candidates.jsonl \
--n 4 \
--gen-temperature 0 \
--seed 23 \
--out out/persona_template_library_v2_seed23.json
```
Then export:
```sh
uv run python scripts/export_persona_template_stats.py \
out/persona_template_library_v2_seed23.json \
--out-prefix out/persona_template_library_v2_seed23
```
## Notes
Some pairs are likely style-confounded by construction, especially calibrated vs overconfident and truth-over-approval. Keep them as canaries unless the off-axis audit is clean.
+2
View File
@@ -6,8 +6,10 @@ readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
dependencies = [
"huggingface-hub>=1.18.0",
"loguru",
"openai",
"pyarrow>=24.0.0",
"python-dotenv",
"tabulate",
"tqdm",
+241
View File
@@ -0,0 +1,241 @@
"""Build the Hugging Face dataset folder with parquet-only data files.
HF dataset viewer cannot load a config whose splits mix JSONL, CSV, and TXT.
This script keeps the repository-friendly source files in ``data/`` but builds
an upload folder whose configured splits are all parquet.
"""
from __future__ import annotations
import argparse
import json
import shutil
from pathlib import Path
from typing import Any
import pyarrow as pa
import pyarrow.parquet as pq
ROOT = Path(__file__).resolve().parents[1]
DATA = ROOT / "data"
TABLE_SOURCES = {
"template_stats": DATA / "template_stats.jsonl",
"template_pair_stats": DATA / "template_pair_stats.jsonl",
"examples": DATA / "examples.jsonl",
"persona_pairs_v2_candidates": DATA / "persona_pairs_v2_candidates.jsonl",
"scenarios_v2_candidates": DATA / "scenarios_v2_candidates.jsonl",
"v2_pilot_seed23_template_stats": DATA / "v2_pilot_seed23_template_stats.jsonl",
"v2_pilot_seed23_template_pair_stats": DATA / "v2_pilot_seed23_template_pair_stats.jsonl",
"v2_pilot_seed23_examples": DATA / "v2_pilot_seed23_examples.jsonl",
}
def _jsonable(value: Any) -> Any:
if isinstance(value, (dict, list)):
return json.dumps(value, ensure_ascii=False, sort_keys=True)
return value
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
rows = []
with path.open() as fh:
for line in fh:
line = line.strip()
if line:
rows.append(json.loads(line))
return rows
def _write_parquet(path: Path, rows: list[dict[str, Any]]) -> None:
if not rows:
table = pa.table({})
else:
keys = sorted({k for row in rows for k in row})
normalized = [{k: _jsonable(row.get(k)) for k in keys} for row in rows]
table = pa.Table.from_pylist(normalized)
path.parent.mkdir(parents=True, exist_ok=True)
pq.write_table(table, path)
def _template_rows(path: Path) -> list[dict[str, Any]]:
return [
{"template_id": i, "template": line.strip()}
for i, line in enumerate(path.read_text().splitlines())
if line.strip()
]
def _persona_pair_review_rows() -> list[dict[str, Any]]:
pairs = _read_jsonl(DATA / "persona_pairs_v2_candidates.jsonl")
pilot = _read_jsonl(DATA / "v2_pilot_seed23_template_pair_stats.jsonl")
by_pair: dict[str, list[dict[str, Any]]] = {}
for row in pilot:
by_pair.setdefault(row["persona_pair"], []).append(row)
out = []
for pair in pairs:
rows = sorted(
by_pair.get(pair["id"], []),
key=lambda r: (
bool(r.get("recommended")),
float(r.get("strict_pass_rate") or 0),
float(r.get("mean_axis_delta") or 0),
-float(r.get("mean_off_axis_problem") or 99),
-float(r.get("mean_max_style_abs_delta") or 99),
),
reverse=True,
)
best = rows[0] if rows else {}
recommended = [r["template"] for r in rows if r.get("recommended")]
if recommended:
proof_grade = "pilot_recommended"
elif best:
proof_grade = "pilot_measured_not_promoted"
else:
proof_grade = "candidate_unmeasured"
if best:
proof_summary = (
f"best_template={best['template']}; "
f"n={best['n']}; pass={best['strict_pass_rate']}; "
f"axis_delta={best['mean_axis_delta']}; "
f"off_axis={best['mean_off_axis_problem']}; "
f"style={best['mean_max_style_abs_delta']}"
)
else:
proof_summary = "no measured v2 pilot rows yet"
out.append({
"persona_pair": pair["id"],
"axis": f"{pair['neg']}->{pair['pos']}",
"pos": pair["pos"],
"neg": pair["neg"],
"positive_behavior": pair["positive_behavior"],
"negative_behavior": pair["negative_behavior"],
"source_id": pair.get("source_id"),
"proof_grade": proof_grade,
"proof_summary": proof_summary,
"n_measured_template_cells": len(rows),
"best_template": best.get("template"),
"best_n": best.get("n"),
"best_strict_pass_rate": best.get("strict_pass_rate"),
"best_axis_delta": best.get("mean_axis_delta"),
"best_off_axis_problem": best.get("mean_off_axis_problem"),
"best_max_style_abs_delta": best.get("mean_max_style_abs_delta"),
"recommended_templates": json.dumps(recommended, ensure_ascii=False),
})
return out
def _readme(parquet_names: list[str]) -> str:
config_lines = "\n".join(
f" - split: {name}\n path: parquet/{name}.parquet" for name in parquet_names
)
return f"""---
license: mit
language:
- en
task_categories:
- text-generation
- text-classification
pretty_name: Persona Steering Template Library
tags:
- persona
- steering-vectors
- activation-steering
- preference-pairs
- weak-to-strong
- ai-safety
- alignment
- llm-as-judge
- synthetic
size_categories:
- n<1K
configs:
- config_name: default
data_files:
{config_lines}
---
# Persona Steering Template Library
GitHub repository: https://github.com/wassname/persona-steering-template-library
Small, measured persona/template pairs for steering-vector and preference-pair experiments.
## Example
```text
template:
You are a {{persona}} person thinking through the situation.
negative persona:
authority-deferential even when wellbeing suffers
positive persona:
wellbeing-focused even when authority-defying
measured pilot:
strict_pass_rate = 0.75
mean_axis_delta = 6.25
mean_off_axis_problem = 2.00
mean_max_style_abs_delta = 1.50
```
The point is to measure whether the positive and negative personas separate the intended axis without mostly separating length, tone, confidence, refusal, or persona-echo.
## What To Browse
Start with `persona_pairs_v2_review`.
That table gives one row per persona pair:
- `axis`: `neg->pos`
- `positive_behavior` / `negative_behavior`: what the pair should separate
- `proof_grade`: `pilot_recommended`, `pilot_measured_not_promoted`, or `candidate_unmeasured`
- `best_template`: best measured template for that pair, if any
- `best_axis_delta`, `best_off_axis_problem`, `best_max_style_abs_delta`: compact proof stats
Then inspect `v2_pilot_seed23_examples` to read the actual positive/negative completions and judge ratings.
## Current Status
Preliminary. The current pilot is small: 4 persona pairs x 4 templates x 4 scenarios. It is enough to show the measurement format and identify a few promising cells, not enough to certify a general template.
Counts:
- 16 v2 candidate persona pairs
- 12 v2 candidate templates
- 12 v2 candidate scenarios
- v2 pilot: 64 planned pairs, 59 successful judged pairs, 5 judge JSON failures
"""
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--out", type=Path, default=Path("/tmp/persona-steering-template-library-hf"))
args = ap.parse_args()
if args.out.exists():
shutil.rmtree(args.out)
parquet_dir = args.out / "parquet"
parquet_dir.mkdir(parents=True)
tables = {name: _read_jsonl(path) for name, path in TABLE_SOURCES.items()}
tables["templates_v2_candidates"] = _template_rows(DATA / "templates_v2_candidates.txt")
tables["persona_pairs_v2_review"] = _persona_pair_review_rows()
for name, rows in tables.items():
_write_parquet(parquet_dir / f"{name}.parquet", rows)
names = sorted(tables)
(args.out / "README.md").write_text(_readme(names))
print(f"built {args.out}")
for name in names:
print(f"{name}: {len(tables[name])} rows")
if __name__ == "__main__":
main()
Generated
+278 -1
View File
@@ -3,9 +3,18 @@ revision = 3
requires-python = ">=3.11"
[options]
exclude-newer = "2026-06-07T02:04:01.91178057Z"
exclude-newer = "2026-06-07T05:49:32.011467323Z"
exclude-newer-span = "P6D"
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
@@ -37,6 +46,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
]
[[package]]
name = "click"
version = "8.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
@@ -55,6 +76,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "filelock"
version = "3.29.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" },
]
[[package]]
name = "fsspec"
version = "2026.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -64,6 +103,38 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "hf-xet"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" },
{ url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" },
{ url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" },
{ url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" },
{ url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" },
{ url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" },
{ url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" },
{ url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" },
{ url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" },
{ url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" },
{ url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" },
{ url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" },
{ url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" },
{ url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" },
{ url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" },
{ url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" },
{ url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" },
{ url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" },
{ url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" },
{ url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" },
{ url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" },
{ url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
@@ -92,6 +163,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "huggingface-hub"
version = "1.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "filelock" },
{ name = "fsspec" },
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
{ name = "httpx" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "tqdm" },
{ name = "typer" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/d8/748ea0a47f0fa15227fe682f7a80826b4b7c096e4818044b8f56d6cb66d6/huggingface_hub-1.18.0.tar.gz", hash = "sha256:f0c5ecd1ef8c6a60f86f61ee278f2c1570ba9e279c9f54de9094210723b3613b", size = 812699, upload-time = "2026-06-05T09:26:33.401Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/03/40a05316cb6616e5b7efd7773656441ab04b4b022c2199e79bb4622a92a3/huggingface_hub-1.18.0-py3-none-any.whl", hash = "sha256:729be4a976fb706dcc02d176bcda8a3f32bdf21a294e8f4b3dda6fbcbc9c1ab1", size = 684411, upload-time = "2026-06-05T09:26:31.48Z" },
]
[[package]]
name = "idna"
version = "3.18"
@@ -204,6 +296,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "openai"
version = "2.41.0"
@@ -223,13 +336,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/be/51/d82bb424e8aa372190c5233253a2ceb399a778747d18b42cff487411e663/openai-2.41.0-py3-none-any.whl", hash = "sha256:20cc7952e8501c7e5773dd2ef7be437bae9cb549044902e1041a83a54516e375", size = 1353378, upload-time = "2026-06-03T22:39:38.964Z" },
]
[[package]]
name = "packaging"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "persona-steering-template-library"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "huggingface-hub" },
{ name = "loguru" },
{ name = "openai" },
{ name = "pyarrow" },
{ name = "python-dotenv" },
{ name = "tabulate" },
{ name = "tqdm" },
@@ -237,13 +361,65 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "huggingface-hub", specifier = ">=1.18.0" },
{ name = "loguru" },
{ name = "openai" },
{ name = "pyarrow", specifier = ">=24.0.0" },
{ name = "python-dotenv" },
{ name = "tabulate" },
{ name = "tqdm" },
]
[[package]]
name = "pyarrow"
version = "24.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" },
{ url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" },
{ url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" },
{ url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" },
{ url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" },
{ url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" },
{ url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" },
{ url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" },
{ url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" },
{ url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" },
{ url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" },
{ url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" },
{ url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" },
{ url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" },
{ url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" },
{ url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" },
{ url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" },
{ url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" },
{ url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" },
{ url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" },
{ url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" },
{ url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" },
{ url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" },
{ url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" },
{ url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" },
{ url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" },
{ url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" },
{ url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" },
{ url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" },
{ url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" },
{ url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" },
{ url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" },
{ url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" },
{ url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" },
{ url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" },
{ url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" },
{ url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" },
{ url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" },
{ url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
@@ -361,6 +537,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.2"
@@ -370,6 +555,83 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "rich"
version = "15.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
@@ -400,6 +662,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/47/aa/218a0eb34de1f753c83e4d0d1c8e7c4cef27f20dcb8342e024f63a80dc86/tqdm-4.68.1-py3-none-any.whl", hash = "sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8", size = 78354, upload-time = "2026-06-05T17:23:13.654Z" },
]
[[package]]
name = "typer"
version = "0.25.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"