Skip to content

Commit ea985ba

Browse files
committed
Decouple PrepAgent prompts and isolate prepagent output namespace
1 parent 4f6286d commit ea985ba

23 files changed

Lines changed: 755 additions & 37 deletions

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ Integration contract:
102102
Reference implementation:
103103
- `examples/prep_agent/run_prepagent.py`
104104
- `examples/prep_agent/README.md`
105+
- `examples/prep_agent/prompts/`
106+
107+
PrepAgent output root is isolated from benchmark run modes:
108+
- `@output/<model_info>/prepagent/case_xxx/solution/...`
105109

106110
## Reproduce Paper Experiments (Secondary Path)
107111

agents/clarify_agent.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,23 @@ class InteractAction:
2020

2121

2222
class ClarifyAgent:
23-
def __init__(self, model_name: str, data_head: Optional[DataHead] = None) -> None:
23+
def __init__(
24+
self,
25+
model_name: str,
26+
data_head: Optional[DataHead] = None,
27+
*,
28+
prompt_dir: Optional[Path] = None,
29+
template_dir: Optional[Path] = None,
30+
prompt_name: str = "clarify_agent",
31+
template_name: str = "clarify_agent.jinja2",
32+
) -> None:
2433
self.model_name = model_name
2534
self.data_head = data_head or DataHead()
26-
template_dir = Path(__file__).parent / "prompts" / "templates"
27-
self.jinja_env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True)
28-
self.template = self.jinja_env.get_template("clarify_agent.jinja2")
35+
self.prompt_dir = prompt_dir
36+
self.prompt_name = prompt_name
37+
tmpl_dir = template_dir or (Path(__file__).parent / "prompts" / "templates")
38+
self.jinja_env = Environment(loader=FileSystemLoader(tmpl_dir), trim_blocks=True, lstrip_blocks=True)
39+
self.template = self.jinja_env.get_template(template_name)
2940

