refactor: rename teacher/student to optimizer/target, remove best skills, fix slow update

- Rename teacher -> optimizer, student -> target across all code, configs, docs, prompts
- CLI: --teacher_model -> --optimizer_model, --student_model -> --target_model
- Remove best_skill files, keep only initial skills
- Fix slow update gate (force write into skill)
- Fix SLOW_UPDATE marker stripping
- Remove deep_reflect and meta_reflect mechanisms
- Update .env.example with export prefix and azure_cli docs
- Add endpoint empty validation in azure_openai.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Cuzyoung
2026-05-24 19:15:03 +00:00
parent 6e165d5347
commit 4a1b984d87
70 changed files with 1083 additions and 2068 deletions
+2 -125
View File
@@ -4,13 +4,12 @@ from __future__ import annotations
import json
import os
from skillopt.gradient.deep_probe import generate_deep_probe_instruction
from skillopt.datasets.base import BatchSpec
from skillopt.envs.base import EnvAdapter
from skillopt.envs.searchqa.dataloader import SearchQADataLoader
from skillopt.envs.searchqa.rollout import run_batch
from skillopt.gradient.reflect import run_minibatch_reflect
from skillopt.model import get_student_backend
from skillopt.model import get_target_backend
class SearchQAAdapter(EnvAdapter):
@@ -32,11 +31,7 @@ class SearchQAAdapter(EnvAdapter):
minibatch_size: int = 8,
edit_budget: int = 4,
seed: int = 42,
limit: int = 0,
use_deep_reflect: bool = False,
deep_reflect_failures: int = 4,
deep_reflect_successes: int = 2,
) -> None:
limit: int = 0, ) -> None:
self.max_turns = max_turns
self.exec_timeout = exec_timeout
self.workers = workers
@@ -44,9 +39,6 @@ class SearchQAAdapter(EnvAdapter):
self.failure_only = failure_only
self.minibatch_size = minibatch_size
self.edit_budget = edit_budget
self.use_deep_reflect = use_deep_reflect
self.deep_reflect_failures = deep_reflect_failures
self.deep_reflect_successes = deep_reflect_successes
self.dataloader = SearchQADataLoader(
split_dir=split_dir,
data_path=data_path,
@@ -128,121 +120,6 @@ class SearchQAAdapter(EnvAdapter):
update_mode=getattr(self, "_cfg", {}).get("skill_update_mode", "patch"),
)
def deep_reflect(
self,
results: list[dict],
skill_content: str,
out_dir: str,
**kwargs,
) -> list[dict | None]:
if not self.use_deep_reflect:
return []
env_manager = kwargs.get("env_manager")
if not isinstance(env_manager, list):
return []
prediction_dir = kwargs.get("prediction_dir", os.path.join(out_dir, "predictions"))
random_seed = kwargs.get("random_seed")
step_buffer_context = kwargs.get("step_buffer_context", "")
meta_skill_context = kwargs.get("meta_skill_context", "")
codex_backend = get_student_backend() == "codex_exec"
selected_items = self.select_representative_items(
results,
env_manager,
n_failures=self.deep_reflect_failures,
n_successes=self.deep_reflect_successes,
seed=random_seed,
)
if not selected_items:
return []
selected_ids = {str(item["id"]) for item in selected_items}
selected_results = [row for row in results if str(row.get("id")) in selected_ids]
selected_examples = (
self.attach_codex_probe_context(selected_results, prediction_dir)
if codex_backend
else selected_results
)
selected_metadata = [
{
"id": str(item["id"]),
"question_preview": str(item.get("question") or "")[:200],
"has_context": bool(str(item.get("context") or "").strip()),
"n_gold_answers": len(item.get("answers") or []),
}
for item in selected_items
]
deep_dir = os.path.join(out_dir, "deep_reflect")
rollout_dir = os.path.join(deep_dir, "rollout")
patches_dir = os.path.join(deep_dir, "patches")
os.makedirs(deep_dir, exist_ok=True)
print(
f" [2b/6 DEEP REFLECT setup] selected={len(selected_items)} "
f"mode=no_reference_probe"
)
probe = generate_deep_probe_instruction(
skill_content=skill_content,
items=selected_examples,
prediction_dir=prediction_dir,
system_prompt=self.get_codex_deep_probe_prompt() if codex_backend else self.get_deep_probe_prompt(),
step_buffer_context=step_buffer_context,
meta_skill_context=meta_skill_context,
output_requirements=[
"- There is no hidden reference block. Use only the question, provided context, the student's output, and the evaluation result to infer what intermediate state is worth probing.",
"- The instruction must explicitly request a short <analysis>...</analysis> block before the final <answer>...</answer>.",
"- The readout should focus on likely evidence span, top candidate and runner-up, decisive clue, or a few short intermediate conclusions.",
"- Do not ask for exhaustive copying of the context or a full chain-of-thought.",
"- The instruction text should be ready to append directly to the student's prompt.",
],
)
if not probe:
return []
diagnostic_trace_context_by_id = None
if codex_backend:
selected_items, diagnostic_trace_context_by_id, probe = self.resolve_codex_probe_target(
selected_items=selected_items,
selected_examples=selected_examples,
prediction_dir=prediction_dir,
probe=probe,
)
with open(os.path.join(deep_dir, "probe.json"), "w", encoding="utf-8") as f:
json.dump(
{
**probe,
"selected_examples": selected_metadata,
},
f,
ensure_ascii=False,
indent=2,
)
deep_results = self.rollout(
selected_items,
skill_content,
rollout_dir,
diagnostic_mode=True,
diagnostic_instruction=probe["probe_instruction"],
diagnostic_trace_context_by_id=diagnostic_trace_context_by_id,
)
return run_minibatch_reflect(
results=deep_results,
skill_content=skill_content,
prediction_dir=os.path.join(rollout_dir, "predictions"),
patches_dir=patches_dir,
workers=self.analyst_workers,
failure_only=self.failure_only,
minibatch_size=self.minibatch_size,
edit_budget=self.edit_budget,
random_seed=random_seed,
error_system=self.get_error_minibatch_prompt(),
success_system=self.get_success_minibatch_prompt(),
step_buffer_context=step_buffer_context,
meta_skill_context=meta_skill_context,
update_mode=getattr(self, "_cfg", {}).get("skill_update_mode", "patch"),
)
def get_task_types(self) -> list[str]:
return ["qa"]
+12 -12
View File
@@ -16,8 +16,8 @@ import time
import traceback
from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
from skillopt.model import chat_student, get_student_backend, is_student_exec_backend
from skillopt.model.codex_harness import prepare_workspace, render_skill_md, run_student_exec
from skillopt.model import chat_target, get_target_backend, is_target_exec_backend
from skillopt.model.codex_harness import prepare_workspace, render_skill_md, run_target_exec
from skillopt.prompts import load_prompt
from skillopt.envs.searchqa.evaluator import evaluate
@@ -123,11 +123,11 @@ def _run_codex_once(
task_text=task_text,
)
prompt = (
"Use the `skillopt-student` skill available in this workspace.\n"
"Use the `skillopt-target` skill available in this workspace.\n"
"Read `task.md` and answer the SearchQA question.\n"
"Return the final answer inside <answer>...</answer>."
)
final_message, raw = run_student_exec(
final_message, raw = run_target_exec(
work_dir=work_dir,
prompt=prompt,
model=model,
@@ -192,7 +192,7 @@ def process_one(
pred_dir = os.path.join(out_root, "predictions", item_id)
os.makedirs(pred_dir, exist_ok=True)
if is_student_exec_backend():
if is_target_exec_backend():
from skillopt.model import azure_openai as _llm
conversation: list[dict] = []
@@ -205,7 +205,7 @@ def process_one(
skill_content=skill_content,
question=question,
context=context,
model=_llm.STUDENT_DEPLOYMENT,
model=_llm.TARGET_DEPLOYMENT,
timeout=exec_timeout,
diagnostic_mode=diagnostic_mode if turn == 0 else False,
diagnostic_instruction=diagnostic_instruction if turn == 0 else "",
@@ -220,9 +220,9 @@ def process_one(
result["agent_ok"] = True
result["n_turns"] = len(conversation)
with open(os.path.join(pred_dir, "student_system_prompt.txt"), "w") as f:
with open(os.path.join(pred_dir, "target_system_prompt.txt"), "w") as f:
f.write(system)
with open(os.path.join(pred_dir, "student_user_prompt.txt"), "w") as f:
with open(os.path.join(pred_dir, "target_user_prompt.txt"), "w") as f:
f.write(user)
with open(os.path.join(pred_dir, "conversation.json"), "w") as f:
json.dump(conversation, f, ensure_ascii=False, indent=2)
@@ -266,7 +266,7 @@ def process_one(
for turn in range(max_turns):
if turn == 0:
resp_text, _ = chat_student(
resp_text, _ = chat_target(
system=system, user=user,
max_completion_tokens=512,
retries=5, stage="rollout",
@@ -279,7 +279,7 @@ def process_one(
f"If correct, repeat it. If wrong, provide a corrected answer.\n"
f"Use <answer>...</answer> tags for your final answer."
)
resp_text, _ = chat_student(
resp_text, _ = chat_target(
system=system, user=refinement,
max_completion_tokens=512,
retries=5, stage="rollout",
@@ -297,9 +297,9 @@ def process_one(
result["n_turns"] = len(conversation)
# Save conversation
with open(os.path.join(pred_dir, "student_system_prompt.txt"), "w") as f:
with open(os.path.join(pred_dir, "target_system_prompt.txt"), "w") as f:
f.write(system)
with open(os.path.join(pred_dir, "student_user_prompt.txt"), "w") as f:
with open(os.path.join(pred_dir, "target_user_prompt.txt"), "w") as f:
f.write(user)
with open(os.path.join(pred_dir, "conversation.json"), "w") as f:
json.dump(conversation, f, ensure_ascii=False, indent=2)