Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design PCR primers for a target sequence using primer3-py. Specify target regions, product size, melting temperature, and other constraints. Returns ranked primer pairs with quality metrics. Use when designing standard PCR primers.
.claude/skills/bio-primer-design-primer-basics/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 82% | 0% |
<!--
#
#
-->
Design PCR primers using primer3-py, the Python binding for Primer3.
pythonimport primer3 from primer3 import p3helpers from Bio import SeqIO from Bio.Seq import Seq
python# Sanitize sequence (uppercase, remove whitespace) raw_seq = ' atgc gatc GATC ' clean_seq = p3helpers.sanitize_sequence(raw_seq) print(f'Cleaned: {clean_seq}') # 'ATGCGATCGATC' # Reverse complement for designing reverse primers seq = 'ATGCGATCGATC' rc_seq = p3helpers.reverse_complement(seq) print(f'Reverse complement: {rc_seq}') # 'GATCGATCGCAT' # Ensure valid DNA sequence (ACGT only, uppercase) valid_seq = p3helpers.ensure_acgt_uppercase('atgcNNgatc') # Raises error if invalid
pythonsequence = 'ATGCGTACGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG' result = primer3.design_primers( seq_args={'SEQUENCE_TEMPLATE': sequence}, global_args={ 'PRIMER_PRODUCT_SIZE_RANGE': [[100, 300]], 'PRIMER_MIN_TM': 57.0, 'PRIMER_OPT_TM': 60.0, 'PRIMER_MAX_TM': 63.0, 'PRIMER_MIN_GC': 40.0, 'PRIMER_MAX_GC': 60.0, } )
pythonnum_returned = result['PRIMER_PAIR_NUM_RETURNED'] print(f'Found {num_returned} primer pairs') for i in range(num_returned): left = result[f'PRIMER_LEFT_{i}_SEQUENCE'] right = result[f'PRIMER_RIGHT_{i}_SEQUENCE'] left_tm = result[f'PRIMER_LEFT_{i}_TM'] right_tm = result[f'PRIMER_RIGHT_{i}_TM'] product_size = result[f'PRIMER_PAIR_{i}_PRODUCT_SIZE'] print(f'Pair {i}: {left} / {right}') print(f' Tm: {left_tm:.1f}C / {right_tm:.1f}C, Product: {product_size}bp')
python# Target a specific region: [start, length] result = primer3.design_primers( seq_args={ 'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_TARGET': [100, 50], # Target region at position 100, length 50 }, global_args={ 'PRIMER_PRODUCT_SIZE_RANGE': [[150, 300]], 'PRIMER_OPT_TM': 60.0, } )
python# Primers must span this region (e.g., exon junction) result = primer3.design_primers( seq_args={ 'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_INCLUDED_REGION': [50, 200], # Primers within this region }, global_args={'PRIMER_PRODUCT_SIZE_RANGE': [[100, 250]]} )
python# Exclude regions (e.g., SNP positions, repeats) result = primer3.design_primers( seq_args={ 'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_EXCLUDED_REGION': [[150, 20], [300, 15]], # Regions to avoid }, global_args={'PRIMER_PRODUCT_SIZE_RANGE': [[100, 300]]} )
python# Force primer to overlap a specific position result = primer3.design_primers( seq_args={ 'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_FORCE_LEFT_START': 50, # Left primer must start here 'SEQUENCE_FORCE_RIGHT_START': 250, # Right primer must start here }, global_args={'PRIMER_PRODUCT_SIZE_RANGE': [[150, 250]]} )
python# Single primer for sequencing result = primer3.design_primers( seq_args={'SEQUENCE_TEMPLATE': sequence}, global_args={ 'PRIMER_PICK_LEFT_PRIMER': 1, 'PRIMER_PICK_RIGHT_PRIMER': 0, # Only design left primer 'PRIMER_PICK_INTERNAL_OLIGO': 0, 'PRIMER_OPT_SIZE': 20, 'PRIMER_MIN_SIZE': 18, 'PRIMER_MAX_SIZE': 25, } )
pythonresult = primer3.design_primers( seq_args={ 'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_TARGET': [200, 50], }, global_args={ 'PRIMER_PRODUCT_SIZE_RANGE': [[150, 300], [300, 500]], # Multiple ranges 'PRIMER_NUM_RETURN': 5, 'PRIMER_MIN_SIZE': 18, 'PRIMER_OPT_SIZE': 20, 'PRIMER_MAX_SIZE': 25, 'PRIMER_MIN_TM': 57.0, 'PRIMER_OPT_TM': 60.0, 'PRIMER_MAX_TM': 63.0, 'PRIMER_MIN_GC': 40.0, 'PRIMER_OPT_GC_PERCENT': 50.0, 'PRIMER_MAX_GC': 60.0, 'PRIMER_MAX_POLY_X': 4, # Max consecutive identical bases 'PRIMER_MAX_NS_ACCEPTED': 0, # No ambiguous bases 'PRIMER_MAX_SELF_ANY': 8, # Self-complementarity 'PRIMER_MAX_SELF_END': 3, # 3' self-complementarity 'PRIMER_PAIR_MAX_COMPL_ANY': 8, # Pair complementarity 'PRIMER_PAIR_MAX_COMPL_END': 3, # Pair 3' complementarity 'PRIMER_MAX_END_STABILITY': 9.0, # Max 3' end stability (delta G) } )
pythonfrom Bio import SeqIO record = SeqIO.read('gene.fasta', 'fasta') sequence = str(record.seq) result = primer3.design_primers( seq_args={'SEQUENCE_TEMPLATE': sequence, 'SEQUENCE_ID': record.id}, global_args={'PRIMER_PRODUCT_SIZE_RANGE': [[100, 300]], 'PRIMER_OPT_TM': 60.0} )
python# Calculate Tm for an existing primer tm = primer3.calc_tm('ATGCGATCGATCGATCGATC') print(f'Tm: {tm:.1f}C') # With custom salt/DNA concentrations tm = primer3.calc_tm('ATGCGATCGATCGATCGATC', mv_conc=50.0, dv_conc=1.5, dntp_conc=0.2, dna_conc=50.0)
| Parameter | Default | Description | |-----------|---------|-------------| | mv_conc | 50.0 mM | Monovalent cations (Na+, K+) | | dv_conc | 0.0 mM | Divalent cations (Mg2+) | | dntp_conc | 0.0 mM | dNTP concentration | | dna_conc | 50.0 nM | DNA oligo concentration |
python# Hairpin Tm hairpin = primer3.calc_hairpin('ATGCGATCGATCGATCGATC') print(f'Hairpin Tm: {hairpin.tm:.1f}C, dG: {hairpin.dg:.1f}') # Homodimer Tm homodimer = primer3.calc_homodimer('ATGCGATCGATCGATCGATC') print(f'Homodimer Tm: {homodimer.tm:.1f}C, dG: {homodimer.dg:.1f}') # Heterodimer Tm (between two different primers) heterodimer = primer3.calc_heterodimer('ATGCGATCGATCGATCGATC', 'GCTAGCTAGCTAGCTAGCTA') print(f'Heterodimer Tm: {heterodimer.tm:.1f}C, dG: {heterodimer.dg:.1f}')
pythonimport pandas as pd def primers_to_dataframe(result): rows = [] for i in range(result['PRIMER_PAIR_NUM_RETURNED']): rows.append({ 'pair': i, 'left_seq': result[f'PRIMER_LEFT_{i}_SEQUENCE'], 'right_seq': result[f'PRIMER_RIGHT_{i}_SEQUENCE'], 'left_tm': result[f'PRIMER_LEFT_{i}_TM'], 'right_tm': result[f'PRIMER_RIGHT_{i}_TM'], 'left_gc': result[f'PRIMER_LEFT_{i}_GC_PERCENT'], 'right_gc': result[f'PRIMER_RIGHT_{i}_GC_PERCENT'], 'product_size': result[f'PRIMER_PAIR_{i}_PRODUCT_SIZE'], 'penalty': result[f'PRIMER_PAIR_{i}_PENALTY'], }) return pd.DataFrame(rows) df = primers_to_dataframe(result) print(df)
| Parameter | Description | Default | |-----------|-------------|---------| | PRIMER_PRODUCT_SIZE_RANGE | Allowed product sizes | 100,300]] | | PRIMER_NUM_RETURN | Number of primer pairs | 5 | | PRIMER_MIN/OPT/MAX_SIZE | Primer length | 18/20/27 | | PRIMER_MIN/OPT/MAX_TM | Melting temperature | 57/60/63 | | PRIMER_MIN/MAX_GC | GC content percent | 20/80 | | PRIMER_MAX_POLY_X | Max poly-X run | 5 | | PRIMER_MAX_SELF_ANY | Self complementarity | 8 | | PRIMER_MAX_SELF_END | 3' self complementarity | 3 |
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 15,795 | 9,170 | -42% | 1 | 1 | 0% | 3,561 | 4,945 | +39% | 0 | 0 | — |
case-18 | pass→pass | 6,112 | 2,820 | -54% | 1 | 1 | 0% | 1,186 | 3,457 | +191% | 0 | 0 | — |
case-02 | fail→pass | 9,443 | 6,397 | -32% | 1 | 1 | 0% | 1,877 | 4,127 | +120% | 0 | 0 | — |
case-03 | fail→pass | 13,776 | 7,279 | -47% | 1 | 1 | 0% | 2,986 | 4,443 | +49% | 0 | 0 | — |
case-04 | pass→pass | 7,233 | 5,980 | -17% | 1 | 1 | 0% | 1,405 | 4,123 | +193% | 0 | 0 | — |
case-05 | pass→pass | 11,559 | 5,835 | -50% | 1 | 1 | 0% | 2,404 | 4,050 | +68% | 0 | 0 | — |
case-06 | fail→pass | 18,404 | 6,692 | -64% | 1 | 1 | 0% | 3,914 | 4,356 | +11% | 0 | 0 | — |
case-07 | fail→pass | 12,081 | 6,710 | -44% | 1 | 1 | 0% | 2,423 | 4,411 | +82% | 0 | 0 | — |
case-08 | pass→pass | 11,992 | 5,383 | -55% | 1 | 1 | 0% | 2,617 | 4,114 | +57% | 0 | 0 | — |
case-09 | pass→pass | 7,694 | 5,519 | -28% | 1 | 1 | 0% | 1,672 | 4,081 | +144% | 0 | 0 | — |
case-10 | fail→pass | 10,446 | 5,133 | -51% | 1 | 1 | 0% | 2,418 | 4,026 | +67% | 0 | 0 | — |
case-11 | fail→pass | 7,300 | 2,802 | -62% | 1 | 1 | 0% | 1,406 | 3,505 | +149% | 0 | 0 | — |
case-12 | pass→pass | 16,058 | 4,501 | -72% | 1 | 1 | 0% | 3,354 | 3,807 | +14% | 0 | 0 | — |
case-13 | pass→pass | 10,460 | 4,199 | -60% | 1 | 1 | 0% | 2,200 | 3,495 | +59% | 0 | 0 | — |
case-14 | fail→pass | 10,581 | 5,181 | -51% | 1 | 1 | 0% | 2,344 | 4,038 | +72% | 0 | 0 | — |
case-15 | pass→pass | 8,622 | 5,283 | -39% | 1 | 1 | 0% | 1,951 | 4,055 | +108% | 0 | 0 | — |
case-16 | pass→pass | 17,944 | 4,430 | -75% | 1 | 1 | 0% | 3,782 | 3,763 | -1% | 0 | 0 | — |
case-17 | pass→pass | 6,260 | 3,670 | -41% | 1 | 1 | 0% | 1,253 | 3,679 | +194% | 0 | 0 | — |
case-19 | pass→pass | 6,036 | 5,662 | -6% | 1 | 1 | 0% | 1,186 | 4,148 | +250% | 0 | 0 | — |
case-20 | pass→pass | 17,795 | 13,672 | -23% | 1 | 1 | 0% | 3,719 | 5,940 | +60% | 0 | 0 | — |
case-21 | pass→pass | 19,020 | 14,493 | -24% | 1 | 1 | 0% | 3,522 | 5,861 | +66% | 0 | 0 | — |
case-22 | pass→fail | 12,960 | 17,529 | +35% | 1 | 1 | 0% | 2,418 | 5,982 | +147% | 0 | 0 | — |
case-23 | pass→pass | 8,398 | 8,954 | +7% | 1 | 1 | 0% | 1,800 | 4,438 | +147% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +30 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/24/2026 | +32% |
Other measured skills in the registry, with their headline benchmark lift.