Skip to main content

skilled-proposer

A custom instruction proposer for GEPA, the reflective prompt optimizer in DSPy. Drop it into dspy.GEPA(instruction_proposer=...) to get instructions that generalize instead of memorizing your training set, informed by reference skills you provide.

Why

GEPA improves a program by asking a reflection model to rewrite each component's instruction based on execution traces and evaluator feedback. The stock proposer tells the reflection model to include "niche and domain specific factual information" from those traces in the new instruction. This helps with some tasks, but it can copy entities, numbers, and answers from your training examples into the prompt, and the prompt might perform worse on inputs it has never seen.

SkilledProposer uses a different meta-prompt. It gives the reflection model a three-step procedure. First, infer the task from the examples, because the assistant will only ever see the instruction. Second, diagnose why each failure happened and find the general rule that would have prevented it. Third, write the replacement instruction from those rules. The prompt then states one principle against overfitting. The proposer also adds three practical controls:

  • Skills. Pass SKILL.md files, skill directories, or inline strings. The reflection model gets them as reference material, e.g., a prompting guide for your student model.
  • Extra guidance. A plain string applied to every proposal.
  • Length budgets. Cap the proposed instruction by words or tokens. The cap is enforced by a prompt constraint, then a compression pass, then truncation.

Install

pip install skilled-proposer

Requires Python 3.10 or newer and dspy 3.0 or newer.

Quickstart

import dspy
from skilled_proposer import SkilledProposer

proposer = SkilledProposer(
    skills=[
        "./skills/prompt-engineering",                   # reads SKILL.md
        "./skills/prompt-engineering/models/openai.md",  # guidance for the student model
    ],
    additional_instructions="Write instructions in imperative voice.",
    max_words=300,
)

optimizer = dspy.GEPA(
    metric=metric,
    reflection_lm=dspy.LM("openai/gpt-5", temperature=1.0, max_tokens=32000),
    instruction_proposer=proposer,
    auto="medium",
)

optimized = optimizer.compile(program, trainset=train, valset=val)

Skills

A skill is reference material for the reflection model. Each entry in skills can be:

  • a path to a directory that contains a SKILL.md (the Agent Skills layout)
  • a path to a markdown file
  • an inline string
  • a Skill(name=..., content=..., description=...) object

If the file starts with YAML frontmatter, name and description are read from it and the block is stripped from the content. This repo ships an example at skills/prompt-engineering, a prompt optimization guide the reflection model can apply when rewriting instructions.

Skills with subfolders

Loading a skill directory reads only its SKILL.md. Files in subfolders such as models/ or references/ are not loaded. This is deliberate. An agent browsing a skill can open those files when it needs them, but GEPA calls the proposer in a plain LM call with no filesystem, many times per run. What the reflection model should see is also known before the run starts, e.g., you know which student model you are optimizing. So you, the developer, pick the extra files and pass them alongside the parent skill:

proposer = SkilledProposer(
    skills=[
        "./skills/prompt-engineering",                    # reads SKILL.md
        "./skills/prompt-engineering/models/openai.md",   # guidance for the student model
    ],
)

Each entry becomes its own <skill> block in the reflection prompt. Pass only the files that apply to your run. Inlining a whole skill folder would grow every proposal call for no benefit.

Options

SkilledProposer(
    skills=None,                   # skills, paths, or inline strings
    additional_instructions=None,  # guidance applied to every proposal
    base_instructions=None,        # replace the built-in meta-prompt
    max_words=None,                # word cap on proposed instructions
    max_tokens=None,               # token cap on proposed instructions
    prompt_model=None,             # (standalone GEPA only)
    max_examples=None,             # cap reflective examples per component
    on_error="keep",               # "keep" or "raise"
)
  • base_instructions replaces the whole meta-prompt, including the anti-overfitting rules. If you still want those rules, include equivalent text in your replacement.
  • on_error="keep" logs a failed proposal and keeps the current instruction, so a long GEPA run survives a flaky call. Use on_error="raise" during development so failures surface. Either way, LM/provider errors (LMError) always propagate, so a dead API key fails the run instead of silently keeping unchanged text for the whole run.
  • max_tokens counts tokens with litellm's tokenizer when it can resolve your model name, and falls back to about 4 characters per token.

