Skip to content

Commit 776ab9b

Browse files
committed
docs(gepa): rewrite Section 8 with accurate custom proposer behavior for ReAct
- Clarify custom proposer receives ALL components (regular + ReAct) - Add realistic signature with ReAct failure patterns and component types - Use exact naming from implementation: examples_with_feedback, component_reflective_data, propose_instruction - Show _format_examples() helper matching real markdown formatting - Remove regular component handling to keep example focused on ReAct - Test code example validates successfully - Fix contradiction: optimize_react_components must be True (not irrelevant) docs(gepa): clarify custom proposer behavior in routing section Change 'overrides the default routing' to 'receives all components and handles the optimization logic' to avoid confusion with optimize_react_components which still controls discovery/serialization docs(gepa): remove discouraging recommendation from custom proposer section Users reading this section want to learn how to implement custom proposers for ReAct - don't discourage them from doing so
1 parent e51158d commit 776ab9b

File tree

1 file changed

+154
-18
lines changed

1 file changed

+154
-18
lines changed

docs/docs/api/optimizers/GEPA/GEPA_Advanced.md

Lines changed: 154 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ When `optimize_react_components=True`, GEPA:
550550
1. **Discovers ReAct modules** - Finds all `dspy.ReAct` instances in your program (including nested modules)
551551
2. **Extracts components** - Collects react instructions, extract instructions, and tool schemas from each ReAct module
552552
3. **Routes to proposers** - Separates components by type and routes them appropriately:
553-
- **With custom `instruction_proposer`**: Your custom proposer overrides the default routing and receives all components (both regular instructions and ReAct components)
553+
- **With custom `instruction_proposer`**: Your custom proposer receives all components (both regular instructions and ReAct components) and handles the optimization logic
554554
- **With default proposer**: Regular instructions use default instruction proposer, ReAct components use specialized `ReActModuleProposer`
555555
4. **Optimizes jointly** - ReAct proposer improves all four components together based on execution feedback
556556
5. **Applies updates** - Updates your ReAct modules with improved instructions and tool descriptions
@@ -686,30 +686,166 @@ for tool_name, tool in optimized_agent.tools.items():
686686
print(f" Argument descriptions:", tool.arg_desc)
687687
```
688688

689-
### Compatibility with Custom Instruction Proposers
689+
### Custom Instruction Proposers and ReAct Optimization
690+
691+
**Important:** When you provide a custom `instruction_proposer`, it receives ALL components (regular predictors AND ReAct modules). You must set `optimize_react_components=True` to enable ReAct module discovery and serialization, then handle the optimization logic yourself.
692+
693+
**How it works internally:**
694+
695+
1. **Component Discovery** - GEPA discovers components in your program:
696+
- Regular predictors → keys like `"predict"`, `"chain_of_thought"`
697+
- ReAct modules → keys like `"react_module"` or `"react_module:agent_name"`
698+
699+
2. **ReAct Serialization** - When `optimize_react_components=True`, GEPA serializes ReAct modules as JSON:
700+
```json
701+
{
702+
"react": "instruction for reasoning and tool selection",
703+
"extract": "instruction for answer extraction",
704+
"tools": {
705+
"tool_name": {
706+
"desc": "what the tool does",
707+
"args": {"param": {"type": "string"}},
708+
"arg_desc": {"param": "description of param"}
709+
}
710+
}
711+
}
712+
```
713+
714+
3. **Custom Proposer Receives**:
715+
- `candidate: dict[str, str]` - **All values are strings**
716+
- Regular component: `candidate["predict"]``"Your instruction here"`
717+
- ReAct component: `candidate["react_module"]``'{"react": "...", "extract": "...", "tools": {...}}'` (JSON as a string)
718+
- `reflective_dataset: dict[str, list[ReflectiveExample]]` - **GEPA provides this**
719+
- Contains execution traces: inputs, outputs (including full ReAct trajectory), and your metric's feedback
720+
- For ReAct: `Generated_Outputs` includes the entire trajectory with all tool calls and reasoning
721+
- Use this to understand what went wrong and guide your improvements
722+
- `components_to_update: list[str]` - Component keys to optimize this round
723+
724+
4. **Your Responsibility**:
725+
- For ReAct components: Use `json.loads()` to parse, improve all 4 parts, use `json.dumps()` to return
726+
- For regular components: Improve the instruction string directly
727+
- Return `dict[str, str]` with same keys
728+
729+
**What this means:**
730+
- Your custom proposer receives ALL components: regular signatures AND ReAct modules
731+
- GEPA still does discovery and JSON serialization, but YOU handle the optimization logic
732+
- ReAct components are passed with keys like `"react_module"` or `"react_module:agent_name"`
733+
734+
#### Implementing a Custom Proposer for ReAct
735+
736+
If you need custom logic, you must handle ReAct components yourself. ReAct components are stored as JSON strings containing all 4 parts:
690737

691-
ReAct component optimization works seamlessly with custom instruction proposers. When you provide a custom instruction proposer AND enable `optimize_react_components=True`:
692-
693-
**Component routing:**
694-
- **Signature instructions** → Your custom instruction proposer
695-
- **Tool descriptions** → Built-in `ToolProposer` with specialized tool reflection prompt
738+
```python
739+
import json
696740

