self-evolve-framework 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/skills/skillopt-sleep/SKILL.md +42 -0
- package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
- package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
- package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
- package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
- package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
- package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
- package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
- package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/scoring.py +28 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
"""Codegen agent for SpreadsheetBench — no tool-call, pure code generation.
|
|
2
|
+
|
|
3
|
+
Two modes:
|
|
4
|
+
- **single**: One LLM call → extract ```python``` block → done.
|
|
5
|
+
- **multi**: Up to max_turns LLM calls; after each, execute code and
|
|
6
|
+
feed errors back for correction.
|
|
7
|
+
|
|
8
|
+
This matches the official SpreadsheetBench evaluation setting (LLM generates
|
|
9
|
+
a Python code block, no function-calling / tool-use).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import random
|
|
16
|
+
import signal
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
import openpyxl
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── Timeout helper ──────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
class TaskTimeout(Exception):
|
|
25
|
+
"""Raised when a task exceeds its time budget."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _timeout_handler(signum, frame):
|
|
29
|
+
raise TaskTimeout("Task timed out")
|
|
30
|
+
|
|
31
|
+
from skillopt.model.azure_openai import (
|
|
32
|
+
get_reasoning_effort,
|
|
33
|
+
get_target_client,
|
|
34
|
+
_needs_responses_api,
|
|
35
|
+
tracker,
|
|
36
|
+
)
|
|
37
|
+
from skillopt.model import get_codex_exec_config, get_target_backend, is_target_exec_backend
|
|
38
|
+
from skillopt.model.codex_harness import prepare_workspace, render_skill_md, run_target_exec
|
|
39
|
+
from skillopt.prompts import load_prompt
|
|
40
|
+
from skillopt.envs.spreadsheetbench.executor import run_generated_code
|
|
41
|
+
from skillopt.envs.spreadsheetbench.evaluator import evaluate
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ── Eval feedback helper (no golden value leakage) ─────────────────────────
|
|
45
|
+
|
|
46
|
+
def _build_eval_feedback(verify_report: str) -> str:
|
|
47
|
+
"""Build Target feedback from a verify report, hiding expected values.
|
|
48
|
+
|
|
49
|
+
The verify report contains lines like:
|
|
50
|
+
Sheet1!D2: got=None, expected=0 ✗
|
|
51
|
+
Sheet1!D10: got=None, expected=None ✓
|
|
52
|
+
|
|
53
|
+
We strip the ``expected=...`` part so the Target sees only its own
|
|
54
|
+
output and whether each cell is correct or wrong.
|
|
55
|
+
"""
|
|
56
|
+
import re
|
|
57
|
+
wrong_lines = []
|
|
58
|
+
n_correct = 0
|
|
59
|
+
for raw_line in verify_report.splitlines():
|
|
60
|
+
raw_line = raw_line.strip()
|
|
61
|
+
if not raw_line:
|
|
62
|
+
continue
|
|
63
|
+
# Match enrichment lines like " Sheet1!D2: got=None, expected=0 ✗"
|
|
64
|
+
m = re.match(
|
|
65
|
+
r"(\S+!?\w+):\s*got=(.+?),\s*expected=.+?\s*(✓|✗)$",
|
|
66
|
+
raw_line,
|
|
67
|
+
)
|
|
68
|
+
if m:
|
|
69
|
+
cell, got_val, mark = m.groups()
|
|
70
|
+
if mark == "✗":
|
|
71
|
+
wrong_lines.append(f" {cell}: your output = {got_val} (WRONG)")
|
|
72
|
+
else:
|
|
73
|
+
n_correct += 1
|
|
74
|
+
lines = ["Your code executed successfully but produced incorrect results.",
|
|
75
|
+
"The following cells have wrong values:"]
|
|
76
|
+
lines.extend(wrong_lines)
|
|
77
|
+
if n_correct:
|
|
78
|
+
lines.append(f" ({n_correct} other cells are correct.)")
|
|
79
|
+
lines.append(
|
|
80
|
+
"\nPlease analyze the spreadsheet data more carefully and fix the code. "
|
|
81
|
+
"Return a complete corrected Python script inside a ```python``` block."
|
|
82
|
+
)
|
|
83
|
+
return "\n".join(lines)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ── Workbook preview (same as official prompt.py) ────────────────────────────
|
|
87
|
+
|
|
88
|
+
def _preview_workbook(path: str, max_rows: int = 5, max_cols: int = 20) -> str:
|
|
89
|
+
"""Generate a text preview of the first few rows of each sheet."""
|
|
90
|
+
wb = openpyxl.load_workbook(path, data_only=False)
|
|
91
|
+
chunks: list[str] = []
|
|
92
|
+
for sheet_name in wb.sheetnames:
|
|
93
|
+
ws = wb[sheet_name]
|
|
94
|
+
chunks.append(
|
|
95
|
+
f"## Sheet: {sheet_name} "
|
|
96
|
+
f"(dim={ws.dimensions}, max_row={ws.max_row}, max_col={ws.max_column})"
|
|
97
|
+
)
|
|
98
|
+
for row in ws.iter_rows(
|
|
99
|
+
min_row=1,
|
|
100
|
+
max_row=min(ws.max_row, max_rows),
|
|
101
|
+
max_col=min(ws.max_column, max_cols),
|
|
102
|
+
values_only=False,
|
|
103
|
+
):
|
|
104
|
+
cells = []
|
|
105
|
+
for cell in row:
|
|
106
|
+
v = cell.value
|
|
107
|
+
if v is None:
|
|
108
|
+
cells.append(f"{cell.coordinate}=")
|
|
109
|
+
else:
|
|
110
|
+
s = str(v)
|
|
111
|
+
if len(s) > 40:
|
|
112
|
+
s = s[:37] + "..."
|
|
113
|
+
cells.append(f"{cell.coordinate}={s}")
|
|
114
|
+
chunks.append(" | ".join(cells))
|
|
115
|
+
if ws.max_row > max_rows:
|
|
116
|
+
chunks.append(f"... ({ws.max_row - max_rows} more rows)")
|
|
117
|
+
chunks.append("")
|
|
118
|
+
wb.close()
|
|
119
|
+
return "\n".join(chunks)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ── Code extraction (same as official prompt.py) ────────────────────────────
|
|
123
|
+
|
|
124
|
+
def extract_code(text: str) -> str:
|
|
125
|
+
"""Extract the first ```python``` fenced code block from LLM output."""
|
|
126
|
+
if "```" not in text:
|
|
127
|
+
return text.strip()
|
|
128
|
+
start = text.find("```")
|
|
129
|
+
nl = text.find("\n", start)
|
|
130
|
+
end = text.find("```", nl + 1)
|
|
131
|
+
if nl == -1 or end == -1:
|
|
132
|
+
return text.strip()
|
|
133
|
+
return text[nl + 1 : end].strip()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── Prompt construction (official SpreadsheetBench prompts) ─────────────────
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _build_system(skill_content: str) -> str:
|
|
140
|
+
base = load_prompt("codegen_system", env="spreadsheetbench")
|
|
141
|
+
if skill_content.strip():
|
|
142
|
+
base += f"\n\n## Skill\n{skill_content.strip()}"
|
|
143
|
+
return base
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _build_user(
|
|
147
|
+
instruction: str,
|
|
148
|
+
input_xlsx: str,
|
|
149
|
+
instruction_type: str = "",
|
|
150
|
+
answer_position: str = "",
|
|
151
|
+
diagnostic_mode: bool = False,
|
|
152
|
+
diagnostic_instruction: str = "",
|
|
153
|
+
diagnostic_trace_context: str = "",
|
|
154
|
+
) -> str:
|
|
155
|
+
try:
|
|
156
|
+
preview = _preview_workbook(input_xlsx)
|
|
157
|
+
except Exception as e: # noqa: BLE001
|
|
158
|
+
preview = f"(failed to preview workbook: {e})"
|
|
159
|
+
extra = ""
|
|
160
|
+
if instruction_type:
|
|
161
|
+
extra += f"\nInstruction type: {instruction_type}"
|
|
162
|
+
if answer_position:
|
|
163
|
+
extra += f"\nExpected answer position: {answer_position}"
|
|
164
|
+
task_suffix = "Return only a ```python``` code block."
|
|
165
|
+
diagnostic = ""
|
|
166
|
+
if diagnostic_mode and diagnostic_instruction.strip():
|
|
167
|
+
task_suffix = (
|
|
168
|
+
"First provide a short diagnostic readout that follows the training "
|
|
169
|
+
"instruction below, then return a single complete ```python``` code block."
|
|
170
|
+
)
|
|
171
|
+
diagnostic = f"\n\n# Training readout\n{diagnostic_instruction.strip()}"
|
|
172
|
+
prefix = ""
|
|
173
|
+
if diagnostic_trace_context.strip():
|
|
174
|
+
prefix = (
|
|
175
|
+
"# Previous Codex Trace Snapshot\n"
|
|
176
|
+
"This is a partial transcript from an earlier attempt. Use it as your current reasoning context.\n\n"
|
|
177
|
+
f"{diagnostic_trace_context.strip()}\n\n"
|
|
178
|
+
)
|
|
179
|
+
return (
|
|
180
|
+
f"{prefix}"
|
|
181
|
+
f"# Instruction\n{instruction}\n{extra}\n\n"
|
|
182
|
+
f"# Input spreadsheet preview\n{preview}\n\n"
|
|
183
|
+
"# Task\n"
|
|
184
|
+
"Write a Python script that reads the workbook from the variable `INPUT_PATH`, "
|
|
185
|
+
"applies the instruction, and writes the modified workbook to `OUTPUT_PATH`. "
|
|
186
|
+
"Preserve all other cells unchanged. "
|
|
187
|
+
"The preview may be truncated — do not hardcode row counts or assume the data ends at the last previewed row; "
|
|
188
|
+
"iterate over all actual rows in the workbook instead. "
|
|
189
|
+
f"{task_suffix}"
|
|
190
|
+
f"{diagnostic}"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ── LLM call with retry ────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
def _llm_call_with_retry(call_fn, *, retries: int = 5, timeout: int | None = 120):
|
|
197
|
+
"""Wrap an LLM API call with retry and per-call timeout."""
|
|
198
|
+
last_err = None
|
|
199
|
+
for attempt in range(retries):
|
|
200
|
+
try:
|
|
201
|
+
return call_fn(timeout=timeout)
|
|
202
|
+
except Exception as e: # noqa: BLE001
|
|
203
|
+
last_err = e
|
|
204
|
+
sleep = min(2 ** attempt + random.random(), 60)
|
|
205
|
+
time.sleep(sleep)
|
|
206
|
+
raise RuntimeError(f"LLM call failed after {retries} retries: {last_err}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _get_deployment() -> str:
|
|
210
|
+
from skillopt.model import azure_openai as _llm
|
|
211
|
+
return _llm.TARGET_DEPLOYMENT
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _build_codex_skill(skill_content: str) -> str:
|
|
215
|
+
return render_skill_md(
|
|
216
|
+
skill_content,
|
|
217
|
+
description="Dynamic ReflACT skill for solving the current SpreadsheetBench task.",
|
|
218
|
+
preamble=(
|
|
219
|
+
"Use this skill when solving the current SpreadsheetBench task in this workspace.\n"
|
|
220
|
+
"Write a single self-contained Python solution to `solution.py`.\n"
|
|
221
|
+
"The solution must operate on the provided `INPUT_PATH` and `OUTPUT_PATH` variables.\n"
|
|
222
|
+
"You may inspect `input.xlsx` and run `python run_solution.py` to validate locally,\n"
|
|
223
|
+
"but do not hardcode values from the preview or from one specific workbook."
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _build_codex_task(
|
|
229
|
+
instruction: str,
|
|
230
|
+
input_xlsx: str,
|
|
231
|
+
instruction_type: str,
|
|
232
|
+
answer_position: str,
|
|
233
|
+
*,
|
|
234
|
+
diagnostic_mode: bool,
|
|
235
|
+
diagnostic_instruction: str,
|
|
236
|
+
diagnostic_trace_context: str,
|
|
237
|
+
) -> str:
|
|
238
|
+
prompt = _build_user(
|
|
239
|
+
instruction,
|
|
240
|
+
input_xlsx,
|
|
241
|
+
instruction_type,
|
|
242
|
+
answer_position,
|
|
243
|
+
diagnostic_mode=diagnostic_mode,
|
|
244
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
245
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
246
|
+
)
|
|
247
|
+
return (
|
|
248
|
+
f"{prompt}\n\n"
|
|
249
|
+
"## Codex Harness Task\n"
|
|
250
|
+
"- Read `.agents/skills/skillopt-target/SKILL.md` before writing code; do not call a Skill tool.\n"
|
|
251
|
+
"- Read and optionally inspect `input.xlsx` in this workspace.\n"
|
|
252
|
+
"- Write the final Python solution to `solution.py`.\n"
|
|
253
|
+
"- The script should use the provided `INPUT_PATH` and `OUTPUT_PATH` variables.\n"
|
|
254
|
+
"- If you want to validate locally, run `python run_solution.py`.\n"
|
|
255
|
+
"- Do not return a code fence as the primary artifact; the source of truth is `solution.py`.\n"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _build_codex_driver() -> str:
|
|
260
|
+
return (
|
|
261
|
+
"import pathlib\n"
|
|
262
|
+
"import re\n"
|
|
263
|
+
"import sys\n"
|
|
264
|
+
"import traceback\n\n"
|
|
265
|
+
'INPUT_PATH = "input.xlsx"\n'
|
|
266
|
+
'OUTPUT_PATH = "output.xlsx"\n'
|
|
267
|
+
"code = pathlib.Path('solution.py').read_text(encoding='utf-8')\n"
|
|
268
|
+
"code = re.sub(r'^\\s*(INPUT_PATH|OUTPUT_PATH)\\s*=\\s*.+$', '', code, flags=re.MULTILINE)\n"
|
|
269
|
+
"globals_dict = {'__name__': '__main__', 'INPUT_PATH': INPUT_PATH, 'OUTPUT_PATH': OUTPUT_PATH}\n"
|
|
270
|
+
"try:\n"
|
|
271
|
+
" exec(compile(code, 'solution.py', 'exec'), globals_dict, globals_dict)\n"
|
|
272
|
+
"except Exception:\n"
|
|
273
|
+
" traceback.print_exc()\n"
|
|
274
|
+
" sys.exit(2)\n"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _prepare_codex_workspace(
|
|
279
|
+
*,
|
|
280
|
+
instruction: str,
|
|
281
|
+
input_xlsx: str,
|
|
282
|
+
output_path: str,
|
|
283
|
+
instruction_type: str,
|
|
284
|
+
answer_position: str,
|
|
285
|
+
skill_content: str,
|
|
286
|
+
diagnostic_mode: bool,
|
|
287
|
+
diagnostic_instruction: str,
|
|
288
|
+
diagnostic_trace_context: str,
|
|
289
|
+
workspace_name: str = "codex_single",
|
|
290
|
+
) -> tuple[str, str, str, str]:
|
|
291
|
+
task_out_dir = os.path.dirname(output_path)
|
|
292
|
+
work_dir = os.path.join(task_out_dir, workspace_name)
|
|
293
|
+
skill_md = _build_codex_skill(skill_content)
|
|
294
|
+
task_md = _build_codex_task(
|
|
295
|
+
instruction,
|
|
296
|
+
input_xlsx,
|
|
297
|
+
instruction_type,
|
|
298
|
+
answer_position,
|
|
299
|
+
diagnostic_mode=diagnostic_mode,
|
|
300
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
301
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
302
|
+
)
|
|
303
|
+
prompt = (
|
|
304
|
+
"Read `.agents/skills/skillopt-target/SKILL.md` directly; do not call a Skill tool.\n"
|
|
305
|
+
"Read `task.md`, inspect `input.xlsx` if useful, and write the final solution to `solution.py`.\n"
|
|
306
|
+
"You may run `python run_solution.py` to validate the script locally.\n"
|
|
307
|
+
"In your final response, briefly confirm whether `solution.py` was written and summarize the approach."
|
|
308
|
+
)
|
|
309
|
+
prepare_workspace(
|
|
310
|
+
work_dir=work_dir,
|
|
311
|
+
skill_md=skill_md,
|
|
312
|
+
task_text=task_md,
|
|
313
|
+
extra_files={"run_solution.py": _build_codex_driver()},
|
|
314
|
+
copy_files=[(input_xlsx, "input.xlsx")],
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return work_dir, skill_md, task_md, prompt
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _run_exec_backend(
|
|
321
|
+
*,
|
|
322
|
+
work_dir: str,
|
|
323
|
+
prompt: str,
|
|
324
|
+
model: str,
|
|
325
|
+
timeout: int,
|
|
326
|
+
) -> tuple[str, str]:
|
|
327
|
+
return run_target_exec(
|
|
328
|
+
work_dir=work_dir,
|
|
329
|
+
prompt=prompt,
|
|
330
|
+
model=model,
|
|
331
|
+
timeout=timeout,
|
|
332
|
+
allow_file_edits=True,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ── Chat (no tools) ────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
def _chat_call(
|
|
339
|
+
client,
|
|
340
|
+
deployment: str,
|
|
341
|
+
messages: list[dict],
|
|
342
|
+
max_output_tokens: int,
|
|
343
|
+
llm_timeout: int | None = 120,
|
|
344
|
+
) -> str:
|
|
345
|
+
"""Single LLM call, no tools. Returns raw text."""
|
|
346
|
+
reasoning_effort = get_reasoning_effort()
|
|
347
|
+
if _needs_responses_api(deployment):
|
|
348
|
+
# Responses API
|
|
349
|
+
system = ""
|
|
350
|
+
api_input = []
|
|
351
|
+
for m in messages:
|
|
352
|
+
if m["role"] == "system":
|
|
353
|
+
system = m["content"]
|
|
354
|
+
else:
|
|
355
|
+
api_input.append({"role": m["role"], "content": m["content"]})
|
|
356
|
+
resp = _llm_call_with_retry(lambda timeout: client.responses.create(
|
|
357
|
+
model=deployment,
|
|
358
|
+
instructions=system,
|
|
359
|
+
input=api_input,
|
|
360
|
+
max_output_tokens=max_output_tokens,
|
|
361
|
+
**({"reasoning": {"effort": reasoning_effort}} if reasoning_effort else {}),
|
|
362
|
+
timeout=timeout,
|
|
363
|
+
), timeout=llm_timeout)
|
|
364
|
+
if hasattr(resp, "usage") and resp.usage:
|
|
365
|
+
tracker.record(
|
|
366
|
+
"rollout",
|
|
367
|
+
getattr(resp.usage, "input_tokens", 0) or 0,
|
|
368
|
+
getattr(resp.usage, "output_tokens", 0) or 0,
|
|
369
|
+
)
|
|
370
|
+
text = getattr(resp, "output_text", None) or ""
|
|
371
|
+
if text:
|
|
372
|
+
return text
|
|
373
|
+
for item in getattr(resp, "output", None) or []:
|
|
374
|
+
for part in getattr(item, "content", []):
|
|
375
|
+
if getattr(part, "type", "") == "output_text":
|
|
376
|
+
return part.text or ""
|
|
377
|
+
return ""
|
|
378
|
+
else:
|
|
379
|
+
# Chat Completions API — no tools
|
|
380
|
+
kwargs = {
|
|
381
|
+
"model": deployment,
|
|
382
|
+
"messages": messages,
|
|
383
|
+
"max_completion_tokens": max_output_tokens,
|
|
384
|
+
}
|
|
385
|
+
if reasoning_effort is not None:
|
|
386
|
+
kwargs["reasoning_effort"] = reasoning_effort
|
|
387
|
+
resp = _llm_call_with_retry(lambda timeout: client.chat.completions.create(
|
|
388
|
+
**kwargs,
|
|
389
|
+
timeout=timeout,
|
|
390
|
+
), timeout=llm_timeout)
|
|
391
|
+
if resp.usage:
|
|
392
|
+
tracker.record(
|
|
393
|
+
"rollout",
|
|
394
|
+
resp.usage.prompt_tokens or 0,
|
|
395
|
+
resp.usage.completion_tokens or 0,
|
|
396
|
+
)
|
|
397
|
+
return resp.choices[0].message.content or ""
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# ── Public API ──────────────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
def run_single(
|
|
403
|
+
instruction: str,
|
|
404
|
+
input_xlsx: str,
|
|
405
|
+
output_path: str,
|
|
406
|
+
instruction_type: str = "",
|
|
407
|
+
answer_position: str = "",
|
|
408
|
+
skill_content: str = "",
|
|
409
|
+
max_output_tokens: int = 16384,
|
|
410
|
+
llm_timeout: int | None = 120,
|
|
411
|
+
task_timeout: int | None = 300,
|
|
412
|
+
diagnostic_mode: bool = False,
|
|
413
|
+
diagnostic_instruction: str = "",
|
|
414
|
+
diagnostic_trace_context: str = "",
|
|
415
|
+
) -> dict:
|
|
416
|
+
"""Single-round code generation. One LLM call, no tools.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
llm_timeout: Per-LLM-call timeout in seconds (default 120).
|
|
420
|
+
task_timeout: Total task timeout in seconds (default 300).
|
|
421
|
+
|
|
422
|
+
Returns ``{"code": str, "raw": str, "n_turns": 1}``.
|
|
423
|
+
"""
|
|
424
|
+
no_task_timeout = task_timeout is None or task_timeout <= 0
|
|
425
|
+
if is_target_exec_backend():
|
|
426
|
+
deadline = None if no_task_timeout else time.time() + task_timeout
|
|
427
|
+
deployment = _get_deployment()
|
|
428
|
+
work_dir, skill_md, task_md, prompt = _prepare_codex_workspace(
|
|
429
|
+
instruction=instruction,
|
|
430
|
+
input_xlsx=input_xlsx,
|
|
431
|
+
output_path=output_path,
|
|
432
|
+
instruction_type=instruction_type,
|
|
433
|
+
answer_position=answer_position,
|
|
434
|
+
skill_content=skill_content,
|
|
435
|
+
diagnostic_mode=diagnostic_mode,
|
|
436
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
437
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
438
|
+
)
|
|
439
|
+
if deadline is None:
|
|
440
|
+
effective_timeout = 10**9
|
|
441
|
+
else:
|
|
442
|
+
remaining = max(10, int(deadline - time.time()))
|
|
443
|
+
effective_timeout = min(task_timeout, remaining)
|
|
444
|
+
final_message, raw = _run_exec_backend(
|
|
445
|
+
work_dir=work_dir,
|
|
446
|
+
prompt=prompt,
|
|
447
|
+
model=deployment,
|
|
448
|
+
timeout=effective_timeout,
|
|
449
|
+
)
|
|
450
|
+
solution_path = os.path.join(work_dir, "solution.py")
|
|
451
|
+
if os.path.exists(solution_path):
|
|
452
|
+
with open(solution_path, encoding="utf-8") as f:
|
|
453
|
+
code = f.read()
|
|
454
|
+
else:
|
|
455
|
+
code = extract_code(final_message or raw)
|
|
456
|
+
return {
|
|
457
|
+
"code": code,
|
|
458
|
+
"raw": raw or final_message,
|
|
459
|
+
"n_turns": 1,
|
|
460
|
+
"conversation": [{"role": "assistant", "content": final_message or raw}],
|
|
461
|
+
"target_system_prompt": skill_md,
|
|
462
|
+
"target_user_prompt": f"{prompt}\n\n## Task File\n\n{task_md}",
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
deadline = None if no_task_timeout else time.time() + task_timeout
|
|
466
|
+
client = get_target_client()
|
|
467
|
+
deployment = _get_deployment()
|
|
468
|
+
system = _build_system(skill_content)
|
|
469
|
+
user = _build_user(
|
|
470
|
+
instruction,
|
|
471
|
+
input_xlsx,
|
|
472
|
+
instruction_type,
|
|
473
|
+
answer_position,
|
|
474
|
+
diagnostic_mode=diagnostic_mode,
|
|
475
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
476
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
messages = [
|
|
480
|
+
{"role": "system", "content": system},
|
|
481
|
+
{"role": "user", "content": user},
|
|
482
|
+
]
|
|
483
|
+
|
|
484
|
+
if deadline is None:
|
|
485
|
+
effective_timeout = None
|
|
486
|
+
else:
|
|
487
|
+
remaining = max(10, int(deadline - time.time()))
|
|
488
|
+
effective_timeout = min(llm_timeout or remaining, remaining)
|
|
489
|
+
raw = _chat_call(client, deployment, messages, max_output_tokens, llm_timeout=effective_timeout)
|
|
490
|
+
time.sleep(3) # Rate-limit cooldown after successful LLM call
|
|
491
|
+
code = extract_code(raw)
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
"code": code,
|
|
495
|
+
"raw": raw,
|
|
496
|
+
"n_turns": 1,
|
|
497
|
+
"conversation": [{"role": "assistant", "content": raw}],
|
|
498
|
+
"target_system_prompt": system,
|
|
499
|
+
"target_user_prompt": user,
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def run_multi(
|
|
504
|
+
instruction: str,
|
|
505
|
+
input_xlsx: str,
|
|
506
|
+
output_path: str,
|
|
507
|
+
instruction_type: str = "",
|
|
508
|
+
answer_position: str = "",
|
|
509
|
+
skill_content: str = "",
|
|
510
|
+
max_turns: int = 5,
|
|
511
|
+
max_output_tokens: int = 16384,
|
|
512
|
+
llm_timeout: int | None = 120,
|
|
513
|
+
task_timeout: int | None = 600,
|
|
514
|
+
gold_path: str = "",
|
|
515
|
+
diagnostic_mode: bool = False,
|
|
516
|
+
diagnostic_instruction: str = "",
|
|
517
|
+
diagnostic_trace_context: str = "",
|
|
518
|
+
) -> dict:
|
|
519
|
+
"""Multi-round code generation with execution feedback. No tools.
|
|
520
|
+
|
|
521
|
+
Each round: LLM generates code → execute → if error, feed back and retry.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
llm_timeout: Per-LLM-call timeout in seconds (default 120).
|
|
525
|
+
task_timeout: Total task timeout in seconds (default 600).
|
|
526
|
+
gold_path: Path to golden answer xlsx for eval feedback during
|
|
527
|
+
training. When non-empty, a successful execution is followed
|
|
528
|
+
by an eval check; if the output is wrong the agent receives
|
|
529
|
+
cell-level feedback (without revealing expected values) and
|
|
530
|
+
gets another turn. Leave empty for eval/test to avoid
|
|
531
|
+
data leakage.
|
|
532
|
+
|
|
533
|
+
Returns ``{"code": str, "raw": str, "n_turns": int, "conversation": [...]}``.
|
|
534
|
+
"""
|
|
535
|
+
no_task_timeout = task_timeout is None or task_timeout <= 0
|
|
536
|
+
if is_target_exec_backend():
|
|
537
|
+
deadline = None if no_task_timeout else time.time() + task_timeout
|
|
538
|
+
deployment = _get_deployment()
|
|
539
|
+
work_dir, skill_md, task_md, initial_prompt = _prepare_codex_workspace(
|
|
540
|
+
instruction=instruction,
|
|
541
|
+
input_xlsx=input_xlsx,
|
|
542
|
+
output_path=output_path,
|
|
543
|
+
instruction_type=instruction_type,
|
|
544
|
+
answer_position=answer_position,
|
|
545
|
+
skill_content=skill_content,
|
|
546
|
+
diagnostic_mode=diagnostic_mode,
|
|
547
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
548
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
549
|
+
workspace_name="codex_multi",
|
|
550
|
+
)
|
|
551
|
+
prompt = (
|
|
552
|
+
f"{initial_prompt}\n\n"
|
|
553
|
+
"## Multi-Turn Repair Mode\n"
|
|
554
|
+
"- This is turn 1. Write or overwrite `solution.py`.\n"
|
|
555
|
+
"- After each turn, the harness will execute your `solution.py`; if it fails, you will receive feedback and may revise it.\n"
|
|
556
|
+
"- Keep the script general: use `INPUT_PATH` and `OUTPUT_PATH`, and do not hardcode one workbook's values."
|
|
557
|
+
)
|
|
558
|
+
conversation: list[dict] = []
|
|
559
|
+
code = ""
|
|
560
|
+
raw = ""
|
|
561
|
+
final_message = ""
|
|
562
|
+
solution_path = os.path.join(work_dir, "solution.py")
|
|
563
|
+
|
|
564
|
+
for turn in range(max_turns):
|
|
565
|
+
if deadline is None:
|
|
566
|
+
effective_timeout = 10**9
|
|
567
|
+
else:
|
|
568
|
+
remaining = deadline - time.time()
|
|
569
|
+
if remaining <= 10:
|
|
570
|
+
break
|
|
571
|
+
effective_timeout = max(10, int(remaining))
|
|
572
|
+
final_message, raw = _run_exec_backend(
|
|
573
|
+
work_dir=work_dir,
|
|
574
|
+
prompt=prompt,
|
|
575
|
+
model=deployment,
|
|
576
|
+
timeout=effective_timeout,
|
|
577
|
+
)
|
|
578
|
+
conversation.append({"role": "assistant", "content": final_message or raw})
|
|
579
|
+
|
|
580
|
+
if os.path.exists(solution_path):
|
|
581
|
+
with open(solution_path, encoding="utf-8") as f:
|
|
582
|
+
code = f.read()
|
|
583
|
+
else:
|
|
584
|
+
code = extract_code(final_message or raw)
|
|
585
|
+
if code.strip():
|
|
586
|
+
with open(solution_path, "w", encoding="utf-8") as f:
|
|
587
|
+
f.write(code)
|
|
588
|
+
|
|
589
|
+
if not code.strip():
|
|
590
|
+
feedback = (
|
|
591
|
+
"No usable `solution.py` or Python code block was produced. "
|
|
592
|
+
"Write a complete `solution.py` that reads `INPUT_PATH` and saves `OUTPUT_PATH`."
|
|
593
|
+
)
|
|
594
|
+
else:
|
|
595
|
+
ok, err = run_generated_code(
|
|
596
|
+
code,
|
|
597
|
+
input_xlsx,
|
|
598
|
+
output_path,
|
|
599
|
+
timeout=None if no_task_timeout else 120,
|
|
600
|
+
)
|
|
601
|
+
if ok:
|
|
602
|
+
if gold_path and answer_position:
|
|
603
|
+
from skillopt.envs.spreadsheetbench.rollout import _auto_verify_output
|
|
604
|
+
eval_result = evaluate(
|
|
605
|
+
output_path, gold_path, instruction_type, answer_position,
|
|
606
|
+
)
|
|
607
|
+
if eval_result["ok"]:
|
|
608
|
+
break
|
|
609
|
+
verify = _auto_verify_output(output_path, gold_path, answer_position)
|
|
610
|
+
feedback = _build_eval_feedback(verify)
|
|
611
|
+
else:
|
|
612
|
+
break
|
|
613
|
+
else:
|
|
614
|
+
feedback = (
|
|
615
|
+
"The current `solution.py` raised an error during harness execution:\n\n"
|
|
616
|
+
f"```\n{err[:3000]}\n```\n\n"
|
|
617
|
+
"Revise `solution.py` to fix the error. Keep using `INPUT_PATH` and `OUTPUT_PATH`."
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
feedback_path = os.path.join(work_dir, f"feedback_turn_{turn + 1:02d}.md")
|
|
621
|
+
with open(feedback_path, "w", encoding="utf-8") as f:
|
|
622
|
+
f.write(feedback)
|
|
623
|
+
conversation.append({"role": "user", "content": feedback})
|
|
624
|
+
prompt = (
|
|
625
|
+
f"The previous `solution.py` was evaluated and needs another revision.\n"
|
|
626
|
+
f"Read `{os.path.basename(feedback_path)}` and update `solution.py` accordingly.\n"
|
|
627
|
+
"You may run `python run_solution.py` for a local syntax/runtime check, but the harness will run the final code separately.\n"
|
|
628
|
+
"Do not hardcode workbook-specific answers; preserve unrelated cells."
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
"code": code,
|
|
633
|
+
"raw": raw or final_message,
|
|
634
|
+
"n_turns": len([m for m in conversation if m["role"] == "assistant"]),
|
|
635
|
+
"conversation": conversation,
|
|
636
|
+
"target_system_prompt": skill_md,
|
|
637
|
+
"target_user_prompt": f"{initial_prompt}\n\n## Task File\n\n{task_md}",
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
deadline = None if no_task_timeout else time.time() + task_timeout
|
|
641
|
+
client = get_target_client()
|
|
642
|
+
deployment = _get_deployment()
|
|
643
|
+
system = _build_system(skill_content)
|
|
644
|
+
user = _build_user(
|
|
645
|
+
instruction,
|
|
646
|
+
input_xlsx,
|
|
647
|
+
instruction_type,
|
|
648
|
+
answer_position,
|
|
649
|
+
diagnostic_mode=diagnostic_mode,
|
|
650
|
+
diagnostic_instruction=diagnostic_instruction,
|
|
651
|
+
diagnostic_trace_context=diagnostic_trace_context,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
messages: list[dict] = [
|
|
655
|
+
{"role": "system", "content": system},
|
|
656
|
+
{"role": "user", "content": user},
|
|
657
|
+
]
|
|
658
|
+
conversation: list[dict] = []
|
|
659
|
+
code = ""
|
|
660
|
+
raw = ""
|
|
661
|
+
|
|
662
|
+
for turn in range(max_turns):
|
|
663
|
+
if deadline is None:
|
|
664
|
+
effective_timeout = None
|
|
665
|
+
else:
|
|
666
|
+
remaining = deadline - time.time()
|
|
667
|
+
if remaining <= 10:
|
|
668
|
+
# Not enough time for another round
|
|
669
|
+
break
|
|
670
|
+
effective_timeout = min(llm_timeout or int(remaining), int(remaining))
|
|
671
|
+
raw = _chat_call(client, deployment, messages, max_output_tokens, llm_timeout=effective_timeout)
|
|
672
|
+
time.sleep(3) # Rate-limit cooldown after successful LLM call
|
|
673
|
+
code = extract_code(raw)
|
|
674
|
+
conversation.append({"role": "assistant", "content": raw})
|
|
675
|
+
messages.append({"role": "assistant", "content": raw})
|
|
676
|
+
|
|
677
|
+
if not code.strip():
|
|
678
|
+
# No code extracted — ask again
|
|
679
|
+
feedback = (
|
|
680
|
+
"No Python code block was found in your response. "
|
|
681
|
+
"Please return a complete Python script inside a ```python``` block."
|
|
682
|
+
)
|
|
683
|
+
messages.append({"role": "user", "content": feedback})
|
|
684
|
+
conversation.append({"role": "user", "content": feedback})
|
|
685
|
+
continue
|
|
686
|
+
|
|
687
|
+
# Execute the code
|
|
688
|
+
ok, err = run_generated_code(
|
|
689
|
+
code,
|
|
690
|
+
input_xlsx,
|
|
691
|
+
output_path,
|
|
692
|
+
timeout=None if no_task_timeout else 120,
|
|
693
|
+
)
|
|
694
|
+
if ok:
|
|
695
|
+
# Execution succeeded — check correctness if gold_path available
|
|
696
|
+
if gold_path and answer_position:
|
|
697
|
+
from skillopt.envs.spreadsheetbench.rollout import _auto_verify_output
|
|
698
|
+
eval_result = evaluate(
|
|
699
|
+
output_path, gold_path, instruction_type, answer_position,
|
|
700
|
+
)
|
|
701
|
+
if eval_result["ok"]:
|
|
702
|
+
break # Genuinely correct — stop
|
|
703
|
+
|
|
704
|
+
# Output is wrong — build feedback without leaking golden values
|
|
705
|
+
verify = _auto_verify_output(output_path, gold_path, answer_position)
|
|
706
|
+
feedback = _build_eval_feedback(verify)
|
|
707
|
+
messages.append({"role": "user", "content": feedback})
|
|
708
|
+
conversation.append({"role": "user", "content": feedback})
|
|
709
|
+
continue
|
|
710
|
+
else:
|
|
711
|
+
# No gold path (eval/test) — accept execution success
|
|
712
|
+
break
|
|
713
|
+
|
|
714
|
+
# Execution failed — feed error back
|
|
715
|
+
feedback = (
|
|
716
|
+
f"The code raised an error during execution:\n\n"
|
|
717
|
+
f"```\n{err[:3000]}\n```\n\n"
|
|
718
|
+
f"Please fix the code and return a complete corrected Python script "
|
|
719
|
+
f"inside a ```python``` block."
|
|
720
|
+
)
|
|
721
|
+
messages.append({"role": "user", "content": feedback})
|
|
722
|
+
conversation.append({"role": "user", "content": feedback})
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
"code": code,
|
|
726
|
+
"raw": raw,
|
|
727
|
+
"n_turns": turn + 1,
|
|
728
|
+
"conversation": conversation,
|
|
729
|
+
"target_system_prompt": system,
|
|
730
|
+
"target_user_prompt": user,
|
|
731
|
+
}
|