Introduction
COPA (Compiler and Prompt Optimization) is a synergistic framework that alternates between optimizing model parameters (via demonstration selection/compilation) and optimizing prompt instructions. Instead of choosing either good examples or good instructions, COPA optimizes both in a coordinated loop.
The Cycle: COPA works by treating the optimization space as unified. It iterates between a "Compilation Phase" (finding better examples) and a "Prompt Phase" (finding better descriptions), transferring insights between the two.
Core Principles
- Unified Optimization Space: Prompts and demonstrations are optimized together.
- Iterative Refinement: Phase 1 (Compilation) informs Phase 2 (Content/Prompt), and vice versa.
- Knowledge Transfer: Insights from compilation (e.g., "these examples work best") help the prompt optimizer (e.g., "therefore, we should emphasize this rule").
💻 Implementation Strategy
Implementing COPA involves creating a custom optimizer class that orchestrates the two phases.
class COPAOptimizer:
def optimize(self, program, trainset, valset):
# Iterative Loop
for i in range(self.max_iterations):
# Phase 1: Compilation (Demonstrations)
# Uses BootstrapFewShot to find best examples
if i % 2 == 0:
print("Phase 1: Compilation")
program = self.compilation_phase(program, trainset)
# Phase 2: Prompt Optimization (Instructions)
# Uses MIPRO to refine instructions based on current examples
else:
print("Phase 2: Prompt Optimization")
program = self.prompt_phase(program, trainset)
# Check for convergence...
return program
✨ Best Practices
- Start with Compilation: It's usually better to anchor the model with good examples first before refining the instructions.
- Balanced Iterations: 2-3 full cycles (4-6 total phases) are usually sufficient for convergence.
- Use Constraints: Applying constraints (as learned in the previous section) helps stabilize the alternating optimization steps.