2e23a25ff9
* feat(claude_code_exec): add claude code optimizer backend with SDK trace support Register claude_code_exec as a full optimizer/target backend (issue #233). --backend claude_code_exec now defaults both roles to claude_code_exec so reflection sees the agent's complete session, and the SDK message stream is parsed into structured trace steps persisted as claude_trace_steps.txt and injected into the analyst prompt. - model/claude_code_backend.py (new): chat_optimizer/chat_optimizer_messages on run_claude_code_chat, reasoning_effort threaded through, retry loop that surfaces non-JSON structured replies as RuntimeError, token tracking. - model/codex_harness.py: parse/format/persist claude trace steps (text, tool_call, tool_result; drops init/thinking_tokens; 200-char tool_result cap; total truncation) + effort override on run_claude_code_chat. - trainer.py/reflect.py: inject Claude Trace Steps gated behind REFLACT_CLAUDE_TRACE_TO_OPTIMIZER, set by the trainer only for claude_code_exec targets with model.claude_trace_to_optimizer (mirrors codex gate; default true). - config.py/default.yaml/docs: model.claude_trace_to_optimizer key + flatten mapping + config.md rows. - backend_config.py + model/__init__.py: register backend, route chat dispatch, token summary, reasoning effort, deployments. - scripts/train.py, eval_only.py: symmetric default + accurate comments. - tests: tests/test_claude_code_backend.py (10 tests: parsing, dispatch, effort, retry, trainer/reflect gating); test_role_backend_resolution.py updated to the symmetric default. Verified: 58 unit tests pass; integration smoke on searchqa improved best-on-val 0.7500 -> 0.9375 with 80 claude_trace_steps.txt written; all output files valid UTF-8 (no GBK mojibake). * fix(claude_code_exec): address #233 review feedback
142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
"""Config-resolution regressions for the claude_code_exec backend (issue #233).
|
|
|
|
Route B: ``--backend claude_code_exec`` defaults only the *target* to Claude
|
|
Code; the optimizer keeps its configured backend (openai_chat by default).
|
|
Opting the optimizer in via ``--optimizer_backend claude_code_exec`` must then
|
|
normalize its model to the Claude default rather than leaving ``gpt-5.5``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
import scripts.train as train_script
|
|
|
|
_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _train_cfg(monkeypatch, *extra_argv: str) -> dict:
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"skillopt-train",
|
|
"--config",
|
|
str(_ROOT / "configs" / "searchqa" / "default.yaml"),
|
|
*extra_argv,
|
|
],
|
|
)
|
|
return train_script.load_config(train_script.parse_args())
|
|
|
|
|
|
def test_train_claude_code_exec_defaults_target_only(monkeypatch) -> None:
|
|
cfg = _train_cfg(monkeypatch, "--backend", "claude_code_exec")
|
|
|
|
assert cfg["optimizer_backend"] == "openai_chat"
|
|
assert cfg["optimizer_model"] == "gpt-5.5"
|
|
assert cfg["target_backend"] == "claude_code_exec"
|
|
assert cfg["target_model"] == "claude-sonnet-4-6"
|
|
|
|
|
|
def test_train_claude_code_exec_optimizer_opt_in_normalizes_model(monkeypatch) -> None:
|
|
cfg = _train_cfg(
|
|
monkeypatch,
|
|
"--backend",
|
|
"claude_code_exec",
|
|
"--optimizer_backend",
|
|
"claude_code_exec",
|
|
)
|
|
|
|
assert cfg["optimizer_backend"] == "claude_code_exec"
|
|
assert cfg["optimizer_model"] == "claude-sonnet-4-6"
|
|
assert cfg["target_backend"] == "claude_code_exec"
|
|
assert cfg["target_model"] == "claude-sonnet-4-6"
|
|
|
|
|
|
class _StopAfterResolution(Exception):
|
|
pass
|
|
|
|
|
|
def _run_eval_resolution(
|
|
monkeypatch, tmp_path, *, optimizer_backend: str | None
|
|
) -> dict:
|
|
import scripts.eval_only as eval_script
|
|
|
|
skill_path = tmp_path / "skill.md"
|
|
skill_path.write_text("# Test skill\n", encoding="utf-8")
|
|
|
|
cfg = {
|
|
"model": {
|
|
"backend": "azure_openai",
|
|
"optimizer": "gpt-5.5",
|
|
"target": "gpt-5.5",
|
|
"optimizer_backend": "openai_chat",
|
|
"target_backend": "openai_chat",
|
|
},
|
|
"env": {"out_root": str(tmp_path / "out")},
|
|
}
|
|
args = SimpleNamespace(
|
|
config="unused.yaml",
|
|
skill=str(skill_path),
|
|
split=None,
|
|
cfg_options=[],
|
|
backend="claude_code_exec",
|
|
optimizer_backend=optimizer_backend,
|
|
)
|
|
monkeypatch.setattr(eval_script, "parse_args", lambda: args)
|
|
monkeypatch.setattr("skillopt.config.load_config", lambda *a, **kw: cfg)
|
|
|
|
observed: dict[str, str] = {}
|
|
|
|
def capture(name):
|
|
def _fn(value, *a, **k):
|
|
observed[name] = value
|
|
|
|
return _fn
|
|
|
|
monkeypatch.setattr(eval_script, "configure_azure_openai", lambda **kw: None)
|
|
monkeypatch.setattr(eval_script, "set_optimizer_backend", capture("optimizer_backend"))
|
|
monkeypatch.setattr(eval_script, "set_target_backend", capture("target_backend"))
|
|
monkeypatch.setattr(eval_script, "set_optimizer_deployment", capture("optimizer_model"))
|
|
monkeypatch.setattr(eval_script, "set_target_deployment", capture("target_model"))
|
|
|
|
def stop(*a, **k):
|
|
raise _StopAfterResolution
|
|
|
|
monkeypatch.setattr(eval_script, "configure_codex_exec_from_config", stop)
|
|
|
|
with pytest.raises(_StopAfterResolution):
|
|
eval_script.main()
|
|
|
|
return observed
|
|
|
|
|
|
def test_eval_claude_code_exec_defaults_target_only(monkeypatch, tmp_path) -> None:
|
|
observed = _run_eval_resolution(monkeypatch, tmp_path, optimizer_backend=None)
|
|
|
|
assert observed == {
|
|
"optimizer_backend": "openai_chat",
|
|
"target_backend": "claude_code_exec",
|
|
"optimizer_model": "gpt-5.5",
|
|
"target_model": "claude-sonnet-4-6",
|
|
}
|
|
|
|
|
|
def test_eval_claude_code_exec_optimizer_opt_in_normalizes_model(
|
|
monkeypatch, tmp_path
|
|
) -> None:
|
|
observed = _run_eval_resolution(
|
|
monkeypatch, tmp_path, optimizer_backend="claude_code_exec"
|
|
)
|
|
|
|
assert observed == {
|
|
"optimizer_backend": "claude_code_exec",
|
|
"target_backend": "claude_code_exec",
|
|
"optimizer_model": "claude-sonnet-4-6",
|
|
"target_model": "claude-sonnet-4-6",
|
|
}
|