3041
def _collect_context(
3142
self,
@@ -57,7 +68,11 @@ def _collect_context(
5768
}
5869

5970
def _build_prompt(self, ctx: Dict[str, Any]) -> str:
60-
cfg = load_prompt_yaml("clarify_agent", required_keys=("system", "guidelines"))
71+
cfg = load_prompt_yaml(
72+
self.prompt_name,
73+
required_keys=("system", "guidelines"),
74+
prompt_dir=self.prompt_dir,
75+
)
6176
return self.template.render(
6277
system_prompt_text=cfg["system"],
6378
guidelines_text=cfg["guidelines"],

agents/code_agent.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@
1616

1717

1818
class CodeAgent:
19-
def __init__(self, model_name: str, data_head: Optional[DataHead] = None):
19+
def __init__(
20+
self,
21+
model_name: str,
22+
data_head: Optional[DataHead] = None,
23+
*,
24+
prompt_dir: Optional[Path] = None,
25+
template_dir: Optional[Path] = None,
26+
prompt_name: str = "code_agent",
27+
template_name: str = "code_agent.jinja2",
28+
):
2029
self.model_name = model_name
2130
self.data_head = data_head or DataHead()
31+
self.prompt_dir = prompt_dir
32+
self.prompt_name = prompt_name
2233
# Setup Jinja2 environment
23-
template_dir = Path(__file__).parent / "prompts" / "templates"
24-
self.jinja_env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True)
34+
tmpl_dir = template_dir or (Path(__file__).parent / "prompts" / "templates")
35+
self.jinja_env = Environment(loader=FileSystemLoader(tmpl_dir), trim_blocks=True, lstrip_blocks=True)
36+
self.template_name = template_name
2537

2638
def _collect_context(self, session_state: Dict[str, Any]) -> Dict:
2739
mode = str(session_state.get("run_mode", ""))
@@ -57,9 +69,12 @@ def _collect_context(self, session_state: Dict[str, Any]) -> Dict:
5769

5870
def _build_prompt(self, ctx: Dict, feedback: Optional[Dict[str, Any]] = None) -> str:
5971
"""Builds the prompt using the Jinja2 template."""
60-
prompt_name = "code_agent"
61-
cfg = load_prompt_yaml(prompt_name, required_keys=("system", "guidelines"))
62-
template = self.jinja_env.get_template("code_agent.jinja2")
72+
cfg = load_prompt_yaml(
73+
self.prompt_name,
74+
required_keys=("system", "guidelines"),
75+
prompt_dir=self.prompt_dir,
76+
)
77+
template = self.jinja_env.get_template(self.template_name)
6378

6479
# Render the template with all the context and feedback
6580
return template.render(

agents/flow_agent.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@
1717
class FlowAgent:
1818
"""Agent that generates flow.json DAG via LLM."""
1919

20-
def __init__(self, model_name: Optional[str] = None):
20+
def __init__(
21+
self,
22+
model_name: Optional[str] = None,
23+
*,
24+
prompt_dir: Optional[Path] = None,
25+
template_dir: Optional[Path] = None,
26+
prompt_name: str = "flow_agent",
27+
template_name: str = "flow_agent.jinja2",
28+
):
2129
self.model_name = model_name or get_model_name()
30+
self.prompt_dir = prompt_dir
31+
self.prompt_name = prompt_name
2232
# Setup Jinja2 environment
23-
template_dir = Path(__file__).parent / "prompts" / "templates"
33+
tmpl_dir = template_dir or (Path(__file__).parent / "prompts" / "templates")
2434
self.jinja_env = Environment(
25-
loader=FileSystemLoader(template_dir),
35+
loader=FileSystemLoader(tmpl_dir),
2636
trim_blocks=True,
2737
lstrip_blocks=True,
2838
)
29-
self.template = self.jinja_env.get_template("flow_agent.jinja2")
39+
self.template = self.jinja_env.get_template(template_name)
3040

3141
def _collect_context(self, session_state: Dict[str, Any]) -> Dict[str, Any]:
3242
"""Collect context for prompt rendering."""
@@ -44,8 +54,9 @@ def _build_prompt(
4454
) -> str:
4555
"""Builds the prompt using the Jinja2 template."""
4656
cfg = load_prompt_yaml(
47-
"flow_agent",
57+
self.prompt_name,
4858
required_keys=("system", "core_guidelines", "operator_definitions"),
59+
prompt_dir=self.prompt_dir,
4960
)
5061

5162
return self.template.render(

agents/profile_agent.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,31 @@ class ProfileAgent:
2020
3. summarize() - If round 2 executed, generate final summary
2121
"""
2222

23-
def __init__(self, model_name: str) -> None:
23+
def __init__(
24+
self,
25+
model_name: str,
26+
*,
27+
prompt_dir: Path | None = None,
28+
template_dir: Path | None = None,
29+
profile_prompt_name: str = "profile_agent",
30+
profile_template_name: str = "profile_agent.jinja2",
31+
decide_prompt_name: str = "profile_agent_decide",
32+
decide_template_name: str = "profile_agent_decide.jinja2",
33+
summarize_prompt_name: str = "profile_agent_summarize",
34+
summarize_template_name: str = "profile_agent_summarize.jinja2",
35+
) -> None:
2436
self.model_name = model_name
2537
self._llm = None
26-
template_dir = Path(__file__).parent / "prompts" / "templates"
38+
self.prompt_dir = prompt_dir
39+
tmpl_dir = template_dir or (Path(__file__).parent / "prompts" / "templates")
40+
self.profile_prompt_name = profile_prompt_name
41+
self.profile_template_name = profile_template_name
42+
self.decide_prompt_name = decide_prompt_name
43+
self.decide_template_name = decide_template_name
44+
self.summarize_prompt_name = summarize_prompt_name
45+
self.summarize_template_name = summarize_template_name
2746
self.jinja_env = Environment(
28-
loader=FileSystemLoader(template_dir),
47+
loader=FileSystemLoader(tmpl_dir),
2948
trim_blocks=True,
3049
lstrip_blocks=True,
3150
)
@@ -37,9 +56,13 @@ def _get_llm(self):
3756
raise RuntimeError("LLM configuration not found.")
3857
return self._llm
3958

40-
def _render_prompt(self, name: str, context: Dict[str, Any]) -> str:
41-
prompt_cfg = load_prompt_yaml(name, required_keys=("system", "guidelines"))
42-
template = self.jinja_env.get_template(f"{name}.jinja2")
59+
def _render_prompt(self, prompt_name: str, template_name: str, context: Dict[str, Any]) -> str:
60+
prompt_cfg = load_prompt_yaml(
61+
prompt_name,
62+
required_keys=("system", "guidelines"),
63+
prompt_dir=self.prompt_dir,
64+
)
65+
template = self.jinja_env.get_template(template_name)
4366
return template.render(
4467
system_prompt_text=prompt_cfg["system"],
4568
guidelines_text=prompt_cfg["guidelines"],
@@ -59,7 +82,8 @@ def generate_profile_code(
5982
(code, raw_response, messages)
6083
"""
6184
prompt_content = self._render_prompt(
62-
"profile_agent",
85+
self.profile_prompt_name,
86+
self.profile_template_name,
6387
{
6488
"query_md": session_state.get("query", ""),
6589
"task_dir": session_state.get("task_dir", ""),
@@ -102,7 +126,8 @@ def decide_or_summarize(
102126
stderr_section = f"- Stderr:\n```\n{stderr[:1000]}\n```"
103127

104128
prompt_content = self._render_prompt(
105-
"profile_agent_decide",
129+
self.decide_prompt_name,
130+
self.decide_template_name,
106131
{
107132
"query_md": session_state.get("query", ""),
108133
"task_dir": session_state.get("task_dir", ""),
@@ -206,7 +231,8 @@ def summarize(
206231
r2_stdout = (round2_result.get("stdout") or "")[:2000]
207232

208233
prompt_content = self._render_prompt(
209-
"profile_agent_summarize",
234+
self.summarize_prompt_name,
235+
self.summarize_template_name,
210236
{
211237
"query_md": session_state.get("query", ""),
212238
"round1_status": r1_status,

core/orchestration/code_phase.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ def run_code_phase(
6464
config: ExperimentConfig,
6565
session_state: Dict[str, Any],
6666
output_root: Path,
67+
code_agent: Optional[CodeAgent] = None,
6768
) -> Dict[str, Any]:
6869
"""Run code generation phase with retry on execution errors."""
6970
rounds_root = output_root / "rounds"
7071
rounds_root.mkdir(parents=True, exist_ok=True)
7172

72-
coder = CodeAgent(config.model_name)
73+
coder = code_agent or CodeAgent(config.model_name)
7374
executor = CodeExecutor()
7475

7576
prev_code: Optional[str] = None

core/orchestration/flow_phase.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def run_flow_impl(
1717
config: ExperimentConfig,
1818
output_root: Path,
1919
solution_text: str,
20+
flow_agent: Optional[FlowAgent] = None,
2021
) -> Dict[str, Any]:
2122
"""Shared flow execution logic for flow mode and e2e."""
2223
from py2flow.errors import FlowExecutionError, FlowValidationError
@@ -47,7 +48,7 @@ def run_flow_impl(
4748
}
4849

4950
max_rounds = getattr(cfg, "max_rounds_debug", 3)
50-
flow_agent = FlowAgent(model_name=cfg.model_name)
51+
flow_agent = flow_agent or FlowAgent(model_name=cfg.model_name)
5152

5253
feedback = None
5354
stopped_reason = "unknown"

core/orchestration/profile_phase.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,13 @@ def run_profile_phase(
7474
input_dir: Path,
7575
inputs: list[str],
7676
inputs_preview: Dict[str, Any],
77+
profile_agent: Optional[ProfileAgent] = None,
7778
) -> dict[str, Any]:
7879
"""Two-round profile flow with LLM decision."""
7980
profile_root = output_root / "profile"
8081
profile_root.mkdir(parents=True, exist_ok=True)
8182

82-
profile_agent = ProfileAgent(config.model_name)
83+
profile_agent = profile_agent or ProfileAgent(config.model_name)
8384
executor = CodeExecutor()
8485
input_files = {p.name: p for p in sorted(input_dir.glob("*.csv"))}
8586

docs/BYOA_E2E.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,19 @@ PrepBench provides a reference `PrepAgent` pipeline for E2E:
3131
./scripts/run_prepagent.sh --case 1 --model openai/gpt-5.2
3232
```
3333

34+
PrepAgent writes to an isolated root:
35+
- `@output/<model_info>/prepagent/case_xxx/solution/...`
36+
37+
And can be evaluated with:
38+
39+
```bash
40+
python -m evaluate.batch --results-root @output/<model_info>/prepagent --candidate-kind auto
41+
```
42+
3443
Source:
3544
- `examples/prep_agent/run_prepagent.py`
3645
- `examples/prep_agent/README.md`
46+
- `examples/prep_agent/prompts/` (PrepAgent-owned prompts/templates)
3747

3848
## 1) Public Inputs Per Case
3949

examples/prep_agent/README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,42 @@
22

33
This is a reference implementation for participants who want to build and evaluate their own agent framework on PrepBench.
44

5-
Pipeline order is fixed:
5+
Pipeline order is fixed and self-contained in this folder:
66
1. profile
7-
2. clarify (via local user simulator API)
7+
2. clarify/interact (via local user simulator API)
88
3. code generation + execution
99
4. flow generation + execution (only when code outputs exist)
1010

11+
## Folder Structure
12+
13+
- `run_prepagent.py`: entrypoint and pipeline orchestration.
14+
- `prompts/*.yaml`: PrepAgent-owned prompt configs.
15+
- `prompts/templates/*.jinja2`: PrepAgent-owned prompt templates.
16+
- `prep_profile*`: profile stage
17+
- `prep_interact*`: interact stage
18+
- `prep_code*`: code stage
19+
- `prep_flow*`: flow stage
20+
21+
PrepAgent does not reuse the benchmark cache/reuse policy in `run.py`.
22+
Each case run starts from a clean output directory to keep behavior deterministic for external users.
23+
1124
## Run
1225

1326
```bash
1427
./scripts/run_prepagent.sh --case 1 --model openai/gpt-5.2
1528
```
1629

17-
Outputs follow the standard evaluation layout under `@output/<model_info>/e2e/case_xxx/solution/`:
30+
Outputs follow the standard evaluation layout under `@output/<model_info>/prepagent/case_xxx/solution/`:
1831
- `cand/*.csv` (code track)
1932
- `flow_cand/*.csv` (flow track)
2033

2134
## Evaluate
2235

2336
```bash
24-
python -m evaluate.batch --results-root @output/<model_info>/e2e --candidate-kind auto
37+
python -m evaluate.batch --results-root @output/<model_info>/prepagent --candidate-kind auto
2538
```
2639

2740
## Notes
2841

29-
- This reference implementation reuses prompts and agent modules under `agents/`.
42+
- This reference implementation reuses core executors/parsers, but uses its own prompt assets under `examples/prep_agent/prompts`.
3043
- User simulator and flow mode require reference solutions in `simulator/assets/solutions/case_xxx.py`.

0 commit comments

Comments
 (0)