Using the standalone gepa package

dspy.GEPA runs the proposer inside the reflection model's context, so you do not pass a model. The standalone gepa package does not set a DSPy context, so pass the model yourself:

proposer = SkilledProposer(
    skills=[
        "./skills/prompt-engineering",
        "./skills/prompt-engineering/models/openai.md",
    ],
    prompt_model=dspy.LM("openai/gpt-5", temperature=1.0, max_tokens=32000),
)

Then pass proposer wherever gepa accepts a ProposalFn.

Using with Flex

dspy 3.3 added dspy.Flex, a module that holds its whole implementation as Python source, which GEPA rewrites during optimization. GEPA sends Flex components to a built-in code proposer, and a custom instruction_proposer never sees them. The built-in prompt does not warn the reflection model against memorizing the training set, and with code the risk is worse than with instructions. The model can write a branch that matches one training input and returns its answer.

SkilledCodeProposer applies this package's approach to Flex source. The reflection model gets the same three step procedure, a rule against overfitting written for code, your reference skills, and your extra guidance. Every proposal is checked before it is used. It must parse and define a class with a forward method, or the current source is kept.

dspy has no code_proposer hook yet, so this package patches the built-in proposer for the duration of a compile call:

import dspy
from skilled_proposer import SkilledCodeProposer, SkilledProposer, use_code_proposer

optimizer = dspy.GEPA(
    metric=metric,
    reflection_lm=dspy.LM("openai/gpt-5", temperature=1.0, max_tokens=32000),
    instruction_proposer=SkilledProposer(skills=["./skills/prompt-engineering"]),
    auto="medium",
)

code_proposer = SkilledCodeProposer(
    skills=["./skills/prompt-engineering"],
    additional_instructions="Prefer few predictors and plain Python.",
)

with use_code_proposer(code_proposer):
    optimized = optimizer.compile(program, trainset=train, valset=val)

Notes:

  • This feature requires dspy 3.3 or newer. The rest of the package still works with dspy 3.0.
  • dspy logs a warning that a custom instruction_proposer skips code components. Under the patch the warning is expected and harmless, because the patched proposer handles them.
  • The patch is a bridge. We are proposing a code_proposer parameter for dspy.GEPA; once it lands, pass SkilledCodeProposer there and drop the patch.
  • SkilledCodeProposer takes skills, additional_instructions, base_instructions, prompt_model, max_examples, and on_error, with the same meanings as SkilledProposer. There is no length budget for code.

Limits

  • v0.1 is text only. Rich values such as dspy.Image are stringified in the reflective examples, so the reflection model cannot see them. Multimodal support is planned for v0.2.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

skilled_proposer-0.1.2.tar.gz (415.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

skilled_proposer-0.1.2-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file skilled_proposer-0.1.2.tar.gz.

File metadata

  • Download URL: skilled_proposer-0.1.2.tar.gz
  • Upload date:
  • Size: 415.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.11

File hashes

Hashes for skilled_proposer-0.1.2.tar.gz
Algorithm Hash digest
SHA256 1a6bc086daf649d0bee07338716061401ebd1be13ef6e943e42933673b6ead31
MD5 1f01417d4404bfb86904afed644bca87
BLAKE2b-256 c9c6c761572a2867d9d87b7e8e3f793b977d19b414cece3394d90b7c798a1dba

See more details on using hashes here.

File details

Details for the file skilled_proposer-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for skilled_proposer-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 271f704928afa15f7ff60d44ccd2e0f2260b829d3202abd7978042e65c5e9fc4
MD5 5a4cd130f5d1b983238b936f3ef165e1
BLAKE2b-256 6255331d91d7c335770fcfe9a265bbba7419ada98bab03f28442a66f2ba6e6a3

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page