697-
**Key points:**
698-
- Both operate independently during the same GEPA run
699-
- Tools receive domain-appropriate optimization guidance (tool selection patterns, usage context)
700-
- Signatures use your custom logic (task-specific reasoning, formatting, etc.)
701-
- The built-in tool proposer is not customizable - it always uses `GenerateImprovedToolDescriptionFromFeedback`
741+
# Define signature for improving ReAct components
742+
class ImproveReActInstruction(dspy.Signature):
743+
"""Analyze agent execution failures and improve the instruction.
744+
745+
Focus on common ReAct failure patterns:
746+
- Tool selection errors (wrong tool chosen)
747+
- Missing tool calls (agent gave up without trying)
748+
- Incorrect tool arguments
749+
- Extraction failures (couldn't extract answer from trajectory)
750+
"""
751+
current_instruction = dspy.InputField(desc="The current instruction being optimized")
752+
component_type = dspy.InputField(desc="Type: 'react' (reasoning), 'extract' (extraction), or 'tool' (tool description)")
753+
examples_with_feedback = dspy.InputField(desc="Examples showing what went wrong: inputs, outputs, and feedback")
754+
improved_instruction = dspy.OutputField(desc="Improved instruction addressing the observed failures")
702755

703-
This separation ensures tools and signatures get appropriate optimization strategies without interference.
704756

705-
```python
706-
from dspy.teleprompt.gepa.instruction_proposal import MultiModalInstructionProposer
757+
class CustomProposer:
758+
def __call__(self, candidate, reflective_dataset, components_to_update):
759+
"""
760+
When you provide a custom proposer, it receives ALL components (regular + ReAct).
761+
762+
Args:
763+
candidate: dict[str, str] - All component instructions to update
764+
- Regular: "predict" -> "Your instruction..."
765+
- ReAct: "react_module" -> JSON string: {"react": "...", "extract": "...", "tools": {...}}
766+
reflective_dataset: dict[str, list[ReflectiveExample]]
767+
- Component name -> list of examples with Inputs, Generated_Outputs, Feedback
768+
components_to_update: list[str] - All components to update this round
769+
770+
Returns:
771+
dict[str, str] - Updated instructions for all components
772+
"""
773+
propose_instruction = dspy.Predict(ImproveReActInstruction)
774+
results = {}
775+
776+
for component in components_to_update:
777+
if not component.startswith("react_module"):
778+
continue # Skip non-ReAct components (handle them separately if needed)
779+
780+
# Parse the JSON config
781+
config = json.loads(candidate[component])
782+
# config contains: {"react": "...", "extract": "...", "tools": {...}}
783+
784+
component_reflective_data = reflective_dataset[component]
785+
786+
# Format examples (limit to first 3 for efficiency)
787+
formatted_examples = self._format_examples(component_reflective_data[:3])
788+
789+
# Improve react instruction (reasoning and tool selection)
790+
improved_react = propose_instruction(
791+
current_instruction=config["react"],
792+
component_type="react",
793+
examples_with_feedback=formatted_examples
794+
).improved_instruction
795+
796+
# Improve extract instruction (answer extraction from trajectory)
797+
improved_extract = config.get("extract", "")
798+
if improved_extract:
799+
improved_extract = propose_instruction(
800+
current_instruction=improved_extract,
801+
component_type="extract",
802+
examples_with_feedback=formatted_examples
803+
).improved_instruction
804+
805+
# Improve tool descriptions (what each tool does and when to use it)
806+
improved_tools = {}
807+
for tool_name, tool_info in config.get("tools", {}).items():
808+
improved_desc = propose_instruction(
809+
current_instruction=tool_info["desc"],
810+
component_type="tool",
811+
examples_with_feedback=formatted_examples
812+
).improved_instruction
813+
814+
improved_tools[tool_name] = {
815+
"desc": improved_desc,
816+
"args": tool_info["args"], # Keep args schema unchanged
817+
"arg_desc": tool_info.get("arg_desc", {}) # Can also improve these
818+
}
819+
820+
# Return as JSON string
821+
results[component] = json.dumps({
822+
"react": improved_react,
823+
"extract": improved_extract,
824+
"tools": improved_tools
825+
})
826+
827+
return results
828+
829+
def _format_examples(self, reflective_data: list) -> str:
830+
"""Format reflective examples into markdown for the LM."""
831+
formatted_parts = []
832+
for i, example in enumerate(reflective_data):
833+
s = f"# Example {i + 1}\n"
834+
for key, val in example.items():
835+
s += f"## {key}\n{str(val).strip()}\n\n"
836+
formatted_parts.append(s)
837+
return "\n\n".join(formatted_parts)
707838

708839
gepa = dspy.GEPA(
709840
metric=my_metric,
710-
reflection_lm=dspy.LM(model="gpt-5", temperature=1.0, max_tokens=32000, api_key=api_key),
711-
instruction_proposer=MultiModalInstructionProposer(), # For signatures
712-
optimize_react_components=True, # Enables ReActModuleProposer
841+
reflection_lm=dspy.LM(model="gpt-5", temperature=1.0, max_tokens=32000),
842+
instruction_proposer=CustomProposer(), # Receives ALL components (regular + ReAct)
843+
optimize_react_components=True, # Must be True to discover ReAct modules
713844
auto="medium"
714845
)
715846
```
847+
848+
**Key points:**
849+
- ReAct components are JSON strings - use `json.loads()` to parse, `json.dumps()` to return
850+
- 4 parts to improve: `react` instruction, `extract` instruction, tool `desc`, tool `arg_desc`
851+
- Tools structure: `{"tool_name": {"desc": "...", "args": {...}, "arg_desc": {...}}}`

0 commit comments

Comments
 (0)