Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1ac570d94 | |||
| d8023a47c9 | |||
| b0b62fcb86 | |||
| 3308c4c5dc | |||
| 0d5b331cd5 | |||
| 1c6a0e75c8 | |||
| 88989d120d | |||
| 44043d4ae5 | |||
| 7dcd612361 | |||
| 0dc84162dc | |||
| ffe581098b | |||
| 372fd56c1e | |||
| f64a41397c | |||
| 5cd22bb71b | |||
| d6c4ca3f6e | |||
| dae974a5e3 | |||
| f9db99853b | |||
| b02ffc2c99 | |||
| e2de84d36f | |||
| 9379e494bf | |||
| a29201adc4 | |||
| 77ac33e8bf | |||
| c179a24c45 | |||
| 6f1351edb9 | |||
| 99ec2caf6b | |||
| acf4545c00 | |||
| 1d20e9db14 | |||
| 937bc1ec4d | |||
| b1f41a7506 | |||
| 4186e5bb73 | |||
| 023950a291 | |||
| d75863eb6f | |||
| c80914b036 | |||
| defb4566ea | |||
| 233b619555 | |||
| a0419bfdbb | |||
| 7d9900b6af | |||
| 63c79b3602 | |||
| 4203086899 | |||
| 309f3141d4 | |||
| 4e7add899d | |||
| 0ac2b35daa | |||
| b5328e8b22 | |||
| c31c50be51 | |||
| ee9931ec01 | |||
| 3f194d58e5 | |||
| c7513d54f3 | |||
| abc9acd82e | |||
| 46cc2efd8a | |||
| 25da7cb2dd | |||
| 4eb4c64b2a | |||
| 2ca2910649 | |||
| fb1a76371d | |||
| 47063e1ceb | |||
| 181d71b737 | |||
| 41012e2d5e | |||
| dd8cd993b5 | |||
| 8ebede0efd | |||
| 266fca72ab | |||
| 9265545c45 | |||
| b4850ce418 | |||
| 643346c9f3 | |||
| c6da31df44 | |||
| e4201074aa | |||
| 309ea64ff4 | |||
| d224d425f9 |
@@ -27,3 +27,8 @@ export AZURE_OPENAI_API_KEY=
|
|||||||
# ── Qwen Local Model (for qwen_chat backend) ────────────────────────
|
# ── Qwen Local Model (for qwen_chat backend) ────────────────────────
|
||||||
# export QWEN_CHAT_BASE_URL=http://localhost:8000/v1
|
# export QWEN_CHAT_BASE_URL=http://localhost:8000/v1
|
||||||
# export QWEN_CHAT_MODEL=Qwen/Qwen3.5-4B
|
# export QWEN_CHAT_MODEL=Qwen/Qwen3.5-4B
|
||||||
|
|
||||||
|
# ── MiniMax (for minimax_chat backend) ──────────────────────────────
|
||||||
|
# export MINIMAX_BASE_URL=https://api.minimax.io/v1
|
||||||
|
# export MINIMAX_API_KEY=...
|
||||||
|
# export MINIMAX_MODEL=MiniMax-M2.7
|
||||||
|
|||||||
+20
-1
@@ -5,7 +5,20 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
site/
|
site/
|
||||||
|
|
||||||
data/
|
data/*
|
||||||
|
!data/README.md
|
||||||
|
!data/searchqa_id_split/
|
||||||
|
!data/searchqa_id_split/**
|
||||||
|
!data/livemathematicianbench_id_split/
|
||||||
|
!data/livemathematicianbench_id_split/**
|
||||||
|
!data/docvqa_id_split/
|
||||||
|
!data/docvqa_id_split/**
|
||||||
|
!data/officeqa_id_split/
|
||||||
|
!data/officeqa_id_split/**
|
||||||
|
!data/spreadsheetbench_id_split/
|
||||||
|
!data/spreadsheetbench_id_split/**
|
||||||
|
!data/alfworld_path_split/
|
||||||
|
!data/alfworld_path_split/**
|
||||||
outputs/
|
outputs/
|
||||||
logs/
|
logs/
|
||||||
external/
|
external/
|
||||||
@@ -40,3 +53,9 @@ docs/reflact_overview.html
|
|||||||
docs/render_ablation_paper_tables.py
|
docs/render_ablation_paper_tables.py
|
||||||
docs/让*
|
docs/让*
|
||||||
.gradio/
|
.gradio/
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Local experiment launchers — contain machine-specific endpoints/identities, never commit
|
||||||
|
tests/run_*.sh
|
||||||
|
tests/launch_*.py
|
||||||
|
*.launch.log
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Publishing SkillOpt-Sleep — how people install and use it
|
||||||
|
|
||||||
|
This is the open-source SkillOpt-Sleep tool: a nightly offline "sleep cycle" for
|
||||||
|
local coding agents, shipped as plugins for **Claude Code**, **Codex**, and
|
||||||
|
**Copilot**. One engine ([`skillopt_sleep/`](skillopt_sleep)), three thin shells
|
||||||
|
([`plugins/`](plugins)), decoupled from the research code.
|
||||||
|
|
||||||
|
## How end users install it
|
||||||
|
|
||||||
|
### Claude Code
|
||||||
|
|
||||||
|
The Claude Code plugin ships a marketplace manifest at
|
||||||
|
`plugins/claude-code/.claude-plugin/marketplace.json`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
# inside Claude Code:
|
||||||
|
/plugin marketplace add microsoft/SkillOpt
|
||||||
|
/plugin install skillopt-sleep
|
||||||
|
/sleep status
|
||||||
|
```
|
||||||
|
|
||||||
|
(`/plugin marketplace add <owner>/<repo>` reads the marketplace manifest from the
|
||||||
|
repo; the entry points at `plugins/claude-code`.)
|
||||||
|
|
||||||
|
### Codex
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/microsoft/SkillOpt.git
|
||||||
|
cd SkillOpt
|
||||||
|
bash plugins/codex/install.sh # installs /sleep prompt + skill
|
||||||
|
export SKILLOPT_SLEEP_REPO="$(pwd)" # so the runner is found anywhere
|
||||||
|
# then, in Codex: /sleep status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copilot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/microsoft/SkillOpt.git
|
||||||
|
# register the MCP server with your Copilot config (see plugins/copilot/README.md
|
||||||
|
# and plugins/copilot/mcp-config.example.json), pointing SKILLOPT_SLEEP_REPO at
|
||||||
|
# the clone. Then ask Copilot to "run the sleep cycle".
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements for all three: Python ≥ 3.10, and the corresponding agent CLI on
|
||||||
|
PATH. The default backend is `mock` (no API spend); `--backend claude|codex`
|
||||||
|
uses the user's own budget.
|
||||||
|
|
||||||
|
## Wider distribution (optional, maintainer steps)
|
||||||
|
|
||||||
|
1. **GitHub Release.** Tag the milestone so users can pin a version:
|
||||||
|
```bash
|
||||||
|
gh release create sleep-v0.1.0 --title "SkillOpt-Sleep v0.1.0" \
|
||||||
|
--notes "Nightly offline self-evolution plugins for Claude Code, Codex, Copilot."
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Official Claude Code plugin marketplace.** To appear in the public
|
||||||
|
directory, open a PR adding a `marketplace.json` entry to
|
||||||
|
[`anthropics/claude-code` / the official marketplace repo], pointing at
|
||||||
|
`microsoft/SkillOpt` subdir `plugins/claude-code`. Users could then
|
||||||
|
`/plugin install skillopt-sleep@<official-marketplace>`.
|
||||||
|
|
||||||
|
3. **PyPI (optional).** `skillopt_sleep` is a standalone package
|
||||||
|
(`pyproject.toml` lists it). A `pip install skillopt-sleep` distribution would
|
||||||
|
let users run `python -m skillopt_sleep ...` without cloning. Build with
|
||||||
|
`python -m build` and publish with `twine`.
|
||||||
|
|
||||||
|
4. **README News.** The main [`README.md`](README.md) already announces the
|
||||||
|
release and links to [`plugins/`](plugins) and
|
||||||
|
[`docs/sleep/FINAL_REPORT.md`](docs/sleep/FINAL_REPORT.md).
|
||||||
|
|
||||||
|
## Verifying a release works
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# deterministic, no API key:
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves
|
||||||
|
# the unit suite:
|
||||||
|
python -m unittest tests.test_sleep_engine
|
||||||
|
# the MCP server (Copilot):
|
||||||
|
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
|
||||||
|
| SKILLOPT_SLEEP_REPO="$(pwd)" python3 plugins/copilot/mcp_server.py
|
||||||
|
```
|
||||||
@@ -2,9 +2,48 @@
|
|||||||
|
|
||||||
*Train agent skills like you train neural networks — with epochs, (mini-)batchsize, learning rates, and validation gates — but without touching model weights.*
|
*Train agent skills like you train neural networks — with epochs, (mini-)batchsize, learning rates, and validation gates — but without touching model weights.*
|
||||||
|
|
||||||
[](https://microsoft.github.io/SkillOpt/) [](https://arxiv.org/abs/2605.23904) [](https://youtu.be/JUBMDTCiM0M) [](https://www.python.org/) [](LICENSE)
|
[](https://microsoft.github.io/SkillOpt/) [](https://arxiv.org/abs/2605.23904) [](https://youtu.be/JUBMDTCiM0M) [](https://pypi.org/project/skillopt/) [](https://www.python.org/) [](LICENSE)
|
||||||
|
|
||||||
## 🎬 SkillOpt Demo Video
|
> 📖 **For installation, data preparation, training/eval commands, the full configuration reference, and framework internals, see the [Documentation & Reproduction Guide](docs/guideline.html)** — view it [rendered online](https://htmlpreview.github.io/?https://github.com/microsoft/SkillOpt/blob/main/docs/guideline.html) or via [GitHub Pages](https://microsoft.github.io/SkillOpt/docs/guideline.html).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## News 🔥🔥🔥
|
||||||
|
- **[2026-06-08]** 😴 **SkillOpt-Sleep is here — plugins for Claude Code, Codex, and Copilot.** Give your local coding agent a nightly *sleep cycle*: it reviews your past sessions offline, replays your recurring tasks, and consolidates validated long-term memory + skills behind a held-out gate, so it gets better the more you use it. Validated on the public [gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1` benchmark with **real Claude and Codex** (deficient skills 0.00 → 1.00 on held-out, all 4 seeds). It's an **open-source tool decoupled from the paper code**. See [`plugins/`](plugins/) and the [SkillOpt-Sleep section](#-skillopt-sleep--the-deployment-time-companion) below.
|
||||||
|
- **[2026-06-03]** 🎉 **[gbrain](https://github.com/garrytan/gbrain), [gbrain-evals](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-06-03-skillopt.md), and [darwin-skill](https://github.com/alchaincyf/darwin-skill) have all integrated SkillOpt.**
|
||||||
|
- **[2026-06-02]** 🎉 **SkillOpt [v0.1.0](https://github.com/microsoft/SkillOpt/releases/tag/v0.1.0) is now available on [PyPI](https://pypi.org/project/skillopt/)!** Install with `pip install skillopt`. This initial release includes the full training loop (rollout → reflect → aggregate → select → update → evaluate), multi-backend support (OpenAI / Azure / Claude / Qwen / MiniMax), six built-in benchmarks, and WebUI dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Modern agent skills are usually hand-crafted, generated one-shot by a strong
|
||||||
|
LLM, or evolved through loosely controlled self-revision — none of which
|
||||||
|
behaves like a deep-learning optimizer for the skill itself, and none of
|
||||||
|
which reliably improves over its starting point under feedback.
|
||||||
|
|
||||||
|
**SkillOpt treats the skill document as the trainable state of a frozen
|
||||||
|
agent**, and trains it with the discipline that makes weight-space
|
||||||
|
optimization reproducible. A separate optimizer model turns scored rollouts
|
||||||
|
into bounded add / delete / replace edits on a single skill document; a
|
||||||
|
candidate edit is accepted only when it strictly improves a held-out
|
||||||
|
validation score. A textual learning-rate budget, a rejected-edit buffer,
|
||||||
|
and an epoch-wise slow / meta update make skill training stable while
|
||||||
|
adding **zero inference-time model calls** at deployment.
|
||||||
|
|
||||||
|
The deployed artifact is a compact `best_skill.md` (typically 300–2,000
|
||||||
|
tokens) that runs against the unchanged target model. Across **six
|
||||||
|
benchmarks, seven target models, and three execution harnesses** (direct
|
||||||
|
chat, Codex CLI, Claude Code CLI), SkillOpt is best or tied-best on **all
|
||||||
|
52 evaluated (model, benchmark, harness) cells** and on GPT-5.5 lifts the
|
||||||
|
average no-skill accuracy by **+23.5 points in direct chat, +24.8 inside
|
||||||
|
the Codex agentic loop, and +19.1 inside Claude Code**. Optimized skill
|
||||||
|
artifacts transfer across model scales, between Codex and Claude Code
|
||||||
|
harnesses, and to nearby benchmarks without further optimization.
|
||||||
|
|
||||||
|
For the full method, ablations, and per-cell results see the [paper](https://arxiv.org/abs/2605.23904); for a visual walkthrough of the loop see the [project page](https://microsoft.github.io/SkillOpt/); for deeper API / backend / benchmark docs see [`docs/`](docs/).
|
||||||
|
|
||||||
|
## 🎬 Demo Video
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/eb12d3bc-371c-467f-904d-91b61f339ed7
|
https://github.com/user-attachments/assets/eb12d3bc-371c-467f-904d-91b61f339ed7
|
||||||
|
|
||||||
@@ -14,244 +53,71 @@ https://github.com/user-attachments/assets/eb12d3bc-371c-467f-904d-91b61f339ed7
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Documentation
|
## 😴 SkillOpt-Sleep — the deployment-time companion
|
||||||
|
|
||||||
A complete, self-contained **Documentation & Reproduction Guide** lives at
|
SkillOpt (above) trains a skill offline on a benchmark. **SkillOpt-Sleep**
|
||||||
[`docs/guideline.html`](docs/guideline.html). It covers installation, data
|
applies the same discipline to *your own daily usage*: it gives a local coding
|
||||||
preparation, training/eval commands, the full configuration reference, the
|
agent a nightly **sleep cycle** that reviews your past sessions, replays your
|
||||||
framework internals (training loop, validation gate, slow update, meta skill),
|
recurring tasks on your own API budget, and consolidates what it learns into
|
||||||
and an API/function reference — all in a single page with a left navigation
|
**validated** long-term memory and skills — behind a held-out gate, staged for
|
||||||
sidebar.
|
your review. The agent gets better the more you use it, with no weight training.
|
||||||
|
|
||||||
Because GitHub shows raw source for `.html` files instead of rendering them,
|
It synthesizes **SkillOpt** (validation-gated bounded text edits), **Claude
|
||||||
open the guide one of these ways:
|
Dreams** (offline consolidation; review-then-adopt), and the **agent sleep**
|
||||||
|
idea (short-term experience → long-term competence). One "night":
|
||||||
- **Locally** — clone the repo and open `docs/guideline.html` in any browser
|
|
||||||
(no build step required).
|
|
||||||
- **Rendered online (no setup)** — via the htmlpreview proxy:
|
|
||||||
[`htmlpreview.github.io/?…/docs/guideline.html`](https://htmlpreview.github.io/?https://github.com/microsoft/SkillOpt/blob/main/docs/guideline.html)
|
|
||||||
- **GitHub Pages** — the repository's GitHub Pages site already serves the
|
|
||||||
project homepage from the repo root, so the guide is reachable alongside it at
|
|
||||||
`https://microsoft.github.io/SkillOpt/docs/guideline.html` (the homepage at
|
|
||||||
`https://microsoft.github.io/SkillOpt/` is unaffected).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
**Requirements:** Python 3.10+
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/microsoft/SkillOpt.git
|
|
||||||
cd SkillOpt
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# For ALFWorld benchmark (optional):
|
|
||||||
pip install -e ".[alfworld]"
|
|
||||||
alfworld-download
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configure API Credentials
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
# Edit .env with your API credentials, then:
|
|
||||||
source .env
|
|
||||||
```
|
|
||||||
|
|
||||||
**Azure OpenAI** (recommended):
|
|
||||||
```bash
|
|
||||||
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
|
|
||||||
# Option 1: API key auth
|
|
||||||
export AZURE_OPENAI_API_KEY="your-key"
|
|
||||||
# Option 2: Azure CLI auth (no API key needed)
|
|
||||||
export AZURE_OPENAI_AUTH_MODE="azure_cli"
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** `AZURE_OPENAI_ENDPOINT` is required for all three modes (`api_key`, `azure_cli`,
|
|
||||||
> `openai_compatible`). Without it, all LLM calls will fail.
|
|
||||||
|
|
||||||
**OpenAI-compatible endpoints**:
|
|
||||||
```bash
|
|
||||||
export AZURE_OPENAI_ENDPOINT="https://api.openai.com/v1"
|
|
||||||
export AZURE_OPENAI_API_KEY="sk-..."
|
|
||||||
export AZURE_OPENAI_AUTH_MODE="openai_compatible"
|
|
||||||
```
|
|
||||||
|
|
||||||
This routes all calls through the plain OpenAI Python client (no Azure auth, no `api-version`
|
|
||||||
header).
|
|
||||||
|
|
||||||
> **Note:** SkillOpt reuses the `AZURE_OPENAI_*` env var names even in this mode — there is no
|
|
||||||
> separate `OPENAI_API_KEY` knob.
|
|
||||||
|
|
||||||
**Anthropic Claude**:
|
|
||||||
```bash
|
|
||||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
||||||
```
|
|
||||||
|
|
||||||
**Qwen (local vLLM)**:
|
|
||||||
```bash
|
|
||||||
export QWEN_CHAT_BASE_URL="http://localhost:8000/v1"
|
|
||||||
export QWEN_CHAT_MODEL="Qwen/Qwen3.5-4B"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Preparation
|
|
||||||
|
|
||||||
SkillOpt expects data in a **split directory** with `train/`, `val/`, `test/` subdirectories, each containing a JSON file (e.g., `items.json`).
|
|
||||||
|
|
||||||
```
|
```
|
||||||
data/my_split/
|
harvest session transcripts → mine recurring tasks → replay offline
|
||||||
├── train/items.json
|
→ consolidate (reflect → bounded edit → GATE on real held-out tasks)
|
||||||
├── val/items.json
|
→ stage proposal → (you) adopt
|
||||||
└── test/items.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each JSON file is an array of task items. The required fields depend on the benchmark. For example, SearchQA items look like:
|
**Plugins for three agents** (one engine, three thin shells — see [`plugins/`](plugins/)):
|
||||||
|
|
||||||
```json
|
| Platform | Folder | Install |
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "unique_item_id",
|
|
||||||
"question": "Who wrote the novel ...",
|
|
||||||
"context": "[DOC] relevant passage text ...",
|
|
||||||
"answers": ["expected answer"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
See `skillopt/envs/<benchmark>/dataloader.py` for the exact format each benchmark expects.
|
|
||||||
|
|
||||||
> **Note:** Benchmark datasets are not included in this repository. Prepare your own data following the format above.
|
|
||||||
|
|
||||||
### Supported Benchmarks
|
|
||||||
|
|
||||||
| Benchmark | Type | Config |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| SearchQA | QA | `configs/searchqa/default.yaml` |
|
| **Claude Code** | [`plugins/claude-code`](plugins/claude-code) | `/plugin marketplace add ./plugins/claude-code` → `/sleep` |
|
||||||
| ALFWorld | Embodied agent | `configs/alfworld/default.yaml` |
|
| **Codex** | [`plugins/codex`](plugins/codex) | `bash plugins/codex/install.sh` → `/sleep` |
|
||||||
| DocVQA | Document QA | `configs/docvqa/default.yaml` |
|
| **Copilot** | [`plugins/copilot`](plugins/copilot) | register `plugins/copilot/mcp_server.py` as an MCP server |
|
||||||
| LiveMathematicianBench | Math | `configs/livemathematicianbench/default.yaml` |
|
|
||||||
| SpreadsheetBench | Code generation | `configs/spreadsheetbench/default.yaml` |
|
**Validated on real models.** On the public
|
||||||
| OfficeQA | Tool-augmented QA | `configs/officeqa/default.yaml` |
|
[gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1` benchmark,
|
||||||
|
deficient skills go **0.00 → 1.00** on held-out sets with **both Claude and
|
||||||
|
Codex** (all 4 seeds, including a real tool-use loop), cross-model transfer is
|
||||||
|
positive, and the gate blocks regressions
|
||||||
|
([full results](docs/sleep/FINAL_REPORT.md)).
|
||||||
|
|
||||||
|
> **Open-source tool, decoupled from the research.** The engine lives in the
|
||||||
|
> top-level [`skillopt_sleep/`](skillopt_sleep) package with **zero dependency**
|
||||||
|
> on the paper's `skillopt/` experiment code (the validation gate is vendored).
|
||||||
|
> Controls — optional gate, multi-rollout contrastive reflection, token/time
|
||||||
|
> budget, multi-objective reward, user preferences, optimizer/target split — are
|
||||||
|
> documented in [`docs/sleep/CONTROLLABLE_DREAMING.md`](docs/sleep/CONTROLLABLE_DREAMING.md).
|
||||||
|
|
||||||
|
Deterministic proof (no API key): `python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Extensibility & WebUI
|
||||||
|
|
||||||
### Training
|
### Adding a new backend
|
||||||
|
|
||||||
```bash
|
A backend = a chat / exec target (e.g. `openai_chat`, `claude_chat`,
|
||||||
# Minimal example — train on SearchQA:
|
`qwen_chat`, `minimax_chat`, `codex_exec`, `claude_code_exec`). See
|
||||||
python scripts/train.py \
|
[`docs/guide/new-backend.md`](docs/guide/new-backend.md) for the full
|
||||||
--config configs/searchqa/default.yaml \
|
contract; in short you add a `skillopt/model/<name>_backend.py` module,
|
||||||
--split_dir /path/to/your/searchqa_split \
|
register it in `skillopt/model/common.py` + `backend_config.py`, and wire
|
||||||
--azure_openai_endpoint https://your-resource.openai.azure.com/ \
|
it through the router in `skillopt/model/__init__.py`. `qwen_backend.py`
|
||||||
--optimizer_model gpt-5.5 \
|
and `minimax_backend.py` are good templates.
|
||||||
--target_model gpt-5.5
|
|
||||||
|
|
||||||
# Train on LiveMathematicianBench:
|
### Adding a new benchmark
|
||||||
python scripts/train.py \
|
|
||||||
--config configs/livemathematicianbench/default.yaml \
|
|
||||||
--split_dir /path/to/your/livemath_split \
|
|
||||||
--azure_openai_endpoint https://your-resource.openai.azure.com/ \
|
|
||||||
--optimizer_model gpt-5.5 \
|
|
||||||
--target_model gpt-5.5
|
|
||||||
|
|
||||||
# Train on ALFWorld:
|
A benchmark = a `skillopt/envs/<name>/` package with a `dataloader.py`, a
|
||||||
python scripts/train.py \
|
`rollout.py`, and an `initial.md` seed skill. See
|
||||||
--config configs/alfworld/default.yaml \
|
[`docs/guide/new-benchmark.md`](docs/guide/new-benchmark.md) for the full
|
||||||
--split_dir /path/to/your/alfworld_split \
|
contract; the simplest reference is `skillopt/envs/searchqa/`.
|
||||||
--azure_openai_endpoint https://your-resource.openai.azure.com/ \
|
|
||||||
--optimizer_model gpt-5.5 \
|
|
||||||
--target_model gpt-5.5
|
|
||||||
```
|
|
||||||
|
|
||||||
Key CLI arguments:
|
### WebUI
|
||||||
|
|
||||||
| Argument | Description | Example |
|
|
||||||
|---|---|---|
|
|
||||||
| `--config` | Benchmark config YAML | `configs/searchqa/default.yaml` |
|
|
||||||
| `--split_dir` | Path to data split directory | `/path/to/split` |
|
|
||||||
| `--azure_openai_endpoint` | Azure OpenAI endpoint URL | `https://your-resource.openai.azure.com/` |
|
|
||||||
| `--optimizer_model` | Optimizer model deployment name | `gpt-5.5` |
|
|
||||||
| `--target_model` | Target model deployment name | `gpt-5.5` |
|
|
||||||
| `--num_epochs` | Number of training epochs | `4` |
|
|
||||||
| `--batch_size` | Batch size per step | `40` |
|
|
||||||
| `--workers` | Parallel rollout workers | `8` |
|
|
||||||
| `--out_root` | Output directory | `outputs/my_run` |
|
|
||||||
|
|
||||||
### Eval Only
|
|
||||||
|
|
||||||
Evaluate a trained skill on specific data splits without training:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Evaluate the packaged GPT-5.5 SearchQA skill on the test split:
|
|
||||||
python scripts/eval_only.py \
|
|
||||||
--config configs/searchqa/default.yaml \
|
|
||||||
--skill ckpt/searchqa/gpt5.5_skill.md \
|
|
||||||
--split valid_unseen \
|
|
||||||
--split_dir /path/to/searchqa_split \
|
|
||||||
--azure_openai_endpoint https://your-resource.openai.azure.com/
|
|
||||||
|
|
||||||
# Evaluate on all splits (train + val + test):
|
|
||||||
python scripts/eval_only.py \
|
|
||||||
--config configs/searchqa/default.yaml \
|
|
||||||
--skill ckpt/searchqa/gpt5.5_skill.md \
|
|
||||||
--split all \
|
|
||||||
--split_dir /path/to/searchqa_split \
|
|
||||||
--azure_openai_endpoint https://your-resource.openai.azure.com/
|
|
||||||
```
|
|
||||||
|
|
||||||
To evaluate a skill produced by a training run, replace `--skill` with that
|
|
||||||
run's best-skill path, for example `outputs/my_run/best_skill.md`.
|
|
||||||
|
|
||||||
| Split | Description |
|
|
||||||
|---|---|
|
|
||||||
| `valid_unseen` | Test set |
|
|
||||||
| `valid_seen` | Validation set |
|
|
||||||
| `train` | Training set |
|
|
||||||
| `all` | All splits combined (default) |
|
|
||||||
|
|
||||||
### Output Structure
|
|
||||||
|
|
||||||
Each run writes to a structured output directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
outputs/<run_name>/
|
|
||||||
├── config.json # Flattened runtime config
|
|
||||||
├── history.json # Per-step training history
|
|
||||||
├── runtime_state.json # Resume checkpoint
|
|
||||||
├── best_skill.md # Best validated skill document
|
|
||||||
├── skills/skill_vXXXX.md # Skill snapshot per step
|
|
||||||
├── steps/step_XXXX/ # Per-step artifacts (patches, evals)
|
|
||||||
├── slow_update/epoch_XX/ # Slow update logs
|
|
||||||
└── meta_skill/epoch_XX/ # Meta skill logs
|
|
||||||
```
|
|
||||||
|
|
||||||
Re-running the same command auto-resumes from the last completed step.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Community-contributed configs
|
|
||||||
|
|
||||||
These are **not** default SkillOpt settings — they are reference configs
|
|
||||||
contributed by users for specific scenarios. The paper-reported numbers
|
|
||||||
were obtained with the default settings, not these.
|
|
||||||
|
|
||||||
- **`configs/examples/soft_gate.yaml`** *(PR #25, contributed by
|
|
||||||
[@lvbaocheng](https://github.com/lvbaocheng))* — switches the
|
|
||||||
validation gate from exact-match (`hard`) to soft / partial-credit
|
|
||||||
(`soft` or `mixed`). Useful when the held-out **selection split is
|
|
||||||
small** (e.g. ≤ ~10 items) and the **reward is continuous**, where the
|
|
||||||
discrete hard gate often rejects every candidate and training stalls.
|
|
||||||
See the comment at the top of the file for details and when not to use
|
|
||||||
it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WebUI
|
|
||||||
|
|
||||||
Launch the monitoring dashboard (optional):
|
Launch the monitoring dashboard (optional):
|
||||||
|
|
||||||
@@ -266,11 +132,6 @@ python -m skillopt_webui.app
|
|||||||
| `--host` | `0.0.0.0` | Bind address |
|
| `--host` | `0.0.0.0` | Bind address |
|
||||||
| `--share` | off | Create a public Gradio share link |
|
| `--share` | off | Create a public Gradio share link |
|
||||||
|
|
||||||
```bash
|
|
||||||
# With public share link (useful for remote servers)
|
|
||||||
python -m skillopt_webui.app --share
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Citation
|
## Citation
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Paper-aligned SkillOpt reference skills (GPT-5.5)
|
||||||
|
|
||||||
|
This folder provides a subset of the paper's main Table 1 GPT-5.5 optimized
|
||||||
|
skills as reference artifacts — one `gpt5.5_skill.md` per currently included
|
||||||
|
benchmark. You can plug them into `scripts/eval_only.py` to evaluate the
|
||||||
|
provided skills on a given split without re-running the training loop.
|
||||||
|
|
||||||
|
> These are checkpoints associated with the paper, not a general-purpose
|
||||||
|
> tool. They're here so you can verify the reported numbers and use the
|
||||||
|
> skills as portable artifacts. If you want to *train* your own skill,
|
||||||
|
> use `scripts/train.py` per the top-level README.
|
||||||
|
>
|
||||||
|
> This is the first artifact batch. We plan to continue uploading the
|
||||||
|
> remaining optimized skills and benchmark split manifests as they are
|
||||||
|
> cleaned and verified.
|
||||||
|
|
||||||
|
## What's here
|
||||||
|
|
||||||
|
| Benchmark | Skill artifact | Matching config |
|
||||||
|
|---|---|---|
|
||||||
|
| SearchQA | `ckpt/searchqa/gpt5.5_skill.md` | `configs/searchqa/default.yaml` |
|
||||||
|
| ALFWorld | `ckpt/alfworld/gpt5.5_skill.md` | `configs/alfworld/default.yaml` |
|
||||||
|
| DocVQA | `ckpt/docvqa/gpt5.5_skill.md` | `configs/docvqa/default.yaml` |
|
||||||
|
| LiveMathematicianBench | `ckpt/livemath/gpt5.5_skill.md` | `configs/livemathematicianbench/default.yaml` |
|
||||||
|
| OfficeQA | `ckpt/officeqa/gpt5.5_skill.md` | `configs/officeqa/default.yaml` |
|
||||||
|
| SpreadsheetBench | `ckpt/spreadsheetbench/gpt5.5_skill.md` | `configs/spreadsheetbench/default.yaml` |
|
||||||
|
|
||||||
|
Each file is a plain Markdown skill document (~2k–13k chars). It contains a
|
||||||
|
protected `SLOW_UPDATE` section at the end that holds epoch-wise
|
||||||
|
longitudinal guidance — that's expected, not a formatting issue.
|
||||||
|
|
||||||
|
## How to evaluate a provided skill
|
||||||
|
|
||||||
|
`scripts/eval_only.py` runs a single skill against a data split without
|
||||||
|
invoking the optimizer. Example for SearchQA against the test split:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/eval_only.py \
|
||||||
|
--config configs/searchqa/default.yaml \
|
||||||
|
--skill ckpt/searchqa/gpt5.5_skill.md \
|
||||||
|
--split valid_unseen \
|
||||||
|
--split_dir data/searchqa_id_split \
|
||||||
|
--azure_openai_endpoint https://your-resource.openai.azure.com/ \
|
||||||
|
--target_model gpt-5.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Substitute the benchmark, config, skill path, and `--split_dir` to evaluate
|
||||||
|
any of the other five. `--split valid_unseen` is the test split, `valid_seen`
|
||||||
|
is the selection / validation split, `train` is the training split, and
|
||||||
|
`all` runs all three.
|
||||||
|
|
||||||
|
## On comparing to the paper numbers
|
||||||
|
|
||||||
|
To compare against the paper-reported cells, use the same dataset split and
|
||||||
|
scorer. SearchQA's split is checked in at `data/searchqa_id_split/` (400
|
||||||
|
train / 200 selection / 1400 test). For the other benchmarks, point
|
||||||
|
`--split_dir` at your own materialized split; the loader is deterministic
|
||||||
|
from `split_seed` (default `42`) + `split_ratio` (default `2:1:7`) when
|
||||||
|
`split_mode: ratio` is used, so a given `data_path` + seed reproduces
|
||||||
|
across machines. Explicit per-benchmark split manifests are being prepared
|
||||||
|
for upload — see issues #14 and #21.
|
||||||
|
|
||||||
|
## Why force-accept vs. gated slow-update matters
|
||||||
|
|
||||||
|
These `ckpt/` skills were produced with the gated slow-update semantics
|
||||||
|
described in paper Section 3.6:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
optimizer:
|
||||||
|
slow_update_gate_with_selection: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Current `main` defaults to `false` (force-accept mode), a newer
|
||||||
|
post-submission behavior where the slow-update guidance is written into
|
||||||
|
`current_skill` and `best_skill` unconditionally at the epoch boundary. If
|
||||||
|
you re-train with the current default, you may produce a *different*
|
||||||
|
`best_skill.md` than the one checked in here. Both modes are supported;
|
||||||
|
see the top-level README's "Configuration -> Slow-update acceptance mode"
|
||||||
|
section.
|
||||||
@@ -44,6 +44,18 @@ model:
|
|||||||
target_azure_openai_ad_scope: "https://cognitiveservices.azure.com/.default"
|
target_azure_openai_ad_scope: "https://cognitiveservices.azure.com/.default"
|
||||||
target_azure_openai_managed_identity_client_id: ""
|
target_azure_openai_managed_identity_client_id: ""
|
||||||
|
|
||||||
|
# MiniMax backend settings (minimax_chat target)
|
||||||
|
minimax_base_url: "" # https://api.minimax.io/v1 if blank
|
||||||
|
minimax_api_key: ""
|
||||||
|
minimax_model: "MiniMax-M2.7"
|
||||||
|
minimax_temperature: "0.7"
|
||||||
|
minimax_max_tokens: "8000"
|
||||||
|
minimax_enable_thinking: "false"
|
||||||
|
optimizer_minimax_base_url: "" # per-role override
|
||||||
|
target_minimax_base_url: "" # per-role override
|
||||||
|
optimizer_minimax_api_key: ""
|
||||||
|
target_minimax_api_key: ""
|
||||||
|
|
||||||
train:
|
train:
|
||||||
num_epochs: 4
|
num_epochs: 4
|
||||||
train_size: 0 # 0 = derive from dataset split when available
|
train_size: 0 # 0 = derive from dataset split when available
|
||||||
@@ -69,6 +81,9 @@ optimizer:
|
|||||||
slow_update_gate_with_selection: false
|
slow_update_gate_with_selection: false
|
||||||
longitudinal_pair_policy: mixed # mixed / changed / unchanged
|
longitudinal_pair_policy: mixed # mixed / changed / unchanged
|
||||||
use_meta_skill: true
|
use_meta_skill: true
|
||||||
|
use_skill_aware_reflection: false # EmbodiSkill: split failures into SKILL_DEFECT (edit body) vs EXECUTION_LAPSE (protected appendix)
|
||||||
|
skill_aware_appendix_source: both # both = success+failure emit appendix notes; failure_only = only EXECUTION_LAPSE (paper-faithful)
|
||||||
|
skill_aware_consolidate_threshold: 0 # 0 = off; >0 = LLM-consolidate the appendix when its note count exceeds N
|
||||||
|
|
||||||
evaluation:
|
evaluation:
|
||||||
use_gate: true
|
use_gate: true
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ env:
|
|||||||
name: alfworld
|
name: alfworld
|
||||||
skill_init: skillopt/envs/alfworld/skills/initial.md
|
skill_init: skillopt/envs/alfworld/skills/initial.md
|
||||||
split_mode: split_dir
|
split_mode: split_dir
|
||||||
split_dir: data/ablation_splits/alfworld/2-1-7_seed42
|
split_dir: data/alfworld_path_split
|
||||||
data_path: ""
|
data_path: ""
|
||||||
split_output_dir: ""
|
split_output_dir: ""
|
||||||
max_steps: 50
|
max_steps: 50
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Example: soft / mixed validation-gate metric (community-contributed, PR #25)
|
# Feature: soft / mixed validation-gate metric (community-contributed, PR #25)
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
#
|
#
|
||||||
# This is NOT a default SkillOpt setting and was NOT used to produce the
|
# This is NOT a default SkillOpt setting and was NOT used to produce the
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
# and matches the design described in the paper.
|
# and matches the design described in the paper.
|
||||||
#
|
#
|
||||||
# To use: inherit your env config from this file, e.g.
|
# To use: inherit your env config from this file, e.g.
|
||||||
# _base_: ../examples/soft_gate.yaml
|
# _base_: ../features/soft_gate.yaml
|
||||||
# or copy the `evaluation:` block below into your config.
|
# or copy the `evaluation:` block below into your config.
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ env:
|
|||||||
name: livemathematicianbench
|
name: livemathematicianbench
|
||||||
skill_init: skillopt/envs/livemathematicianbench/skills/initial.md
|
skill_init: skillopt/envs/livemathematicianbench/skills/initial.md
|
||||||
split_mode: split_dir
|
split_mode: split_dir
|
||||||
split_dir: data/ablation_splits/livemathematicianbench/2-1-7_seed42
|
split_dir: data/livemathematicianbench_split
|
||||||
data_path: ""
|
data_path: ""
|
||||||
split_output_dir: ""
|
split_output_dir: ""
|
||||||
max_turns: 1
|
max_turns: 1
|
||||||
|
|||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
# Data Manifests
|
||||||
|
|
||||||
|
This directory releases lightweight split manifests for the SkillOpt paper
|
||||||
|
splits. These manifests are not full runnable benchmark payloads. To evaluate a
|
||||||
|
benchmark, first materialize the full examples from the raw data source when
|
||||||
|
needed, then point `--split_dir` at the split directory listed below.
|
||||||
|
|
||||||
|
In this README, "coverage" describes which part of the upstream benchmark the
|
||||||
|
manifest references. It does not mean the released manifest directory contains
|
||||||
|
the full runnable examples.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
Every released manifest directory uses the same file layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/<benchmark>_<manifest_type>/
|
||||||
|
|-- split_manifest.json
|
||||||
|
|-- train/items.json
|
||||||
|
|-- val/items.json
|
||||||
|
`-- test/items.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`split_manifest.json` records source metadata, split counts, and item fields.
|
||||||
|
Each `items.json` contains only stable IDs or source-path hints.
|
||||||
|
|
||||||
|
## Released Splits
|
||||||
|
|
||||||
|
| Manifest directory | Benchmark | Counts | Coverage | Raw data source | `split_dir` |
|
||||||
|
|---|---|---:|---|---|---|
|
||||||
|
| `searchqa_id_split/` | SearchQA | 400 / 200 / 1400 | Official HF dataset IDs | [lucadiliello/searchqa](https://huggingface.co/datasets/lucadiliello/searchqa) | `data/searchqa_split` |
|
||||||
|
| `livemathematicianbench_id_split/` | LiveMathematicianBench | 35 / 18 / 124 | Four official monthly files | [LiveMathematicianBench/LiveMathematicianBench](https://huggingface.co/datasets/LiveMathematicianBench/LiveMathematicianBench) | `data/livemathematicianbench_split` |
|
||||||
|
| `docvqa_id_split/` | DocVQA | 107 / 53 / 374 | 10% subset of validation | [lmms-lab/DocVQA](https://huggingface.co/datasets/lmms-lab/DocVQA) | `data/docvqa/splits` |
|
||||||
|
| `officeqa_id_split/` | OfficeQA | 50 / 24 / 172 | OfficeQA Full | [databricks/officeqa](https://huggingface.co/datasets/databricks/officeqa) | `data/officeqa_split` |
|
||||||
|
| `spreadsheetbench_id_split/` | SpreadsheetBench | 80 / 40 / 280 | SpreadsheetBench Verified 400 | [KAKA22/SpreadsheetBench](https://huggingface.co/datasets/KAKA22/SpreadsheetBench) | `data/spreadsheetbench_split` |
|
||||||
|
| `alfworld_path_split/` | ALFWorld | 39 / 18 / 134 | ALFWorld `json_2.1.1` paths | [alfworld/alfworld](https://github.com/alfworld/alfworld) | `data/alfworld_path_split` |
|
||||||
|
|
||||||
|
Counts are ordered as train / val / test.
|
||||||
|
|
||||||
|
## Direct Use
|
||||||
|
|
||||||
|
Only `alfworld_path_split/` can be used directly as `--split_dir` from this
|
||||||
|
release, because the ALFWorld loader reads `gamefile` and `task_type` from the
|
||||||
|
split items.
|
||||||
|
|
||||||
|
This does not mean the ALFWorld raw data is included. You still need to
|
||||||
|
download ALFWorld separately with `alfworld-download` and set `$ALFWORLD_DATA`
|
||||||
|
to the data root containing `json_2.1.1`.
|
||||||
|
|
||||||
|
The other manifest directories are lookup manifests. They intentionally omit
|
||||||
|
full example fields such as questions, answers, contexts, images, or task
|
||||||
|
instructions. Materialize those benchmarks into the `split_dir` paths listed
|
||||||
|
above before running SkillOpt.
|
||||||
|
|
||||||
|
## Lookup Keys
|
||||||
|
|
||||||
|
The manifests are sufficient to locate the corresponding raw examples after
|
||||||
|
the raw data has been downloaded or otherwise made available:
|
||||||
|
|
||||||
|
| Benchmark | Manifest lookup key |
|
||||||
|
|---|---|
|
||||||
|
| SearchQA | Match `items.json[].id` to the `key` field in `lucadiliello/searchqa`. |
|
||||||
|
| LiveMathematicianBench | Open `source_file`, then match `no`; the manifest `id` is `<month>:<no>`. |
|
||||||
|
| DocVQA | Match `questionId` within the official DocVQA `validation` split; `image_path` records the expected local image path. |
|
||||||
|
| OfficeQA | Match `uid` in `officeqa_full.csv`; `source_files` and `source_docs` identify the supporting document. |
|
||||||
|
| SpreadsheetBench | Match `id`; `spreadsheet_path` identifies the referenced spreadsheet directory. |
|
||||||
|
| ALFWorld | Resolve `gamefile` relative to `$ALFWORLD_DATA`. |
|
||||||
|
|
||||||
|
## Manifest Item Examples
|
||||||
|
|
||||||
|
SearchQA:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "221c83e6630f4e7983da48fa28da1882"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
LiveMathematicianBench:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "202602:22",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 22,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10700v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
DocVQA:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "50877",
|
||||||
|
"questionId": "50877",
|
||||||
|
"docId": "14724",
|
||||||
|
"image_path": "data/docvqa_images/q50877_d14724.png",
|
||||||
|
"source_split": "validation"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
OfficeQA:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "UID0002",
|
||||||
|
"uid": "UID0002",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1944_01.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SpreadsheetBench:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "32438",
|
||||||
|
"spreadsheet_path": "spreadsheet/32438",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ALFWorld:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "train:0000",
|
||||||
|
"gamefile": "json_2.1.1/train/.../game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark Notes
|
||||||
|
|
||||||
|
### SearchQA
|
||||||
|
|
||||||
|
`searchqa_id_split/` is an ID-only manifest. Each released `id` exactly matches
|
||||||
|
the `key` field in `lucadiliello/searchqa`.
|
||||||
|
|
||||||
|
Materialized examples must include the fields consumed by the SearchQA
|
||||||
|
environment, including:
|
||||||
|
|
||||||
|
```text
|
||||||
|
question
|
||||||
|
context
|
||||||
|
answers
|
||||||
|
```
|
||||||
|
|
||||||
|
### LiveMathematicianBench
|
||||||
|
|
||||||
|
`livemathematicianbench_id_split/` was generated from these raw files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/202511/qa_202511_final.json
|
||||||
|
data/202512/qa_202512_final.json
|
||||||
|
data/202601/qa_202601_final.json
|
||||||
|
data/202602/qa_202602_final.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest stores IDs in the loader format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<month>:<no>
|
||||||
|
```
|
||||||
|
|
||||||
|
Materialized examples must include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
question
|
||||||
|
choices
|
||||||
|
correct_choice
|
||||||
|
theorem_type
|
||||||
|
theorem
|
||||||
|
sketch
|
||||||
|
paper_link
|
||||||
|
```
|
||||||
|
|
||||||
|
### DocVQA
|
||||||
|
|
||||||
|
`docvqa_id_split/` records `docvqa_validation_10pct`: a 10% subset sampled from
|
||||||
|
the official DocVQA `validation` split.
|
||||||
|
|
||||||
|
```text
|
||||||
|
source_split: validation
|
||||||
|
docvqa_validation_10pct: train=107, val=53, test=374
|
||||||
|
```
|
||||||
|
|
||||||
|
Each manifest item contains question/document IDs plus image location metadata.
|
||||||
|
Materialized examples must provide `question`, `answer` or `ground_truth`, and
|
||||||
|
an `image_path` that resolves locally.
|
||||||
|
|
||||||
|
### OfficeQA
|
||||||
|
|
||||||
|
`officeqa_id_split/` records the split over OfficeQA Full
|
||||||
|
(`officeqa_full.csv`). The official OfficeQA CSVs are gated on Hugging Face, so
|
||||||
|
materialization requires authorized access.
|
||||||
|
|
||||||
|
Each manifest item contains `uid`, `category`, `source_files`, and
|
||||||
|
`source_docs` hints. Materialized examples must include `question` and
|
||||||
|
`ground_truth` or `answer`.
|
||||||
|
|
||||||
|
### SpreadsheetBench
|
||||||
|
|
||||||
|
`spreadsheetbench_id_split/` records the split over SpreadsheetBench Verified
|
||||||
|
400, from `spreadsheetbench_verified_400.tar.gz`.
|
||||||
|
|
||||||
|
Each manifest item contains task identity metadata such as `id`,
|
||||||
|
`spreadsheet_path`, and `instruction_type`. Materialization must also place the
|
||||||
|
referenced spreadsheet directories at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/spreadsheetbench_verified_400
|
||||||
|
```
|
||||||
|
|
||||||
|
### ALFWorld
|
||||||
|
|
||||||
|
`alfworld_path_split/` records `gamefile` paths relative to `$ALFWORLD_DATA`.
|
||||||
|
The source payload is `json_2.1.1`, which must be downloaded separately with
|
||||||
|
`alfworld-download`.
|
||||||
|
|
||||||
|
This manifest can be used directly as `--split_dir` after `$ALFWORLD_DATA`
|
||||||
|
points to the local ALFWorld data root containing `json_2.1.1`.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "ALFWorld",
|
||||||
|
"manifest_type": "path_split",
|
||||||
|
"source_repo": "alfworld/alfworld",
|
||||||
|
"source_repo_type": "repository",
|
||||||
|
"source_url": "https://github.com/alfworld/alfworld",
|
||||||
|
"source_file": "json_2.1.1",
|
||||||
|
"source_method": "generated by alfworld-download",
|
||||||
|
"source_split_files": [
|
||||||
|
"split_train.json",
|
||||||
|
"split_val.json",
|
||||||
|
"split_test.json"
|
||||||
|
],
|
||||||
|
"counts": {
|
||||||
|
"train": 39,
|
||||||
|
"val": 18,
|
||||||
|
"test": 134
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id",
|
||||||
|
"gamefile",
|
||||||
|
"task_type"
|
||||||
|
],
|
||||||
|
"path_root": "$ALFWORLD_DATA",
|
||||||
|
"notes": [
|
||||||
|
"This is a path manifest, not the ALFWorld game payload.",
|
||||||
|
"The gamefile field is relative to ALFWORLD_DATA and must be expanded before direct use as split_dir data."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,672 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "test:0000",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-AlarmClock-None-DeskLamp-308/trial_T20190908_222917_366542/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0001",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-AlarmClock-None-DeskLamp-308/trial_T20190908_222933_607649/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0002",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-AlarmClock-None-DeskLamp-308/trial_T20190908_222951_616606/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0003",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Book-None-DeskLamp-308/trial_T20190908_020029_636862/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0004",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Book-None-DeskLamp-308/trial_T20190908_020048_814402/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0005",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Book-None-DeskLamp-308/trial_T20190908_144951_587345/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0006",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Bowl-None-DeskLamp-308/trial_T20190907_133919_856963/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0007",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Bowl-None-DeskLamp-308/trial_T20190907_133935_066606/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0008",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Bowl-None-DeskLamp-308/trial_T20190907_133953_562557/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0009",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-CD-None-DeskLamp-308/trial_T20190908_141942_810052/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0010",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-CD-None-DeskLamp-308/trial_T20190908_141958_463362/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0011",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-CD-None-DeskLamp-308/trial_T20190908_142046_281296/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0012",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Mug-None-DeskLamp-308/trial_T20190908_161733_213242/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0013",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Mug-None-DeskLamp-308/trial_T20190908_201421_021646/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0014",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Mug-None-DeskLamp-308/trial_T20190908_201444_037645/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0015",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Pencil-None-DeskLamp-308/trial_T20190908_220545_153480/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0016",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Pencil-None-DeskLamp-308/trial_T20190908_220604_010430/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0017",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/look_at_obj_in_light-Pencil-None-DeskLamp-308/trial_T20190908_220656_510400/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0018",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Mug-None-Desk-308/trial_T20190908_125200_737896/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0019",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Mug-None-Desk-308/trial_T20190909_203041_433487/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0020",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Mug-None-Desk-308/trial_T20190909_210238_431966/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0021",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Pencil-None-Shelf-308/trial_T20190908_121952_610012/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0022",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Pencil-None-Shelf-308/trial_T20190908_122024_052056/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0023",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Pencil-None-Shelf-308/trial_T20190908_122154_042763/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0024",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-PepperShaker-None-Drawer-10/trial_T20190906_184021_215264/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0025",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-PepperShaker-None-Drawer-10/trial_T20190918_154326_823501/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0026",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-PepperShaker-None-Drawer-10/trial_T20190918_154424_844749/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0027",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Cabinet-10/trial_T20190906_191429_743650/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0028",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Cabinet-10/trial_T20190906_191445_723170/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0029",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Cabinet-10/trial_T20190906_191501_563086/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0030",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Drawer-10/trial_T20190909_021613_077537/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0031",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Drawer-10/trial_T20190909_021650_880235/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0032",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SaltShaker-None-Drawer-10/trial_T20190909_021728_339782/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0033",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SoapBottle-None-Toilet-424/trial_T20190907_004321_405868/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0034",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SoapBottle-None-Toilet-424/trial_T20190907_004351_281384/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0035",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-SoapBottle-None-Toilet-424/trial_T20190907_004404_604165/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0036",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Vase-None-Safe-219/trial_T20190908_205204_244321/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0037",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Vase-None-Safe-219/trial_T20190908_205221_748352/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0038",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Vase-None-Safe-219/trial_T20190908_205246_776817/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0039",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Watch-None-Safe-219/trial_T20190907_074524_006355/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0040",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Watch-None-Safe-219/trial_T20190907_074556_124850/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0041",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_and_place_simple-Watch-None-Safe-219/trial_T20190907_074643_810052/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0042",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Bowl-None-Cabinet-10/trial_T20190909_061130_844814/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0043",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Bowl-None-Cabinet-10/trial_T20190909_061158_110530/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0044",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Bowl-None-Cabinet-10/trial_T20190909_061232_368489/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0045",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Cloth-None-Cabinet-424/trial_T20190908_022321_380927/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0046",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Cloth-None-Cabinet-424/trial_T20190908_022436_073995/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0047",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Cloth-None-CounterTop-424/trial_T20190908_100632_546757/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0048",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Cloth-None-CounterTop-424/trial_T20190908_114340_674467/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0049",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Egg-None-Microwave-10/trial_T20190909_120554_888709/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0050",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Egg-None-Microwave-10/trial_T20190909_120632_691361/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0051",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Egg-None-Microwave-10/trial_T20190909_120712_273910/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0052",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Knife-None-CounterTop-10/trial_T20190909_110347_624008/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0053",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Knife-None-CounterTop-10/trial_T20190909_110445_675754/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0054",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Knife-None-CounterTop-10/trial_T20190909_110531_148235/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0055",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_221208_560499/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0056",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_221300_362511/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0057",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_221355_558505/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0058",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_032434_013084/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0059",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_032518_891433/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0060",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_032543_712058/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0061",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Plate-None-CounterTop-10/trial_T20190908_213356_017769/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0062",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Plate-None-CounterTop-10/trial_T20190908_213420_728917/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0063",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Plate-None-CounterTop-10/trial_T20190908_213533_897289/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0064",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-Cabinet-424/trial_T20190908_214926_337906/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0065",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-Cabinet-424/trial_T20190908_214946_567644/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0066",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-Cabinet-424/trial_T20190908_215019_162873/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0067",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-CounterTop-424/trial_T20190907_074045_109439/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0068",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-CounterTop-424/trial_T20190907_074106_050405/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0069",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-SoapBar-None-CounterTop-424/trial_T20190907_074124_966890/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0070",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Spatula-None-Drawer-10/trial_T20190907_080730_211959/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0071",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Spatula-None-Drawer-10/trial_T20190907_080800_275989/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0072",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_clean_then_place_in_recep-Spatula-None-Drawer-10/trial_T20190907_080825_222432/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0073",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Bread-None-CounterTop-10/trial_T20190908_091747_866951/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0074",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Bread-None-CounterTop-10/trial_T20190908_091811_414150/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0075",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Bread-None-CounterTop-10/trial_T20190908_091835_825830/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0076",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Lettuce-None-CounterTop-10/trial_T20190909_123133_763972/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0077",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Lettuce-None-CounterTop-10/trial_T20190909_174807_646433/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0078",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Lettuce-None-CounterTop-10/trial_T20190909_174840_771703/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0079",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_121559_082363/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0080",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_121635_622676/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0081",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_121710_650938/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0082",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_183715_299073/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0083",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_183807_477267/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0084",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_183853_958104/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0085",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_114545_244903/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0086",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_114622_738670/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0087",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Pan-None-CounterTop-10/trial_T20190908_114656_768805/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0088",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Potato-None-Microwave-10/trial_T20190907_033157_424297/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0089",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Potato-None-Microwave-10/trial_T20190907_033228_194678/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0090",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Potato-None-Microwave-10/trial_T20190907_033306_962974/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0091",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Tomato-None-Microwave-10/trial_T20190909_102608_318800/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0092",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Tomato-None-Microwave-10/trial_T20190909_102644_926781/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0093",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_cool_then_place_in_recep-Tomato-None-Microwave-10/trial_T20190909_102710_795182/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0094",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-Fridge-10/trial_T20190906_182259_116320/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0095",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-Fridge-10/trial_T20190906_182353_418140/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0096",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-Fridge-10/trial_T20190906_182435_622538/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0097",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-GarbageCan-10/trial_T20190908_145050_918567/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0098",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-GarbageCan-10/trial_T20190908_145143_820541/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0099",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Apple-None-GarbageCan-10/trial_T20190908_145356_918528/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0100",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Cup-None-Cabinet-10/trial_T20190907_083346_800823/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0101",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Cup-None-Cabinet-10/trial_T20190907_083429_887065/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0102",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Cup-None-Cabinet-10/trial_T20190907_083507_594820/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0103",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Egg-None-GarbageCan-10/trial_T20190908_113432_673307/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0104",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Egg-None-GarbageCan-10/trial_T20190908_113523_123938/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0105",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Egg-None-GarbageCan-10/trial_T20190908_113610_425142/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0106",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_021100_341887/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0107",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_021200_669381/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0108",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-Cabinet-10/trial_T20190909_021247_306737/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0109",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_171806_406231/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0110",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_171850_960211/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0111",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Mug-None-CoffeeMachine-10/trial_T20190907_171933_349922/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0112",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Potato-None-GarbageCan-10/trial_T20190907_161745_664033/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0113",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Potato-None-GarbageCan-10/trial_T20190907_161853_945788/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0114",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Tomato-None-GarbageCan-10/trial_T20190908_225046_020282/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0115",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Tomato-None-GarbageCan-10/trial_T20190908_225359_617900/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0116",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_heat_then_place_in_recep-Tomato-None-GarbageCan-10/trial_T20190908_225453_272533/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0117",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-CD-None-Safe-308/trial_T20190907_050942_897916/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0118",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-CD-None-Safe-308/trial_T20190907_051013_060265/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0119",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-CD-None-Safe-308/trial_T20190907_051056_585414/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0120",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-KeyChain-None-Safe-219/trial_T20190909_011803_423115/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0121",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-KeyChain-None-Safe-219/trial_T20190909_012027_782483/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0122",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-PepperShaker-None-Drawer-10/trial_T20190908_010306_215435/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0123",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-PepperShaker-None-Drawer-10/trial_T20190912_221016_460197/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0124",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-PepperShaker-None-Drawer-10/trial_T20190912_221141_608117/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0125",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-Pillow-None-Sofa-219/trial_T20190907_163240_345855/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0126",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-Pillow-None-Sofa-219/trial_T20190907_163327_486300/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0127",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-Pillow-None-Sofa-219/trial_T20190907_163408_914117/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0128",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-SoapBar-None-Cabinet-424/trial_T20190909_081720_491733/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0129",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-SoapBar-None-Cabinet-424/trial_T20190909_081746_857594/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0130",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-SoapBar-None-GarbageCan-424/trial_T20190909_064053_839817/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0131",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-SoapBar-None-GarbageCan-424/trial_T20190909_064221_368939/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0132",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-SoapBar-None-GarbageCan-424/trial_T20190909_064309_357168/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test:0133",
|
||||||
|
"gamefile": "json_2.1.1/valid_unseen/pick_two_obj_and_place-ToiletPaper-None-Cabinet-424/trial_T20190906_202926_527010/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "train:0000",
|
||||||
|
"gamefile": "json_2.1.1/train/look_at_obj_in_light-AlarmClock-None-DeskLamp-305/trial_T20190908_082736_108723/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0001",
|
||||||
|
"gamefile": "json_2.1.1/train/look_at_obj_in_light-CD-None-DeskLamp-304/trial_T20190907_185649_782438/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0002",
|
||||||
|
"gamefile": "json_2.1.1/train/look_at_obj_in_light-CD-None-DeskLamp-320/trial_T20190907_224439_174735/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0003",
|
||||||
|
"gamefile": "json_2.1.1/train/look_at_obj_in_light-Pillow-None-DeskLamp-316/trial_T20190908_232421_645610/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0004",
|
||||||
|
"gamefile": "json_2.1.1/train/look_at_obj_in_light-Statue-None-DeskLamp-319/trial_T20190907_035546_167548/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0005",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-CellPhone-None-Shelf-313/trial_T20190908_123725_452958/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0006",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-Newspaper-None-Sofa-211/trial_T20190906_175004_203092/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0007",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-Pencil-None-Desk-302/trial_T20190908_032836_462632/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0008",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-SoapBar-None-GarbageCan-416/trial_T20190908_020839_714699/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0009",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-Statue-None-CoffeeTable-222/trial_T20190907_131249_788749/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0010",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-ToiletPaper-None-ToiletPaperHanger-406/trial_T20190908_122807_136741/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0011",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_and_place_simple-ToiletPaper-None-ToiletPaperHanger-415/trial_T20190908_050443_333939/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0012",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Apple-None-DiningTable-4/trial_T20190908_104413_450768/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0013",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-DishSponge-None-Shelf-20/trial_T20190907_222429_992578/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0014",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-DishSponge-None-Shelf-401/trial_T20190908_072225_397518/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0015",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Kettle-None-Cabinet-2/trial_T20190909_043103_418752/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0016",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Knife-None-Drawer-22/trial_T20190907_224827_746945/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0017",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Lettuce-None-DiningTable-20/trial_T20190906_191148_519826/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0018",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Lettuce-None-Fridge-13/trial_T20190908_203022_601787/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0019",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Plate-None-Fridge-5/trial_T20190909_112954_869911/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0020",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_clean_then_place_in_recep-Spoon-None-DiningTable-18/trial_T20190909_102159_277894/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0021",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Bread-None-CounterTop-1/trial_T20190908_212439_711334/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0022",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Bread-None-CounterTop-15/trial_T20190909_085448_256298/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0023",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Bread-None-CounterTop-16/trial_T20190908_143948_082471/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0024",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Pan-None-StoveBurner-27/trial_T20190906_212619_469871/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0025",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Plate-None-DiningTable-17/trial_T20190909_122939_032098/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0026",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_cool_then_place_in_recep-Pot-None-CounterTop-1/trial_T20190909_124252_504581/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0027",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Apple-None-Fridge-20/trial_T20190908_013911_274341/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0028",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Egg-None-CounterTop-12/trial_T20190908_215527_416490/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0029",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Mug-None-CoffeeMachine-1/trial_T20190907_222924_821086/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0030",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Mug-None-CoffeeMachine-28/trial_T20190908_062730_537428/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0031",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Plate-None-Cabinet-13/trial_T20190907_062749_759882/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0032",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Potato-None-Fridge-2/trial_T20190909_030845_198194/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0033",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_heat_then_place_in_recep-Tomato-None-CounterTop-26/trial_T20190907_005525_499114/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0034",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_two_obj_and_place-CD-None-Drawer-319/trial_T20190907_145515_348252/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0035",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_two_obj_and_place-Candle-None-Drawer-427/trial_T20190909_043917_251333/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0036",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_two_obj_and_place-KeyChain-None-ArmChair-222/trial_T20190909_100312_677332/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0037",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_two_obj_and_place-Newspaper-None-Sofa-212/trial_T20190908_112632_208041/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "train:0038",
|
||||||
|
"gamefile": "json_2.1.1/train/pick_two_obj_and_place-SaltShaker-None-SideTable-21/trial_T20190909_041626_844806/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "val:0000",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/look_at_obj_in_light-AlarmClock-None-DeskLamp-323/trial_T20190909_044715_250790/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0001",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/look_at_obj_in_light-Bowl-None-DeskLamp-301/trial_T20190909_150719_492274/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0002",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/look_at_obj_in_light-Pillow-None-DeskLamp-323/trial_T20190908_053153_077977/game.tw-pddl",
|
||||||
|
"task_type": "look_at_obj_in_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0003",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_and_place_simple-Mug-None-SideTable-329/trial_T20190909_032318_169393/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0004",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_and_place_simple-Mug-None-SideTable-329/trial_T20190909_032340_274147/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0005",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_and_place_simple-Pencil-None-Desk-310/trial_T20190909_113054_894334/game.tw-pddl",
|
||||||
|
"task_type": "pick_and_place_simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0006",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_clean_then_place_in_recep-ButterKnife-None-Drawer-30/trial_T20190908_052007_212776/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0007",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_clean_then_place_in_recep-ButterKnife-None-Drawer-8/trial_T20190909_124425_112757/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0008",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_clean_then_place_in_recep-SoapBar-None-Cabinet-402/trial_T20190908_055221_984342/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0009",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_clean_then_place_in_recep-SoapBar-None-Toilet-410/trial_T20190906_201106_979461/game.tw-pddl",
|
||||||
|
"task_type": "pick_clean_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0010",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_cool_then_place_in_recep-Apple-None-Microwave-19/trial_T20190906_210937_878489/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0011",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_cool_then_place_in_recep-Plate-None-CounterTop-1/trial_T20190906_205324_559361/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0012",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_cool_then_place_in_recep-Tomato-None-Microwave-18/trial_T20190909_012524_159092/game.tw-pddl",
|
||||||
|
"task_type": "pick_cool_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0013",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_heat_then_place_in_recep-Apple-None-DiningTable-26/trial_T20190907_060234_011675/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0014",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_heat_then_place_in_recep-Tomato-None-Fridge-15/trial_T20190909_020200_054379/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0015",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_heat_then_place_in_recep-Tomato-None-Fridge-23/trial_T20190909_082320_103350/game.tw-pddl",
|
||||||
|
"task_type": "pick_heat_then_place_in_recep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0016",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_two_obj_and_place-Book-None-Desk-313/trial_T20190908_125930_920681/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "val:0017",
|
||||||
|
"gamefile": "json_2.1.1/valid_seen/pick_two_obj_and_place-CreditCard-None-Safe-323/trial_T20190907_001129_214240/game.tw-pddl",
|
||||||
|
"task_type": "pick_two_obj_and_place"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "DocVQA",
|
||||||
|
"manifest_type": "id_split",
|
||||||
|
"source_repo": "lmms-lab/DocVQA",
|
||||||
|
"source_repo_type": "dataset",
|
||||||
|
"source_url": "https://huggingface.co/datasets/lmms-lab/DocVQA",
|
||||||
|
"source_revision": "539088ef8a8ada01ac8e2e6d4e372586748a265e",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"source_split_name": "docvqa_validation_10pct",
|
||||||
|
"split_method": "10% subset sampled from the DocVQA validation split",
|
||||||
|
"counts": {
|
||||||
|
"train": 107,
|
||||||
|
"val": 53,
|
||||||
|
"test": 374
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id",
|
||||||
|
"questionId",
|
||||||
|
"docId",
|
||||||
|
"image_path",
|
||||||
|
"ucsf_document_id",
|
||||||
|
"ucsf_document_page_no",
|
||||||
|
"topic",
|
||||||
|
"source_dataset",
|
||||||
|
"source_config",
|
||||||
|
"source_split",
|
||||||
|
"sample_seed"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"This is a split manifest, not the full DocVQA payload.",
|
||||||
|
"Materialize full CSV rows and image files before evaluation.",
|
||||||
|
"This manifest corresponds to docvqa_validation_10pct.",
|
||||||
|
"All released train/val/test items originate from a 10% subset of the official DocVQA validation split."
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,691 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "62409",
|
||||||
|
"questionId": "62409",
|
||||||
|
"docId": "8554",
|
||||||
|
"image_path": "data/docvqa_images/q62409_d8554.png",
|
||||||
|
"ucsf_document_id": "pgjw0227",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50961",
|
||||||
|
"questionId": "50961",
|
||||||
|
"docId": "549",
|
||||||
|
"image_path": "data/docvqa_images/q50961_d549.png",
|
||||||
|
"ucsf_document_id": "qtjf0226",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46461",
|
||||||
|
"questionId": "46461",
|
||||||
|
"docId": "13361",
|
||||||
|
"image_path": "data/docvqa_images/q46461_d13361.png",
|
||||||
|
"ucsf_document_id": "ysbw0217",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3041",
|
||||||
|
"questionId": "3041",
|
||||||
|
"docId": "1204",
|
||||||
|
"image_path": "data/docvqa_images/q3041_d1204.png",
|
||||||
|
"ucsf_document_id": "xfjv0228",
|
||||||
|
"ucsf_document_page_no": "3",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "41716",
|
||||||
|
"questionId": "41716",
|
||||||
|
"docId": "11835",
|
||||||
|
"image_path": "data/docvqa_images/q41716_d11835.png",
|
||||||
|
"ucsf_document_id": "qjgn0226",
|
||||||
|
"ucsf_document_page_no": "131",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "61123",
|
||||||
|
"questionId": "61123",
|
||||||
|
"docId": "7374",
|
||||||
|
"image_path": "data/docvqa_images/q61123_d7374.png",
|
||||||
|
"ucsf_document_id": "mldg0227",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "43068",
|
||||||
|
"questionId": "43068",
|
||||||
|
"docId": "12393",
|
||||||
|
"image_path": "data/docvqa_images/q43068_d12393.png",
|
||||||
|
"ucsf_document_id": "rmwn0226",
|
||||||
|
"ucsf_document_page_no": "52",
|
||||||
|
"topic": "figure/diagram",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "51221",
|
||||||
|
"questionId": "51221",
|
||||||
|
"docId": "764",
|
||||||
|
"image_path": "data/docvqa_images/q51221_d764.png",
|
||||||
|
"ucsf_document_id": "kzbn0226",
|
||||||
|
"ucsf_document_page_no": "14",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6397",
|
||||||
|
"questionId": "6397",
|
||||||
|
"docId": "2242",
|
||||||
|
"image_path": "data/docvqa_images/q6397_d2242.png",
|
||||||
|
"ucsf_document_id": "jkcn0000",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57428",
|
||||||
|
"questionId": "57428",
|
||||||
|
"docId": "4779",
|
||||||
|
"image_path": "data/docvqa_images/q57428_d4779.png",
|
||||||
|
"ucsf_document_id": "rnbx0223",
|
||||||
|
"ucsf_document_page_no": "208",
|
||||||
|
"topic": "Image/Photo",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3135",
|
||||||
|
"questionId": "3135",
|
||||||
|
"docId": "1221",
|
||||||
|
"image_path": "data/docvqa_images/q3135_d1221.png",
|
||||||
|
"ucsf_document_id": "ngph0227",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "18819",
|
||||||
|
"questionId": "18819",
|
||||||
|
"docId": "5749",
|
||||||
|
"image_path": "data/docvqa_images/q18819_d5749.png",
|
||||||
|
"ucsf_document_id": "jhfd0079",
|
||||||
|
"ucsf_document_page_no": "9",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "15382",
|
||||||
|
"questionId": "15382",
|
||||||
|
"docId": "4890",
|
||||||
|
"image_path": "data/docvqa_images/q15382_d4890.png",
|
||||||
|
"ucsf_document_id": "kjvw0217",
|
||||||
|
"ucsf_document_page_no": "3",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5772",
|
||||||
|
"questionId": "5772",
|
||||||
|
"docId": "1940",
|
||||||
|
"image_path": "data/docvqa_images/q5772_d1940.png",
|
||||||
|
"ucsf_document_id": "pzyw0224",
|
||||||
|
"ucsf_document_page_no": "10",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49077",
|
||||||
|
"questionId": "49077",
|
||||||
|
"docId": "14179",
|
||||||
|
"image_path": "data/docvqa_images/q49077_d14179.png",
|
||||||
|
"ucsf_document_id": "nrxb0228",
|
||||||
|
"ucsf_document_page_no": "3",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58519",
|
||||||
|
"questionId": "58519",
|
||||||
|
"docId": "5347",
|
||||||
|
"image_path": "data/docvqa_images/q58519_d5347.png",
|
||||||
|
"ucsf_document_id": "sjbw0217",
|
||||||
|
"ucsf_document_page_no": "11",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50720",
|
||||||
|
"questionId": "50720",
|
||||||
|
"docId": "281",
|
||||||
|
"image_path": "data/docvqa_images/q50720_d281.png",
|
||||||
|
"ucsf_document_id": "nrcj0037",
|
||||||
|
"ucsf_document_page_no": "7",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56785",
|
||||||
|
"questionId": "56785",
|
||||||
|
"docId": "14289",
|
||||||
|
"image_path": "data/docvqa_images/q56785_d14289.png",
|
||||||
|
"ucsf_document_id": "xkbv0228",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59653",
|
||||||
|
"questionId": "59653",
|
||||||
|
"docId": "6579",
|
||||||
|
"image_path": "data/docvqa_images/q59653_d6579.png",
|
||||||
|
"ucsf_document_id": "mzbx0227",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "61791",
|
||||||
|
"questionId": "61791",
|
||||||
|
"docId": "8072",
|
||||||
|
"image_path": "data/docvqa_images/q61791_d8072.png",
|
||||||
|
"ucsf_document_id": "hfmf0227",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "37229",
|
||||||
|
"questionId": "37229",
|
||||||
|
"docId": "10742",
|
||||||
|
"image_path": "data/docvqa_images/q37229_d10742.png",
|
||||||
|
"ucsf_document_id": "nkcd0227",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "60407",
|
||||||
|
"questionId": "60407",
|
||||||
|
"docId": "7135",
|
||||||
|
"image_path": "data/docvqa_images/q60407_d7135.png",
|
||||||
|
"ucsf_document_id": "gkpk0226",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "64420",
|
||||||
|
"questionId": "64420",
|
||||||
|
"docId": "10230",
|
||||||
|
"image_path": "data/docvqa_images/q64420_d10230.png",
|
||||||
|
"ucsf_document_id": "jnjm0223",
|
||||||
|
"ucsf_document_page_no": "107",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47365",
|
||||||
|
"questionId": "47365",
|
||||||
|
"docId": "13813",
|
||||||
|
"image_path": "data/docvqa_images/q47365_d13813.png",
|
||||||
|
"ucsf_document_id": "nxym0227",
|
||||||
|
"ucsf_document_page_no": "28",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47458",
|
||||||
|
"questionId": "47458",
|
||||||
|
"docId": "13639",
|
||||||
|
"image_path": "data/docvqa_images/q47458_d13639.png",
|
||||||
|
"ucsf_document_id": "skdv0228",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7621",
|
||||||
|
"questionId": "7621",
|
||||||
|
"docId": "2668",
|
||||||
|
"image_path": "data/docvqa_images/q7621_d2668.png",
|
||||||
|
"ucsf_document_id": "flxn0020",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53575",
|
||||||
|
"questionId": "53575",
|
||||||
|
"docId": "2766",
|
||||||
|
"image_path": "data/docvqa_images/q53575_d2766.png",
|
||||||
|
"ucsf_document_id": "hsfn0020",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "free_text|table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "60913",
|
||||||
|
"questionId": "60913",
|
||||||
|
"docId": "7349",
|
||||||
|
"image_path": "data/docvqa_images/q60913_d7349.png",
|
||||||
|
"ucsf_document_id": "jzhd0227",
|
||||||
|
"ucsf_document_page_no": "61",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "60454",
|
||||||
|
"questionId": "60454",
|
||||||
|
"docId": "7163",
|
||||||
|
"image_path": "data/docvqa_images/q60454_d7163.png",
|
||||||
|
"ucsf_document_id": "jgyk0226",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57978",
|
||||||
|
"questionId": "57978",
|
||||||
|
"docId": "4920",
|
||||||
|
"image_path": "data/docvqa_images/q57978_d4920.png",
|
||||||
|
"ucsf_document_id": "lkvw0217",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "64547",
|
||||||
|
"questionId": "64547",
|
||||||
|
"docId": "10361",
|
||||||
|
"image_path": "data/docvqa_images/q64547_d10361.png",
|
||||||
|
"ucsf_document_id": "lpdl0226",
|
||||||
|
"ucsf_document_page_no": "32",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59481",
|
||||||
|
"questionId": "59481",
|
||||||
|
"docId": "6243",
|
||||||
|
"image_path": "data/docvqa_images/q59481_d6243.png",
|
||||||
|
"ucsf_document_id": "psgv0228",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "61472",
|
||||||
|
"questionId": "61472",
|
||||||
|
"docId": "7757",
|
||||||
|
"image_path": "data/docvqa_images/q61472_d7757.png",
|
||||||
|
"ucsf_document_id": "ymkp0227",
|
||||||
|
"ucsf_document_page_no": "13",
|
||||||
|
"topic": "handwritten|table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5673",
|
||||||
|
"questionId": "5673",
|
||||||
|
"docId": "1908",
|
||||||
|
"image_path": "data/docvqa_images/q5673_d1908.png",
|
||||||
|
"ucsf_document_id": "lldj0224",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49109",
|
||||||
|
"questionId": "49109",
|
||||||
|
"docId": "13644",
|
||||||
|
"image_path": "data/docvqa_images/q49109_d13644.png",
|
||||||
|
"ucsf_document_id": "mzdv0228",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46123",
|
||||||
|
"questionId": "46123",
|
||||||
|
"docId": "13503",
|
||||||
|
"image_path": "data/docvqa_images/q46123_d13503.png",
|
||||||
|
"ucsf_document_id": "xmww0217",
|
||||||
|
"ucsf_document_page_no": "17",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48158",
|
||||||
|
"questionId": "48158",
|
||||||
|
"docId": "13976",
|
||||||
|
"image_path": "data/docvqa_images/q48158_d13976.png",
|
||||||
|
"ucsf_document_id": "zqhm0227",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1955",
|
||||||
|
"questionId": "1955",
|
||||||
|
"docId": "892",
|
||||||
|
"image_path": "data/docvqa_images/q1955_d892.png",
|
||||||
|
"ucsf_document_id": "jsbn0226",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8127",
|
||||||
|
"questionId": "8127",
|
||||||
|
"docId": "2754",
|
||||||
|
"image_path": "data/docvqa_images/q8127_d2754.png",
|
||||||
|
"ucsf_document_id": "xtvn0020",
|
||||||
|
"ucsf_document_page_no": "2",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57431",
|
||||||
|
"questionId": "57431",
|
||||||
|
"docId": "4779",
|
||||||
|
"image_path": "data/docvqa_images/q57431_d4779.png",
|
||||||
|
"ucsf_document_id": "rnbx0223",
|
||||||
|
"ucsf_document_page_no": "208",
|
||||||
|
"topic": "Image/Photo",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "64306",
|
||||||
|
"questionId": "64306",
|
||||||
|
"docId": "10149",
|
||||||
|
"image_path": "data/docvqa_images/q64306_d10149.png",
|
||||||
|
"ucsf_document_id": "lpjm0223",
|
||||||
|
"ucsf_document_page_no": "23",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "64887",
|
||||||
|
"questionId": "64887",
|
||||||
|
"docId": "9754",
|
||||||
|
"image_path": "data/docvqa_images/q64887_d9754.png",
|
||||||
|
"ucsf_document_id": "szpg0227",
|
||||||
|
"ucsf_document_page_no": "9",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58680",
|
||||||
|
"questionId": "58680",
|
||||||
|
"docId": "5545",
|
||||||
|
"image_path": "data/docvqa_images/q58680_d5545.png",
|
||||||
|
"ucsf_document_id": "hhwh0078",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5287",
|
||||||
|
"questionId": "5287",
|
||||||
|
"docId": "1785",
|
||||||
|
"image_path": "data/docvqa_images/q5287_d1785.png",
|
||||||
|
"ucsf_document_id": "mtnh0227",
|
||||||
|
"ucsf_document_page_no": "10",
|
||||||
|
"topic": "form",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55471",
|
||||||
|
"questionId": "55471",
|
||||||
|
"docId": "4340",
|
||||||
|
"image_path": "data/docvqa_images/q55471_d4340.png",
|
||||||
|
"ucsf_document_id": "fsgj0223",
|
||||||
|
"ucsf_document_page_no": "96",
|
||||||
|
"topic": "free_text",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53095",
|
||||||
|
"questionId": "53095",
|
||||||
|
"docId": "296",
|
||||||
|
"image_path": "data/docvqa_images/q53095_d296.png",
|
||||||
|
"ucsf_document_id": "qhxj0037",
|
||||||
|
"ucsf_document_page_no": "3",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53726",
|
||||||
|
"questionId": "53726",
|
||||||
|
"docId": "2008",
|
||||||
|
"image_path": "data/docvqa_images/q53726_d2008.png",
|
||||||
|
"ucsf_document_id": "hhnf0094",
|
||||||
|
"ucsf_document_page_no": "5",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57321",
|
||||||
|
"questionId": "57321",
|
||||||
|
"docId": "4722",
|
||||||
|
"image_path": "data/docvqa_images/q57321_d4722.png",
|
||||||
|
"ucsf_document_id": "xybx0223",
|
||||||
|
"ucsf_document_page_no": "32",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "26659",
|
||||||
|
"questionId": "26659",
|
||||||
|
"docId": "7470",
|
||||||
|
"image_path": "data/docvqa_images/q26659_d7470.png",
|
||||||
|
"ucsf_document_id": "lhmg0227",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "38920",
|
||||||
|
"questionId": "38920",
|
||||||
|
"docId": "11157",
|
||||||
|
"image_path": "data/docvqa_images/q38920_d11157.png",
|
||||||
|
"ucsf_document_id": "klnf0227",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "table/list|layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50837",
|
||||||
|
"questionId": "50837",
|
||||||
|
"docId": "14742",
|
||||||
|
"image_path": "data/docvqa_images/q50837_d14742.png",
|
||||||
|
"ucsf_document_id": "ysmc0228",
|
||||||
|
"ucsf_document_page_no": "4",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59615",
|
||||||
|
"questionId": "59615",
|
||||||
|
"docId": "6569",
|
||||||
|
"image_path": "data/docvqa_images/q59615_d6569.png",
|
||||||
|
"ucsf_document_id": "hnnp0227",
|
||||||
|
"ucsf_document_page_no": "45",
|
||||||
|
"topic": "handwritten|table/list|layout",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58687",
|
||||||
|
"questionId": "58687",
|
||||||
|
"docId": "5545",
|
||||||
|
"image_path": "data/docvqa_images/q58687_d5545.png",
|
||||||
|
"ucsf_document_id": "hhwh0078",
|
||||||
|
"ucsf_document_page_no": "1",
|
||||||
|
"topic": "table/list",
|
||||||
|
"source_dataset": "lmms-lab/DocVQA",
|
||||||
|
"source_config": "DocVQA",
|
||||||
|
"source_split": "validation",
|
||||||
|
"sample_seed": "full_validation_5349"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "LiveMathematicianBench",
|
||||||
|
"manifest_type": "id_split",
|
||||||
|
"source_repo": "LiveMathematicianBench/LiveMathematicianBench",
|
||||||
|
"source_repo_type": "dataset",
|
||||||
|
"source_url": "https://huggingface.co/datasets/LiveMathematicianBench/LiveMathematicianBench",
|
||||||
|
"source_revision": "b72450f6ce96c26158d64d945a5d31ef7727be41",
|
||||||
|
"source_files": [
|
||||||
|
"data/202511/qa_202511_final.json",
|
||||||
|
"data/202512/qa_202512_final.json",
|
||||||
|
"data/202601/qa_202601_final.json",
|
||||||
|
"data/202602/qa_202602_final.json"
|
||||||
|
],
|
||||||
|
"split_mode": "ratio",
|
||||||
|
"split_ratio": "2:1:7",
|
||||||
|
"split_seed": 42,
|
||||||
|
"counts": {
|
||||||
|
"train": 35,
|
||||||
|
"val": 18,
|
||||||
|
"test": 124
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id",
|
||||||
|
"month",
|
||||||
|
"no",
|
||||||
|
"paper_link",
|
||||||
|
"source_file"
|
||||||
|
],
|
||||||
|
"id_format": "<month>:<no>",
|
||||||
|
"notes": [
|
||||||
|
"This is an ID split manifest, not the full LiveMathematicianBench payload.",
|
||||||
|
"Materialize full split items from the official LiveMathematicianBench raw qa_*_final.json files before evaluation."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,870 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "202602:12",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 12,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.07171v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:3",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 3,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.01447v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:4",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 4,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.23123v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:20",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 20,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.13212v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:42",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 42,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.09348v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:38",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 38,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.19831v2",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:4",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 4,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.03141v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:4",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 4,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.14368v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:15",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 15,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.17325v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:32",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 32,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.14817v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:51",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 51,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.14581v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:26",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 26,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.19586v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:13",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 13,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.10017v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:1",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 1,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.23137v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:18",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 18,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.10795v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:5",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 5,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.00348v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:19",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 19,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.06951v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:40",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 40,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.20462v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:29",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 29,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10676v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:35",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 35,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08840v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:48",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 48,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.03482v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:52",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 52,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.11246v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:44",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 44,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.10385v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:28",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 28,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.03812v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:43",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 43,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.22555v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:9",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 9,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.19882v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:23",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 23,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.09180v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:21",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 21,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10509v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:5",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 5,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.20164v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:35",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 35,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.15606v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:50",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 50,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.05652v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:13",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 13,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.22861v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:49",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 49,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.07167v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:18",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 18,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.20124v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:15",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 15,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.05327v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:21",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 21,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.04994v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:32",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 32,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.09183v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:34",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 34,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.21118v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:20",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 20,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.16506v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:5",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 5,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.09806v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:40",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 40,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.16535v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:22",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 22,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.07607v2",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:36",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 36,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.12457v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:49",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 49,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.21565v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:10",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 10,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.06484v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:2",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 2,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.07068v4",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:19",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 19,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.18179v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:9",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 9,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.17765v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:6",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 6,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.23079v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:5",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 5,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.20344v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:14",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 14,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.09177v2",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:17",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 17,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.11657v2",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:19",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 19,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.16655v2",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:33",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 33,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.13734v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:18",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 18,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.22960v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:26",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 26,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.06814v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:1",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 1,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.18276v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:30",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 30,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.07260v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:44",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 44,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.01138v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:20",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 20,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.14575v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:13",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 13,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.16910v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:30",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 30,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.12140v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:40",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 40,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.05146v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:29",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 29,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.12846v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:11",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 11,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.17548v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:9",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 9,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08817v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:18",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 18,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.01797v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:1",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 1,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.20055v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:4",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 4,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.21223v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:6",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 6,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.14959v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:38",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 38,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.08398v2",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:10",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 10,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.15524v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:11",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 11,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.11045v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:45",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 45,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08395v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:12",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 12,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.11877v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:47",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 47,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.09683v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:21",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 21,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.21288v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:16",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 16,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.05008v2",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:3",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 3,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.13450v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:37",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 37,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.09443v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:12",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 12,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.04978v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:39",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 39,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.19003v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:8",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 8,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.19754v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:11",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 11,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.13552v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:25",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 25,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.10548v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:17",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 17,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.02655v2",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:36",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 36,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.13001v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:43",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 43,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.06897v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:6",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 6,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.04747v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:35",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 35,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.20938v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:11",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 11,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.03294v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:23",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 23,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.09201v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:7",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 7,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.02859v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:39",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 39,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.21659v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:12",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 12,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.00690v3",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:2",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 2,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.19681v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:43",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 43,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.10820v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:24",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 24,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.08680v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:34",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 34,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.07318v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:28",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 28,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.11294v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:27",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 27,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.05692v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:42",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 42,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.09749v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:22",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 22,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.11658v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:17",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 17,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.22504v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:48",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 48,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.08760v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:28",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 28,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.11595v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:3",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 3,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.17369v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:31",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 31,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.23668v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:27",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 27,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.16505v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:24",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 24,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.12549v2",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:8",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 8,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.12657v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:9",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 9,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.09015v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:28",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 28,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.14825v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:25",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 25,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.16048v3",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:23",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 23,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.06595v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:13",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 13,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.12261v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:27",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 27,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.04407v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:7",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 7,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.09490v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:29",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 29,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08562v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:34",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 34,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.09598v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:42",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 42,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.10845v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:7",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 7,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.13976v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:29",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 29,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.03722v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:37",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 37,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.08644v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "202602:22",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 22,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10700v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:8",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 8,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08863v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:16",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 16,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.15668v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:44",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 44,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.21267v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:14",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 14,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.13447v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:30",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 30,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.16692v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:2",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 2,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.22933v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:41",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 41,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.01164v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:23",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 23,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.02528v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:20",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 20,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.02963v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:22",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 22,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.03984v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:14",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 14,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.22459v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:26",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 26,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.07817v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:3",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 3,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.11409v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:33",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 33,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.07747v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:7",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 7,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.22912v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:27",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 27,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.13968v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:31",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 31,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.15528v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:41",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 41,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10707v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:25",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 25,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.04531v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:10",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 10,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.17863v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:16",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 16,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.02723v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:16",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 16,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.11601v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:2",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 2,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.16120v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:24",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 24,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.08391v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:32",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 32,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.23224v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:47",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 47,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.10391v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:46",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 46,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.13727v2",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:21",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 21,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.12835v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:33",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 33,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.19500v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:45",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 45,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.23912v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:26",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 26,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.14658v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:41",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 41,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.15177v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:38",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 38,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.07817v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:14",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 14,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.08704v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "202602:8",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 8,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.19529v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:50",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 50,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.15277v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:36",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 36,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.06696v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:1",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 1,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.04651v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:31",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 31,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.10298v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202511:17",
|
||||||
|
"month": "202511",
|
||||||
|
"no": 17,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2511.13215v1",
|
||||||
|
"source_file": "data/202511/qa_202511_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:37",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 37,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.20498v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:39",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 39,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.06601v2",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:25",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 25,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.10996v3",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:24",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 24,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.12250v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:45",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 45,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.12113v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:19",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 19,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.00779v1",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:10",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 10,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.07073v2",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202601:46",
|
||||||
|
"month": "202601",
|
||||||
|
"no": 46,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2601.07793v2",
|
||||||
|
"source_file": "data/202601/qa_202601_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:15",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 15,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.16165v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:15",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 15,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.05303v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202602:6",
|
||||||
|
"month": "202602",
|
||||||
|
"no": 6,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2602.01571v1",
|
||||||
|
"source_file": "data/202602/qa_202602_final.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "202512:46",
|
||||||
|
"month": "202512",
|
||||||
|
"no": 46,
|
||||||
|
"paper_link": "http://arxiv.org/abs/2512.05945v1",
|
||||||
|
"source_file": "data/202512/qa_202512_final.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "OfficeQA",
|
||||||
|
"manifest_type": "id_split",
|
||||||
|
"source_repo": "databricks/officeqa",
|
||||||
|
"source_repo_type": "dataset",
|
||||||
|
"source_url": "https://huggingface.co/datasets/databricks/officeqa",
|
||||||
|
"source_revision": "8ecbf18d3833daf4750a903d14963e4c4c1d4cd8",
|
||||||
|
"source_file": "officeqa_full.csv",
|
||||||
|
"source_split_name": "officeqa_split",
|
||||||
|
"counts": {
|
||||||
|
"train": 50,
|
||||||
|
"val": 24,
|
||||||
|
"test": 172
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id",
|
||||||
|
"uid",
|
||||||
|
"category",
|
||||||
|
"source_files",
|
||||||
|
"source_docs",
|
||||||
|
"source_split"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"This is a split manifest, not the full OfficeQA payload.",
|
||||||
|
"The official OfficeQA CSV is gated on Hugging Face; materialization requires authorized access."
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "UID0002",
|
||||||
|
"uid": "UID0002",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1944_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1944-6565?page=18",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0007",
|
||||||
|
"uid": "UID0007",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1950_02.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1950-6637?page=15",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0014",
|
||||||
|
"uid": "UID0014",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1942_07.txt\r\ntreasury_bulletin_2001_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/july-1942-6547?page=76\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2001-7108?page=17&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0017",
|
||||||
|
"uid": "UID0017",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1982_08.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/august-1982-7028?page=13",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0018",
|
||||||
|
"uid": "UID0018",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1985_03.txt\r\ntreasury_bulletin_1986_03.txt\r\ntreasury_bulletin_1987_03.txt\r\ntreasury_bulletin_1988_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1985-7040?page=22\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1986-7045?page=26\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1987-7049?page=24\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1988-7052?page=36",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0019",
|
||||||
|
"uid": "UID0019",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2016_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-2016-533966?page=54\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/september-2016-533966?page=58",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0026",
|
||||||
|
"uid": "UID0026",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1963_01.txt\r\ntreasury_bulletin_1962_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1963-6793?page=88\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1962-6781?page=82&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0028",
|
||||||
|
"uid": "UID0028",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1970_06.txt\r\ntreasury_bulletin_1964_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1970-6882?page=89&deep=true\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1964-6816?page=25&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0030",
|
||||||
|
"uid": "UID0030",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1990_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1990-7062?page=19&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0031",
|
||||||
|
"uid": "UID0031",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1992_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1992-7068?page=158&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0033",
|
||||||
|
"uid": "UID0033",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1977_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1977-6964?page=9",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0034",
|
||||||
|
"uid": "UID0034",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1992_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1992-7069?page=32",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0044",
|
||||||
|
"uid": "UID0044",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1939_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1939-6506?page=61",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0046",
|
||||||
|
"uid": "UID0046",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1988_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1988-7054?page=37",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0049",
|
||||||
|
"uid": "UID0049",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1942_02.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1942-6542?page=19&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0056",
|
||||||
|
"uid": "UID0056",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1991_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1991-7066?page=30&deep=true",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0063",
|
||||||
|
"uid": "UID0063",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1990_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1990-7061?page=127",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0065",
|
||||||
|
"uid": "UID0065",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1998_06.txt\r\ntreasury_bulletin_1995_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1998-7094?page=7\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1995-7084?page=16",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0073",
|
||||||
|
"uid": "UID0073",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1982_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1982-7023?page=24",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0079",
|
||||||
|
"uid": "UID0079",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2011_12.txt\r\ntreasury_bulletin_2013_12.txt\r\ntreasury_bulletin_2015_12.txt\r\ntreasury_bulletin_2017_12.txt\r\ntreasury_bulletin_2019_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2011-7149?page=25\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2013-7155?page=24\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2015-519209?page=23\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2017-575188?page=23\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2019-584842?page=22",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0083",
|
||||||
|
"uid": "UID0083",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1981_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1981-7020?page=24",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0085",
|
||||||
|
"uid": "UID0085",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2019_12.txt\r\ntreasury_bulletin_2018_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2019-584842?page=23\r\n\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2018-581283?page=22",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0087",
|
||||||
|
"uid": "UID0087",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2013_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2013-7155?page=17",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0092",
|
||||||
|
"uid": "UID0092",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1987_12.txt\r\ntreasury_bulletin_1992_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1987-7051?page=69\r\n\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1992-7071?page=84",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0098",
|
||||||
|
"uid": "UID0098",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2020_12.txt\r\ntreasury_bulletin_2024_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2020-598551?page=21\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2024-679984?page=22",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0101",
|
||||||
|
"uid": "UID0101",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2011_12.txt\r\ntreasury_bulletin_2019_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2011-7149?page=25\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2019-584842?page=22",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0110",
|
||||||
|
"uid": "UID0110",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2020_03.txt\r\ntreasury_bulletin_2016_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2020-587316?page=10\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2016-527290?page=9",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0115",
|
||||||
|
"uid": "UID0115",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1980_02.txt\r\ntreasury_bulletin_1981_02.txt\r\ntreasury_bulletin_1982_02.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1980-6998?page=27\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1981-7010?page=38\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1982-7022?page=31",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0122",
|
||||||
|
"uid": "UID0122",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2001_03.txt\r\ntreasury_bulletin_2002_03.txt\r\ntreasury_bulletin_2003_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2001-7105?page=112\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2002-7110?page=115\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2003-7109?page=113",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0123",
|
||||||
|
"uid": "UID0123",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1941_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1941-6540?page=91",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0133",
|
||||||
|
"uid": "UID0133",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2004_09.txt\r\ntreasury_bulletin_2013_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-2004-7119?page=63\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2013-7155?page=68",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0141",
|
||||||
|
"uid": "UID0141",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1962_04.txt\r\ntreasury_bulletin_1963_04.txt\r\ntreasury_bulletin_1964_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1962-6784?page=75\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1963-6796?page=79\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1964-6808?page=82",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0144",
|
||||||
|
"uid": "UID0144",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1980_11.txt\r\ntreasury_bulletin_1981_11.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/november-1980-7007?page=76\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/november-1981-7019?page=67",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0145",
|
||||||
|
"uid": "UID0145",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1943_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1943-6553?page=41",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0150",
|
||||||
|
"uid": "UID0150",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1972_04.txt\r\ntreasury_bulletin_1973_04.txt\r\ntreasury_bulletin_1974_04.txt\r\ntreasury_bulletin_1975_04.txt\r\ntreasury_bulletin_1976_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1972-6905?page=89\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1973-6916?page=91\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1974-6927?page=88\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1975-6940?page=88\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1976-6952?page=104",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0151",
|
||||||
|
"uid": "UID0151",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1953_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1953-6674?page=54",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0162",
|
||||||
|
"uid": "UID0162",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2011_06.txt\r\ntreasury_bulletin_2012_06.txt\r\ntreasury_bulletin_2013_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2011-7129?page=105\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2012-7151?page=105\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2013-7153?page=104",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0163",
|
||||||
|
"uid": "UID0163",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1981_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1981-7020?page=28",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0165",
|
||||||
|
"uid": "UID0165",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2010_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2010-7143?page=49",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0166",
|
||||||
|
"uid": "UID0166",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1943_03.txt\r\ntreasury_bulletin_1944_03.txt\r\ntreasury_bulletin_1945_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1943-6555?page=68\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1944-6567?page=83\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1945-6577?page=71",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0169",
|
||||||
|
"uid": "UID0169",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1982_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1982-7023?page=73",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0189",
|
||||||
|
"uid": "UID0189",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1970_08.txt\r\ntreasury_bulletin_1970_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/august-1970-6884?page=70\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1970-6885?page=70",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0195",
|
||||||
|
"uid": "UID0195",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1956_08.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/august-1956-6715?page=59",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0202",
|
||||||
|
"uid": "UID0202",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1939_07.txt\r\ntreasury_bulletin_1939_08.txt\r\ntreasury_bulletin_1939_09.txt\r\ntreasury_bulletin_1939_10.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/july-1939-6509?page=99\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/august-1939-6510?page=107\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1939-6511?page=60\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/october-1939-6520?page=62",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0212",
|
||||||
|
"uid": "UID0212",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1964_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1964-6805?page=99",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0222",
|
||||||
|
"uid": "UID0222",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2001_06.txt\r\ntreasury_bulletin_2006_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2001-7106?page=50\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2006-7126?page=50",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0228",
|
||||||
|
"uid": "UID0228",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1956_03.txt\r\ntreasury_bulletin_1956_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1956-6710?page=22\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1956-6711?page=22",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0229",
|
||||||
|
"uid": "UID0229",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2005_03.txt\r\ntreasury_bulletin_2006_03.txt\r\ntreasury_bulletin_2007_03.txt\r\ntreasury_bulletin_2008_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2005-7121?page=109\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2006-7125?page=106\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2007-7130?page=109\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2008-7134?page=107",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0238",
|
||||||
|
"uid": "UID0238",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1982_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1982-7023?page=44",
|
||||||
|
"source_split": "train"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0241",
|
||||||
|
"uid": "UID0241",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1963_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1963-6798?page=13",
|
||||||
|
"source_split": "train"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "UID0001",
|
||||||
|
"uid": "UID0001",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1941_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1941-6529?page=15",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0027",
|
||||||
|
"uid": "UID0027",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1970_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1970-6882?page=89&deep=true",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0039",
|
||||||
|
"uid": "UID0039",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2004_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2004-7117?page=20\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-2004-7117?page=21&deep=true",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0041",
|
||||||
|
"uid": "UID0041",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1970_10.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/october-1970-6886?page=35",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0052",
|
||||||
|
"uid": "UID0052",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2000_06.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/june-2000-7102?page=56",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0070",
|
||||||
|
"uid": "UID0070",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1939_01.txt\r\ntreasury_bulletin_1939_02.txt\r\ntreasury_bulletin_1939_03.txt\r\ntreasury_bulletin_1939_04.txt\r\ntreasury_bulletin_1939_05.txt\r\ntreasury_bulletin_1939_06.txt\r\ntreasury_bulletin_1939_07.txt\r\ntreasury_bulletin_1939_08.txt\r\ntreasury_bulletin_1939_09.txt\r\ntreasury_bulletin_1939_10.txt\r\ntreasury_bulletin_1939_11.txt\r\ntreasury_bulletin_1939_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1939-6518?page=81\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1939-6505?page=111\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1939-6519?page=117\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1939-6506?page=95\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/may-1939-6507?page=109\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/june-1939-6508?page=117\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/july-1939-6509?page=109\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/august-1939-6510?page=117\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/september-1939-6511?page=66&deep=true \r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/october-1939-6520?page=68\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/november-1939-6512?page=70\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1939-6513?page=72",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0072",
|
||||||
|
"uid": "UID0072",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2011_12.txt\r\ntreasury_bulletin_2016_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2011-7149?page=58\r\n\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2016-535293?page=57",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0086",
|
||||||
|
"uid": "UID0086",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2022_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2022-627778?page=86",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0091",
|
||||||
|
"uid": "UID0091",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1940_12.txt\r\ntreasury_bulletin_1941_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1940-6528?page=21\r\n\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1941-6540?page=64",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0109",
|
||||||
|
"uid": "UID0109",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_2015_12.txt\r\ntreasury_bulletin_2020_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2015-519209?page=21\r\n \r\n https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-2020-598551?page=24",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0142",
|
||||||
|
"uid": "UID0142",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1944_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1944-6567?page=93",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0154",
|
||||||
|
"uid": "UID0154",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1977_03.txt\r\ntreasury_bulletin_1978_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1977-6963?page=83\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1978-6975?page=84",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0159",
|
||||||
|
"uid": "UID0159",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_2000_09.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/september-2000-7103?page=109",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0161",
|
||||||
|
"uid": "UID0161",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1980_03.txt\r\ntreasury_bulletin_1985_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1980-6999?page=88\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1985-7040?page=48",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0170",
|
||||||
|
"uid": "UID0170",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1960_03.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/march-1960-6759?page=64",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0190",
|
||||||
|
"uid": "UID0190",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1939_10.txt\r\ntreasury_bulletin_1939_11.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/october-1939-6520?page=14\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/november-1939-6512?page=14",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0213",
|
||||||
|
"uid": "UID0213",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1947_04.txt\r\ntreasury_bulletin_1948_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1947-6603?page=28\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1948-6615?page=18",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0217",
|
||||||
|
"uid": "UID0217",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1963_10.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/october-1963-6802?page=15",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0220",
|
||||||
|
"uid": "UID0220",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1939_02.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1939-6505?page=25",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0233",
|
||||||
|
"uid": "UID0233",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1948_04.txt\r\ntreasury_bulletin_1958_04.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1948-6615?page=42\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1958-6735?page=54",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0234",
|
||||||
|
"uid": "UID0234",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1958_01.txt\r\ntreasury_bulletin_1958_02.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1958-6732?page=28\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/february-1958-6733?page=32",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0235",
|
||||||
|
"uid": "UID0235",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1948_04.txt\r\ntreasury_bulletin_1948_05.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/april-1948-6615?page=27\r\nhttps://fraser.stlouisfed.org/title/treasury-bulletin-407/may-1948-6616?page=27",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0239",
|
||||||
|
"uid": "UID0239",
|
||||||
|
"category": "easy",
|
||||||
|
"source_files": "treasury_bulletin_1953_01.txt\r\ntreasury_bulletin_1954_01.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1953-6672?page=62\r\nhttp://fraser.stlouisfed.org/title/treasury-bulletin-407/january-1954-6684?page=51",
|
||||||
|
"source_split": "val"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "UID0240",
|
||||||
|
"uid": "UID0240",
|
||||||
|
"category": "hard",
|
||||||
|
"source_files": "treasury_bulletin_1957_12.txt",
|
||||||
|
"source_docs": "https://fraser.stlouisfed.org/title/treasury-bulletin-407/december-1957-6731?page=26",
|
||||||
|
"source_split": "val"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "SearchQA",
|
||||||
|
"manifest_type": "id_split",
|
||||||
|
"source_repo": "lucadiliello/searchqa",
|
||||||
|
"source_repo_type": "dataset",
|
||||||
|
"source_url": "https://huggingface.co/datasets/lucadiliello/searchqa",
|
||||||
|
"source_id_field": "key",
|
||||||
|
"counts": {
|
||||||
|
"train": 400,
|
||||||
|
"val": 200,
|
||||||
|
"test": 1400
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"This is a split manifest, not the full SearchQA payload.",
|
||||||
|
"Materialize full split items from lucadiliello/searchqa before evaluation.",
|
||||||
|
"The IDs in items.json exactly match the key field in lucadiliello/searchqa."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "SpreadsheetBench",
|
||||||
|
"manifest_type": "id_split",
|
||||||
|
"source_repo": "KAKA22/SpreadsheetBench",
|
||||||
|
"source_repo_type": "dataset",
|
||||||
|
"source_url": "https://huggingface.co/datasets/KAKA22/SpreadsheetBench",
|
||||||
|
"source_revision": "ab0b742b0fc95b946f212d80ac7771b5531272e4",
|
||||||
|
"source_file": "spreadsheetbench_verified_400.tar.gz",
|
||||||
|
"source_split_name": "spreadsheetbench_split",
|
||||||
|
"counts": {
|
||||||
|
"train": 80,
|
||||||
|
"val": 40,
|
||||||
|
"test": 280
|
||||||
|
},
|
||||||
|
"item_fields": [
|
||||||
|
"id",
|
||||||
|
"spreadsheet_path",
|
||||||
|
"instruction_type"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"This is a split manifest, not the full SpreadsheetBench payload.",
|
||||||
|
"Materialize full task JSON rows plus spreadsheet files from SpreadsheetBench Verified 400 before evaluation."
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "32438",
|
||||||
|
"spreadsheet_path": "spreadsheet/32438",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "398-14",
|
||||||
|
"spreadsheet_path": "spreadsheet/398-14",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47766",
|
||||||
|
"spreadsheet_path": "spreadsheet/47766",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48365",
|
||||||
|
"spreadsheet_path": "spreadsheet/48365",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "32255",
|
||||||
|
"spreadsheet_path": "spreadsheet/32255",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10747",
|
||||||
|
"spreadsheet_path": "spreadsheet/10747",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50916",
|
||||||
|
"spreadsheet_path": "spreadsheet/50916",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "577-40",
|
||||||
|
"spreadsheet_path": "spreadsheet/577-40",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "35742",
|
||||||
|
"spreadsheet_path": "spreadsheet/35742",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46121",
|
||||||
|
"spreadsheet_path": "spreadsheet/46121",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "51090",
|
||||||
|
"spreadsheet_path": "spreadsheet/51090",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "51249",
|
||||||
|
"spreadsheet_path": "spreadsheet/51249",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "82-30",
|
||||||
|
"spreadsheet_path": "spreadsheet/82-30",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56274",
|
||||||
|
"spreadsheet_path": "spreadsheet/56274",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57445",
|
||||||
|
"spreadsheet_path": "spreadsheet/57445",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46646",
|
||||||
|
"spreadsheet_path": "spreadsheet/46646",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "105-24",
|
||||||
|
"spreadsheet_path": "spreadsheet/105-24",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6239",
|
||||||
|
"spreadsheet_path": "spreadsheet/6239",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "414-20",
|
||||||
|
"spreadsheet_path": "spreadsheet/414-20",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "165-23",
|
||||||
|
"spreadsheet_path": "spreadsheet/165-23",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "40892",
|
||||||
|
"spreadsheet_path": "spreadsheet/40892",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48745",
|
||||||
|
"spreadsheet_path": "spreadsheet/48745",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "32612",
|
||||||
|
"spreadsheet_path": "spreadsheet/32612",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "325-44",
|
||||||
|
"spreadsheet_path": "spreadsheet/325-44",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "262-17",
|
||||||
|
"spreadsheet_path": "spreadsheet/262-17",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "141-20",
|
||||||
|
"spreadsheet_path": "spreadsheet/141-20",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "52216",
|
||||||
|
"spreadsheet_path": "spreadsheet/52216",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22-47",
|
||||||
|
"spreadsheet_path": "spreadsheet/22-47",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55421",
|
||||||
|
"spreadsheet_path": "spreadsheet/55421",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56427",
|
||||||
|
"spreadsheet_path": "spreadsheet/56427",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "36097",
|
||||||
|
"spreadsheet_path": "spreadsheet/36097",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "32902",
|
||||||
|
"spreadsheet_path": "spreadsheet/32902",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "32023",
|
||||||
|
"spreadsheet_path": "spreadsheet/32023",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1818",
|
||||||
|
"spreadsheet_path": "spreadsheet/1818",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "170-13",
|
||||||
|
"spreadsheet_path": "spreadsheet/170-13",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "66-24",
|
||||||
|
"spreadsheet_path": "spreadsheet/66-24",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58949",
|
||||||
|
"spreadsheet_path": "spreadsheet/58949",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "42354",
|
||||||
|
"spreadsheet_path": "spreadsheet/42354",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "194-19",
|
||||||
|
"spreadsheet_path": "spreadsheet/194-19",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31915",
|
||||||
|
"spreadsheet_path": "spreadsheet/31915",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58499",
|
||||||
|
"spreadsheet_path": "spreadsheet/58499",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "45372",
|
||||||
|
"spreadsheet_path": "spreadsheet/45372",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "11842",
|
||||||
|
"spreadsheet_path": "spreadsheet/11842",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57558",
|
||||||
|
"spreadsheet_path": "spreadsheet/57558",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "472-15",
|
||||||
|
"spreadsheet_path": "spreadsheet/472-15",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55060",
|
||||||
|
"spreadsheet_path": "spreadsheet/55060",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31011",
|
||||||
|
"spreadsheet_path": "spreadsheet/31011",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "408-39",
|
||||||
|
"spreadsheet_path": "spreadsheet/408-39",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "54085",
|
||||||
|
"spreadsheet_path": "spreadsheet/54085",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "39903",
|
||||||
|
"spreadsheet_path": "spreadsheet/39903",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48983",
|
||||||
|
"spreadsheet_path": "spreadsheet/48983",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "108-24",
|
||||||
|
"spreadsheet_path": "spreadsheet/108-24",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58484",
|
||||||
|
"spreadsheet_path": "spreadsheet/58484",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "118-50",
|
||||||
|
"spreadsheet_path": "spreadsheet/118-50",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10452",
|
||||||
|
"spreadsheet_path": "spreadsheet/10452",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "39931",
|
||||||
|
"spreadsheet_path": "spreadsheet/39931",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3413",
|
||||||
|
"spreadsheet_path": "spreadsheet/3413",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "247-24",
|
||||||
|
"spreadsheet_path": "spreadsheet/247-24",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56786",
|
||||||
|
"spreadsheet_path": "spreadsheet/56786",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55965",
|
||||||
|
"spreadsheet_path": "spreadsheet/55965",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "379-36",
|
||||||
|
"spreadsheet_path": "spreadsheet/379-36",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58109",
|
||||||
|
"spreadsheet_path": "spreadsheet/58109",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "433-47",
|
||||||
|
"spreadsheet_path": "spreadsheet/433-47",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "192-22",
|
||||||
|
"spreadsheet_path": "spreadsheet/192-22",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49333",
|
||||||
|
"spreadsheet_path": "spreadsheet/49333",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "493-18",
|
||||||
|
"spreadsheet_path": "spreadsheet/493-18",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "54638",
|
||||||
|
"spreadsheet_path": "spreadsheet/54638",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "34033",
|
||||||
|
"spreadsheet_path": "spreadsheet/34033",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "30930",
|
||||||
|
"spreadsheet_path": "spreadsheet/30930",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "585-41",
|
||||||
|
"spreadsheet_path": "spreadsheet/585-41",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "32337",
|
||||||
|
"spreadsheet_path": "spreadsheet/32337",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55427",
|
||||||
|
"spreadsheet_path": "spreadsheet/55427",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "263-1",
|
||||||
|
"spreadsheet_path": "spreadsheet/263-1",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "254-34",
|
||||||
|
"spreadsheet_path": "spreadsheet/254-34",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57113",
|
||||||
|
"spreadsheet_path": "spreadsheet/57113",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57743",
|
||||||
|
"spreadsheet_path": "spreadsheet/57743",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "43589",
|
||||||
|
"spreadsheet_path": "spreadsheet/43589",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "250-20",
|
||||||
|
"spreadsheet_path": "spreadsheet/250-20",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48080",
|
||||||
|
"spreadsheet_path": "spreadsheet/48080",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "370-43",
|
||||||
|
"spreadsheet_path": "spreadsheet/370-43",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "45635",
|
||||||
|
"spreadsheet_path": "spreadsheet/45635",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "560-12",
|
||||||
|
"spreadsheet_path": "spreadsheet/560-12",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55049",
|
||||||
|
"spreadsheet_path": "spreadsheet/55049",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9569",
|
||||||
|
"spreadsheet_path": "spreadsheet/9569",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7902",
|
||||||
|
"spreadsheet_path": "spreadsheet/7902",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "227-40",
|
||||||
|
"spreadsheet_path": "spreadsheet/227-40",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "463-17",
|
||||||
|
"spreadsheet_path": "spreadsheet/463-17",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "54144",
|
||||||
|
"spreadsheet_path": "spreadsheet/54144",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "80-42",
|
||||||
|
"spreadsheet_path": "spreadsheet/80-42",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2768",
|
||||||
|
"spreadsheet_path": "spreadsheet/2768",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "37456",
|
||||||
|
"spreadsheet_path": "spreadsheet/37456",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "12864",
|
||||||
|
"spreadsheet_path": "spreadsheet/12864",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "55979",
|
||||||
|
"spreadsheet_path": "spreadsheet/55979",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48620",
|
||||||
|
"spreadsheet_path": "spreadsheet/48620",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48588",
|
||||||
|
"spreadsheet_path": "spreadsheet/48588",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "395-36",
|
||||||
|
"spreadsheet_path": "spreadsheet/395-36",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "382-10",
|
||||||
|
"spreadsheet_path": "spreadsheet/382-10",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59595",
|
||||||
|
"spreadsheet_path": "spreadsheet/59595",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53383",
|
||||||
|
"spreadsheet_path": "spreadsheet/53383",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48921",
|
||||||
|
"spreadsheet_path": "spreadsheet/48921",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "416-15",
|
||||||
|
"spreadsheet_path": "spreadsheet/416-15",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47798",
|
||||||
|
"spreadsheet_path": "spreadsheet/47798",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56563",
|
||||||
|
"spreadsheet_path": "spreadsheet/56563",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46897",
|
||||||
|
"spreadsheet_path": "spreadsheet/46897",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9726",
|
||||||
|
"spreadsheet_path": "spreadsheet/9726",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50768",
|
||||||
|
"spreadsheet_path": "spreadsheet/50768",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "51-12",
|
||||||
|
"spreadsheet_path": "spreadsheet/51-12",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31628",
|
||||||
|
"spreadsheet_path": "spreadsheet/31628",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "39046",
|
||||||
|
"spreadsheet_path": "spreadsheet/39046",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8942",
|
||||||
|
"spreadsheet_path": "spreadsheet/8942",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "48527",
|
||||||
|
"spreadsheet_path": "spreadsheet/48527",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59196",
|
||||||
|
"spreadsheet_path": "spreadsheet/59196",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6698",
|
||||||
|
"spreadsheet_path": "spreadsheet/6698",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "43436",
|
||||||
|
"spreadsheet_path": "spreadsheet/43436",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "38462",
|
||||||
|
"spreadsheet_path": "spreadsheet/38462",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "402-43",
|
||||||
|
"spreadsheet_path": "spreadsheet/402-43",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "267-18",
|
||||||
|
"spreadsheet_path": "spreadsheet/267-18",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "37378",
|
||||||
|
"spreadsheet_path": "spreadsheet/37378",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53647",
|
||||||
|
"spreadsheet_path": "spreadsheet/53647",
|
||||||
|
"instruction_type": "Cell-Level Manipulation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "142-12",
|
||||||
|
"spreadsheet_path": "spreadsheet/142-12",
|
||||||
|
"instruction_type": "Sheet-Level Manipulation"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -61,6 +61,36 @@ optimizer:
|
|||||||
use_meta_skill: true # Cross-epoch strategy memory
|
use_meta_skill: true # Cross-epoch strategy memory
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Skill-Aware Reflection (optional, off by default)
|
||||||
|
|
||||||
|
EmbodiSkill-style failure routing: the failure analyst classifies each
|
||||||
|
failure pattern as **SKILL_DEFECT** (the rule is wrong or missing → normal
|
||||||
|
gated body edit) or **EXECUTION_LAPSE** (a valid rule exists but was not
|
||||||
|
followed → a short reminder appended to a protected appendix region inside
|
||||||
|
the skill that step-level edits can never modify).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
optimizer:
|
||||||
|
use_skill_aware_reflection: false # Master switch (default off = baseline-identical)
|
||||||
|
skill_aware_appendix_source: both # both | failure_only (paper-faithful S_app)
|
||||||
|
skill_aware_consolidate_threshold: 0 # >0: LLM-compact the appendix past N notes (experimental)
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- The switch is resolved process-wide from the config
|
||||||
|
(`configure_skill_aware_reflection`), so it applies to every benchmark
|
||||||
|
with no per-adapter wiring.
|
||||||
|
- `failure_only` restricts appendix notes to the failure analyst, matching
|
||||||
|
the original S_app formulation; `both` additionally lets the success
|
||||||
|
analyst re-emphasize existing rules.
|
||||||
|
- Appendix notes bypass the validation gate by design and accumulate with
|
||||||
|
order-preserving dedup; lapse-only steps (no body edits) still flush
|
||||||
|
their notes.
|
||||||
|
- Not supported together with `skill_update_mode=rewrite_from_suggestions`
|
||||||
|
or the full-rewrite modes: whole-document rewrites can drop the appendix
|
||||||
|
region.
|
||||||
|
|
||||||
### Evaluation
|
### Evaluation
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
+346
-134
@@ -1,181 +1,393 @@
|
|||||||
# Add a New Benchmark
|
# Add a New Benchmark
|
||||||
|
|
||||||
Extend SkillOpt with your own benchmark in ~100 lines of code.
|
Extend SkillOpt with your own benchmark in ~200 lines of code. We will use
|
||||||
|
a tiny worked example, `docfaithful`, that scores a target model on
|
||||||
|
how faithfully it answers questions grounded in a small reference doc.
|
||||||
|
|
||||||
## Overview
|
> **Working reference.** The easiest way to copy-cargo-cult a new env is
|
||||||
|
> to read [`skillopt/envs/officeqa/`](https://github.com/microsoft/SkillOpt/tree/main/skillopt/envs/officeqa).
|
||||||
|
> Everything below is the same shape, simplified.
|
||||||
|
|
||||||
To add a benchmark, you need:
|
## What you need to build
|
||||||
|
|
||||||
1. **Data Loader** — Loads and splits your dataset
|
To add a benchmark you implement four things:
|
||||||
2. **Environment Adapter** — Executes tasks and returns scores
|
|
||||||
3. **Config** — YAML configuration file
|
|
||||||
|
|
||||||
## Step 1: Create the Benchmark Package
|
1. **A `SplitDataLoader` subclass** — knows how to load train / val / test
|
||||||
|
item dicts from disk.
|
||||||
|
2. **A rollout helper** — runs the target model on a batch of items
|
||||||
|
under the current skill and scores each prediction.
|
||||||
|
3. **An `EnvAdapter` subclass** — wires the loader + rollout helper into
|
||||||
|
SkillOpt's lifecycle (`build_*_env`, `rollout`, `reflect`,
|
||||||
|
`get_task_types`).
|
||||||
|
4. **A YAML config** — references your env name plus the standard
|
||||||
|
train / optimizer / gradient knobs.
|
||||||
|
|
||||||
|
Then one line in `scripts/train.py`'s `_register_builtins()` makes it
|
||||||
|
discoverable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Create the package
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p skillopt/envs/my_benchmark
|
mkdir -p skillopt/envs/docfaithful
|
||||||
touch skillopt/envs/my_benchmark/__init__.py
|
touch skillopt/envs/docfaithful/__init__.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Implement the Data Loader
|
## Step 2 — Implement the data loader
|
||||||
|
|
||||||
Create `skillopt/envs/my_benchmark/loader.py`:
|
`skillopt/envs/docfaithful/loader.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from skillopt.data.base import DataLoader, DataItem
|
from __future__ import annotations
|
||||||
|
|
||||||
class MyBenchmarkDataLoader(DataLoader):
|
import json
|
||||||
"""Load and split your benchmark data."""
|
from pathlib import Path
|
||||||
|
|
||||||
def __init__(self, data_dir: str, **kwargs):
|
from skillopt.datasets.base import SplitDataLoader
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.data_dir = data_dir
|
|
||||||
|
def _normalize(raw: dict) -> dict:
|
||||||
def setup(self, cfg: dict):
|
"""Make sure every item has an ``id``. Other keys are env-specific."""
|
||||||
"""Initialize splits based on config."""
|
return {
|
||||||
self.split_mode = cfg.get('split_mode', 'ratio')
|
"id": str(raw["uid"]),
|
||||||
# Load your data here
|
"question": raw["question"],
|
||||||
self.items = self._load_items()
|
"ground_truth": raw["answer"],
|
||||||
self._create_splits(cfg)
|
"reference_text": raw.get("reference", ""),
|
||||||
|
"task_type": raw.get("category", "docfaithful"),
|
||||||
def _load_items(self) -> list[DataItem]:
|
}
|
||||||
"""Load raw data into DataItem objects."""
|
|
||||||
items = []
|
|
||||||
# TODO: Load your data
|
class DocFaithfulDataLoader(SplitDataLoader):
|
||||||
for entry in your_data:
|
"""Load DocFaithful items from JSON files inside each split dir."""
|
||||||
items.append(DataItem(
|
|
||||||
id=entry['id'],
|
def load_split_items(self, split_path: str) -> list[dict]:
|
||||||
input=entry['question'],
|
# split_path is e.g. data/docfaithful_split/train/
|
||||||
ground_truth=entry['answer'],
|
json_files = sorted(Path(split_path).glob("*.json"))
|
||||||
metadata=entry.get('metadata', {})
|
if not json_files:
|
||||||
))
|
raise FileNotFoundError(f"No .json file found in {split_path}")
|
||||||
return items
|
with json_files[0].open(encoding="utf-8") as f:
|
||||||
|
raw = json.load(f)
|
||||||
def get_split_items(self, split: str) -> list[DataItem]:
|
return [_normalize(item) for item in raw]
|
||||||
"""Return items for a given split (train/valid/test)."""
|
|
||||||
return self.splits[split]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 3: Implement the Environment Adapter
|
Only `load_split_items()` is mandatory. If you also want to support
|
||||||
|
`split_mode="ratio"` (auto-split a single raw file into train/val/test),
|
||||||
|
override `load_raw_items(data_path)` as well — see
|
||||||
|
`skillopt/datasets/base.py` docstrings.
|
||||||
|
|
||||||
Create `skillopt/envs/my_benchmark/env.py`:
|
## Step 3 — Write the rollout helper
|
||||||
|
|
||||||
|
`skillopt/envs/docfaithful/rollout.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from skillopt.envs.base import EnvAdapter, TaskResult
|
from __future__ import annotations
|
||||||
|
|
||||||
class MyBenchmarkEnv(EnvAdapter):
|
import json
|
||||||
"""Execute tasks and evaluate results."""
|
import os
|
||||||
|
from pathlib import Path
|
||||||
def __init__(self, cfg: dict):
|
|
||||||
super().__init__(cfg)
|
from skillopt.model import chat_target
|
||||||
|
|
||||||
async def execute(self, item: DataItem, skill: str, model) -> TaskResult:
|
|
||||||
"""
|
def _score(prediction: str, ground_truth: str) -> tuple[int, float]:
|
||||||
Execute a single task.
|
"""Trivial exact-match scorer. Replace with F1 / ROUGE / LLM-judge."""
|
||||||
|
p = (prediction or "").strip().lower()
|
||||||
Args:
|
g = (ground_truth or "").strip().lower()
|
||||||
item: The data item to process
|
hard = int(p == g and bool(g))
|
||||||
skill: Current skill document content
|
soft = 1.0 if hard else 0.0
|
||||||
model: The target model instance
|
return hard, soft
|
||||||
|
|
||||||
Returns:
|
|
||||||
TaskResult with prediction, score, and trajectory
|
def _rollout_one(item: dict, skill_content: str,
|
||||||
"""
|
*, max_completion_tokens: int) -> dict:
|
||||||
# Build prompt with skill document
|
system = skill_content
|
||||||
prompt = self.build_prompt(item, skill)
|
user = (
|
||||||
|
f"Question: {item['question']}\n\n"
|
||||||
# Get model response
|
f"Reference:\n{item.get('reference_text', '')}\n\n"
|
||||||
response = await model.generate(prompt)
|
"Answer:"
|
||||||
|
)
|
||||||
# Extract prediction
|
prediction, _usage = chat_target(
|
||||||
prediction = self.parse_response(response)
|
system=system,
|
||||||
|
user=user,
|
||||||
# Score against ground truth
|
max_completion_tokens=max_completion_tokens,
|
||||||
score = self.evaluate(prediction, item.ground_truth)
|
)
|
||||||
|
hard, soft = _score(prediction, item.get("ground_truth", ""))
|
||||||
return TaskResult(
|
return {
|
||||||
item_id=item.id,
|
"id": str(item["id"]),
|
||||||
prediction=prediction,
|
"hard": hard,
|
||||||
score=score,
|
"soft": soft,
|
||||||
trajectory=[
|
"predicted_answer": prediction,
|
||||||
{"role": "system", "content": skill},
|
"question": item.get("question", ""),
|
||||||
{"role": "user", "content": item.input},
|
"reference_text": item.get("reference_text", ""),
|
||||||
{"role": "assistant", "content": response}
|
"task_type": item.get("task_type", "docfaithful"),
|
||||||
]
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run_batch(*, items: list[dict], skill_content: str, out_root: str,
|
||||||
|
workers: int = 4, max_completion_tokens: int = 4096) -> list[dict]:
|
||||||
|
"""Run a batch of episodes sequentially or with a thread pool."""
|
||||||
|
os.makedirs(out_root, exist_ok=True)
|
||||||
|
# For brevity we go sequentially — swap in concurrent.futures.ThreadPoolExecutor
|
||||||
|
# when network / model latency dominates.
|
||||||
|
results = [
|
||||||
|
_rollout_one(item, skill_content,
|
||||||
|
max_completion_tokens=max_completion_tokens)
|
||||||
|
for item in items
|
||||||
|
]
|
||||||
|
Path(out_root, "rollouts.json").write_text(
|
||||||
|
json.dumps(results, ensure_ascii=False, indent=2)
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
```
|
||||||
|
|
||||||
|
Two design points worth flagging:
|
||||||
|
|
||||||
|
- **Scoring lives here, not in `EnvAdapter`.** There is no `evaluate()`
|
||||||
|
method on the ABC. Whatever signal you put in `hard` (0/1, or a float
|
||||||
|
in [0, 1] for smoothed reward) and `soft` (float in [0, 1]) is what
|
||||||
|
the optimizer reads.
|
||||||
|
- **Use `skillopt.model.chat_target`**, not raw OpenAI/Claude calls.
|
||||||
|
That routes through whichever **chat** target backend the user
|
||||||
|
configured (`openai_chat` / `claude_chat` / `qwen_chat` /
|
||||||
|
`minimax_chat`) without your adapter caring. Exec-style backends
|
||||||
|
(`codex_exec`, `claude_code_exec`) need env-specific rollout code —
|
||||||
|
see `skillopt/envs/swebench/` for an example.
|
||||||
|
|
||||||
|
## Step 4 — Implement the environment adapter
|
||||||
|
|
||||||
|
`skillopt/envs/docfaithful/adapter.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from skillopt.datasets.base import BatchSpec
|
||||||
|
from skillopt.envs.base import EnvAdapter
|
||||||
|
from skillopt.envs.docfaithful.loader import DocFaithfulDataLoader
|
||||||
|
from skillopt.envs.docfaithful.rollout import run_batch
|
||||||
|
from skillopt.gradient.reflect import run_minibatch_reflect
|
||||||
|
|
||||||
|
|
||||||
|
class DocFaithfulAdapter(EnvAdapter):
|
||||||
|
"""SkillOpt adapter for the DocFaithful benchmark."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
split_dir: str = "",
|
||||||
|
data_path: str = "",
|
||||||
|
split_mode: str = "split_dir",
|
||||||
|
split_ratio: str = "2:1:7",
|
||||||
|
split_seed: int = 42,
|
||||||
|
split_output_dir: str = "",
|
||||||
|
workers: int = 4,
|
||||||
|
analyst_workers: int = 4,
|
||||||
|
failure_only: bool = False,
|
||||||
|
minibatch_size: int = 8,
|
||||||
|
edit_budget: int = 4,
|
||||||
|
seed: int = 42,
|
||||||
|
limit: int = 0,
|
||||||
|
max_completion_tokens: int = 4096,
|
||||||
|
) -> None:
|
||||||
|
self.workers = workers
|
||||||
|
self.analyst_workers = analyst_workers
|
||||||
|
self.failure_only = failure_only
|
||||||
|
self.minibatch_size = minibatch_size
|
||||||
|
self.edit_budget = edit_budget
|
||||||
|
self.max_completion_tokens = int(max_completion_tokens)
|
||||||
|
self.dataloader = DocFaithfulDataLoader(
|
||||||
|
split_dir=split_dir,
|
||||||
|
data_path=data_path,
|
||||||
|
split_mode=split_mode,
|
||||||
|
split_ratio=split_ratio,
|
||||||
|
split_seed=split_seed,
|
||||||
|
split_output_dir=split_output_dir,
|
||||||
|
seed=seed,
|
||||||
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
def evaluate(self, prediction: str, ground_truth: str) -> float:
|
# ── Lifecycle ───────────────────────────────────────────────────────
|
||||||
"""
|
|
||||||
Score a prediction against ground truth.
|
def setup(self, cfg: dict) -> None:
|
||||||
|
super().setup(cfg)
|
||||||
Returns:
|
self.dataloader.setup(cfg)
|
||||||
Float between 0.0 and 1.0
|
|
||||||
"""
|
def get_dataloader(self):
|
||||||
# TODO: Implement your scoring logic
|
return self.dataloader
|
||||||
# Examples: exact match, F1, ANLS, etc.
|
|
||||||
return float(prediction.strip() == ground_truth.strip())
|
# ── Env construction ────────────────────────────────────────────────
|
||||||
|
|
||||||
def build_prompt(self, item, skill: str) -> str:
|
def build_env_from_batch(self, batch: BatchSpec, **kwargs):
|
||||||
"""Combine skill document with task input."""
|
# For dataset-backed envs the "manager" is just the items list.
|
||||||
return f"{skill}\n\n---\n\nQuestion: {item.input}"
|
return list(batch.payload or [])
|
||||||
|
|
||||||
def parse_response(self, response: str) -> str:
|
def build_train_env(self, batch_size: int, seed: int, **kwargs):
|
||||||
"""Extract the answer from model response."""
|
batch = self.dataloader.build_train_batch(
|
||||||
return response.strip()
|
batch_size=batch_size, seed=seed, **kwargs
|
||||||
|
)
|
||||||
|
return self.build_env_from_batch(batch, **kwargs)
|
||||||
|
|
||||||
|
def build_eval_env(self, env_num: int, split: str, seed: int, **kwargs):
|
||||||
|
batch = self.dataloader.build_eval_batch(
|
||||||
|
env_num=env_num, split=split, seed=seed, **kwargs
|
||||||
|
)
|
||||||
|
return self.build_env_from_batch(batch, **kwargs)
|
||||||
|
|
||||||
|
# ── The two real action methods ─────────────────────────────────────
|
||||||
|
|
||||||
|
def rollout(self, env_manager, skill_content: str,
|
||||||
|
out_dir: str, **kwargs) -> list[dict]:
|
||||||
|
items: list[dict] = env_manager
|
||||||
|
return run_batch(
|
||||||
|
items=items,
|
||||||
|
skill_content=skill_content,
|
||||||
|
out_root=out_dir,
|
||||||
|
workers=self.workers,
|
||||||
|
max_completion_tokens=self.max_completion_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
|
def reflect(self, results: list[dict], skill_content: str,
|
||||||
|
out_dir: str, **kwargs) -> list[dict | None]:
|
||||||
|
return run_minibatch_reflect(
|
||||||
|
results=results,
|
||||||
|
skill_content=skill_content,
|
||||||
|
prediction_dir=kwargs.get(
|
||||||
|
"prediction_dir", os.path.join(out_dir, "predictions")
|
||||||
|
),
|
||||||
|
patches_dir=kwargs.get(
|
||||||
|
"patches_dir", os.path.join(out_dir, "patches")
|
||||||
|
),
|
||||||
|
workers=self.analyst_workers,
|
||||||
|
failure_only=self.failure_only,
|
||||||
|
minibatch_size=self.minibatch_size,
|
||||||
|
edit_budget=self.edit_budget,
|
||||||
|
random_seed=kwargs.get("random_seed"),
|
||||||
|
error_system=self.get_error_minibatch_prompt(),
|
||||||
|
success_system=self.get_success_minibatch_prompt(),
|
||||||
|
step_buffer_context=kwargs.get("step_buffer_context", ""),
|
||||||
|
update_mode=getattr(self, "_cfg", {}).get("skill_update_mode", "patch"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_types(self) -> list[str]:
|
||||||
|
seen: list[str] = []
|
||||||
|
for item in (
|
||||||
|
self.dataloader.train_items
|
||||||
|
+ self.dataloader.val_items
|
||||||
|
+ self.dataloader.test_items
|
||||||
|
):
|
||||||
|
tt = str(item.get("task_type") or "docfaithful")
|
||||||
|
if tt not in seen:
|
||||||
|
seen.append(tt)
|
||||||
|
return seen or ["docfaithful"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 4: Register the Benchmark
|
### What the rollout actually does
|
||||||
|
|
||||||
Add to `skillopt/envs/__init__.py`:
|
Look back at `run_batch` from Step 3 — it sends each `item["question"]`
|
||||||
|
to the target model with `skill_content` as the system prompt, scores
|
||||||
|
the answer against `item["ground_truth"]`, and returns a list of dicts:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .my_benchmark.env import MyBenchmarkEnv
|
[
|
||||||
from .my_benchmark.loader import MyBenchmarkDataLoader
|
{"id": "ex_001", "hard": 1, "soft": 0.92,
|
||||||
|
"predicted_answer": "...", "question": "...",
|
||||||
BENCHMARK_REGISTRY = {
|
"reference_text": item["reference_text"]},
|
||||||
# ... existing benchmarks ...
|
{"id": "ex_002", "hard": 0, "soft": 0.13, "fail_reason": "...", ...},
|
||||||
'my_benchmark': {
|
...
|
||||||
'env': MyBenchmarkEnv,
|
]
|
||||||
'loader': MyBenchmarkDataLoader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 5: Create Config
|
The trainer only requires `id`, `hard`, `soft`. The rest is preserved on
|
||||||
|
`RolloutResult.extras` (see `skillopt/types.py`) and is what your
|
||||||
|
`reflect()` consumes via `run_minibatch_reflect`.
|
||||||
|
|
||||||
Create `configs/my_benchmark/default.yaml`:
|
## Step 5 — Register the adapter
|
||||||
|
|
||||||
|
Edit [`scripts/train.py`](https://github.com/microsoft/SkillOpt/blob/main/scripts/train.py)
|
||||||
|
and add to `_register_builtins()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
from skillopt.envs.docfaithful.adapter import DocFaithfulAdapter
|
||||||
|
_ENV_REGISTRY["docfaithful"] = DocFaithfulAdapter
|
||||||
|
except ImportError:
|
||||||
|
pass # docfaithful deps not installed — skip
|
||||||
|
```
|
||||||
|
|
||||||
|
There is **no `BENCHMARK_REGISTRY` dict in `skillopt/envs/__init__.py`** —
|
||||||
|
the registry lives in `scripts/train.py` and is populated lazily so that
|
||||||
|
optional deps don't break `--help`.
|
||||||
|
|
||||||
|
## Step 6 — Create the YAML config
|
||||||
|
|
||||||
|
`configs/docfaithful/default.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
_base_: ['../_base_/default.yaml']
|
_base_: ../_base_/default.yaml # NOTE: string, not list
|
||||||
|
|
||||||
env:
|
model:
|
||||||
name: my_benchmark
|
reasoning_effort: medium
|
||||||
data_path: data/my_benchmark
|
|
||||||
split_mode: ratio
|
|
||||||
split_ratio: "2:1:7"
|
|
||||||
|
|
||||||
train:
|
train:
|
||||||
|
batch_size: 16
|
||||||
|
accumulation: 1
|
||||||
num_epochs: 4
|
num_epochs: 4
|
||||||
batch_size: 40
|
|
||||||
|
gradient:
|
||||||
|
minibatch_size: 8
|
||||||
|
merge_batch_size: 8
|
||||||
|
|
||||||
optimizer:
|
optimizer:
|
||||||
learning_rate: 4
|
learning_rate: 4
|
||||||
lr_scheduler: cosine
|
|
||||||
use_slow_update: true
|
|
||||||
use_meta_skill: true
|
|
||||||
|
|
||||||
gradient:
|
env:
|
||||||
analyst_workers: 16
|
name: docfaithful
|
||||||
|
# Optional: a seed skill document. Create this file (or any markdown
|
||||||
|
# file) yourself before the first run, or omit the key to let SkillOpt
|
||||||
|
# start from an empty skill.
|
||||||
|
skill_init: skillopt/envs/docfaithful/skills/initial.md
|
||||||
|
split_mode: split_dir
|
||||||
|
split_dir: data/docfaithful_split
|
||||||
|
workers: 4
|
||||||
|
max_completion_tokens: 4096
|
||||||
|
limit: 0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 6: Run
|
> ⚠️ `_base_` is currently parsed as a **string path**, not a list. Write
|
||||||
|
> `_base_: ../_base_/default.yaml`, not `_base_: ['../_base_/default.yaml']`.
|
||||||
|
> See [`skillopt/config.py`](https://github.com/microsoft/SkillOpt/blob/main/skillopt/config.py)
|
||||||
|
> if you want to add list-form inheritance.
|
||||||
|
|
||||||
|
## Step 7 — Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts/train.py --config configs/my_benchmark/default.yaml
|
# If you set skill_init above, create the seed skill first:
|
||||||
|
# mkdir -p skillopt/envs/docfaithful/skills
|
||||||
|
# echo "# DocFaithful initial skill" > skillopt/envs/docfaithful/skills/initial.md
|
||||||
|
|
||||||
|
python scripts/train.py --config configs/docfaithful/default.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you get `ValueError: Unknown environment 'docfaithful'. Available: [...]`,
|
||||||
|
you forgot Step 5.
|
||||||
|
|
||||||
|
If you get `TypeError: Can't instantiate abstract class DocFaithfulAdapter`,
|
||||||
|
you forgot to implement one of the five abstract methods on `EnvAdapter`:
|
||||||
|
`build_train_env`, `build_eval_env`, `rollout`, `reflect`,
|
||||||
|
`get_task_types`.
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
!!! tip
|
- Start with `train.batch_size: 4` and `limit: 10` while debugging.
|
||||||
- Use a small `batch_size` (10-20) for initial testing
|
- The `evaluate` half lives **inside your `rollout`**, not as a separate
|
||||||
- The `evaluate()` method is critical — a noisy metric will confuse the optimizer
|
method — there is no `evaluate()` in the `EnvAdapter` ABC. Score the
|
||||||
|
prediction in `run_batch` and put the score on each result dict's
|
||||||
|
`hard` / `soft`.
|
||||||
|
- Noisy scoring kills the optimizer. Spend time on `run_batch`'s scoring
|
||||||
|
before you spend time on prompts.
|
||||||
|
- If your benchmark needs heavy optional deps (selenium, vllm, ...),
|
||||||
|
wrap the registration block with `try / except ImportError` (Step 5)
|
||||||
|
so people without those deps can still `--help`.
|
||||||
|
- Copy `skillopt/envs/_template/` as a starting skeleton — it now
|
||||||
|
implements the real abstract methods.
|
||||||
|
|||||||
+126
-52
@@ -244,18 +244,19 @@
|
|||||||
<a href="#verify">Verify installation</a>
|
<a href="#verify">Verify installation</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<div class="glabel"><span class="num">3</span> Data Preparation</div>
|
<div class="glabel"><span class="num">3</span> Quick Start</div>
|
||||||
<a href="#split-dir">Split directory format</a>
|
<a href="#first-demo">Your first demo</a>
|
||||||
<a href="#item-schema">Item JSON schema</a>
|
|
||||||
<a href="#split-modes">Split modes</a>
|
|
||||||
</div>
|
|
||||||
<div class="group">
|
|
||||||
<div class="glabel"><span class="num">4</span> Quick Start</div>
|
|
||||||
<a href="#train">Train a skill</a>
|
<a href="#train">Train a skill</a>
|
||||||
<a href="#eval">Evaluate a skill</a>
|
<a href="#eval">Evaluate a skill</a>
|
||||||
<a href="#outputs">Output structure</a>
|
<a href="#outputs">Output structure</a>
|
||||||
<a href="#resume">Auto-resume</a>
|
<a href="#resume">Auto-resume</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<div class="glabel"><span class="num">4</span> Run on Your Own Data</div>
|
||||||
|
<a href="#split-dir">Split directory format</a>
|
||||||
|
<a href="#item-schema">Item JSON schema</a>
|
||||||
|
<a href="#split-modes">Split modes</a>
|
||||||
|
</div>
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<div class="glabel"><span class="num">5</span> How It Works</div>
|
<div class="glabel"><span class="num">5</span> How It Works</div>
|
||||||
<a href="#loop">The training loop</a>
|
<a href="#loop">The training loop</a>
|
||||||
@@ -374,12 +375,21 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
<ul>
|
<ul>
|
||||||
<li>Python ≥ 3.10</li>
|
<li>Python ≥ 3.10</li>
|
||||||
<li>Credentials for at least one model backend (Azure OpenAI, OpenAI-compatible, Anthropic, or a local Qwen server)</li>
|
<li>Credentials for at least one model backend (Azure OpenAI, OpenAI-compatible, Anthropic, or a local Qwen server)</li>
|
||||||
<li>Benchmark datasets are <strong>not</strong> bundled — prepare your own splits (see §3)</li>
|
<li>Benchmark datasets are <strong>not</strong> bundled — prepare your own splits (see §4)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="install">
|
<section id="install">
|
||||||
<h2>2.2 Install the Package <a class="anchor" href="#install">#</a></h2>
|
<h2>2.2 Install the Package <a class="anchor" href="#install">#</a></h2>
|
||||||
|
<p><strong>Option A — from PyPI:</strong></p>
|
||||||
|
<pre><code><span class="tok-k">pip</span> install skillopt
|
||||||
|
|
||||||
|
<span class="tok-c"># Optional extras:</span>
|
||||||
|
<span class="tok-k">pip</span> install skillopt[alfworld] <span class="tok-c"># ALFWorld benchmark</span>
|
||||||
|
<span class="tok-k">pip</span> install skillopt[webui] <span class="tok-c"># Gradio monitoring dashboard</span>
|
||||||
|
<span class="tok-k">pip</span> install skillopt[claude] <span class="tok-c"># Claude model backend</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Option B — from source (for development):</strong></p>
|
||||||
<pre><code><span class="tok-k">git</span> clone https://github.com/microsoft/SkillOpt.git
|
<pre><code><span class="tok-k">git</span> clone https://github.com/microsoft/SkillOpt.git
|
||||||
<span class="tok-k">cd</span> SkillOpt
|
<span class="tok-k">cd</span> SkillOpt
|
||||||
<span class="tok-k">pip</span> install -e .
|
<span class="tok-k">pip</span> install -e .
|
||||||
@@ -429,49 +439,62 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
<pre><code><span class="tok-k">python</span> -c <span class="tok-s">"import skillopt; print('SkillOpt ready!')"</span></code></pre>
|
<pre><code><span class="tok-k">python</span> -c <span class="tok-s">"import skillopt; print('SkillOpt ready!')"</span></code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ===================== 3. DATA ===================== -->
|
<!-- ===================== 3. QUICK START ===================== -->
|
||||||
<section id="split-dir">
|
<section id="first-demo">
|
||||||
<h2>3.1 Split Directory Format <a class="anchor" href="#split-dir">#</a></h2>
|
<h2>3.1 Your First Demo <a class="anchor" href="#first-demo">#</a></h2>
|
||||||
<p>With <code>env.split_mode: split_dir</code> (the recommended, deterministic mode), SkillOpt reads a directory containing <code>train/</code>, <code>val/</code>, and <code>test/</code> subfolders, each holding a JSON array of task items:</p>
|
<p><strong>What ships in this repo:</strong> ready-to-use configs and
|
||||||
<pre><code>data/my_split/
|
pretrained skills (<code>ckpt/</code>) for six benchmarks, plus
|
||||||
├─ train/items.json <span class="tok-c"># used for rollout (the "train split")</span>
|
lightweight <em>ID manifests</em> under <code>data/</code>. The manifests
|
||||||
├─ val/items.json <span class="tok-c"># selection split → validation gate (valid_seen)</span>
|
pin exactly which examples each split uses but do <strong>not</strong>
|
||||||
└─ test/items.json <span class="tok-c"># held-out final eval (valid_unseen)</span></code></pre>
|
contain the example contents — so you materialize the data once before
|
||||||
<div class="note info"><span class="nh">Split naming</span>
|
the first run.</p>
|
||||||
<p>Internally the splits are referred to as <code>train</code>, <code>valid_seen</code> (validation/selection), and <code>valid_unseen</code> (test). The <code>--split</code> flag of <code>eval_only.py</code> uses these names.</p>
|
<p><strong>Step 1 — materialize the SearchQA splits</strong> (one-time; downloads the ~6.5 GB source dataset). The manifest IDs match the <code>key</code> field of the
|
||||||
</div>
|
<a href="https://huggingface.co/datasets/lucadiliello/searchqa">lucadiliello/searchqa</a>
|
||||||
|
dataset:</p>
|
||||||
|
<pre><code><span class="tok-k">pip</span> install datasets
|
||||||
|
<span class="tok-k">python</span> - <<'PY'
|
||||||
|
import json, os
|
||||||
|
from datasets import load_dataset
|
||||||
|
|
||||||
|
ds = load_dataset("lucadiliello/searchqa")
|
||||||
|
by_key = {r["key"]: r for split in ds.values() for r in split}
|
||||||
|
|
||||||
|
for split in ["train", "val", "test"]:
|
||||||
|
ids = json.load(open(f"data/searchqa_id_split/{split}/items.json"))
|
||||||
|
items = []
|
||||||
|
for x in ids:
|
||||||
|
r = by_key[x["id"]]
|
||||||
|
items.append({"id": r["key"], "question": r["question"],
|
||||||
|
"context": r["context"], "answers": r["answers"]})
|
||||||
|
os.makedirs(f"data/searchqa_split/{split}", exist_ok=True)
|
||||||
|
json.dump(items, open(f"data/searchqa_split/{split}/items.json", "w"))
|
||||||
|
print(split, len(items))
|
||||||
|
PY</code></pre>
|
||||||
|
<p><strong>Step 2 — train</strong> (4 epochs × batch 40; see §3.2
|
||||||
|
for the CLI reference):</p>
|
||||||
|
<pre><code><span class="tok-k">python</span> scripts/train.py \
|
||||||
|
--config configs/searchqa/default.yaml \
|
||||||
|
--split_dir data/searchqa_split \
|
||||||
|
--azure_openai_endpoint https://your-resource.openai.azure.com/ \
|
||||||
|
--optimizer_model gpt-5.5 \
|
||||||
|
--target_model gpt-5.5</code></pre>
|
||||||
|
<p>Other benchmarks follow the same pattern — materialize from the raw
|
||||||
|
source listed in
|
||||||
|
<a href="https://github.com/microsoft/SkillOpt/blob/main/data/README.md"><code>data/README.md</code></a>
|
||||||
|
(it documents the lookup key per benchmark), then point
|
||||||
|
<code>--split_dir</code> at the result. The one exception is
|
||||||
|
<strong>ALFWorld</strong>, whose bundled
|
||||||
|
<code>data/alfworld_path_split</code> works directly: just
|
||||||
|
<code>pip install -e ".[alfworld]" && alfworld-download</code> and
|
||||||
|
set <code>$ALFWORLD_DATA</code>.</p>
|
||||||
|
<p>To sanity-check your setup <em>without</em> training, evaluate a
|
||||||
|
packaged pretrained skill instead (§3.3 uses
|
||||||
|
<code>ckpt/searchqa/gpt5.5_skill.md</code>), or launch the monitoring
|
||||||
|
WebUI (§8.4).</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="item-schema">
|
|
||||||
<h2>3.2 Item JSON Schema <a class="anchor" href="#item-schema">#</a></h2>
|
|
||||||
<p>Required fields depend on the benchmark; consult <code>skillopt/envs/<benchmark>/dataloader.py</code> for the exact contract. A SearchQA item, for example:</p>
|
|
||||||
<pre><code>[
|
|
||||||
{
|
|
||||||
<span class="tok-f">"id"</span>: <span class="tok-s">"unique_item_id"</span>,
|
|
||||||
<span class="tok-f">"question"</span>: <span class="tok-s">"Who wrote the novel ..."</span>,
|
|
||||||
<span class="tok-f">"context"</span>: <span class="tok-s">"[DOC] relevant passage text ..."</span>,
|
|
||||||
<span class="tok-f">"answers"</span>: [<span class="tok-s">"expected answer"</span>]
|
|
||||||
}
|
|
||||||
]</code></pre>
|
|
||||||
<div class="note warn"><span class="nh">Datasets not included</span>
|
|
||||||
<p>This repository ships no benchmark data. Prepare your own splits in the format above before training.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="split-modes">
|
|
||||||
<h2>3.3 Split Modes <a class="anchor" href="#split-modes">#</a></h2>
|
|
||||||
<div class="table-wrap"><table>
|
|
||||||
<thead><tr><th><code>env.split_mode</code></th><th>Behavior</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td><code>split_dir</code></td><td>Use a pre-built directory with explicit <code>train/val/test</code> folders (set <code>env.split_dir</code>). Deterministic and reproducible.</td></tr>
|
|
||||||
<tr><td><code>ratio</code></td><td>Build a deterministic split on the fly from a single <code>env.data_path</code>, using <code>split_seed</code> (and a train:val:test ratio). Convenient for quick experiments.</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ===================== 4. QUICK START ===================== -->
|
|
||||||
<section id="train">
|
<section id="train">
|
||||||
<h2>4.1 Train a Skill <a class="anchor" href="#train">#</a></h2>
|
<h2>3.2 Train a Skill <a class="anchor" href="#train">#</a></h2>
|
||||||
<pre><code><span class="tok-c"># Minimal SearchQA run</span>
|
<pre><code><span class="tok-c"># Minimal SearchQA run</span>
|
||||||
<span class="tok-k">python</span> scripts/train.py \
|
<span class="tok-k">python</span> scripts/train.py \
|
||||||
<span class="tok-f">--config</span> configs/searchqa/default.yaml \
|
<span class="tok-f">--config</span> configs/searchqa/default.yaml \
|
||||||
@@ -495,7 +518,7 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="eval">
|
<section id="eval">
|
||||||
<h2>4.2 Evaluate a Skill <a class="anchor" href="#eval">#</a></h2>
|
<h2>3.3 Evaluate a Skill <a class="anchor" href="#eval">#</a></h2>
|
||||||
<p>Evaluate any skill document (a packaged reference skill, or a trained run's <code>best_skill.md</code>) without training:</p>
|
<p>Evaluate any skill document (a packaged reference skill, or a trained run's <code>best_skill.md</code>) without training:</p>
|
||||||
<pre><code><span class="tok-c"># Evaluate the packaged GPT-5.5 SearchQA skill on the test split</span>
|
<pre><code><span class="tok-c"># Evaluate the packaged GPT-5.5 SearchQA skill on the test split</span>
|
||||||
<span class="tok-k">python</span> scripts/eval_only.py \
|
<span class="tok-k">python</span> scripts/eval_only.py \
|
||||||
@@ -516,7 +539,7 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="outputs">
|
<section id="outputs">
|
||||||
<h2>4.3 Output Structure <a class="anchor" href="#outputs">#</a></h2>
|
<h2>3.4 Output Structure <a class="anchor" href="#outputs">#</a></h2>
|
||||||
<pre><code>outputs/<run_name>/
|
<pre><code>outputs/<run_name>/
|
||||||
├─ config.json <span class="tok-c"># flattened runtime config</span>
|
├─ config.json <span class="tok-c"># flattened runtime config</span>
|
||||||
├─ history.json <span class="tok-c"># per-step training history</span>
|
├─ history.json <span class="tok-c"># per-step training history</span>
|
||||||
@@ -529,10 +552,58 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="resume">
|
<section id="resume">
|
||||||
<h2>4.4 Auto-Resume <a class="anchor" href="#resume">#</a></h2>
|
<h2>3.5 Auto-Resume <a class="anchor" href="#resume">#</a></h2>
|
||||||
<p>Each completed step persists its state to <code>runtime_state.json</code> and a <code>steps/step_XXXX/</code> directory. Re-running the <em>same command</em> against the same <code>out_root</code> detects finished work and continues from the last completed step — including epoch-boundary slow-update and meta-skill stages.</p>
|
<p>Each completed step persists its state to <code>runtime_state.json</code> and a <code>steps/step_XXXX/</code> directory. Re-running the <em>same command</em> against the same <code>out_root</code> detects finished work and continues from the last completed step — including epoch-boundary slow-update and meta-skill stages.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ===================== 3. DATA ===================== -->
|
||||||
|
<section id="split-dir">
|
||||||
|
<h2>4.1 Split Directory Format <a class="anchor" href="#split-dir">#</a></h2>
|
||||||
|
<p><strong>Bringing your own dataset takes three steps:</strong>
|
||||||
|
(1) create a split directory with <code>train/ val/ test/</code> item
|
||||||
|
files in the format below; (2) make sure each item carries the fields
|
||||||
|
the closest existing benchmark adapter expects (§4.2); (3) point
|
||||||
|
<code>--split_dir</code> at it and train with that benchmark's config.
|
||||||
|
If no existing adapter matches your task shape (different rollout or
|
||||||
|
scoring logic), write a new benchmark adapter instead — see §7.2.</p>
|
||||||
|
|
||||||
|
<p>With <code>env.split_mode: split_dir</code> (the recommended, deterministic mode), SkillOpt reads a directory containing <code>train/</code>, <code>val/</code>, and <code>test/</code> subfolders, each holding a JSON array of task items:</p>
|
||||||
|
<pre><code>data/my_split/
|
||||||
|
├─ train/items.json <span class="tok-c"># used for rollout (the "train split")</span>
|
||||||
|
├─ val/items.json <span class="tok-c"># selection split → validation gate (valid_seen)</span>
|
||||||
|
└─ test/items.json <span class="tok-c"># held-out final eval (valid_unseen)</span></code></pre>
|
||||||
|
<div class="note info"><span class="nh">Split naming</span>
|
||||||
|
<p>Internally the splits are referred to as <code>train</code>, <code>valid_seen</code> (validation/selection), and <code>valid_unseen</code> (test). The <code>--split</code> flag of <code>eval_only.py</code> uses these names.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="item-schema">
|
||||||
|
<h2>4.2 Item JSON Schema <a class="anchor" href="#item-schema">#</a></h2>
|
||||||
|
<p>Required fields depend on the benchmark; consult <code>skillopt/envs/<benchmark>/dataloader.py</code> for the exact contract. A SearchQA item, for example:</p>
|
||||||
|
<pre><code>[
|
||||||
|
{
|
||||||
|
<span class="tok-f">"id"</span>: <span class="tok-s">"unique_item_id"</span>,
|
||||||
|
<span class="tok-f">"question"</span>: <span class="tok-s">"Who wrote the novel ..."</span>,
|
||||||
|
<span class="tok-f">"context"</span>: <span class="tok-s">"[DOC] relevant passage text ..."</span>,
|
||||||
|
<span class="tok-f">"answers"</span>: [<span class="tok-s">"expected answer"</span>]
|
||||||
|
}
|
||||||
|
]</code></pre>
|
||||||
|
<div class="note warn"><span class="nh">Datasets not included</span>
|
||||||
|
<p>This repository ships no benchmark data. Prepare your own splits in the format above before training.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="split-modes">
|
||||||
|
<h2>4.3 Split Modes <a class="anchor" href="#split-modes">#</a></h2>
|
||||||
|
<div class="table-wrap"><table>
|
||||||
|
<thead><tr><th><code>env.split_mode</code></th><th>Behavior</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>split_dir</code></td><td>Use a pre-built directory with explicit <code>train/val/test</code> folders (set <code>env.split_dir</code>). Deterministic and reproducible.</td></tr>
|
||||||
|
<tr><td><code>ratio</code></td><td>Build a deterministic split on the fly from a single <code>env.data_path</code>, using <code>split_seed</code> (and a train:val:test ratio). Convenient for quick experiments.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- ===================== 5. HOW IT WORKS ===================== -->
|
<!-- ===================== 5. HOW IT WORKS ===================== -->
|
||||||
<section id="loop">
|
<section id="loop">
|
||||||
<h2>5.1 The Training Loop <a class="anchor" href="#loop">#</a></h2>
|
<h2>5.1 The Training Loop <a class="anchor" href="#loop">#</a></h2>
|
||||||
@@ -708,6 +779,9 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
<tr><td><code>slow_update_gate_with_selection</code></td><td>bool</td><td class="def">false</td><td>—</td><td><code>false</code> = force-inject guidance; <code>true</code> = gate it on the selection split (see §5.4).</td></tr>
|
<tr><td><code>slow_update_gate_with_selection</code></td><td>bool</td><td class="def">false</td><td>—</td><td><code>false</code> = force-inject guidance; <code>true</code> = gate it on the selection split (see §5.4).</td></tr>
|
||||||
<tr><td><code>longitudinal_pair_policy</code></td><td>str</td><td class="def">mixed</td><td>—</td><td><code>mixed</code> / <code>changed</code> / <code>unchanged</code> — which comparison pairs to keep.</td></tr>
|
<tr><td><code>longitudinal_pair_policy</code></td><td>str</td><td class="def">mixed</td><td>—</td><td><code>mixed</code> / <code>changed</code> / <code>unchanged</code> — which comparison pairs to keep.</td></tr>
|
||||||
<tr><td><code>use_meta_skill</code></td><td>bool</td><td class="def">true</td><td>Meta-learning</td><td>Enable cross-epoch optimizer memory.</td></tr>
|
<tr><td><code>use_meta_skill</code></td><td>bool</td><td class="def">true</td><td>Meta-learning</td><td>Enable cross-epoch optimizer memory.</td></tr>
|
||||||
|
<tr><td><code>use_skill_aware_reflection</code></td><td>bool</td><td class="def">false</td><td>—</td><td>EmbodiSkill-style failure routing: <code>SKILL_DEFECT</code> (rule wrong/missing → gated body edit) vs <code>EXECUTION_LAPSE</code> (valid rule not followed → reminder appended to a protected appendix region that step-level edits never modify). Off = baseline-identical; resolved process-wide, works on every benchmark. Not supported with <code>rewrite_from_suggestions</code> / full-rewrite modes.</td></tr>
|
||||||
|
<tr><td><code>skill_aware_appendix_source</code></td><td>str</td><td class="def">both</td><td>—</td><td><code>both</code> (success analyst may also re-emphasize rules) / <code>failure_only</code> (paper-faithful S_app: failure side only).</td></tr>
|
||||||
|
<tr><td><code>skill_aware_consolidate_threshold</code></td><td>int</td><td class="def">0</td><td>—</td><td><code>>0</code>: LLM-compact the appendix once it exceeds N notes (experimental); <code>0</code> = off.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -737,7 +811,7 @@ skillopt/ <span class="tok-c"># the package</span>
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><code>name</code></td><td>str</td><td class="def">""</td><td>Benchmark name (<code>searchqa</code>, <code>docvqa</code>, <code>alfworld</code>, …). Selects the env module.</td></tr>
|
<tr><td><code>name</code></td><td>str</td><td class="def">""</td><td>Benchmark name (<code>searchqa</code>, <code>docvqa</code>, <code>alfworld</code>, …). Selects the env module.</td></tr>
|
||||||
<tr><td><code>skill_init</code></td><td>str</td><td class="def">""</td><td>Path to a seed skill (empty = start from scratch).</td></tr>
|
<tr><td><code>skill_init</code></td><td>str</td><td class="def">""</td><td>Path to a seed skill (empty = start from scratch).</td></tr>
|
||||||
<tr><td><code>split_mode</code></td><td>str</td><td class="def">ratio</td><td><code>ratio</code> or <code>split_dir</code> (see §3.3).</td></tr>
|
<tr><td><code>split_mode</code></td><td>str</td><td class="def">ratio</td><td><code>ratio</code> or <code>split_dir</code> (see §4.3).</td></tr>
|
||||||
<tr><td><code>split_dir</code></td><td>str</td><td class="def">""</td><td>Pre-split directory (when <code>split_mode = split_dir</code>).</td></tr>
|
<tr><td><code>split_dir</code></td><td>str</td><td class="def">""</td><td>Pre-split directory (when <code>split_mode = split_dir</code>).</td></tr>
|
||||||
<tr><td><code>data_path</code></td><td>str</td><td class="def">""</td><td>Single dataset path (when <code>split_mode = ratio</code>).</td></tr>
|
<tr><td><code>data_path</code></td><td>str</td><td class="def">""</td><td>Single dataset path (when <code>split_mode = ratio</code>).</td></tr>
|
||||||
<tr><td><code>split_seed</code></td><td>int</td><td class="def">42</td><td>Seed for deterministic ratio splitting.</td></tr>
|
<tr><td><code>split_seed</code></td><td>int</td><td class="def">42</td><td>Seed for deterministic ratio splitting.</td></tr>
|
||||||
|
|||||||
+166
-52
@@ -1,81 +1,195 @@
|
|||||||
# API Reference
|
# API Reference
|
||||||
|
|
||||||
|
This page documents the public Python API SkillOpt exposes for **extending the
|
||||||
|
framework** with new environments / benchmarks. For ready-made adapters,
|
||||||
|
browse [`skillopt/envs/`](https://github.com/microsoft/SkillOpt/tree/main/skillopt/envs).
|
||||||
|
|
||||||
|
> **Source of truth.** The classes below are real Python ABCs defined in
|
||||||
|
> `skillopt/envs/base.py`, `skillopt/datasets/base.py`, `skillopt/types.py`,
|
||||||
|
> and `skillopt/evaluation/gate.py`. If this page ever drifts, the code
|
||||||
|
> wins — please open an issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Core Classes
|
## Core Classes
|
||||||
|
|
||||||
### `EnvAdapter`
|
### `EnvAdapter`
|
||||||
|
|
||||||
Abstract base class for benchmark environments.
|
`skillopt/envs/base.py` — abstract adapter that connects the SkillOpt
|
||||||
|
trainer to an environment (benchmark, simulator, REST API, ...).
|
||||||
|
Subclasses **must** implement the five abstract methods below.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from skillopt.datasets.base import BaseDataLoader, BatchSpec
|
||||||
|
|
||||||
class EnvAdapter(ABC):
|
class EnvAdapter(ABC):
|
||||||
async def execute(self, item, skill, model) -> TaskResult
|
|
||||||
def evaluate(self, prediction, ground_truth) -> float
|
# ── Lifecycle hooks (have defaults; override only if needed) ────────
|
||||||
def build_prompt(self, item, skill) -> str
|
|
||||||
|
def setup(self, cfg: dict) -> None: ...
|
||||||
|
def get_dataloader(self) -> BaseDataLoader | None: ...
|
||||||
|
def requires_ray(self) -> bool: ... # default False
|
||||||
|
|
||||||
|
# ── Abstract methods (subclasses MUST implement) ────────────────────
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def build_train_env(self, batch_size: int, seed: int, **kwargs):
|
||||||
|
"""Return an environment-manager object to be passed to rollout()."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def build_eval_env(self, env_num: int, split: str, seed: int, **kwargs):
|
||||||
|
"""Like build_train_env() but for a fixed eval split."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def rollout(self, env_manager, skill_content: str,
|
||||||
|
out_dir: str, **kwargs) -> list[dict]:
|
||||||
|
"""Run a batch of episodes with the current skill.
|
||||||
|
|
||||||
|
Each returned dict MUST contain:
|
||||||
|
- "id": str episode/task identifier
|
||||||
|
- "hard": int (0|1) pass/fail (may be float 0.0-1.0 if smoothed)
|
||||||
|
- "soft": float partial-credit score in [0.0, 1.0]
|
||||||
|
It MAY contain env-specific extra keys (parsed into RolloutResult.extras).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def reflect(self, results: list[dict], skill_content: str,
|
||||||
|
out_dir: str, **kwargs) -> list[dict | None]:
|
||||||
|
"""Turn rollout results into a list of raw patch dicts.
|
||||||
|
|
||||||
|
Each dict (or None to drop the slot) MUST contain:
|
||||||
|
- "patch": {"edits": [...]} a Patch.to_dict() payload
|
||||||
|
- "source_type": "failure" | "success"
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_task_types(self) -> list[str]:
|
||||||
|
"""Distinct task-type strings used for stratified sampling."""
|
||||||
```
|
```
|
||||||
|
|
||||||
### `DataLoader`
|
The trainer also calls a few default-implemented helpers on every adapter:
|
||||||
|
`build_reference_text`, `get_reference_metadata`, `attach_reference_context`,
|
||||||
|
`select_representative_items`, and `build_env_from_batch`. Read the docstrings
|
||||||
|
in `skillopt/envs/base.py` if you need to override any of these — most
|
||||||
|
benchmarks don't.
|
||||||
|
|
||||||
Abstract base class for data loading and splitting.
|
### `BaseDataLoader` / `SplitDataLoader`
|
||||||
|
|
||||||
|
`skillopt/datasets/base.py` — episode-planning loaders.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class DataLoader(ABC):
|
class BaseDataLoader(ABC):
|
||||||
def setup(self, cfg: dict) -> None
|
def setup(self, cfg: dict) -> None: ...
|
||||||
def get_split_items(self, split: str) -> list[DataItem]
|
@abstractmethod
|
||||||
|
def build_train_batch(self, batch_size: int, seed: int, **kwargs) -> BatchSpec: ...
|
||||||
|
@abstractmethod
|
||||||
|
def build_eval_batch(self, env_num: int, split: str, seed: int, **kwargs) -> BatchSpec: ...
|
||||||
|
|
||||||
|
class SplitDataLoader(BaseDataLoader):
|
||||||
|
"""Concrete base for dataset-backed envs with on-disk train/val/test splits.
|
||||||
|
|
||||||
|
Subclasses only need to implement load_split_items() (and optionally
|
||||||
|
load_raw_items() if you also want ``split_mode='ratio'``).
|
||||||
|
"""
|
||||||
|
def load_split_items(self, split_path: str) -> list[dict]: ...
|
||||||
|
def load_raw_items(self, data_path: str) -> list[dict]: ... # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
### `ModelBackend`
|
`SplitDataLoader` handles two layout modes:
|
||||||
|
|
||||||
Abstract base class for LLM backends.
|
| `split_mode` | What it expects |
|
||||||
|
|---|---|
|
||||||
|
| `"split_dir"` | A directory with `train/`, `val/`, `test/` subdirs already split. |
|
||||||
|
| `"ratio"` | A raw dataset path + `split_ratio: "2:1:7"` style string. |
|
||||||
|
|
||||||
|
In either case the items returned by `load_split_items()` are plain
|
||||||
|
`dict` objects with at minimum an `"id"` key.
|
||||||
|
|
||||||
|
### `BatchSpec`
|
||||||
|
|
||||||
|
`skillopt/datasets/base.py` — a slotted dataclass describing one batch
|
||||||
|
request the trainer hands to the adapter.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class ModelBackend(ABC):
|
@dataclass(slots=True)
|
||||||
async def generate(self, messages, **kwargs) -> ModelResponse
|
class BatchSpec:
|
||||||
async def generate_with_tools(self, messages, tools, **kwargs) -> ModelResponse
|
phase: str # "train" | "eval"
|
||||||
```
|
split: str # "train" | "val" | "test" | "valid_seen" | ...
|
||||||
|
seed: int
|
||||||
### `Trainer`
|
batch_size: int
|
||||||
|
payload: object | None = None # what the loader produced (e.g. list[dict])
|
||||||
Main training loop orchestrator.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Trainer:
|
|
||||||
def __init__(self, cfg: dict)
|
|
||||||
async def train(self) -> TrainResult
|
|
||||||
async def evaluate(self, skill: str, split: str) -> EvalResult
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Classes
|
|
||||||
|
|
||||||
### `DataItem`
|
|
||||||
|
|
||||||
```python
|
|
||||||
@dataclass
|
|
||||||
class DataItem:
|
|
||||||
id: str
|
|
||||||
input: str
|
|
||||||
ground_truth: str
|
|
||||||
metadata: dict = field(default_factory=dict)
|
metadata: dict = field(default_factory=dict)
|
||||||
```
|
```
|
||||||
|
|
||||||
### `TaskResult`
|
### `Edit` / `Patch`
|
||||||
|
|
||||||
|
`skillopt/types.py` — the I/O types Reflect / Aggregate / Update produce
|
||||||
|
and consume.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
EditOp = Literal["append", "insert_after", "replace", "delete"]
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TaskResult:
|
class Edit:
|
||||||
item_id: str
|
op: EditOp
|
||||||
prediction: str
|
content: str = ""
|
||||||
score: float
|
target: str = ""
|
||||||
trajectory: list[dict]
|
support_count: int | None = None
|
||||||
|
source_type: Literal["failure", "success"] | None = None
|
||||||
|
merge_level: int | None = None
|
||||||
|
update_origin: str = ""
|
||||||
|
update_target: str = ""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Patch:
|
||||||
|
edits: list[Edit] = field(default_factory=list)
|
||||||
|
reasoning: str = ""
|
||||||
|
ranking_details: dict[str, Any] | None = None
|
||||||
```
|
```
|
||||||
|
|
||||||
### `ModelResponse`
|
Both types support `to_dict()` / `from_dict()` for serialization.
|
||||||
|
|
||||||
```python
|
### `RolloutResult`
|
||||||
@dataclass
|
|
||||||
class ModelResponse:
|
|
||||||
content: str
|
|
||||||
usage: dict
|
|
||||||
model: str
|
|
||||||
```
|
|
||||||
|
|
||||||
For detailed source code, see the [`skillopt/`](https://github.com/microsoft/SkillOpt/tree/main/skillopt) directory.
|
`skillopt/types.py` — the normalised rollout return type. The trainer
|
||||||
|
calls `RolloutResult.from_dict(...)` on each dict returned from
|
||||||
|
`EnvAdapter.rollout()`, so the only **hard** requirement on those dicts is
|
||||||
|
the three keys above (`id`, `hard`, `soft`). Extra fields are preserved
|
||||||
|
into `RolloutResult.extras`.
|
||||||
|
|
||||||
|
### `GateResult` / `GateAction`
|
||||||
|
|
||||||
|
`skillopt/evaluation/gate.py` — the validation-gate decision types
|
||||||
|
returned each epoch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registering an environment
|
||||||
|
|
||||||
|
Environments are not registered via decorators or a `BENCHMARK_REGISTRY`
|
||||||
|
dict. The trainer keeps a lazy registry inside `scripts/train.py` —
|
||||||
|
`_ENV_REGISTRY` — populated by `_register_builtins()`. To add a new env
|
||||||
|
you append a `try / except ImportError` block there. See
|
||||||
|
[Add a New Benchmark](../guide/new-benchmark.md) for the full step-by-step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backends (model layer)
|
||||||
|
|
||||||
|
The model layer lives under `skillopt.model.*`. Backends are selected
|
||||||
|
via `model.optimizer_backend` and `model.target_backend` in the config —
|
||||||
|
not via a base class subclass. Supported values (as of this writing):
|
||||||
|
|
||||||
|
| Backend | Optimizer? | Target? |
|
||||||
|
|---|---|---|
|
||||||
|
| `openai_chat` | ✓ | ✓ |
|
||||||
|
| `claude_chat` | ✓ | ✓ |
|
||||||
|
| `qwen_chat` | ✓ | ✓ |
|
||||||
|
| `minimax_chat` | ✓ | ✓ |
|
||||||
|
| `codex_exec` | — | ✓ |
|
||||||
|
| `claude_code_exec` | — | ✓ |
|
||||||
|
|
||||||
|
See `skillopt/model/backend_config.py` for the live whitelist and
|
||||||
|
[`docs/reference/config.md`](./config.md) for the per-backend
|
||||||
|
configuration keys.
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ Complete reference for all SkillOpt configuration parameters.
|
|||||||
| `model.optimizer` | str | `gpt-5.5` | Optimizer model (for reflection & slow update) |
|
| `model.optimizer` | str | `gpt-5.5` | Optimizer model (for reflection & slow update) |
|
||||||
| `model.target` | str | `gpt-5.5` | Target model (for rollout execution) |
|
| `model.target` | str | `gpt-5.5` | Target model (for rollout execution) |
|
||||||
| `model.reasoning_effort` | str | `medium` | Reasoning effort level |
|
| `model.reasoning_effort` | str | `medium` | Reasoning effort level |
|
||||||
|
| `model.optimizer_backend` | str | `openai_chat` | Optimizer backend: `openai_chat` / `claude_chat` / `qwen_chat` / `minimax_chat` |
|
||||||
|
| `model.target_backend` | str | `openai_chat` | Target backend: chat backends plus execution harnesses |
|
||||||
|
| `model.qwen_chat_base_url` | str | `http://localhost:8000/v1` | Shared Qwen/vLLM OpenAI-compatible endpoint |
|
||||||
|
| `model.qwen_chat_enable_thinking` | bool | `false` | Shared Qwen thinking flag |
|
||||||
|
| `model.optimizer_qwen_chat_base_url` | str | — | Optimizer-specific Qwen/vLLM endpoint; overrides shared `qwen_chat_base_url` |
|
||||||
|
| `model.target_qwen_chat_base_url` | str | — | Target-specific Qwen/vLLM endpoint; overrides shared `qwen_chat_base_url` |
|
||||||
|
|
||||||
## Training (`train`)
|
## Training (`train`)
|
||||||
|
|
||||||
@@ -70,3 +76,10 @@ Complete reference for all SkillOpt configuration parameters.
|
|||||||
| `AZURE_OPENAI_API_KEY` / `model.azure_openai_api_key` | Azure API key |
|
| `AZURE_OPENAI_API_KEY` / `model.azure_openai_api_key` | Azure API key |
|
||||||
| `OPENAI_API_KEY` | OpenAI API key (for `openai_chat` backend) |
|
| `OPENAI_API_KEY` | OpenAI API key (for `openai_chat` backend) |
|
||||||
| `ANTHROPIC_API_KEY` | Anthropic API key (for `claude_code_exec` backend) |
|
| `ANTHROPIC_API_KEY` | Anthropic API key (for `claude_code_exec` backend) |
|
||||||
|
| `QWEN_CHAT_BASE_URL` | Shared local vLLM endpoint for `qwen_chat` |
|
||||||
|
| `QWEN_CHAT_MODEL` | Shared served model name for `qwen_chat` |
|
||||||
|
| `QWEN_CHAT_API_KEY` | Optional API key for the shared Qwen endpoint |
|
||||||
|
| `OPTIMIZER_QWEN_CHAT_BASE_URL` | Optimizer-specific local vLLM endpoint |
|
||||||
|
| `OPTIMIZER_QWEN_CHAT_MODEL` | Optimizer-specific served model name |
|
||||||
|
| `TARGET_QWEN_CHAT_BASE_URL` | Target-specific local vLLM endpoint |
|
||||||
|
| `TARGET_QWEN_CHAT_MODEL` | Target-specific served model name |
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# SkillOpt-Sleep — controllable dreaming architecture
|
||||||
|
|
||||||
|
The sleep engine is no longer a single fixed pipeline. It is a controllable
|
||||||
|
offline "dream / imagination" loop the user steers. This documents the knobs
|
||||||
|
added in the four-stage refactor and how they map to the user's design.
|
||||||
|
|
||||||
|
## The mental model
|
||||||
|
|
||||||
|
> Sleep = an offline imagination rollout. Re-run the user's real
|
||||||
|
> tasks (and dream-augmented variants) many times, look at what went well vs
|
||||||
|
> badly, distil durable rules, and keep only what survives a real-task check —
|
||||||
|
> unless the user opts out of that check.
|
||||||
|
|
||||||
|
## 1. Data splits — train (dream) / val (real) / test (real)
|
||||||
|
|
||||||
|
The anti-overfitting foundation:
|
||||||
|
|
||||||
|
| Split | Source | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| **train** | real tasks **+ dream-augmented** variants | drives reflection (the imagination pool — over-dreaming is fine) |
|
||||||
|
| **val** | **real only**, disjoint from test | gates updates (prevents overfitting) |
|
||||||
|
| **test** | **real only**, disjoint from val | the final held-out measure, kept close to real usage |
|
||||||
|
|
||||||
|
Hard guarantee (unit-tested): a task with `origin='dream'` **never** lands in
|
||||||
|
val or test. `assign_splits(val_fraction, test_fraction)` does the deterministic
|
||||||
|
3-way split; gbrain's own held-out maps to our `test`.
|
||||||
|
|
||||||
|
## 2. The validation gate is optional
|
||||||
|
|
||||||
|
`--gate on` (default): an edit is accepted only if it strictly improves the
|
||||||
|
**val** score — the SkillOpt discipline that blocks regressions and reward
|
||||||
|
hacking.
|
||||||
|
|
||||||
|
`--gate off`: greedy. Edits are kept without the hard val-improvement
|
||||||
|
requirement (the user decides they don't want hard filtering), but val/test
|
||||||
|
movement is still reported (`greedy_improved` / `greedy_regressed` /
|
||||||
|
`greedy_flat`) so nothing is hidden.
|
||||||
|
|
||||||
|
## 3. Slow-update — long-term memory, gate-independent
|
||||||
|
|
||||||
|
Even with the gate off, the engine runs a **slow-update** at the end of the
|
||||||
|
nights: it compares behaviour under the first-night vs final skill across the
|
||||||
|
val tasks and distils durable longitudinal guidance into a **protected field**
|
||||||
|
(`<!-- SLOW_UPDATE_START --> … <!-- SLOW_UPDATE_END -->`, the same markers as
|
||||||
|
the main SkillOpt repo). Step-level edits never touch this field. This is the
|
||||||
|
"short-term experience → long-term memory" consolidation; turning the gate off
|
||||||
|
does not cost you long-term memory.
|
||||||
|
|
||||||
|
## 4. Budget — the user picks the spend
|
||||||
|
|
||||||
|
`--budget-tokens N` / `--budget-minutes M`: the engine auto-plans depth
|
||||||
|
(`nights × rollouts_per_task`) to fit the budget (`plan_depth`). Stops cleanly
|
||||||
|
when exhausted and logs what it skipped — no silent truncation. The whole thing
|
||||||
|
is offline imagination on the user's own quota.
|
||||||
|
|
||||||
|
## 5. Multi-rollout contrastive reflection — the imagination core
|
||||||
|
|
||||||
|
`--rollouts-k K` (K>1): each train task is rolled out K times. The optimizer is
|
||||||
|
shown the **high-scoring vs low-scoring** attempts of the same task and asked
|
||||||
|
what the good ones did that the bad ones didn't, distilling a general rule. This
|
||||||
|
is a far stronger signal than a single failure, and it is exactly the user's
|
||||||
|
"run it many times, learn from the contrast" idea. Tasks with the highest score
|
||||||
|
*spread* (some passed, some failed) are the most informative and are prioritised.
|
||||||
|
|
||||||
|
## 6. Multi-objective reward — accuracy ↑, tokens ↓, latency ↓
|
||||||
|
|
||||||
|
Every rollout records its `tokens` and `latency_ms`.
|
||||||
|
`multi_objective_reward(w_acc, w_tokens, w_latency)` is a weighted reward so a
|
||||||
|
skill can be optimised to be **cheaper and faster**, not only more accurate
|
||||||
|
(cost terms normalised against a reference; default weights = accuracy-only, so
|
||||||
|
existing behaviour is unchanged). This turns "gets better the more you use it"
|
||||||
|
into "more accurate, cheaper, and faster the more you use it".
|
||||||
|
|
||||||
|
## 7. User preferences as a prior
|
||||||
|
|
||||||
|
`--preferences "<free text>"`: injected into the optimizer's reflect prompt as a
|
||||||
|
prior (set on the optimizer model for dual backends), so the user's stated
|
||||||
|
preferences steer what rules get written.
|
||||||
|
|
||||||
|
## How the knobs compose (one command)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m skillopt.sleep.experiments.run_gbrain \
|
||||||
|
--optimizer-backend claude --optimizer-model sonnet \ # strong optimizer
|
||||||
|
--target-backend claude --target-model haiku \ # cheap target (transfer)
|
||||||
|
--seeds thorough-analyst \
|
||||||
|
--gate on \ # or off for greedy
|
||||||
|
--rollouts-k 2 \ # contrastive imagination
|
||||||
|
--budget-tokens 60000 \ # auto-plan depth
|
||||||
|
--preferences "Prefer concise, British English." \ # prior
|
||||||
|
--nights 3
|
||||||
|
```
|
||||||
|
|
||||||
|
All of this is exercised by the deterministic test suite (29 tests) and
|
||||||
|
validated on real Claude + Codex (see `real_api_results.md` / `FINAL_REPORT.md`).
|
||||||
|
|
||||||
|
## Real cross-validation of the new features (Claude ⟷ Codex)
|
||||||
|
|
||||||
|
Three live runs exercised the new code paths on both runtimes (raw logs under
|
||||||
|
`docs/sleep/raw/crosscheck_*.txt`):
|
||||||
|
|
||||||
|
| # | Config | What it proves | Result |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **A** | Claude Sonnet→Haiku, **gate=off**, **rollouts_k=2** | greedy mode + multi-rollout + 3-way split (val & test both reported) | brief-writer **test 0→1.00**, action `greedy_improved`, val=1.0 test=1.0 |
|
||||||
|
| **B** | **Codex**, gate=on, **rollouts_k=2** | new paths on the other runtime | brief-writer **test 0→1.00**, 2-night `accept_new_best`, val+test reported |
|
||||||
|
| **C** | Claude Sonnet→Haiku, thorough-analyst, 3 nights | **slow-update** long-term memory fires | test 0→0.33 (val gate holds nights 2–3) and the slow-update distilled a durable meta-rule |
|
||||||
|
|
||||||
|
The slow-update guidance C produced is the kind of cross-night lesson the field
|
||||||
|
is for — note it is general, not task-specific:
|
||||||
|
|
||||||
|
> *"On character-constrained tasks (≤1200 chars), plan structure before writing:
|
||||||
|
> allocate space per point explicitly and cut until the outline fits, then fill —
|
||||||
|
> never draft freely and trim after."*
|
||||||
|
|
||||||
|
Takeaways confirmed live: the **gate-off greedy path**, the **3-way val/test
|
||||||
|
split**, **multi-rollout** on both runtimes, and the **gate-independent
|
||||||
|
slow-update** all work with real models on both Claude and Codex.
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# SkillOpt-Sleep — final validation report
|
||||||
|
|
||||||
|
> **What this is:** the consolidated, presented results for the SkillOpt-Sleep
|
||||||
|
> Claude Code plugin — a tool that lets a local agent improve itself overnight by
|
||||||
|
> reviewing past sessions, replaying tasks, and consolidating validated memory +
|
||||||
|
> skills behind a held-out gate. Every real-model result here was run on **both
|
||||||
|
> Claude and Codex**, including the honest failures and the bugs they exposed.
|
||||||
|
|
||||||
|
**Date:** 2026-06-07 · **Branch:** `feat/claude-code-sleep-plugin`
|
||||||
|
**Benchmark:** [gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1`
|
||||||
|
(the same public suite gbrain scores its own optimizer against).
|
||||||
|
**Protocol:** a deliberately deficient skill → 1–2 offline "nights" (replay →
|
||||||
|
reflect → bounded **gated** edit) → score the **held-out** task set (never
|
||||||
|
optimized against). Held-out scoring uses a local rule judge — the optimizer
|
||||||
|
never grades itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Headline — clean, all green (full gbrain parity)
|
||||||
|
|
||||||
|
**Strong optimizer (Claude Sonnet 4.6) → weak target (Claude Haiku 4.5)**, fully
|
||||||
|
isolated calls, 3 held-out tasks/seed. All **4** gbrain `skillopt-v1` seeds —
|
||||||
|
matching gbrain's own scorecard coverage:
|
||||||
|
|
||||||
|
| Optimizer → Target | Seed | Flaw | Held-out before → after | Nights |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Sonnet → Haiku | brief-writer | missing structure | **0.00 → 1.00** | 1 |
|
||||||
|
| Sonnet → Haiku | advisor | no verdict | **0.00 → 1.00** | 1 |
|
||||||
|
| Sonnet → Haiku | thorough-analyst | no length discipline | **0.00 → 1.00** | 2 |
|
||||||
|
| Sonnet → Haiku | quick-answerer | never uses tools | **0.00 → 1.00** | 1 |
|
||||||
|
| Codex → Codex (gpt-5.5) | brief-writer | missing structure | **0.00 → 1.00** | 2 |
|
||||||
|
| Codex → Codex (gpt-5.5) | advisor | no verdict | **0.00 → 1.00** | 2 |
|
||||||
|
|
||||||
|
**4/4 Claude seeds reach a perfect held-out score** (gbrain's headline is the same
|
||||||
|
4/4 0→1.00), plus Codex on the text seeds. Every change is gated and staged.
|
||||||
|
|
||||||
|
The `quick-answerer` seed is judged by **real tool use** (`tool_called: search`):
|
||||||
|
the deficient skill says *"never look anything up — answer from memory"*; the
|
||||||
|
optimizer wrote an OVERRIDE rule, and the Haiku target **genuinely invoked a
|
||||||
|
`./search` shell tool** (detected from the tool's own log, not self-reported) →
|
||||||
|
held-out 1.00. The thorough-analyst run shows textbook **2-night convergence**
|
||||||
|
(0.33 → 1.00).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The finding that matters most: the optimizer model is decisive
|
||||||
|
|
||||||
|
This is the direct answer to "let me specify the optimizer and target separately,
|
||||||
|
and watch the skill." It matters a lot:
|
||||||
|
|
||||||
|
| Optimizer | Target | brief-writer | advisor | thorough-analyst |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Haiku** (weak) | Haiku | 1.00 *or* 0.00 (flaky) | 1.00 | 0.33 |
|
||||||
|
| **Sonnet** (strong) | Haiku | **1.00** | **1.00** | **1.00** |
|
||||||
|
|
||||||
|
A weak self-optimizing model (Haiku proposing its own edits) is **unreliable** —
|
||||||
|
it intermittently emits non-JSON and wastes a night, so the same seed scores 1.00
|
||||||
|
on one run and 0.00 on another. A **strong optimizer** (Sonnet) reliably produces
|
||||||
|
clean, concrete edit rules and lifts every seed to 1.00. This is exactly the
|
||||||
|
SkillOpt design (strong optimizer, frozen target) and the reason the
|
||||||
|
optimizer/target split is a first-class feature here.
|
||||||
|
|
||||||
|
**Practical guidance baked into the plugin:** default to a strong optimizer; the
|
||||||
|
sweep's `direct` plan now uses Sonnet→Haiku.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Two real bugs we found by running against live models
|
||||||
|
|
||||||
|
Per gbrain's own lesson ("the bugs that matter only show up when the whole thing
|
||||||
|
actually runs"), the first live runs surfaced two real defects. Both are fixed.
|
||||||
|
|
||||||
|
1. **Ambient-context leak (Claude).** `claude -p` was injecting the user's
|
||||||
|
*global* skills + project `CLAUDE.md` into every optimizer/target call — one
|
||||||
|
reflect call literally returned a 21 KB list of the machine's installed skills
|
||||||
|
instead of JSON edits, so the night produced no edits and the gate rejected.
|
||||||
|
Some early Claude "successes" were partly leak-assisted. **Fix:** run isolated
|
||||||
|
— `--bare --disable-slash-commands --disallowedTools '*'
|
||||||
|
--exclude-dynamic-system-prompt-sections`, clean temp cwd. (Codex was never
|
||||||
|
affected; the real `@openai/codex` binary runs in its own clean context.)
|
||||||
|
|
||||||
|
2. **Wasted nights on transient non-JSON.** A single malformed reply zeroed a
|
||||||
|
night. **Fix:** `reflect()` retries once with a firmer "JSON only" instruction.
|
||||||
|
|
||||||
|
We report these because a tool people build on has to be honest about where it was
|
||||||
|
weak and what changed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Cross-model transfer (the price-difference value prop)
|
||||||
|
|
||||||
|
> *Optimize cheap overnight, deploy anywhere.* A skill is just text, so a good
|
||||||
|
> rewrite should help a model it was never optimized on.
|
||||||
|
|
||||||
|
Optimize on SOURCE, **freeze** the learned skill, evaluate held-out on TARGET with
|
||||||
|
no further optimization. All four pairs are positive — including **across
|
||||||
|
runtimes** (Codex ↔ Claude):
|
||||||
|
|
||||||
|
| Source (optimizer) | Target (deploy) | Seed | Target baseline → transferred | Gain |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Claude Haiku (cheap) | Claude Sonnet (expensive) | brief-writer | 0.00 → **1.00** | +1.00 |
|
||||||
|
| Claude Sonnet | Claude Haiku | brief-writer | 0.00 → **1.00** | +1.00 |
|
||||||
|
| **Codex** | **Claude Haiku** | brief-writer | 0.00 → **1.00** | +1.00 |
|
||||||
|
| **Claude Haiku** | **Codex** | brief-writer | 0.00 → **1.00** | +1.00 |
|
||||||
|
|
||||||
|
**4/4 transfers positive.** A skill optimized on a cheap model deploys for free on
|
||||||
|
an expensive one, and skills move between Codex and Claude — the Sleep-setting
|
||||||
|
analogue of SkillOpt's cross-model and cross-harness transfer tables. This is the
|
||||||
|
quantified answer to "optimize cheap overnight, deploy anywhere."
|
||||||
|
|
||||||
|
Full machine-generated scorecard: [`benchmark_report.md`](benchmark_report.md)
|
||||||
|
(source data `sweep.jsonl`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Reproduce everything
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/garrytan/gbrain-evals /tmp/gbrain-evals
|
||||||
|
cd <repo>/SkillOpt-sleep
|
||||||
|
|
||||||
|
# the clean headline result (strong optimizer -> weak target)
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_gbrain \
|
||||||
|
--optimizer-backend claude --optimizer-model sonnet \
|
||||||
|
--target-backend claude --target-model haiku \
|
||||||
|
--seeds brief-writer,advisor,thorough-analyst \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 --nights 2 --limit-replay 3 --limit-holdout 3
|
||||||
|
|
||||||
|
# Codex self-optimized
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_gbrain --backend codex --seeds brief-writer \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 --nights 2 --limit-replay 3 --limit-holdout 3
|
||||||
|
|
||||||
|
# cross-model transfer
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_transfer \
|
||||||
|
--source-backend claude --source-model haiku --target-backend claude --target-model sonnet \
|
||||||
|
--seeds brief-writer
|
||||||
|
|
||||||
|
# the whole sweep + report
|
||||||
|
python3.12 -m skillopt.sleep.experiments.sweep --plan full \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 --out docs/sleep/sweep.jsonl
|
||||||
|
python3.12 -m skillopt.sleep.experiments.report --in docs/sleep/sweep.jsonl --out docs/sleep/benchmark_report.md
|
||||||
|
|
||||||
|
# deterministic, no API (CI anchor)
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_experiment --persona researcher --assert-improves
|
||||||
|
```
|
||||||
|
|
||||||
|
Raw run logs are under `docs/sleep/raw/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Honest limitations
|
||||||
|
|
||||||
|
- **Latency:** each CLI call is ~14–15 s startup-dominated, so runs are capped at
|
||||||
|
a few tasks/nights. Fine for nightly cron; we note it plainly.
|
||||||
|
- **Weak optimizers are flaky:** use a strong optimizer model (§2).
|
||||||
|
- **Tool-use seed covered honestly:** `quick-answerer` (`tool_called: search`)
|
||||||
|
runs a real tool loop — a callable `./search` shim, detected from its log.
|
||||||
|
Deeper multi-tool / multi-turn workflows are future work.
|
||||||
|
- **Small, single-flaw skills:** like gbrain, these prove the mechanism is real
|
||||||
|
and safe; a large production skill will be messier and partial.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
TITLE:
|
||||||
|
Add SkillOpt-Sleep: nightly offline self-evolution plugins (Claude Code, Codex, Copilot)
|
||||||
|
|
||||||
|
BODY:
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Adds **SkillOpt-Sleep** — a nightly offline "sleep cycle" that gives a local
|
||||||
|
coding agent the deployment-time analogue of training: it reviews past sessions,
|
||||||
|
replays recurring tasks on the user's own API budget, and consolidates what it
|
||||||
|
learns into **validated** long-term memory and skills behind a held-out gate.
|
||||||
|
Synthesizes SkillOpt (validation-gated bounded text edits), Claude Dreams
|
||||||
|
(offline consolidation; review-then-adopt), and the agent-sleep idea
|
||||||
|
(short-term experience -> long-term competence).
|
||||||
|
|
||||||
|
Shipped as plugins for **three agents**, one engine + three thin shells:
|
||||||
|
|
||||||
|
- **Claude Code** — `.claude-plugin` + `/sleep` command + skill + hooks
|
||||||
|
- **Codex** — `~/.codex/prompts/sleep.md` + `~/.agents/skills` + `install.sh`
|
||||||
|
- **Copilot** — a stdlib-only MCP server exposing `sleep_*` tools
|
||||||
|
|
||||||
|
## Design notes
|
||||||
|
|
||||||
|
- **Open-source tool, decoupled from the research code.** The engine lives in the
|
||||||
|
new top-level `skillopt_sleep/` package with **zero dependency** on the paper's
|
||||||
|
`skillopt/` experiment package (the validation gate is vendored).
|
||||||
|
- Controllable: optional gate (`--gate on|off`), train(dream)/val(real)/test(real)
|
||||||
|
splits, slow-update long-term memory, token/time budget, multi-rollout
|
||||||
|
contrastive reflection, multi-objective reward (accuracy/tokens/latency), user
|
||||||
|
preferences, and separate optimizer/target models.
|
||||||
|
|
||||||
|
## Validation (real models)
|
||||||
|
|
||||||
|
On the public [gbrain-evals](https://github.com/garrytan/gbrain-evals)
|
||||||
|
`skillopt-v1` benchmark, deficient skills go **0.00 -> 1.00** on held-out sets
|
||||||
|
with **both Claude and Codex** (all 4 seeds, including a real tool-use loop);
|
||||||
|
cross-model transfer is positive; the gate blocks regressions. Independently
|
||||||
|
load-tested on a fresh non-benchmark persona ("SQL must always include LIMIT"):
|
||||||
|
held-out test **0.00 -> 1.00** on both backends. See `docs/sleep/FINAL_REPORT.md`
|
||||||
|
and `docs/sleep/plugin_load_test.md`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- 29 deterministic unit tests (`tests/test_sleep_engine.py`), no API key required.
|
||||||
|
- `python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves`
|
||||||
|
proves held-out lift and that the gate blocks a harmful edit.
|
||||||
|
|
||||||
|
## Test plan
|
||||||
|
|
||||||
|
- [ ] `python -m unittest tests.test_sleep_engine` (29 pass)
|
||||||
|
- [ ] `python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves`
|
||||||
|
- [ ] Claude Code: `/plugin marketplace add ./plugins/claude-code` -> `/sleep status`
|
||||||
|
- [ ] Codex: `bash plugins/codex/install.sh`
|
||||||
|
- [ ] Copilot: MCP server `tools/list` returns the `sleep_*` tools
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# SkillOpt-Sleep — benchmark report
|
||||||
|
|
||||||
|
Auto-generated from `sweep.jsonl`. Benchmark: [gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1` (deficient skills, train/held-out split, local rule judge — no judge-API).
|
||||||
|
Held-out scores are computed by the harness, not the optimizer.
|
||||||
|
|
||||||
|
## Direct improvement (optimize, then deploy)
|
||||||
|
|
||||||
|
| Optimizer → Target | Seed | Held-out before | Held-out after | Nights | Tokens |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| claude:sonnet → claude:haiku | brief-writer | 0.00 | **1.00** | 2 | 6657 |
|
||||||
|
| claude:sonnet → claude:haiku | advisor | 0.00 | **1.00** | 2 | 7891 |
|
||||||
|
| claude:sonnet → claude:haiku | thorough-analyst | 0.00 | **1.00** | 2 | 17960 |
|
||||||
|
| codex:default → codex:default | brief-writer | 0.00 | **1.00** | 2 | 9969 |
|
||||||
|
| codex:default → codex:default | advisor | 0.00 | **1.00** | 2 | 6210 |
|
||||||
|
| claude:sonnet → claude:haiku | quick-answerer | 0.00 | **1.00** | 2 | 10988 |
|
||||||
|
| codex:default → codex:default | quick-answerer | 0.00 | **1.00** | 2 | 7347 |
|
||||||
|
|
||||||
|
**7/7 configurations improved on held-out.**
|
||||||
|
|
||||||
|
## Cross-model transfer (optimize on SOURCE, deploy frozen on TARGET)
|
||||||
|
|
||||||
|
The price-difference story: spend cheap tokens optimizing overnight, then deploy the frozen skill on any model with no further optimization.
|
||||||
|
|
||||||
|
| Source (optimizer) | Target (deploy) | Seed | Target baseline | Transferred | Gain |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| claude:haiku | claude:sonnet | brief-writer | 0.00 | **1.00** | +1.00 |
|
||||||
|
| claude:sonnet | claude:haiku | brief-writer | 0.00 | **1.00** | +1.00 |
|
||||||
|
| codex:default | claude:haiku | brief-writer | 0.00 | **1.00** | +1.00 |
|
||||||
|
| claude:haiku | codex:default | brief-writer | 0.00 | **1.00** | +1.00 |
|
||||||
|
|
||||||
|
**4/4 transfers were positive** (frozen skill helped a different model than it was optimized on).
|
||||||
|
|
||||||
|
## How to reproduce
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/garrytan/gbrain-evals /tmp/gbrain-evals
|
||||||
|
python -m skillopt.sleep.experiments.sweep --plan full \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 --out docs/sleep/sweep.jsonl
|
||||||
|
python -m skillopt.sleep.experiments.report \
|
||||||
|
--in docs/sleep/sweep.jsonl --out docs/sleep/benchmark_report.md
|
||||||
|
```
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# SkillOpt-Sleep — validation experiment results
|
||||||
|
|
||||||
|
Generated: 2026-06-07 (autonomous offline session)
|
||||||
|
Backend: mock (deterministic, no API). Reproducible via the commands below.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3.12 -m skillopt.sleep.experiments.run_experiment --persona researcher --nights 4 --json
|
||||||
|
{
|
||||||
|
"persona": "researcher",
|
||||||
|
"backend": "mock",
|
||||||
|
"nights_run": 1,
|
||||||
|
"baseline_holdout": 0.3333,
|
||||||
|
"after_holdout": 1.0,
|
||||||
|
"lift": 0.6667,
|
||||||
|
"improved": true,
|
||||||
|
"gate_blocks_harmful": true,
|
||||||
|
"final_skill_excerpt": "T -->\n## Learned preferences & procedures\n\n_This block is maintained by SkillOpt-Sleep. Edits here are proposed offline, validated against your past tasks, and adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- Always wrap the final answer in <answer>...</answer> tags.\n- Report arXiv ids in the exact form arXiv:XXXX.XXXXX.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n",
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"holdout_score": 0.3333,
|
||||||
|
"action": "baseline",
|
||||||
|
"n_edits": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"holdout_score": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"n_edits": 2,
|
||||||
|
"edits": [
|
||||||
|
"Always wrap the final answer in <answer>...</answer> tags.",
|
||||||
|
"Report arXiv ids in the exact form arXiv:XXXX.XXXXX."
|
||||||
|
],
|
||||||
|
"n_rejected": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3.12 -m skillopt.sleep.experiments.run_experiment --persona programmer --nights 4 --json
|
||||||
|
{
|
||||||
|
"persona": "programmer",
|
||||||
|
"backend": "mock",
|
||||||
|
"nights_run": 1,
|
||||||
|
"baseline_holdout": 0.3194,
|
||||||
|
"after_holdout": 1.0,
|
||||||
|
"lift": 0.6806,
|
||||||
|
"improved": true,
|
||||||
|
"gate_blocks_harmful": true,
|
||||||
|
"final_skill_excerpt": "laude Code sessions.\n\n<!-- SKILLOPT-SLEEP:LEARNED START -->\n## Learned preferences & procedures\n\n_This block is maintained by SkillOpt-Sleep. Edits here are proposed offline, validated against your past tasks, and adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- Write git commit subjects in imperative mood, max 50 chars.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n",
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"holdout_score": 0.3194,
|
||||||
|
"action": "baseline",
|
||||||
|
"n_edits": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"holdout_score": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"n_edits": 1,
|
||||||
|
"edits": [
|
||||||
|
"Write git commit subjects in imperative mood, max 50 chars."
|
||||||
|
],
|
||||||
|
"n_rejected": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# SkillOpt-Sleep — plugin load-test (fresh examples)
|
||||||
|
|
||||||
|
This records an actual end-to-end load-test of all three plugin shells on a
|
||||||
|
**brand-new example** (not the gbrain benchmark seeds), run on 2026-06-08.
|
||||||
|
|
||||||
|
## The fresh persona
|
||||||
|
|
||||||
|
A data analyst whose SQL queries must always include a `LIMIT` clause — built
|
||||||
|
from scratch for this test. Two forms were used:
|
||||||
|
|
||||||
|
1. **Real transcripts** — crafted Claude Code session JSONL where the analyst
|
||||||
|
asks for SQL, the agent forgets `LIMIT`, and the user complains ("you forgot
|
||||||
|
a LIMIT again", "always cap results"). This exercises the real
|
||||||
|
harvest → mine pipeline.
|
||||||
|
2. **Checkable tasks** — the same intent with a rule judge
|
||||||
|
(`regex: (?i)LIMIT\s+100`), so the optimizer can be scored on whether future
|
||||||
|
SQL follows the house rule.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
### Shell plumbing (all three drive the engine)
|
||||||
|
|
||||||
|
| Shell | What was run | Result |
|
||||||
|
|---|---|---|
|
||||||
|
| **Claude Code** (`scripts/sleep.sh`) | `harvest`, full `run`, `adopt` | harvest found 2 sessions → 2 tasks; `run` staged a proposal; `adopt` honored the safety contract (no live change when nothing was accepted) |
|
||||||
|
| **Codex** (`install.sh` + shared runner) | `install.sh` into a temp HOME | placed `~/.codex/prompts/sleep.md` and `~/.agents/skills/skillopt-sleep/SKILL.md` correctly |
|
||||||
|
| **Copilot** (`mcp_server.py`) | `initialize` → `tools/list` → `tools/call sleep_harvest` | 5 tools listed; `sleep_harvest` returned real engine output (2 sessions → 2 tasks) |
|
||||||
|
|
||||||
|
### Genuine improvement (real model, fresh persona)
|
||||||
|
|
||||||
|
Optimizer **Claude Sonnet 4.6** → target **Claude Haiku 4.5**, 3-way split
|
||||||
|
(5 train / 2 val / 5 test), scored on the held-out **test** queries; and the same
|
||||||
|
fresh persona self-optimized on **Codex**:
|
||||||
|
|
||||||
|
| Backend | Held-out **test** (fraction of SQL with `LIMIT 100`) before → after |
|
||||||
|
|---|---|
|
||||||
|
| Claude (Sonnet → Haiku) | **0.00 → 1.00** |
|
||||||
|
| Codex | **0.00 → 1.00** |
|
||||||
|
|
||||||
|
In one night each optimizer wrote, into the protected learned block, a rule like:
|
||||||
|
|
||||||
|
> *"OVERRIDE: Every SQL query you generate MUST include `LIMIT 100` …"* (Claude)
|
||||||
|
> *"Hard requirement: every SQL query response must include …"* (Codex)
|
||||||
|
|
||||||
|
and the target then applied it to the **unseen** test queries. This is the whole
|
||||||
|
claim on a task family the engine had never seen: it learned the user's house
|
||||||
|
rule from their failures and generalized it — confirmed on both backends.
|
||||||
|
|
||||||
|
## An honest finding from load-testing
|
||||||
|
|
||||||
|
The **first** attempt used `val_fraction=0.34, test_fraction=0.34`, which left
|
||||||
|
only **1 train task** for an 8-task set — too little signal — so reflect produced
|
||||||
|
nothing and the night was a no-op (val already 0.75). Re-balancing the split to a
|
||||||
|
real train pool (5 train) fixed it and produced the 0 → 1.00 result above. This
|
||||||
|
is exactly the kind of issue that only surfaces when you actually run the thing,
|
||||||
|
and it motivates a future guardrail: warn when the train pool is too small for
|
||||||
|
the chosen split fractions.
|
||||||
|
|
||||||
|
## Reproduce
|
||||||
|
|
||||||
|
The checkable persona run (real Claude):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# see the snippet in docs/sleep/plugin_load_test.md history, or run:
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona programmer --assert-improves # deterministic
|
||||||
|
```
|
||||||
|
|
||||||
|
Shell checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copilot MCP server
|
||||||
|
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
|
||||||
|
| SKILLOPT_SLEEP_REPO="$(pwd)" python3 plugins/copilot/mcp_server.py
|
||||||
|
# Codex installer (into a throwaway HOME)
|
||||||
|
HOME=$(mktemp -d) bash plugins/codex/install.sh
|
||||||
|
```
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
=== gbrain brief-writer CODEX, improved prompt, 2 nights, 3+3 tasks ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "codex",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 9990,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "brief-writer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 2,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"Every brief must include a clearly labeled section exactly titled `Key Risks`.",
|
||||||
|
"Every brief must include a line beginning `Confidence:` followed by a concise confidence level or rationale."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"- Preserve required sections even when keeping the brief short; shorten the analysis before omitting `## Key Risks` or `Confidence:`."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "tside this block are never touched._\n\n- Every brief must include a clearly labeled section exactly titled `Key Risks`.\n- Every brief must include a line beginning `Confidence:` followed by a concise confidence level or rationale.\n- Preserve required sections even when keeping the brief short; shorten the analysis before omitting `## Key Risks` or `Confidence:`.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
=== REAL cross-check A: Sonnet->Haiku, gate=OFF, rollouts_k=2, brief-writer (exercises new paths) ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "target=claude/optimizer=claude",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 11271,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "brief-writer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"test_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"val_hard": 1.0,
|
||||||
|
"test_hard": 1.0,
|
||||||
|
"action": "greedy_improved",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"Every brief MUST include a section with the exact heading '## Key Risks' that lists the primary risks relevant to the recommendation. This section is required in every output regardless of topic.",
|
||||||
|
"Every brief MUST include a 'Confidence:' label (satisfying /[Cc]onfidence\\s*[:=]/) that states the confidence level in the recommendation (e.g., 'Confidence: Medium'). Place it near the answer/recommendation line or at the end of the brief."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slow_update": null,
|
||||||
|
"final_skill_tail": "at lists the primary risks relevant to the recommendation. This section is required in every output regardless of topic.\n- Every brief MUST include a 'Confidence:' label (satisfying /[Cc]onfidence\\s*[:=]/) that states the confidence level in the recommendation (e.g., 'Confidence: Medium'). Place it near the answer/recommendation line or at the end of the brief.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
=== REAL cross-check B: Codex, gate=ON (default), rollouts_k=2, brief-writer ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "codex",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 17251,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "brief-writer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 2,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"test_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"val_hard": 0.667,
|
||||||
|
"test_hard": 0.333,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"Every brief must include a section/heading titled exactly 'Key Risks'.",
|
||||||
|
"Every brief must include a confidence line labeled exactly 'Confidence:' so the response matches /[Cc]onfidence\\s*[:=]/."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"val_hard": 1.0,
|
||||||
|
"test_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE any brevity guidance: every brief must include a standalone Markdown heading line exactly '## Key Risks' to satisfy section_present=Key Risks, even when the brief is very short."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slow_update": null,
|
||||||
|
"final_skill_tail": "clude a section/heading titled exactly 'Key Risks'.\n- Every brief must include a confidence line labeled exactly 'Confidence:' so the response matches /[Cc]onfidence\\s*[:=]/.\n- OVERRIDE any brevity guidance: every brief must include a standalone Markdown heading line exactly '## Key Risks' to satisfy section_present=Key Risks, even when the brief is very short.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
=== cross-check C: Sonnet->Haiku thorough-analyst (2 nights, slow-update should fire) ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "target=claude/optimizer=claude",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 26010,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "thorough-analyst",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 0.333,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 3,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"test_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"val_hard": 0.667,
|
||||||
|
"test_hard": 0.667,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE (supersedes 'be exhaustive and detailed', 'Explore every angle', 'consider many scenarios', and 'Write multiple paragraphs'): the ENTIRE response must be at most 1200 characters long, counting every character including spaces, newlines, and punctuation. This hard character limit takes priority over all instructions to be thorough, exhaustive, or multi-paragraph.",
|
||||||
|
"To stay within 1200 characters while still being useful: lead with the single most critical trade-off, then list 2-3 key considerations as tight bullet points. Omit headers, preamble, and restating the question."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"val_hard": 0.667,
|
||||||
|
"test_hard": 0.667,
|
||||||
|
"action": "reject",
|
||||||
|
"accepted": false,
|
||||||
|
"edits": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 3,
|
||||||
|
"val_hard": 0.667,
|
||||||
|
"test_hard": 0.667,
|
||||||
|
"action": "reject",
|
||||||
|
"accepted": false,
|
||||||
|
"edits": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slow_update": "• On character-constrained tasks (≤1200 chars), plan structure before writing: allocate space per point explicitly and cut until the outline fits, then fill — never draft freely and trim after.\n• Multi-variable business/strategy analyses are high-risk for overrun; default to covering only the 2–3 most decisive factors rather than attempting exhaustive coverage.\n• Lead with the conclusion or recommendation first; eliminate all introductory restatement of the question, hedging preamble, and transitional filler under tight limits.\n• Persistent failures on the same task signal a structural habit, not a one-off error — treat repeated length violations as a signal to change the drafting approach entirely, not just edit more aggressively.",
|
||||||
|
"final_skill_tail": "ead with the conclusion or recommendation first; eliminate all introductory restatement of the question, hedging preamble, and transitional filler under tight limits.\n• Persistent failures on the same task signal a structural habit, not a one-off error — treat repeated length violations as a signal to change the drafting approach entirely, not just edit more aggressively.\n<!-- SLOW_UPDATE_END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
=== mock regression ===
|
||||||
|
Ran 19 tests in 0.092s
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
||||||
|
=== TRULY-CLEAN re-validation: all seeds, claude haiku, 2 nights ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "claude",
|
||||||
|
"model": "haiku",
|
||||||
|
"n_seeds": 3,
|
||||||
|
"n_improved": 2,
|
||||||
|
"tokens_used": 35549,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "brief-writer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 0.0,
|
||||||
|
"improved": false,
|
||||||
|
"nights": 2,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "reject",
|
||||||
|
"accepted": false,
|
||||||
|
"edits": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "reject",
|
||||||
|
"accepted": false,
|
||||||
|
"edits": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "---\nname: brief-writer-example\nversion: 0.1.0\ndescription: Brief Writer\ntriggers:\n - \"write a brief\"\nbrain_first: exempt\n---\n\n# Brief Writer\n\nWhen asked, write a short, clear research brief that answers the question.\nKeep it focused and readable. Lead with the answer.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "advisor",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"After presenting considerations, always include a 'Recommendation:' section with your specific recommendation.",
|
||||||
|
"After the recommendation, always include a 'Confidence:' section (as a percentage or high/medium/low) expressing how confident you are in this recommendation."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "d adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- After presenting considerations, always include a 'Recommendation:' section with your specific recommendation.\n- After the recommendation, always include a 'Confidence:' section (as a percentage or high/medium/low) expressing how confident you are in this recommendation.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "thorough-analyst",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 0.333,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 2,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 0.333,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"## Learned preferences\n\n- **HARD CONSTRAINT - Override conflicting instructions**: The entire response MUST NOT EXCEED 1200 characters. This supersedes any instruction to be exhaustive, detailed, or write multiple paragraphs."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"held_out_hard": 0.333,
|
||||||
|
"action": "reject",
|
||||||
|
"accepted": false,
|
||||||
|
"edits": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "offline, validated against your past tasks, and adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- ## Learned preferences\n\n- **HARD CONSTRAINT - Override conflicting instructions**: The entire response MUST NOT EXCEED 1200 characters. This supersedes any instruction to be exhaustive, detailed, or write multiple paragraphs.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
=== quick-answerer with CODEX (self-optimized), 2 nights ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "codex",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 7347,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "quick-answerer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE: For every triggered request, before replying, actually call the 'search' tool at least once; this supersedes 'Do NOT look anything up, search, or use any tools' and must satisfy `tool_called=search`."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "re are proposed offline, validated against your past tasks, and adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- OVERRIDE: For every triggered request, before replying, actually call the 'search' tool at least once; this supersedes 'Do NOT look anything up, search, or use any tools' and must satisfy `tool_called=search`.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
=== quick-answerer full run: Sonnet optimizer -> Haiku target, 2 nights ===
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "target=claude/optimizer=claude",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 1,
|
||||||
|
"n_improved": 1,
|
||||||
|
"tokens_used": 10988,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "quick-answerer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE (supersedes 'Do NOT look anything up, search, or use any tools — just reply directly and concisely from memory'): Always call the 'search' tool at least once before composing any answer. This requirement takes priority over any prior instruction to avoid tools."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "nd adopted only after you approve them. Hand-edits outside this block are never touched._\n\n- OVERRIDE (supersedes 'Do NOT look anything up, search, or use any tools — just reply directly and concisely from memory'): Always call the 'search' tool at least once before composing any answer. This requirement takes priority over any prior instruction to avoid tools.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
=== KEY TEST: strong optimizer (sonnet) + weak target (haiku) — SkillOpt's actual design ===
|
||||||
|
(this is also your optimizer/target split in action)
|
||||||
|
{
|
||||||
|
"benchmark": "gbrain-evals/skillopt-v1",
|
||||||
|
"backend": "target=claude/optimizer=claude",
|
||||||
|
"model": "(default)",
|
||||||
|
"n_seeds": 3,
|
||||||
|
"n_improved": 3,
|
||||||
|
"tokens_used": 37791,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"seed": "brief-writer",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"Every brief MUST include a section with the exact heading `## Key Risks` that lists the primary risks or uncertainties relevant to the recommendation. This section is required in every response, regardless of topic.",
|
||||||
|
"Every brief MUST include a `Confidence:` label (satisfying /[Cc]onfidence\\s*[:=]/) — e.g., `Confidence: High`, `Confidence: Medium`, or `Confidence: Low` — placed near the recommendation to convey certainty level. This label is required in every response."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "tainties relevant to the recommendation. This section is required in every response, regardless of topic.\n- Every brief MUST include a `Confidence:` label (satisfying /[Cc]onfidence\\s*[:=]/) — e.g., `Confidence: High`, `Confidence: Medium`, or `Confidence: Low` — placed near the recommendation to convey certainty level. This label is required in every response.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "advisor",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 1,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE: The instruction 'so the reader can make up their own mind' must NOT suppress a conclusion. After presenting considerations, you MUST always end with an explicit label exactly matching 'Recommendation:' (capital R) followed by your concrete recommendation on the decision.",
|
||||||
|
"Always include a 'Confidence:' label (e.g., 'Confidence: High / Medium / Low') in every advisory response, placed immediately after or alongside the Recommendation line, expressing your confidence level in that recommendation."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "ys end with an explicit label exactly matching 'Recommendation:' (capital R) followed by your concrete recommendation on the decision.\n- Always include a 'Confidence:' label (e.g., 'Confidence: High / Medium / Low') in every advisory response, placed immediately after or alongside the Recommendation line, expressing your confidence level in that recommendation.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "thorough-analyst",
|
||||||
|
"held_out_before": 0.0,
|
||||||
|
"held_out_after": 1.0,
|
||||||
|
"improved": true,
|
||||||
|
"nights": 2,
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"night": 0,
|
||||||
|
"held_out_hard": 0.0,
|
||||||
|
"action": "baseline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 1,
|
||||||
|
"held_out_hard": 0.333,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE — supersedes all instructions to be 'exhaustive and detailed' or 'write multiple paragraphs': The ENTIRE response must be at most 1200 characters long (every character, including spaces, headers, and punctuation, counts toward this limit). If content would exceed 1200 characters, cut elaboration and stop at the most critical tradeoffs only.",
|
||||||
|
"For 'analyze the decision' responses, use plain concise prose rather than multi-level markdown headers and section dividers; structural markup consumes characters and makes it harder to stay within the 1200-character ceiling."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"night": 2,
|
||||||
|
"held_out_hard": 1.0,
|
||||||
|
"action": "accept_new_best",
|
||||||
|
"accepted": true,
|
||||||
|
"edits": [
|
||||||
|
"OVERRIDE — supersedes all instructions to be 'exhaustive and detailed' or 'write multiple paragraphs': The ENTIRE response must be at most 1200 characters long (every character counts). Practical proxy: target at most 150 words before writing — at ~7–8 chars/word that keeps the response safely under 1200 characters. Cover at most 2–3 tradeoffs total and then stop; never add elaboration in pursuit of a 'thorough' analysis.",
|
||||||
|
"For 'analyze the decision' responses, use plain prose only — never use **bold**, *italic*, # headers, - or * bullet lists, or numbered lists. Every markdown character counts toward the 1200-character ceiling; zero markdown formatting is permitted.",
|
||||||
|
"Limit every 'analyze the decision' response to at most 5 sentences total. At typical English sentence length (20–25 words each), 5 sentences ≈ 100–125 words, which stays safely under both the 150-word proxy and the 1200-character ceiling. Stop after the 5th sentence regardless of how much more could be said."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_skill_tail": "ter ceiling; zero markdown formatting is permitted.\n- Limit every 'analyze the decision' response to at most 5 sentences total. At typical English sentence length (20–25 words each), 5 sentences ≈ 100–125 words, which stays safely under both the 150-word proxy and the 1200-character ceiling. Stop after the 5th sentence regardless of how much more could be said.\n<!-- SKILLOPT-SLEEP:LEARNED END -->\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# SkillOpt-Sleep — REAL API results (Claude + Codex)
|
||||||
|
|
||||||
|
**Date:** 2026-06-07 (autonomous offline session)
|
||||||
|
**Benchmark:** [gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1` —
|
||||||
|
the same public suite gbrain publishes its own SkillOpt scorecard against
|
||||||
|
([docs/benchmarks/2026-06-03-skillopt.md](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-06-03-skillopt.md)).
|
||||||
|
|
||||||
|
These are **real model runs**, not the deterministic mock. The agent's
|
||||||
|
`attempt` (and the optimizer's `reflect`) call live models via the `claude`
|
||||||
|
and `codex` CLIs. Held-out scoring is done **locally** by the rule judge
|
||||||
|
(`skillopt/sleep/judges.py`), so no judge-API spend and no way for the
|
||||||
|
optimizer to grade its own homework.
|
||||||
|
|
||||||
|
## Headline
|
||||||
|
|
||||||
|
| Backend | Seed | Held-out before | Held-out after | Nights | Tokens |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **Claude (Haiku 4.5)** | brief-writer | **0.00** | **1.00** | 1 | ~6.7k |
|
||||||
|
| **Codex (default)** | brief-writer | **0.00** | **0.67** | 1 | ~5.1k |
|
||||||
|
| **Codex (directive prompt)** | brief-writer | **0.00** | **1.00** | 2 | ~10k |
|
||||||
|
|
||||||
|
Both backends took a **deliberately deficient** skill (a brief-writer with no
|
||||||
|
risks section and no confidence level) and, within 1–2 sleep nights, proposed
|
||||||
|
gated edits that lifted the held-out score to perfect. The edits went into the
|
||||||
|
protected `SKILLOPT-SLEEP:LEARNED` block; nothing else in the skill was touched.
|
||||||
|
|
||||||
|
This reproduces gbrain's published `0 → 1.00` headline with **our** engine and
|
||||||
|
shows it works across **two different agent runtimes** — the core of the
|
||||||
|
"Claude now, Codex next" plan.
|
||||||
|
|
||||||
|
### The multi-night convergence (Codex, why it matters)
|
||||||
|
|
||||||
|
The 2-night Codex run is the most informative trace in this whole exercise:
|
||||||
|
|
||||||
|
- **Night 1** — added two precise rules (a `Key Risks` section, a `Confidence:`
|
||||||
|
line). Held-out still **0.00**: the rules were right but the agent, told to
|
||||||
|
keep briefs short, was *dropping* them under length pressure.
|
||||||
|
- **Night 2** — the optimizer diagnosed its own residual failure and added a
|
||||||
|
meta-rule: *"Preserve required sections even when keeping the brief short;
|
||||||
|
shorten the analysis before omitting Key Risks or Confidence."* Held-out → **1.00**.
|
||||||
|
|
||||||
|
That second edit is not pattern-matching a checklist — it is reasoning about
|
||||||
|
*why the previous night underperformed*. This is exactly the iterative,
|
||||||
|
slow-update behavior SkillOpt's design predicts, and it is the strongest
|
||||||
|
argument for the sleep **loop** over a one-shot rewrite.
|
||||||
|
|
||||||
|
## What the optimizer actually wrote
|
||||||
|
|
||||||
|
**Claude** synthesized a full format template:
|
||||||
|
|
||||||
|
```
|
||||||
|
**Recommendation:** [Clear yes/no or specific answer]
|
||||||
|
**Rationale:** [2-3 bullet points supporting the answer]
|
||||||
|
**Key Risks:** [Downsides, edge cases, or assumptions that could invalidate this]
|
||||||
|
**Confidence:** [High/Medium/Low] — [Why]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Codex** wrote a terser rule:
|
||||||
|
|
||||||
|
```
|
||||||
|
For every brief, include a `Key Risks` section and end with
|
||||||
|
`Confidence: Low|Medium|High`.
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are correct, general, reusable rules (not task-specific answers). Claude's
|
||||||
|
fuller template made the agent satisfy the checks on **3/3** held-out items;
|
||||||
|
Codex's terser rule landed **2/3** — the missing item is a consistency miss the
|
||||||
|
agent would likely fix with one more night (see "Honest notes").
|
||||||
|
|
||||||
|
## How to reproduce
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clone the benchmark data
|
||||||
|
git clone https://github.com/garrytan/gbrain-evals /tmp/gbrain-evals
|
||||||
|
|
||||||
|
cd <repo>/SkillOpt-sleep # this worktree
|
||||||
|
|
||||||
|
# Claude backend
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_gbrain \
|
||||||
|
--backend claude --model haiku --seeds brief-writer \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 \
|
||||||
|
--nights 1 --limit-replay 3 --limit-holdout 3 --json
|
||||||
|
|
||||||
|
# Codex backend (auto-detects the real @openai/codex binary, not the wrapper)
|
||||||
|
python3.12 -m skillopt.sleep.experiments.run_gbrain \
|
||||||
|
--backend codex --seeds brief-writer \
|
||||||
|
--data-root /tmp/gbrain-evals/eval/data/skillopt-v1 \
|
||||||
|
--nights 1 --limit-replay 3 --limit-holdout 3 --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Honest notes (in the spirit of gbrain's own scorecard)
|
||||||
|
|
||||||
|
- **Latency:** each CLI call is ~14–15 s of startup-dominated wall time, so runs
|
||||||
|
were capped at 3 train + 3 held-out tasks and 1 night to keep them ~2.5 min.
|
||||||
|
The response cache makes re-scoring an unchanged (skill, memory) free.
|
||||||
|
- **Codex 0.67, not 1.00:** a single terse edit + single night under-shoots on
|
||||||
|
one held-out item. Two improvements (below) are expected to close it. We report
|
||||||
|
the 0.67, we don't dress it up.
|
||||||
|
- **3 of gbrain's 4 seeds are scored with zero API beyond `attempt`:**
|
||||||
|
`section_present`, `regex`, `max_chars` are pure-text checks. Only the
|
||||||
|
`quick-answerer` seed (`tool_called: search`) needs a real tool loop, which is
|
||||||
|
Phase-3 `fresh` replay.
|
||||||
|
- **The gate is real:** every accepted edit had to beat the held-out score; a
|
||||||
|
no-op night is rejected and the skill is left unchanged.
|
||||||
|
|
||||||
|
## Improvements this run motivated (applied + verified)
|
||||||
|
|
||||||
|
1. **A more directive `reflect` prompt** that aggregates the *exact* failing
|
||||||
|
judge criteria and tells the optimizer to satisfy every one (gbrain's lesson:
|
||||||
|
"the optimizer was never told what the scorer rewards"). Applied in
|
||||||
|
`skillopt/sleep/backend.py`. **Verified**: lifted Codex from 0.67 → 1.00.
|
||||||
|
2. **Multi-night convergence** — a terse first edit gets a sharper second pass;
|
||||||
|
the night-2 trace above shows the optimizer self-correcting. Recommend
|
||||||
|
`nights >= 2` for real backends.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 6657, "cfg": {"kind": "dual", "optimizer_backend": "claude", "optimizer_model": "sonnet", "target_backend": "claude", "target_model": "haiku", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"kind\": \"dual\", \"nights\": 2, \"optimizer_backend\": \"claude\", \"optimizer_model\": \"sonnet\", \"seed\": \"brief-writer\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}", "elapsed_s": 71.5}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 7891, "cfg": {"kind": "dual", "optimizer_backend": "claude", "optimizer_model": "sonnet", "target_backend": "claude", "target_model": "haiku", "seed": "advisor", "nights": 2}, "cfg_key": "{\"kind\": \"dual\", \"nights\": 2, \"optimizer_backend\": \"claude\", \"optimizer_model\": \"sonnet\", \"seed\": \"advisor\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}", "elapsed_s": 79.3}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 17960, "cfg": {"kind": "dual", "optimizer_backend": "claude", "optimizer_model": "sonnet", "target_backend": "claude", "target_model": "haiku", "seed": "thorough-analyst", "nights": 2}, "cfg_key": "{\"kind\": \"dual\", \"nights\": 2, \"optimizer_backend\": \"claude\", \"optimizer_model\": \"sonnet\", \"seed\": \"thorough-analyst\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}", "elapsed_s": 319.3}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 9969, "cfg": {"kind": "direct", "backend": "codex", "model": "", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"backend\": \"codex\", \"kind\": \"direct\", \"model\": \"\", \"nights\": 2, \"seed\": \"brief-writer\"}", "elapsed_s": 187.6}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 6210, "cfg": {"kind": "direct", "backend": "codex", "model": "", "seed": "advisor", "nights": 2}, "cfg_key": "{\"backend\": \"codex\", \"kind\": \"direct\", \"model\": \"\", \"nights\": 2, \"seed\": \"advisor\"}", "elapsed_s": 114.1}
|
||||||
|
{"baseline_target": 0.0, "transferred": 1.0, "transfer_gain": 1.0, "tokens": 13673, "cfg": {"kind": "transfer", "source_backend": "claude", "source_model": "haiku", "target_backend": "claude", "target_model": "sonnet", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"kind\": \"transfer\", \"nights\": 2, \"seed\": \"brief-writer\", \"source_backend\": \"claude\", \"source_model\": \"haiku\", \"target_backend\": \"claude\", \"target_model\": \"sonnet\"}", "elapsed_s": 180.3}
|
||||||
|
{"baseline_target": 0.0, "transferred": 1.0, "transfer_gain": 1.0, "tokens": 11668, "cfg": {"kind": "transfer", "source_backend": "claude", "source_model": "sonnet", "target_backend": "claude", "target_model": "haiku", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"kind\": \"transfer\", \"nights\": 2, \"seed\": \"brief-writer\", \"source_backend\": \"claude\", \"source_model\": \"sonnet\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}", "elapsed_s": 173.9}
|
||||||
|
{"baseline_target": 0.0, "transferred": 1.0, "transfer_gain": 1.0, "tokens": 13707, "cfg": {"kind": "transfer", "source_backend": "codex", "source_model": "", "target_backend": "claude", "target_model": "haiku", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"kind\": \"transfer\", \"nights\": 2, \"seed\": \"brief-writer\", \"source_backend\": \"codex\", \"source_model\": \"\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}", "elapsed_s": 215.7}
|
||||||
|
{"baseline_target": 0.0, "transferred": 1.0, "transfer_gain": 1.0, "tokens": 11284, "cfg": {"kind": "transfer", "source_backend": "claude", "source_model": "haiku", "target_backend": "codex", "target_model": "", "seed": "brief-writer", "nights": 2}, "cfg_key": "{\"kind\": \"transfer\", \"nights\": 2, \"seed\": \"brief-writer\", \"source_backend\": \"claude\", \"source_model\": \"haiku\", \"target_backend\": \"codex\", \"target_model\": \"\"}", "elapsed_s": 145.5}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 10988, "cfg": {"kind": "dual", "optimizer_backend": "claude", "optimizer_model": "sonnet", "target_backend": "claude", "target_model": "haiku", "seed": "quick-answerer", "nights": 2}, "elapsed_s": null, "note": "real tool loop", "cfg_key": "{\"kind\": \"dual\", \"nights\": 2, \"optimizer_backend\": \"claude\", \"optimizer_model\": \"sonnet\", \"seed\": \"quick-answerer\", \"target_backend\": \"claude\", \"target_model\": \"haiku\"}"}
|
||||||
|
{"baseline": 0.0, "after": 1.0, "improved": true, "tokens": 7347, "cfg": {"kind": "direct", "backend": "codex", "model": "", "seed": "quick-answerer", "nights": 2}, "elapsed_s": null, "note": "real tool loop", "cfg_key": "{\"backend\": \"codex\", \"kind\": \"direct\", \"model\": \"\", \"nights\": 2, \"seed\": \"quick-answerer\"}"}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
# SkillOpt Sleep — Claude Code self-evolving plugin (design)
|
||||||
|
|
||||||
|
**Status:** approved-for-build (autonomous offline session, 2026-06-07)
|
||||||
|
**Author:** generated for Yifan Yang, executed autonomously while user is asleep
|
||||||
|
**Branch:** `feat/claude-code-sleep-plugin` (worktree `my_repo/SkillOpt-sleep`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. One-paragraph summary
|
||||||
|
|
||||||
|
`skillopt-sleep` is a Claude Code plugin that gives a user's local Claude
|
||||||
|
agent a nightly **sleep cycle**. While the user is offline, it (1) **harvests**
|
||||||
|
the day's real Claude Code session transcripts from `~/.claude`, (2) **mines**
|
||||||
|
them into discrete *task records* with checkable outcomes, (3) **replays /
|
||||||
|
"dreams"** those tasks offline using the user's own API budget, and (4) runs
|
||||||
|
the **SkillOpt optimizer loop** (reflect → bounded edit → held-out gate) to
|
||||||
|
consolidate short-term experience into long-term **memory** (`CLAUDE.md`) and
|
||||||
|
**skills** (`SKILL.md`). Only changes that pass a validation gate are kept, and
|
||||||
|
every change is written to a **review staging area** the user approves before it
|
||||||
|
touches live config — mirroring Claude Dream's "input store is never modified"
|
||||||
|
safety contract. The result: an agent that measurably gets better at *this
|
||||||
|
user's* recurring work, every night, with zero model-weight training.
|
||||||
|
|
||||||
|
## 2. Why this is the right synthesis of the three ingredients
|
||||||
|
|
||||||
|
| Ingredient | What we take from it | Where it lives in this design |
|
||||||
|
|---|---|---|
|
||||||
|
| **SkillOpt** (your paper/code) | Skill = trainable text state; bounded add/delete/replace edits under a textual learning rate; **held-out validation gate**; rejected-edit buffer; epoch-wise slow/meta update. | The `consolidate` stage *is* a single SkillOpt epoch, reusing `skillopt.optimizer.*` and `skillopt.evaluation.gate`. |
|
||||||
|
| **Claude Dreams** | Async offline job: read a memory store + 1–100 session transcripts → emit a **new, separate** reorganized memory store (dedup / merge / resolve contradictions / surface insights). Input never mutated; output reviewed then adopted or discarded. | The `harvest` + `consolidate-memory` stages and the **staging/adopt** safety model are modeled directly on Dreams. |
|
||||||
|
| **Agent Sleep paper** (2605.26099) | Agents need periodic offline consolidation: short-term experience buffer → synthetic replay/self-generated data → self-update; "sleep" turns episodes into durable competence. | The whole nightly schedule, the `replay` step, and the short-term→long-term framing. |
|
||||||
|
|
||||||
|
The key novel claim this enables for the project (and a future paper section):
|
||||||
|
**SkillOpt's validation-gated bounded-edit optimizer is the missing "safe
|
||||||
|
update rule" for Dream-style memory consolidation.** Dreams reorganize memory
|
||||||
|
but don't *prove* the reorganization helps; the Sleep paper consolidates but
|
||||||
|
assumes weight updates. SkillOpt-Sleep consolidates **text** (memory + skills)
|
||||||
|
and **gates each change on replayed task performance**, so nightly evolution is
|
||||||
|
both weight-free and regression-protected.
|
||||||
|
|
||||||
|
## 3. Goals / non-goals
|
||||||
|
|
||||||
|
**Goals**
|
||||||
|
1. A working Claude Code plugin: scheduled (nightly/cron) **and** user-triggered (`/sleep`).
|
||||||
|
2. Look back over the user's real past prompts & trajectories from local `~/.claude` records.
|
||||||
|
3. Offline "dream training": re-run mined tasks (mock-env or fresh retry) on the user's budget.
|
||||||
|
4. Continuous evolution of **memory** (`CLAUDE.md`) and **skills** (`SKILL.md`) via the SkillOpt gate.
|
||||||
|
5. A reproducible experiment that answers: *does the nightly loop actually improve a held-out score?*
|
||||||
|
6. Safety: never silently overwrite user config; stage → user approves → adopt.
|
||||||
|
|
||||||
|
**Non-goals (now)**
|
||||||
|
- Codex version (explicitly deferred by user; architecture keeps it pluggable).
|
||||||
|
- Anthropic managed Dreams API integration (we *emulate* Dreams locally; managed API is a future backend).
|
||||||
|
- Model fine-tuning / weight updates (out of scope by design — text-only).
|
||||||
|
- Fully unattended auto-adopt by default (opt-in; default is review-gated).
|
||||||
|
|
||||||
|
## 4. The local data we read (verified on this machine)
|
||||||
|
|
||||||
|
- **Prompt history:** `~/.claude/history.jsonl` — one JSON/line: `{display, pastedContents, timestamp, project}`. The cross-session list of every prompt the user typed, with project path + epoch-ms timestamp.
|
||||||
|
- **Full transcripts:** `~/.claude/projects/<path-slug>/<sessionId>.jsonl` — one record/line. Record `type` ∈ {`user`,`assistant`,`mode`,`permission-mode`,`attachment`,`file-history-snapshot`,`last-prompt`,…}. User/assistant records carry `message` (role+content blocks), plus `cwd`, `gitBranch`, `timestamp`, `sessionId`, `version`, `userType`. ~215k transcripts present on this box.
|
||||||
|
- **Deployment targets we may evolve:**
|
||||||
|
- Project memory: `<project>/CLAUDE.md` (and `~/.claude/CLAUDE.md` global).
|
||||||
|
- User skills: `~/.claude/skills/<name>/SKILL.md` (frontmatter: `name`, `description`, optional `allowed-tools`, `argument-hint`).
|
||||||
|
- Plugin skills under `~/.claude/plugins/...`.
|
||||||
|
|
||||||
|
Everything stays **on-disk and local**; the only network calls are the LLM
|
||||||
|
optimizer/replay calls the user already pays for.
|
||||||
|
|
||||||
|
## 5. Architecture
|
||||||
|
|
||||||
|
### 5.1 The nightly Sleep Cycle (stages)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────── SLEEP CYCLE (one "night") ──────────────────────────┐
|
||||||
|
│ │
|
||||||
|
trigger → │ 1.HARVEST 2.MINE 3.REPLAY 4.CONSOLIDATE 5.STAGE │ → wake report
|
||||||
|
(cron or │ read ~/.claude scan sessions re-run tasks SkillOpt epoch: write to │
|
||||||
|
/sleep) │ transcripts → → task records offline (mock or reflect→edit→ .skillopt-│
|
||||||
|
│ + history w/ outcomes & fresh retry) under GATE on held-out sleep/ │
|
||||||
|
│ checkable refs current skill/mem replay split staging/ │
|
||||||
|
│ ↓ │
|
||||||
|
│ 6.ADOPT (opt-in / user-approved) │
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**1. Harvest** (`harvest.py`)
|
||||||
|
Read `history.jsonl` + per-project transcript JSONLs for a time window
|
||||||
|
(default: since last sleep, fallback last 24–72h). Group by project (`cwd` /
|
||||||
|
`project`). Emit normalized `SessionDigest` objects: ordered user prompts,
|
||||||
|
assistant final texts, tool-call summary, files touched (from
|
||||||
|
`file-history-snapshot`), git branch, errors seen, and **user-feedback signals**
|
||||||
|
(e.g. "still broken", "that's wrong", "perfect", re-asks of the same thing).
|
||||||
|
|
||||||
|
**2. Mine** (`mine.py`)
|
||||||
|
Turn digests into `TaskRecord`s — the unit the optimizer trains on. A task is a
|
||||||
|
self-contained intent (the user's request) plus an *outcome label* and, where
|
||||||
|
possible, a **checkable reference**:
|
||||||
|
- *Explicit success/failure* from feedback signals ("works now" after N retries → the early attempts are failures, the fix is the success exemplar).
|
||||||
|
- *Self-consistency check*: re-derivable answers (math, lookups) get a reference; open-ended ones get an LLM-judge rubric instead.
|
||||||
|
- Each TaskRecord: `{id, project, intent, context_excerpt, attempted_solution, outcome ∈ {success,fail,mixed}, reference_kind ∈ {exact, rubric, none}, reference, tags}`.
|
||||||
|
Mining is itself an LLM call (the **miner**), prompt-tunable, with a deterministic regex/heuristic fallback for offline/no-key runs.
|
||||||
|
|
||||||
|
**3. Replay / "Dream"** (`replay.py`)
|
||||||
|
For mined tasks, re-run the intent **offline** under the *current* skill+memory
|
||||||
|
to get a fresh trajectory & score. Two modes:
|
||||||
|
- `mock` (default, safe): reconstruct a sandboxed prompt from the task's captured context (no live repo mutation, no network side effects) and run the target model. Deterministic, cheap, safe to run unattended.
|
||||||
|
- `fresh` (opt-in): actually re-attempt in a throwaway git worktree of the project. Higher fidelity, heavier, never touches the user's working tree.
|
||||||
|
Scoring: exact-match / substring for `exact` refs; LLM-judge (0–1) for `rubric` refs; this yields the `hard`/`soft` scores SkillOpt already expects.
|
||||||
|
|
||||||
|
**4. Consolidate** (`consolidate.py`) — *this is one SkillOpt epoch*
|
||||||
|
Reuse the existing optimizer pieces rather than reinventing:
|
||||||
|
- `reflect`: partition replayed tasks into failure/success minibatches → propose add/delete/replace edits to **skill** and a parallel proposer for **memory** (`CLAUDE.md`). (Memory consolidation also does Dream-style dedup/merge/contradiction-resolution over existing `CLAUDE.md` lines.)
|
||||||
|
- `aggregate` + `rank_and_select` under an **edit budget** (textual learning rate).
|
||||||
|
- `apply_patch_with_report` → candidate skill / candidate memory.
|
||||||
|
- **GATE** (`skillopt.evaluation.gate.evaluate_gate`): replay a *held-out* slice of tasks with the candidate; accept only if it strictly beats current. Rejected edits go to the rejected-edit buffer (negative feedback) exactly as in the paper.
|
||||||
|
- A **slow/meta** pass across nights (not just within one night) carries durable, cross-session lessons — the literal "short-term experience → long-term knowledge" of the Sleep paper. Per-night state persists in `~/.skillopt-sleep/state.json`.
|
||||||
|
|
||||||
|
**5. Stage** (`staging/`)
|
||||||
|
Write `proposed_CLAUDE.md`, `proposed_SKILL.md`, a unified diff, and a
|
||||||
|
`sleep_report.md` (what changed, why, gate deltas, token cost) into
|
||||||
|
`<project>/.skillopt-sleep/staging/<date>/`. **Nothing live is modified.**
|
||||||
|
|
||||||
|
**6. Adopt**
|
||||||
|
`/sleep adopt` (or `auto_adopt: true` in config for power users) copies staged
|
||||||
|
files over the live `CLAUDE.md` / `SKILL.md`, after a `git`-style backup. This
|
||||||
|
is the only stage that mutates user-facing config, and it is explicit by default
|
||||||
|
— the Dreams "review the output, then adopt or discard" contract.
|
||||||
|
|
||||||
|
### 5.2 Components & boundaries (each independently testable)
|
||||||
|
|
||||||
|
```
|
||||||
|
skillopt/sleep/
|
||||||
|
__init__.py
|
||||||
|
types.py # SessionDigest, TaskRecord, ReplayResult, SleepConfig, SleepReport (dataclasses)
|
||||||
|
harvest.py # ~/.claude transcripts + history.jsonl -> list[SessionDigest]
|
||||||
|
mine.py # list[SessionDigest] -> list[TaskRecord] (LLM miner + heuristic fallback)
|
||||||
|
replay.py # TaskRecord + skill + memory -> ReplayResult (hard/soft) (mock | fresh)
|
||||||
|
consolidate.py # ReplayResults -> candidate skill+memory -> GATE -> accepted artifacts
|
||||||
|
memory.py # CLAUDE.md read/merge/dedup/diff (Dream-style) + protected-region markers
|
||||||
|
state.py # ~/.skillopt-sleep/state.json: last_sleep, night counter, slow/meta memory
|
||||||
|
staging.py # write/adopt staging dir, backups
|
||||||
|
cli.py # `python -m skillopt.sleep {run|status|adopt|harvest|dry-run}`
|
||||||
|
config.py # SleepConfig load/merge (defaults + ~/.skillopt-sleep/config.yaml)
|
||||||
|
optimizer_backend.py # thin: route reflect/judge to a chosen backend; mock backend for tests
|
||||||
|
|
||||||
|
skillopt-sleep-plugin/ # the Claude Code plugin surface
|
||||||
|
.claude-plugin/plugin.json
|
||||||
|
commands/sleep.md # /sleep [run|status|adopt|dry-run]
|
||||||
|
commands/sleep-status.md
|
||||||
|
skills/skillopt-sleep/SKILL.md # so Claude knows how to drive the engine
|
||||||
|
hooks/hooks.json # optional: schedule + on-session-end harvest
|
||||||
|
scripts/* # shims that call `python -m skillopt.sleep ...`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reuse, don't fork:** `consolidate.py` calls into existing
|
||||||
|
`skillopt.optimizer.clip.rank_and_select`, `skillopt.gradient.aggregate.merge_patches`,
|
||||||
|
`skillopt.optimizer.skill.apply_patch_with_report`, and
|
||||||
|
`skillopt.evaluation.gate.evaluate_gate`. The sleep layer is an **EnvAdapter-shaped
|
||||||
|
shim** over the user's own life, not a new optimizer.
|
||||||
|
|
||||||
|
### 5.3 Data flow (one task, end to end)
|
||||||
|
|
||||||
|
```
|
||||||
|
history.jsonl + <session>.jsonl
|
||||||
|
└─harvest→ SessionDigest{prompts, finals, tools, feedback}
|
||||||
|
└─mine→ TaskRecord{intent, attempted, outcome, reference}
|
||||||
|
└─replay(current skill+mem)→ ReplayResult{hard, soft, trajectory}
|
||||||
|
└─reflect→ edits(skill), edits(memory)
|
||||||
|
└─rank/clip(edit_budget)→ candidate
|
||||||
|
└─GATE(replay held-out)→ accept? → staging/ → (adopt) live CLAUDE.md/SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Scheduling & triggering
|
||||||
|
|
||||||
|
- **Cron/scheduled:** documented `crontab` line + an optional Claude Code hook; default `0 3 * * *` (3am local; pick an off-:00 minute in practice). The engine is a plain CLI so it works under cron, systemd-timer, or the Claude Code scheduler.
|
||||||
|
- **User-triggered:** `/sleep run` (full cycle), `/sleep dry-run` (harvest+mine+replay, no edits), `/sleep status`, `/sleep adopt`.
|
||||||
|
- **On-session-end harvest (optional hook):** cheaply append the just-finished session to the night's buffer so the 3am run has fresh data without a full rescan.
|
||||||
|
|
||||||
|
## 7. Safety model (hard requirements)
|
||||||
|
|
||||||
|
1. **Never mutate live `CLAUDE.md`/`SKILL.md` except via explicit `adopt`** (or opt-in `auto_adopt`). Default = staged + reviewed (Dreams contract).
|
||||||
|
2. **Backups:** every adopt snapshots the prior file to `staging/<date>/backup/`.
|
||||||
|
3. **Read-only harvest:** transcripts are read, never written.
|
||||||
|
4. **`fresh` replay runs only in throwaway worktrees**, never the user's checkout; no `rm -rf`, no force-push, network off unless `replay.network: true`.
|
||||||
|
5. **Budget cap:** `max_tokens_per_night` + `max_tasks_per_night`; stop early when hit, log what was skipped (no silent truncation).
|
||||||
|
6. **Secret hygiene:** redact obvious secrets from digests before they enter prompts (reuse `_redact_*` ideas from trainer).
|
||||||
|
7. **PII/scope:** only harvest projects on an allowlist (default: the project the plugin is invoked in) or `projects: all` opt-in.
|
||||||
|
|
||||||
|
## 8. Validation experiment — "does it actually improve?"
|
||||||
|
|
||||||
|
A self-contained, **deterministic-by-default** experiment lives in
|
||||||
|
`skillopt/sleep/experiments/` and is the acceptance test for the whole idea.
|
||||||
|
|
||||||
|
**Setup:** a synthetic "user persona" (e.g. *researcher who keeps asking for
|
||||||
|
arXiv-id extraction in a fixed format*, or *programmer who keeps mis-formatting
|
||||||
|
git commit messages*). We ship 12–20 tiny tasks with **exact checkable
|
||||||
|
references**, split into `replay` (train) and `holdout` (test).
|
||||||
|
|
||||||
|
**Procedure:**
|
||||||
|
1. Score the holdout with an **empty** skill+memory → `baseline`.
|
||||||
|
2. Run `N` sleep nights (each: replay train slice → reflect → gated edit).
|
||||||
|
3. Score holdout with the evolved skill+memory → `after`.
|
||||||
|
4. Report `after − baseline`, accept/reject counts, edit count, tokens.
|
||||||
|
|
||||||
|
**Two backends:**
|
||||||
|
- `mock` (default, **no API key, fully deterministic**): a scripted optimizer that proposes the known-good rule on failure and a scripted judge. Proves the *plumbing* (harvest→mine→replay→gate→adopt) monotonically improves the score and the gate blocks regressions. This is the CI-able acceptance test.
|
||||||
|
- `anthropic` (opt-in, uses `ANTHROPIC_API_KEY`): the real optimizer/judge, to demonstrate genuine lift on the persona tasks.
|
||||||
|
|
||||||
|
**Success criteria:**
|
||||||
|
- Mock: `after > baseline`, gate rejects an injected harmful edit, adopt+backup works, re-run is reproducible. (Hard gate in CI.)
|
||||||
|
- Anthropic (when run): `after ≥ baseline` on holdout with ≥1 accepted, human-readable edit; documented in the wake-up report.
|
||||||
|
|
||||||
|
## 9. Personas (the user's framing) → concrete recurring-task families
|
||||||
|
|
||||||
|
- **Programmer:** commit-message conventions, repo-specific build/test commands, "always run X before Y", framework gotchas → consolidated into project `CLAUDE.md` + a `repo-workflow` skill.
|
||||||
|
- **Researcher:** citation/format preferences, experiment-logging habits, paper-section style, dataset-path memory → `research-prefs` skill + memory.
|
||||||
|
- **Finance/analyst:** report formatting, recurring data-pull recipes, terminology → `report-style` skill + memory.
|
||||||
|
The engine is domain-agnostic; the persona only changes which tasks get mined.
|
||||||
|
|
||||||
|
## 10. Phased delivery
|
||||||
|
|
||||||
|
- **Phase 0 — scaffold + types + harvest** (read-only, no API). Provable on this box's real `~/.claude`.
|
||||||
|
- **Phase 1 — mine + replay(mock) + consolidate + gate + staging**, with the **mock** optimizer backend and the deterministic experiment green. *(primary deliverable of the offline session)*
|
||||||
|
- **Phase 2 — plugin surface** (`/sleep`, skill, hooks, plugin.json) wired to the CLI.
|
||||||
|
- **Phase 3 — real Anthropic backend** for miner/reflect/judge + `fresh` replay in worktrees.
|
||||||
|
- **Phase 4 — slow/meta cross-night memory**, adopt automation, multi-project, polish + docs.
|
||||||
|
|
||||||
|
This session targets **Phase 0 + Phase 1 fully**, **Phase 2 scaffolded**, and the
|
||||||
|
**deterministic experiment passing**, all committed (not pushed) for review.
|
||||||
|
|
||||||
|
## 11. Open questions for the user (answer when awake)
|
||||||
|
|
||||||
|
1. **Adopt policy:** keep default *review-gated*, or do you want `auto_adopt` for your own machine?
|
||||||
|
2. **Scope:** harvest only the invoked project, or all projects in `~/.claude/projects`?
|
||||||
|
3. **Real-API demo:** want me to spend live `ANTHROPIC_API_KEY` budget on the persona demo, or keep everything mock until you say go?
|
||||||
|
4. **Skill target:** evolve a *new* dedicated `skillopt-sleep`-managed skill, or also edit your existing hand-written skills in `~/.claude/skills`?
|
||||||
|
5. **Paper:** should this become a section/figure in the SkillOpt arXiv (Dream+Sleep framing as "deployment-time continual skill optimization")?
|
||||||
|
```
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# SkillOpt-Sleep — plugins for Claude Code, Codex, and Copilot
|
||||||
|
|
||||||
|
One engine, three thin shells. **SkillOpt-Sleep** gives a local coding agent a
|
||||||
|
nightly **sleep cycle**: it reviews your past sessions offline, replays your
|
||||||
|
recurring tasks on your own API budget, and consolidates what it learns into
|
||||||
|
**validated** long-term memory and skills — behind a held-out gate, staged for
|
||||||
|
your review. Your agent gets better the more you use it, with no model-weight
|
||||||
|
training.
|
||||||
|
|
||||||
|
It synthesizes three ideas: **SkillOpt** (validation-gated bounded text
|
||||||
|
optimization — the research in this repo), **Claude Dreams** (offline memory
|
||||||
|
consolidation; input never mutated; review-then-adopt), and the **agent sleep**
|
||||||
|
literature (short-term experience → long-term competence).
|
||||||
|
|
||||||
|
> **This is an open-source tool, decoupled from the research code.** The engine
|
||||||
|
> lives in the top-level [`skillopt_sleep/`](../skillopt_sleep) package and has
|
||||||
|
> **zero dependency** on the paper's `skillopt/` experiment package (the
|
||||||
|
> validation gate is vendored). You can ship/use it without the research stack.
|
||||||
|
|
||||||
|
## The three integrations
|
||||||
|
|
||||||
|
| Platform | Folder | Mechanism | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Claude Code** | [`claude-code/`](claude-code) | `.claude-plugin` + `/sleep` command + skill + hooks | full, installable |
|
||||||
|
| **Codex** | [`codex/`](codex) | `~/.codex/prompts/sleep.md` + `~/.agents/skills` + `AGENTS.md` | full |
|
||||||
|
| **Copilot** | [`copilot/`](copilot) | MCP server (`sleep_*` tools) + `copilot-instructions` | full (MCP) |
|
||||||
|
|
||||||
|
All three call the **same** [`plugins/run-sleep.sh`](run-sleep.sh) → `python -m
|
||||||
|
skillopt_sleep`, so behaviour is identical everywhere. Per-platform setup is in
|
||||||
|
each folder's README.
|
||||||
|
|
||||||
|
## Quick start (Claude Code)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> && cd SkillOpt-Sleep
|
||||||
|
# Claude Code:
|
||||||
|
/plugin marketplace add ./plugins/claude-code
|
||||||
|
/plugin install skillopt-sleep@skillopt-sleep
|
||||||
|
/sleep status
|
||||||
|
```
|
||||||
|
Codex: `bash plugins/codex/install.sh`.
|
||||||
|
Copilot: register `plugins/copilot/mcp_server.py` as an MCP server.
|
||||||
|
|
||||||
|
## What one "night" does
|
||||||
|
|
||||||
|
```
|
||||||
|
harvest ~/.claude (or session) transcripts → mine recurring tasks → replay offline
|
||||||
|
→ consolidate (reflect → bounded edit → GATE on real held-out tasks)
|
||||||
|
→ stage proposal → (you) adopt
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing live changes until you adopt; every adopt backs up first.
|
||||||
|
|
||||||
|
## Controls (work on all platforms)
|
||||||
|
|
||||||
|
`--gate on|off` · `--rollouts-k K` (multi-rollout contrastive reflection) ·
|
||||||
|
`--budget-tokens/--budget-minutes` · `--preferences "..."` · separate
|
||||||
|
optimizer/target models (`--optimizer-model` / `--target-model`) · slow-update
|
||||||
|
long-term memory. Full guide:
|
||||||
|
[`../docs/sleep/CONTROLLABLE_DREAMING.md`](../docs/sleep/CONTROLLABLE_DREAMING.md).
|
||||||
|
|
||||||
|
## Does it actually work?
|
||||||
|
|
||||||
|
Validated on the public
|
||||||
|
[gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1` benchmark
|
||||||
|
with **real models on both Claude and Codex**: deficient skills go **0.00 →
|
||||||
|
1.00** on held-out sets (all 4 seeds incl. a real tool-use loop), cross-model
|
||||||
|
transfer is positive, and the gate blocks regressions. Full results:
|
||||||
|
[`../docs/sleep/FINAL_REPORT.md`](../docs/sleep/FINAL_REPORT.md).
|
||||||
|
|
||||||
|
Deterministic proof (no API key):
|
||||||
|
```bash
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves
|
||||||
|
```
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
||||||
|
"name": "skillopt-sleep",
|
||||||
|
"description": "SkillOpt-Sleep: give your local Claude agent a nightly sleep cycle that reviews past sessions and consolidates validated memory + skills.",
|
||||||
|
"owner": {
|
||||||
|
"name": "Yifan Yang",
|
||||||
|
"email": "yifanyang@microsoft.com"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "skillopt-sleep",
|
||||||
|
"description": "Nightly offline self-evolution: harvest your past Claude Code sessions, replay recurring tasks on your own API budget, and consolidate what the agent learns into validated CLAUDE.md memory and SKILL.md skills, behind a held-out gate, staged for your review. Synthesizes SkillOpt (validation-gated skill optimization), Claude Dreams (offline memory consolidation), and agent sleep/consolidation.",
|
||||||
|
"author": {
|
||||||
|
"name": "Yifan Yang"
|
||||||
|
},
|
||||||
|
"category": "productivity",
|
||||||
|
"source": {
|
||||||
|
"source": "git-subdir",
|
||||||
|
"url": "https://github.com/microsoft/SkillOpt.git",
|
||||||
|
"path": "plugins/claude-code",
|
||||||
|
"ref": "main"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/microsoft/SkillOpt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "skillopt-sleep",
|
||||||
|
"description": "Give your local Claude agent a nightly 'sleep cycle': it reviews your past sessions offline, replays recurring tasks on your own API budget, and consolidates what it learns into validated memory (CLAUDE.md) and skills (SKILL.md) so it gets better the more you use it. Synthesizes SkillOpt (validation-gated skill optimization), Claude Dreams (offline memory consolidation), and agent sleep/consolidation.",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Yifan Yang",
|
||||||
|
"email": "yifanyang@microsoft.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/microsoft/SkillOpt",
|
||||||
|
"repository": "https://github.com/microsoft/SkillOpt",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"skillopt",
|
||||||
|
"self-improvement",
|
||||||
|
"memory-consolidation",
|
||||||
|
"dreams",
|
||||||
|
"sleep",
|
||||||
|
"skills",
|
||||||
|
"continual-learning",
|
||||||
|
"offline-optimization"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# SkillOpt-Sleep (Claude Code plugin)
|
||||||
|
|
||||||
|
> Give your local Claude agent a **sleep cycle**. Every night it reviews your
|
||||||
|
> past sessions offline, replays your recurring tasks on your own API budget,
|
||||||
|
> and consolidates what it learns into **validated** memory (`CLAUDE.md`) and
|
||||||
|
> skills (`SKILL.md`). Your agent gets better the more you use it — no
|
||||||
|
> model-weight training.
|
||||||
|
|
||||||
|
SkillOpt-Sleep is the **deployment-time** companion to
|
||||||
|
[SkillOpt](https://github.com/microsoft/SkillOpt). SkillOpt trains a skill
|
||||||
|
offline on a benchmark; SkillOpt-Sleep applies the same discipline to *your own
|
||||||
|
daily usage*: bounded text edits, accepted only through a held-out validation
|
||||||
|
gate, with rejected edits kept as negative feedback.
|
||||||
|
|
||||||
|
It synthesizes three ideas:
|
||||||
|
|
||||||
|
| Idea | Contribution |
|
||||||
|
|---|---|
|
||||||
|
| **SkillOpt** | skill/memory = trainable text; bounded add/delete/replace edits; **held-out gate** keeps only changes that help. |
|
||||||
|
| **Claude Dreams** | offline consolidation over past sessions; input never mutated; output **reviewed then adopted**. |
|
||||||
|
| **Agent sleep** | periodic offline replay turns short-term episodes into long-term skill. |
|
||||||
|
|
||||||
|
## What it does (one "night")
|
||||||
|
|
||||||
|
```
|
||||||
|
harvest ~/.claude transcripts → mine recurring tasks → replay offline
|
||||||
|
→ consolidate (reflect → bounded edit → GATE) → stage proposal → (you) adopt
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing live is modified until **you** run `/sleep adopt` (the Dreams "review,
|
||||||
|
then adopt or discard" contract). Every adopt backs up the prior file first.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
**Requirements:** Python ≥ 3.10, and the `claude` CLI (and/or `codex` CLI) on PATH.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1) get the code (the plugin ships inside the SkillOpt repo)
|
||||||
|
git clone https://github.com/microsoft/SkillOpt.git
|
||||||
|
cd SkillOpt
|
||||||
|
|
||||||
|
# 2) add the plugin to Claude Code as a local marketplace
|
||||||
|
/plugin marketplace add ./skillopt-sleep-plugin
|
||||||
|
/plugin install skillopt-sleep@skillopt-sleep
|
||||||
|
|
||||||
|
# 3) verify
|
||||||
|
/sleep status
|
||||||
|
```
|
||||||
|
|
||||||
|
The plugin's bundled runner (`scripts/sleep.sh`) auto-selects a Python ≥ 3.10
|
||||||
|
interpreter and calls the `skillopt_sleep` engine in the repo. No `pip install`
|
||||||
|
is required for the default `mock` backend or for `claude`/`codex` backends —
|
||||||
|
they shell out to the CLIs you already have.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from inside any project you use with Claude Code:
|
||||||
|
/sleep dry-run # safe preview: what it would learn, no changes staged
|
||||||
|
/sleep run # full cycle: stages a reviewed proposal (still no live edits)
|
||||||
|
/sleep status # see history + the latest staged proposal
|
||||||
|
/sleep adopt # apply the staged proposal to CLAUDE.md / SKILL.md (with backup)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or call the engine directly (Python ≥ 3.10):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m skillopt_sleep run --project "$(pwd)" --scope invoked --backend mock
|
||||||
|
python -m skillopt_sleep run --project "$(pwd)" --backend claude # real lift via Claude
|
||||||
|
python -m skillopt_sleep run --project "$(pwd)" --backend codex # real lift via Codex
|
||||||
|
```
|
||||||
|
|
||||||
|
Default backend is **`mock`** — deterministic, no API spend — so you can try the
|
||||||
|
plumbing for free. Switch to `--backend claude` or `--backend codex` for genuine
|
||||||
|
improvement on your own budget.
|
||||||
|
|
||||||
|
## Does it actually improve? (real models, public benchmark)
|
||||||
|
|
||||||
|
SkillOpt-Sleep is validated against [gbrain-evals](https://github.com/garrytan/gbrain-evals)'
|
||||||
|
public `skillopt-v1` suite — the same benchmark gbrain scores its own skill
|
||||||
|
optimizer against. We take a deliberately **deficient** skill and run one sleep
|
||||||
|
night; held-out scoring is done by a local rule judge (no judge-API, no way to
|
||||||
|
grade its own homework).
|
||||||
|
|
||||||
|
| Backend | Seed | Held-out before → after | Nights |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Claude (Haiku 4.5)** | brief-writer | **0.00 → 1.00** | 1 |
|
||||||
|
| **Codex** | brief-writer | **0.00 → 1.00** | 2 |
|
||||||
|
|
||||||
|
Both took a brief-writer with no risks section / no confidence level and, within
|
||||||
|
1–2 nights, proposed gated edits that lifted the held-out score to perfect —
|
||||||
|
into the protected `LEARNED` block, nothing else touched. The Codex 2-night
|
||||||
|
trace even shows the optimizer **diagnosing its own residual failure** and
|
||||||
|
adding a meta-rule to fix it. Full writeup + reproduction:
|
||||||
|
[`docs/sleep/real_api_results.md`](../docs/sleep/real_api_results.md).
|
||||||
|
|
||||||
|
Reproduce:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/garrytan/gbrain-evals /tmp/gbrain-evals
|
||||||
|
python -m skillopt_sleep.experiments.run_gbrain --backend claude --model haiku \
|
||||||
|
--seeds brief-writer --data-root /tmp/gbrain-evals/eval/data/skillopt-v1 \
|
||||||
|
--nights 1 --limit-replay 3 --limit-holdout 3
|
||||||
|
python -m skillopt_sleep.experiments.run_gbrain --backend codex \
|
||||||
|
--seeds brief-writer --data-root /tmp/gbrain-evals/eval/data/skillopt-v1 \
|
||||||
|
--nights 1 --limit-replay 3 --limit-holdout 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deterministic proof (no API, no keys)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona programmer --assert-improves
|
||||||
|
```
|
||||||
|
|
||||||
|
Each prints the held-out score rising from baseline toward 1.0 as the gate
|
||||||
|
accepts the general rules your tasks need, and confirms the gate **rejects** an
|
||||||
|
injected harmful edit. Recorded output: [`docs/sleep/experiment_results.md`](../docs/sleep/experiment_results.md).
|
||||||
|
|
||||||
|
## Schedule it nightly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/install-cron.sh" "$(pwd)" # prints a crontab line; installs nothing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- **Read-only** harvest of `~/.claude`. `mock` replay has no side effects.
|
||||||
|
- Proposals are **staged**, never auto-applied (unless you opt in with `--auto-adopt`).
|
||||||
|
- Every adopt writes a backup under the staging dir's `backup/`.
|
||||||
|
- Per-night **token/task budget caps**; secrets redacted from prompts.
|
||||||
|
- `fresh` replay (Phase 3) runs only in throwaway git worktrees.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Phase 1 (engine + deterministic experiment + plugin surface) is complete.
|
||||||
|
Phase 3 adds the real-API miner/judge and `fresh` worktree replay. See
|
||||||
|
[`docs/superpowers/specs/2026-06-07-skillopt-sleep-claude-code-plugin-design.md`](../docs/superpowers/specs/2026-06-07-skillopt-sleep-claude-code-plugin-design.md).
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
description: Run or manage the SkillOpt-Sleep self-evolution cycle (review past sessions, replay tasks offline, consolidate validated memory + skills)
|
||||||
|
argument-hint: "[run | dry-run | status | adopt | harvest] (default: status)"
|
||||||
|
allowed-tools: Bash, Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# /sleep — SkillOpt-Sleep nightly self-evolution
|
||||||
|
|
||||||
|
You are driving **SkillOpt-Sleep**: a tool that lets this user's Claude agent
|
||||||
|
improve offline by reviewing past sessions, replaying recurring tasks, and
|
||||||
|
consolidating what it learns into **validated** memory (`CLAUDE.md`) and skills
|
||||||
|
(`SKILL.md`). It is gated like SkillOpt: a change is kept only if it improves a
|
||||||
|
held-out replay score, and nothing live is modified until the user adopts it.
|
||||||
|
|
||||||
|
## Requested action: $ARGUMENTS
|
||||||
|
|
||||||
|
(If `$ARGUMENTS` is empty, treat it as `status`.)
|
||||||
|
|
||||||
|
## How to run it
|
||||||
|
|
||||||
|
The engine is the `skillopt_sleep` Python package in this repo. Use the
|
||||||
|
**plugin's bundled runner** so the right interpreter and repo are on the path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" <action> --project "$(pwd)" --scope invoked
|
||||||
|
```
|
||||||
|
|
||||||
|
`<action>` is one of:
|
||||||
|
|
||||||
|
| action | what it does |
|
||||||
|
|-----------|--------------|
|
||||||
|
| `status` | show how many nights have run + the latest staged proposal (READ-ONLY) |
|
||||||
|
| `dry-run` | harvest → mine → replay → report, but **stage nothing** (safe preview) |
|
||||||
|
| `run` | full cycle: also **stage** a reviewed proposal (still does NOT touch live files) |
|
||||||
|
| `adopt` | apply the latest staged proposal to live `CLAUDE.md` / `SKILL.md` (backs up first) |
|
||||||
|
| `harvest` | debug: print the recurring tasks mined from recent sessions |
|
||||||
|
|
||||||
|
Default backend is `mock` (deterministic, no API spend). To use real Anthropic
|
||||||
|
budget for genuine improvement, add `--backend anthropic`.
|
||||||
|
|
||||||
|
## Steps to follow
|
||||||
|
|
||||||
|
1. **Run the requested action** via the bundled runner above. Capture stdout.
|
||||||
|
2. **For `run` / `dry-run`:** after it completes, `Read` the generated
|
||||||
|
`report.md` in the staging dir it prints, and show the user:
|
||||||
|
- held-out score: baseline → candidate (the proof it helped)
|
||||||
|
- the gate decision (accept/reject) and the exact edits it proposes
|
||||||
|
- where the proposal is staged
|
||||||
|
3. **For `run` that produced an accepted proposal:** tell the user the diff is
|
||||||
|
staged and that **nothing live changed yet**. Offer to run `/sleep adopt`.
|
||||||
|
4. **For `adopt`:** confirm which live files were updated and that backups were
|
||||||
|
written under the staging dir's `backup/`.
|
||||||
|
5. **Never** edit `CLAUDE.md` or `SKILL.md` yourself — only the `adopt` action
|
||||||
|
does that, with a backup. Respect the review gate.
|
||||||
|
|
||||||
|
## Safety reminders
|
||||||
|
|
||||||
|
- Harvest is **read-only** over `~/.claude`. Replay in `mock` mode runs no
|
||||||
|
shell side effects.
|
||||||
|
- The cycle stages proposals; the user is in control of adoption.
|
||||||
|
- If the user asks to schedule this nightly, point them at
|
||||||
|
`${CLAUDE_PLUGIN_ROOT}/scripts/install-cron.sh` (prints a crontab line; does
|
||||||
|
not install anything without confirmation).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionEnd": [
|
||||||
|
{
|
||||||
|
"matcher": "*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/on-session-end.sh\"",
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SkillOpt-Sleep SessionEnd hook (async, best-effort, NON-BLOCKING).
|
||||||
|
#
|
||||||
|
# This does NOT run the optimizer. It only appends a tiny marker so the next
|
||||||
|
# nightly cycle knows there is fresh activity to harvest, and (optionally)
|
||||||
|
# nudges the user once that a sleep cycle is available. It must never fail the
|
||||||
|
# session or spend API budget.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
STATE_DIR="${HOME}/.skillopt-sleep"
|
||||||
|
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
||||||
|
|
||||||
|
# Record that a session just ended (cheap; used for "is there new data?").
|
||||||
|
printf '%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${PWD}" \
|
||||||
|
>> "$STATE_DIR/session-end.log" 2>/dev/null || true
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Print (does NOT install) a crontab line that runs SkillOpt-Sleep nightly.
|
||||||
|
# The user copies the line into `crontab -e` if they want it.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
RUNNER="$PLUGIN_ROOT/scripts/sleep.sh"
|
||||||
|
PROJECT="${1:-$(pwd)}"
|
||||||
|
BACKEND="${2:-mock}"
|
||||||
|
|
||||||
|
# 3:17am local — deliberately off the :00 mark so many users don't all hit the
|
||||||
|
# API at once (and we leave room for jitter).
|
||||||
|
MIN=17
|
||||||
|
HOUR=3
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
# ── SkillOpt-Sleep nightly cycle ────────────────────────────────────────────
|
||||||
|
# Review past sessions, replay tasks, stage validated memory/skill updates.
|
||||||
|
# Runs at ${HOUR}:$(printf '%02d' $MIN) local every day. Output goes to the project's
|
||||||
|
# .skillopt-sleep/ dir; nothing live is changed until you run '/sleep adopt'
|
||||||
|
# (unless you pass --auto-adopt below).
|
||||||
|
#
|
||||||
|
# Copy the next line into 'crontab -e':
|
||||||
|
${MIN} ${HOUR} * * * "${RUNNER}" run --project "${PROJECT}" --scope invoked --backend ${BACKEND} >> "${PROJECT}/.skillopt-sleep/cron.log" 2>&1
|
||||||
|
#
|
||||||
|
# For fully-autonomous adoption (power users), append: --auto-adopt
|
||||||
|
# To spend real API budget for genuine lift, set BACKEND=anthropic above.
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
EOF
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Claude Code plugin runner — thin wrapper over the shared runner so all three
|
||||||
|
# platform plugins share one engine launcher. The shared runner lives at
|
||||||
|
# <repo>/plugins/run-sleep.sh and handles repo-root + interpreter resolution.
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # <repo>/plugins/claude-code/scripts
|
||||||
|
SHARED="$(cd "$HERE/../.." && pwd)/run-sleep.sh" # <repo>/plugins/run-sleep.sh
|
||||||
|
if [ ! -f "$SHARED" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
||||||
|
SHARED="$(cd "$CLAUDE_PLUGIN_ROOT/.." && pwd)/run-sleep.sh"
|
||||||
|
fi
|
||||||
|
exec bash "$SHARED" "$@"
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
name: skillopt-sleep
|
||||||
|
description: "Use when the user wants their Claude agent to self-improve from past usage, asks about a nightly/offline 'sleep' or 'dream' cycle, memory/skill consolidation, or says things like 'make my agent better the more I use it', 'review my past sessions', 'learn my preferences', 'consolidate what you learned', 'run the sleep cycle', or wants to schedule offline self-optimization. Drives the skillopt_sleep engine: harvest past sessions -> mine recurring tasks -> replay offline -> consolidate validated CLAUDE.md/SKILL.md behind a held-out gate."
|
||||||
|
---
|
||||||
|
|
||||||
|
# SkillOpt-Sleep: offline self-evolution for a local Claude agent
|
||||||
|
|
||||||
|
SkillOpt-Sleep gives the user's agent a **sleep cycle**. While the user is
|
||||||
|
offline (e.g. nightly), it reviews their real past Claude Code sessions,
|
||||||
|
re-runs recurring tasks on their own API budget, and consolidates what it
|
||||||
|
learns into **memory** (`CLAUDE.md`) and **skills** (`SKILL.md`) — but only
|
||||||
|
keeps changes that pass a held-out validation gate, and only after the user
|
||||||
|
adopts them. The agent gets measurably better at *this* user's recurring work,
|
||||||
|
with no model-weight training. It is the deployment-time analogue of training:
|
||||||
|
short-term experience → long-term competence.
|
||||||
|
|
||||||
|
It synthesizes three ideas:
|
||||||
|
- **SkillOpt** — the skill/memory doc is trainable text; bounded add/delete/replace
|
||||||
|
edits; accepted only through a held-out gate; rejected edits become negative feedback.
|
||||||
|
- **Claude Dreams** — offline consolidation that reads past sessions and rebuilds
|
||||||
|
memory (dedup/merge/resolve); the input is never mutated; output is reviewed then adopted.
|
||||||
|
- **Agent sleep** — periodic offline replay turns episodes into durable skill.
|
||||||
|
|
||||||
|
## When to use this skill
|
||||||
|
|
||||||
|
Trigger when the user wants any of:
|
||||||
|
- "make my agent learn from how I use it" / "get better the more I use it" / "remember my preferences across sessions"
|
||||||
|
- a nightly/scheduled or on-demand **offline self-improvement / dream / sleep** run
|
||||||
|
- to **review past sessions/trajectories** and distill recurring tasks
|
||||||
|
- to **consolidate** feedback into `CLAUDE.md` or a managed skill
|
||||||
|
- to **schedule** the cycle (cron) or **adopt** a staged proposal
|
||||||
|
|
||||||
|
## The cycle (six stages)
|
||||||
|
|
||||||
|
1. **Harvest** — read `~/.claude/projects/*/<session>.jsonl` + `~/.claude/history.jsonl` (READ-ONLY) → session digests.
|
||||||
|
2. **Mine** — digests → `TaskRecord`s (recurring intents + outcome labels + checkable refs where possible).
|
||||||
|
3. **Replay** — re-run tasks offline under the *current* skill+memory → (hard, soft) scores.
|
||||||
|
4. **Consolidate** — reflect on failures → propose bounded edits → **gate** on a held-out slice; accept only if it strictly improves.
|
||||||
|
5. **Stage** — write `proposed_CLAUDE.md`, `proposed_SKILL.md`, a diff, and `report.md` into `<project>/.skillopt-sleep/staging/<date>/`. **Nothing live changes.**
|
||||||
|
6. **Adopt** — explicit (or opt-in auto): copy staged files over live ones, backing up first.
|
||||||
|
|
||||||
|
## How to drive it
|
||||||
|
|
||||||
|
Prefer the `/sleep` command. Under the hood it calls the bundled runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" status # what's happened
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" dry-run --project "$(pwd)" # safe preview
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" run --project "$(pwd)" # full cycle, stages a proposal
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" adopt --project "$(pwd)" # apply staged proposal (with backup)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Default backend is `mock` (deterministic, **no API spend**) — good for trying the plumbing.
|
||||||
|
- Add `--backend claude` or `--backend codex` to spend the user's real budget for genuine improvement.
|
||||||
|
- Scope defaults to the invoked project; `--scope all` harvests every project.
|
||||||
|
|
||||||
|
## Hard rules
|
||||||
|
|
||||||
|
- **Never** hand-edit the user's `CLAUDE.md` / `SKILL.md` as part of this skill.
|
||||||
|
Only the `adopt` action changes live files, and it backs them up first.
|
||||||
|
- Harvest is read-only. `mock` replay has no side effects.
|
||||||
|
- Always show the user the **held-out baseline → candidate** score and the
|
||||||
|
exact proposed edits before suggesting adoption. Evidence before adoption.
|
||||||
|
- If asked whether it really helps, run
|
||||||
|
`python -m skillopt_sleep.experiments.run_experiment --persona researcher --json`
|
||||||
|
— a deterministic demo that proves held-out lift and that the gate blocks
|
||||||
|
harmful edits.
|
||||||
|
|
||||||
|
## Validate / demo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# deterministic proof (no API): held-out score rises, gate blocks regressions
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves
|
||||||
|
python -m skillopt_sleep.experiments.run_experiment --persona programmer --assert-improves
|
||||||
|
```
|
||||||
|
|
||||||
|
See `docs/sleep/experiment_results.md` for recorded output and
|
||||||
|
`docs/superpowers/specs/2026-06-07-skillopt-sleep-claude-code-plugin-design.md`
|
||||||
|
for the full design.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# SkillOpt-Sleep — Codex integration
|
||||||
|
|
||||||
|
Give your **Codex** agent a nightly **sleep cycle**: it reviews past sessions
|
||||||
|
offline, replays your recurring tasks on your own Codex budget, and consolidates
|
||||||
|
what it learns into validated memory + skills behind a held-out gate. Same engine
|
||||||
|
as the Claude Code plugin (`skillopt_sleep`), wrapped for Codex.
|
||||||
|
|
||||||
|
> **Verified on Codex:** on the public
|
||||||
|
> [gbrain-evals](https://github.com/garrytan/gbrain-evals) `skillopt-v1`
|
||||||
|
> benchmark, a deliberately deficient skill goes **0.00 → 1.00** on a held-out
|
||||||
|
> set with the Codex backend (incl. the tool-use seed via a real tool loop).
|
||||||
|
> See [`../../docs/sleep/FINAL_REPORT.md`](../../docs/sleep/FINAL_REPORT.md).
|
||||||
|
|
||||||
|
## What Codex supports (and what we use)
|
||||||
|
|
||||||
|
Codex (`@openai/codex`) extends via **`AGENTS.md`** instructions, **skills** at
|
||||||
|
`~/.agents/skills/<name>/SKILL.md`, and **custom prompts** at
|
||||||
|
`~/.codex/prompts/<name>.md` (invoked as `/<name>`). This integration ships all
|
||||||
|
three, plus a shared runner.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> SkillOpt-Sleep
|
||||||
|
cd SkillOpt-Sleep
|
||||||
|
bash plugins/codex/install.sh # installs the /sleep prompt + skill
|
||||||
|
export SKILLOPT_SLEEP_REPO="$(pwd)" # so the runner is found from anywhere
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires Python ≥ 3.10 and the `codex` CLI on PATH.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
```text
|
||||||
|
/sleep status # what's happened
|
||||||
|
/sleep dry-run # safe preview, stages nothing
|
||||||
|
/sleep run # full cycle, stages a reviewed proposal (no live edits)
|
||||||
|
/sleep adopt # apply the staged proposal (with backup)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or call the engine directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m skillopt_sleep run --project "$(pwd)" --backend codex
|
||||||
|
```
|
||||||
|
|
||||||
|
Default backend is `mock` (no API spend). `--backend codex` uses your Codex
|
||||||
|
budget for real improvement. All the controllable knobs (`--gate on|off`,
|
||||||
|
`--rollouts-k`, `--budget-tokens`, `--preferences`, optimizer/target split) work
|
||||||
|
identically — see [`../../docs/sleep/CONTROLLABLE_DREAMING.md`](../../docs/sleep/CONTROLLABLE_DREAMING.md).
|
||||||
|
|
||||||
|
## Notes / status
|
||||||
|
|
||||||
|
- Codex's `exec` runs shell, so the real-tool-loop replay (e.g. the
|
||||||
|
`tool_called: search` benchmark seed) works natively.
|
||||||
|
- Codex's standalone *plugin-package manifest* format is not yet a stable public
|
||||||
|
spec; this integration uses the documented `AGENTS.md` + skills + prompts
|
||||||
|
mechanisms, which are stable. If/when a `codex plugin` package format ships,
|
||||||
|
we'll add a one-file manifest.
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the SkillOpt-Sleep Codex integration into the user's ~/.codex and
|
||||||
|
# ~/.agents directories. Idempotent; prints what it does.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||||
|
AGENTS_SKILLS="${HOME}/.agents/skills"
|
||||||
|
|
||||||
|
echo "[install] repo: $REPO_ROOT"
|
||||||
|
|
||||||
|
# 1) custom /sleep prompt
|
||||||
|
mkdir -p "$CODEX_HOME/prompts"
|
||||||
|
cp "$REPO_ROOT/plugins/codex/prompts/sleep.md" "$CODEX_HOME/prompts/sleep.md"
|
||||||
|
echo "[install] /sleep prompt -> $CODEX_HOME/prompts/sleep.md"
|
||||||
|
|
||||||
|
# 2) user-level skill
|
||||||
|
mkdir -p "$AGENTS_SKILLS/skillopt-sleep"
|
||||||
|
cp "$REPO_ROOT/plugins/codex/skills/skillopt-sleep/SKILL.md" "$AGENTS_SKILLS/skillopt-sleep/SKILL.md"
|
||||||
|
echo "[install] skill -> $AGENTS_SKILLS/skillopt-sleep/SKILL.md"
|
||||||
|
|
||||||
|
# 3) record the repo location so the runner is found from anywhere
|
||||||
|
echo "[install] add to your shell profile:"
|
||||||
|
echo " export SKILLOPT_SLEEP_REPO=\"$REPO_ROOT\""
|
||||||
|
|
||||||
|
# 4) optional: append an AGENTS.md hint (only if the user opts in)
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
[install] Optional — add this to ~/.codex/AGENTS.md so Codex always knows the tool:
|
||||||
|
|
||||||
|
## SkillOpt-Sleep
|
||||||
|
An offline self-improvement cycle is available. To run it:
|
||||||
|
\`bash "$REPO_ROOT/plugins/run-sleep.sh" status\`. Use \`/sleep\` for the guided flow.
|
||||||
|
|
||||||
|
Done. Try: /sleep status
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# /sleep — SkillOpt-Sleep for Codex
|
||||||
|
#
|
||||||
|
# Custom prompt: copy this file to ~/.codex/prompts/sleep.md and invoke with
|
||||||
|
# `/sleep` in the Codex CLI. ($ARGUMENTS is the text after /sleep.)
|
||||||
|
|
||||||
|
Run the SkillOpt-Sleep offline self-evolution cycle. Action: $ARGUMENTS
|
||||||
|
(empty → "status").
|
||||||
|
|
||||||
|
Use the bundled runner via shell:
|
||||||
|
|
||||||
|
bash "${SKILLOPT_SLEEP_REPO:?set SKILLOPT_SLEEP_REPO to the repo root}/plugins/run-sleep.sh" $ARGUMENTS --project "$(pwd)"
|
||||||
|
|
||||||
|
Then:
|
||||||
|
- For `run`/`dry-run`: read the staged `report.md` and show the held-out
|
||||||
|
baseline → candidate score and the proposed edits. `run` only stages a
|
||||||
|
proposal; nothing live changes until `adopt`.
|
||||||
|
- For `adopt`: confirm which files were updated and that a backup was written.
|
||||||
|
- Never edit the user's AGENTS.md / skills yourself; only `adopt` does that.
|
||||||
|
|
||||||
|
Default backend is `mock` (no API spend). Add `--backend codex` for real
|
||||||
|
improvement on the user's Codex budget.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: skillopt-sleep
|
||||||
|
description: Nightly offline self-evolution for a Codex agent. Reviews past sessions, replays recurring tasks, and consolidates validated memory + skills behind a held-out gate. Use when the user wants Codex to learn from past usage, run a "sleep"/"dream" cycle, or schedule offline self-optimization.
|
||||||
|
---
|
||||||
|
|
||||||
|
# SkillOpt-Sleep (Codex skill)
|
||||||
|
|
||||||
|
This skill drives the `skillopt_sleep` engine — an offline "sleep cycle" that
|
||||||
|
makes a Codex agent better at the user's recurring work without retraining.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
Trigger when the user wants to: review past sessions, learn their preferences,
|
||||||
|
consolidate feedback into long-term memory/skills, run a nightly/offline
|
||||||
|
self-improvement cycle, or adopt a staged proposal.
|
||||||
|
|
||||||
|
## How to run it
|
||||||
|
|
||||||
|
Invoke the bundled runner via shell (Codex `exec` has shell access). The runner
|
||||||
|
finds the engine and a Python ≥ 3.10 automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# point at the repo if it isn't auto-detected from CWD:
|
||||||
|
export SKILLOPT_SLEEP_REPO=/path/to/SkillOpt-Sleep
|
||||||
|
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" <action> --project "$(pwd)"
|
||||||
|
```
|
||||||
|
|
||||||
|
`<action>` ∈ `status | dry-run | run | adopt | harvest`. Use `--backend codex`
|
||||||
|
for real improvement on the user's own Codex budget (default `mock` = no spend).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Run the requested action; capture stdout.
|
||||||
|
2. For `run`/`dry-run`: read the staged `report.md` it prints and show the user
|
||||||
|
the held-out baseline → candidate score and the exact proposed edits.
|
||||||
|
3. `run` only **stages** a proposal under `<project>/.skillopt-sleep/staging/`;
|
||||||
|
nothing live changes until `adopt`. Offer `/sleep adopt`.
|
||||||
|
4. Never hand-edit the user's `AGENTS.md` / skills yourself — only `adopt` does,
|
||||||
|
and it backs up first.
|
||||||
|
|
||||||
|
## Validate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m skillopt_sleep.experiments.run_gbrain --backend codex \
|
||||||
|
--seeds brief-writer --data-root /path/to/gbrain-evals/eval/data/skillopt-v1 \
|
||||||
|
--nights 2 --limit-replay 3 --limit-holdout 3
|
||||||
|
```
|
||||||
|
A deficient skill goes 0.00 → 1.00 on a held-out set; the optimizer's edits are
|
||||||
|
gated on real-task performance.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# SkillOpt-Sleep — GitHub Copilot integration
|
||||||
|
|
||||||
|
Give **Copilot** (CLI or VS Code) a nightly **sleep cycle** via a tiny **MCP
|
||||||
|
server** that exposes the `skillopt_sleep` engine as tools. MCP is GitHub's
|
||||||
|
supported way to extend Copilot, so this works across Copilot CLI, VS Code, and
|
||||||
|
other MCP clients with the same server.
|
||||||
|
|
||||||
|
## What's here
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `mcp_server.py` | stdlib-only MCP (stdio) server exposing `sleep_*` tools |
|
||||||
|
| `mcp-config.example.json` | drop-in MCP server config |
|
||||||
|
| `copilot-instructions.snippet.md` | paste into `.github/copilot-instructions.md` |
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Requires Python ≥ 3.10. No third-party packages — the server is pure stdlib.
|
||||||
|
|
||||||
|
1. **Register the MCP server.** Add the server to your Copilot MCP config
|
||||||
|
(Copilot CLI: `~/.copilot/mcp-config.json`; VS Code: your MCP settings).
|
||||||
|
Use `mcp-config.example.json` as a template — set `SKILLOPT_SLEEP_REPO` to
|
||||||
|
this repo's path:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"skillopt-sleep": {
|
||||||
|
"command": "python3",
|
||||||
|
"args": ["/abs/path/SkillOpt-Sleep/plugins/copilot/mcp_server.py"],
|
||||||
|
"env": { "SKILLOPT_SLEEP_REPO": "/abs/path/SkillOpt-Sleep" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **(Optional) Tell Copilot about it.** Append
|
||||||
|
`copilot-instructions.snippet.md` to your repo's
|
||||||
|
`.github/copilot-instructions.md` so Copilot reaches for the tools when the
|
||||||
|
user asks to "run the sleep cycle".
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
Ask Copilot things like *"run the sleep cycle"*, *"what did the last sleep
|
||||||
|
propose?"*, *"adopt the staged sleep proposal"*. Copilot calls the MCP tools:
|
||||||
|
`sleep_status`, `sleep_dry_run`, `sleep_run`, `sleep_adopt`, `sleep_harvest`.
|
||||||
|
|
||||||
|
Each tool takes optional `project`, `backend` (`mock`/`claude`/`codex`), and
|
||||||
|
`scope` arguments. Default backend is `mock` (no API spend).
|
||||||
|
|
||||||
|
## Verify the server directly (no Copilot needed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf '%s\n' \
|
||||||
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
|
||||||
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
||||||
|
| SKILLOPT_SLEEP_REPO="$(pwd)" python3 plugins/copilot/mcp_server.py
|
||||||
|
```
|
||||||
|
You should see the server info and the five `sleep_*` tools.
|
||||||
|
|
||||||
|
## Notes / status
|
||||||
|
|
||||||
|
- MCP is the stable, official Copilot extension surface, so this is the most
|
||||||
|
portable of the three integrations (one server → CLI + IDE).
|
||||||
|
- The engine and all its controls (gate on/off, multi-rollout, budget,
|
||||||
|
preferences, optimizer/target split) are identical across platforms — see
|
||||||
|
[`../../docs/sleep/CONTROLLABLE_DREAMING.md`](../../docs/sleep/CONTROLLABLE_DREAMING.md).
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
Copy this block into your repo's .github/copilot-instructions.md so Copilot
|
||||||
|
knows the SkillOpt-Sleep tools exist. (Copilot reads copilot-instructions.md
|
||||||
|
automatically as ambient guidance.)
|
||||||
|
-->
|
||||||
|
|
||||||
|
## SkillOpt-Sleep (offline self-evolution)
|
||||||
|
|
||||||
|
This project has SkillOpt-Sleep available via an MCP server (`skillopt-sleep`).
|
||||||
|
It gives the agent a nightly "sleep cycle": it reviews past sessions, replays
|
||||||
|
recurring tasks offline, and consolidates validated memory + skills behind a
|
||||||
|
held-out gate.
|
||||||
|
|
||||||
|
When the user asks to "run the sleep cycle", "review my past sessions", "learn
|
||||||
|
my preferences", or "make the agent improve from past usage", use the MCP tools:
|
||||||
|
|
||||||
|
- `sleep_status` — what's happened + the latest staged proposal
|
||||||
|
- `sleep_dry_run` — safe preview, stages nothing
|
||||||
|
- `sleep_run` — full cycle, stages a reviewed proposal (nothing live changes)
|
||||||
|
- `sleep_adopt` — apply the staged proposal (backs up first)
|
||||||
|
- `sleep_harvest` — list mined recurring tasks
|
||||||
|
|
||||||
|
Always show the user the held-out baseline → candidate score and the proposed
|
||||||
|
edits before suggesting `sleep_adopt`. Never hand-edit the user's memory/skill
|
||||||
|
files; only `sleep_adopt` does that, with a backup.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"skillopt-sleep": {
|
||||||
|
"command": "python3",
|
||||||
|
"args": ["plugins/copilot/mcp_server.py"],
|
||||||
|
"env": {
|
||||||
|
"SKILLOPT_SLEEP_REPO": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+128
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""SkillOpt-Sleep — minimal MCP server (stdio, stdlib-only).
|
||||||
|
|
||||||
|
Exposes the sleep engine as MCP tools so any MCP-capable client (GitHub Copilot
|
||||||
|
CLI / VS Code, Claude Desktop, etc.) can drive it. No third-party deps: speaks
|
||||||
|
JSON-RPC 2.0 over stdio with just the handful of MCP methods clients need.
|
||||||
|
|
||||||
|
Tools exposed:
|
||||||
|
- sleep_status : how many nights have run + the latest staged proposal
|
||||||
|
- sleep_dry_run : harvest+mine+replay, report only (no staging)
|
||||||
|
- sleep_run : full cycle, stages a proposal (nothing live changes)
|
||||||
|
- sleep_adopt : apply the latest staged proposal (with backup)
|
||||||
|
- sleep_harvest : debug — list mined recurring tasks
|
||||||
|
|
||||||
|
Each tool shells out to `python -m skillopt_sleep <action> ...` and returns its
|
||||||
|
stdout. Configure your client to launch: python plugins/copilot/mcp_server.py
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REPO_ROOT = os.environ.get("SKILLOPT_SLEEP_REPO") or os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "..", "..")
|
||||||
|
)
|
||||||
|
PROTOCOL_VERSION = "2024-11-05"
|
||||||
|
|
||||||
|
TOOLS = [
|
||||||
|
{"name": "sleep_status", "action": "status",
|
||||||
|
"description": "Show how many SkillOpt-Sleep nights have run and the latest staged proposal."},
|
||||||
|
{"name": "sleep_dry_run", "action": "dry-run",
|
||||||
|
"description": "Preview a sleep cycle (harvest+mine+replay) without staging anything."},
|
||||||
|
{"name": "sleep_run", "action": "run",
|
||||||
|
"description": "Run a full sleep cycle; stages a reviewed proposal. Nothing live changes until adopt."},
|
||||||
|
{"name": "sleep_adopt", "action": "adopt",
|
||||||
|
"description": "Apply the latest staged proposal to CLAUDE.md/SKILL.md (backs up first)."},
|
||||||
|
{"name": "sleep_harvest", "action": "harvest",
|
||||||
|
"description": "Debug: list the recurring tasks mined from recent sessions."},
|
||||||
|
]
|
||||||
|
_BY_NAME = {t["name"]: t for t in TOOLS}
|
||||||
|
|
||||||
|
_TOOL_SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {"type": "string", "description": "Project dir to evolve (default: cwd)."},
|
||||||
|
"backend": {"type": "string", "enum": ["mock", "claude", "codex"],
|
||||||
|
"description": "mock = no API spend (default); claude/codex = real."},
|
||||||
|
"scope": {"type": "string", "enum": ["invoked", "all"]},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _run_engine(action: str, args: dict) -> str:
|
||||||
|
py = sys.executable or "python3"
|
||||||
|
cmd = [py, "-m", "skillopt_sleep", action]
|
||||||
|
if args.get("project"):
|
||||||
|
cmd += ["--project", str(args["project"])]
|
||||||
|
if args.get("backend"):
|
||||||
|
cmd += ["--backend", str(args["backend"])]
|
||||||
|
if args.get("scope"):
|
||||||
|
cmd += ["--scope", str(args["scope"])]
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(cmd, cwd=REPO_ROOT, capture_output=True, text=True, timeout=3600)
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
return f"[error] failed to run engine: {e}"
|
||||||
|
out = (proc.stdout or "").strip()
|
||||||
|
err = (proc.stderr or "").strip()
|
||||||
|
return out + (("\n[stderr]\n" + err) if err else "")
|
||||||
|
|
||||||
|
|
||||||
|
def _result(id_, result):
|
||||||
|
return {"jsonrpc": "2.0", "id": id_, "result": result}
|
||||||
|
|
||||||
|
|
||||||
|
def _error(id_, code, message):
|
||||||
|
return {"jsonrpc": "2.0", "id": id_, "error": {"code": code, "message": message}}
|
||||||
|
|
||||||
|
|
||||||
|
def handle(req: dict):
|
||||||
|
method = req.get("method")
|
||||||
|
id_ = req.get("id")
|
||||||
|
if method == "initialize":
|
||||||
|
return _result(id_, {
|
||||||
|
"protocolVersion": PROTOCOL_VERSION,
|
||||||
|
"capabilities": {"tools": {}},
|
||||||
|
"serverInfo": {"name": "skillopt-sleep", "version": "0.1.0"},
|
||||||
|
})
|
||||||
|
if method in ("notifications/initialized", "initialized"):
|
||||||
|
return None # notification, no response
|
||||||
|
if method == "tools/list":
|
||||||
|
return _result(id_, {"tools": [
|
||||||
|
{"name": t["name"], "description": t["description"], "inputSchema": _TOOL_SCHEMA}
|
||||||
|
for t in TOOLS
|
||||||
|
]})
|
||||||
|
if method == "tools/call":
|
||||||
|
params = req.get("params") or {}
|
||||||
|
name = params.get("name")
|
||||||
|
tool = _BY_NAME.get(name)
|
||||||
|
if not tool:
|
||||||
|
return _error(id_, -32602, f"unknown tool: {name}")
|
||||||
|
text = _run_engine(tool["action"], params.get("arguments") or {})
|
||||||
|
return _result(id_, {"content": [{"type": "text", "text": text}]})
|
||||||
|
if method == "ping":
|
||||||
|
return _result(id_, {})
|
||||||
|
return _error(id_, -32601, f"method not found: {method}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
req = json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
resp = handle(req)
|
||||||
|
if resp is not None:
|
||||||
|
sys.stdout.write(json.dumps(resp) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SkillOpt-Sleep shared runner — used by all platform plugins (Claude Code,
|
||||||
|
# Codex, Copilot). Resolves the repo root (which contains the skillopt_sleep
|
||||||
|
# package), picks a Python >= 3.10, and execs the engine CLI.
|
||||||
|
#
|
||||||
|
# Usage: run-sleep.sh <run|dry-run|status|adopt|harvest|...> [args...]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# This script lives at <repo>/plugins/run-sleep.sh, so the repo root (which
|
||||||
|
# holds skillopt_sleep/) is one level up. CLAUDE_PLUGIN_ROOT (if set by Claude
|
||||||
|
# Code) points at the plugin dir; the engine is then two levels above it.
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
if [ -d "$SCRIPT_DIR/../skillopt_sleep" ]; then
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "$CLAUDE_PLUGIN_ROOT/../../skillopt_sleep" ]; then
|
||||||
|
REPO_ROOT="$(cd "$CLAUDE_PLUGIN_ROOT/../.." && pwd)"
|
||||||
|
elif [ -n "${SKILLOPT_SLEEP_REPO:-}" ] && [ -d "$SKILLOPT_SLEEP_REPO/skillopt_sleep" ]; then
|
||||||
|
REPO_ROOT="$SKILLOPT_SLEEP_REPO"
|
||||||
|
else
|
||||||
|
# last resort: search upward from CWD
|
||||||
|
d="$PWD"
|
||||||
|
while [ "$d" != "/" ]; do
|
||||||
|
[ -d "$d/skillopt_sleep" ] && { REPO_ROOT="$d"; break; }
|
||||||
|
d="$(dirname "$d")"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -z "${REPO_ROOT:-}" ]; then
|
||||||
|
echo "[sleep] ERROR: could not locate the skillopt_sleep package. Set SKILLOPT_SLEEP_REPO to the repo root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PY=""
|
||||||
|
for cand in python3.12 python3.11 python3.10 python3; do
|
||||||
|
if command -v "$cand" >/dev/null 2>&1; then
|
||||||
|
ver="$("$cand" -c 'import sys; print("%d%d" % sys.version_info[:2])' 2>/dev/null || echo 0)"
|
||||||
|
if [ "${ver:-0}" -ge 310 ]; then PY="$cand"; break; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -z "$PY" ]; then
|
||||||
|
echo "[sleep] ERROR: need Python >= 3.10 (found none)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$#" -eq 0 ]; then set -- status; fi
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
exec "$PY" -m skillopt_sleep "$@"
|
||||||
+3
-1
@@ -64,7 +64,9 @@ Repository = "https://github.com/microsoft/SkillOpt"
|
|||||||
Issues = "https://github.com/microsoft/SkillOpt/issues"
|
Issues = "https://github.com/microsoft/SkillOpt/issues"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["skillopt*", "scripts*"]
|
# skillopt* = the research package; skillopt_sleep = the open-source Sleep tool
|
||||||
|
# (decoupled, zero dependency on the research code).
|
||||||
|
include = ["skillopt", "skillopt.*", "skillopt_sleep", "skillopt_sleep.*", "scripts*"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|||||||
+63
-1
@@ -137,7 +137,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
# Legacy flat CLI overrides (still work, prefer --cfg-options for new usage)
|
# Legacy flat CLI overrides (still work, prefer --cfg-options for new usage)
|
||||||
p.add_argument("--env", type=str)
|
p.add_argument("--env", type=str)
|
||||||
p.add_argument("--backend", type=str,
|
p.add_argument("--backend", type=str,
|
||||||
choices=["azure_openai", "codex", "codex_exec", "claude", "claude_chat", "claude_code_exec", "qwen", "qwen_chat"])
|
choices=["azure_openai", "codex", "codex_exec", "claude", "claude_chat", "claude_code_exec", "qwen", "qwen_chat", "minimax", "minimax_chat"])
|
||||||
p.add_argument("--optimizer_model", type=str)
|
p.add_argument("--optimizer_model", type=str)
|
||||||
p.add_argument("--target_model", type=str)
|
p.add_argument("--target_model", type=str)
|
||||||
p.add_argument("--optimizer_backend", type=str)
|
p.add_argument("--optimizer_backend", type=str)
|
||||||
@@ -173,6 +173,24 @@ def parse_args() -> argparse.Namespace:
|
|||||||
p.add_argument("--qwen_chat_timeout_seconds", type=float)
|
p.add_argument("--qwen_chat_timeout_seconds", type=float)
|
||||||
p.add_argument("--qwen_chat_max_tokens", type=int)
|
p.add_argument("--qwen_chat_max_tokens", type=int)
|
||||||
p.add_argument("--qwen_chat_enable_thinking", type=_BOOL)
|
p.add_argument("--qwen_chat_enable_thinking", type=_BOOL)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_base_url", type=str)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_api_key", type=str)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_temperature", type=float)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_timeout_seconds", type=float)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_max_tokens", type=int)
|
||||||
|
p.add_argument("--optimizer_qwen_chat_enable_thinking", type=_BOOL)
|
||||||
|
p.add_argument("--target_qwen_chat_base_url", type=str)
|
||||||
|
p.add_argument("--target_qwen_chat_api_key", type=str)
|
||||||
|
p.add_argument("--target_qwen_chat_temperature", type=float)
|
||||||
|
p.add_argument("--target_qwen_chat_timeout_seconds", type=float)
|
||||||
|
p.add_argument("--target_qwen_chat_max_tokens", type=int)
|
||||||
|
p.add_argument("--target_qwen_chat_enable_thinking", type=_BOOL)
|
||||||
|
p.add_argument("--minimax_base_url", type=str)
|
||||||
|
p.add_argument("--minimax_api_key", type=str)
|
||||||
|
p.add_argument("--minimax_model", type=str)
|
||||||
|
p.add_argument("--minimax_temperature", type=float)
|
||||||
|
p.add_argument("--minimax_max_tokens", type=int)
|
||||||
|
p.add_argument("--minimax_enable_thinking", type=_BOOL)
|
||||||
p.add_argument("--codex_exec_path", type=str)
|
p.add_argument("--codex_exec_path", type=str)
|
||||||
p.add_argument("--codex_exec_sandbox", type=str)
|
p.add_argument("--codex_exec_sandbox", type=str)
|
||||||
p.add_argument("--codex_exec_profile", type=str)
|
p.add_argument("--codex_exec_profile", type=str)
|
||||||
@@ -227,6 +245,10 @@ def parse_args() -> argparse.Namespace:
|
|||||||
p.add_argument("--longitudinal_pair_policy", type=str,
|
p.add_argument("--longitudinal_pair_policy", type=str,
|
||||||
choices=["mixed", "changed", "unchanged"])
|
choices=["mixed", "changed", "unchanged"])
|
||||||
p.add_argument("--use_meta_skill", type=_BOOL)
|
p.add_argument("--use_meta_skill", type=_BOOL)
|
||||||
|
p.add_argument("--use_skill_aware_reflection", type=_BOOL)
|
||||||
|
p.add_argument("--skill_aware_appendix_source", type=str,
|
||||||
|
choices=["both", "failure_only"])
|
||||||
|
p.add_argument("--skill_aware_consolidate_threshold", type=int)
|
||||||
p.add_argument("--data_path", type=str)
|
p.add_argument("--data_path", type=str)
|
||||||
p.add_argument("--split_mode", type=str,
|
p.add_argument("--split_mode", type=str,
|
||||||
choices=["ratio", "split_dir"])
|
choices=["ratio", "split_dir"])
|
||||||
@@ -289,6 +311,24 @@ _LEGACY_TO_STRUCTURED: dict[str, str] = {
|
|||||||
"qwen_chat_timeout_seconds": "model.qwen_chat_timeout_seconds",
|
"qwen_chat_timeout_seconds": "model.qwen_chat_timeout_seconds",
|
||||||
"qwen_chat_max_tokens": "model.qwen_chat_max_tokens",
|
"qwen_chat_max_tokens": "model.qwen_chat_max_tokens",
|
||||||
"qwen_chat_enable_thinking": "model.qwen_chat_enable_thinking",
|
"qwen_chat_enable_thinking": "model.qwen_chat_enable_thinking",
|
||||||
|
"optimizer_qwen_chat_base_url": "model.optimizer_qwen_chat_base_url",
|
||||||
|
"optimizer_qwen_chat_api_key": "model.optimizer_qwen_chat_api_key",
|
||||||
|
"optimizer_qwen_chat_temperature": "model.optimizer_qwen_chat_temperature",
|
||||||
|
"optimizer_qwen_chat_timeout_seconds": "model.optimizer_qwen_chat_timeout_seconds",
|
||||||
|
"optimizer_qwen_chat_max_tokens": "model.optimizer_qwen_chat_max_tokens",
|
||||||
|
"optimizer_qwen_chat_enable_thinking": "model.optimizer_qwen_chat_enable_thinking",
|
||||||
|
"target_qwen_chat_base_url": "model.target_qwen_chat_base_url",
|
||||||
|
"target_qwen_chat_api_key": "model.target_qwen_chat_api_key",
|
||||||
|
"target_qwen_chat_temperature": "model.target_qwen_chat_temperature",
|
||||||
|
"target_qwen_chat_timeout_seconds": "model.target_qwen_chat_timeout_seconds",
|
||||||
|
"target_qwen_chat_max_tokens": "model.target_qwen_chat_max_tokens",
|
||||||
|
"target_qwen_chat_enable_thinking": "model.target_qwen_chat_enable_thinking",
|
||||||
|
"minimax_base_url": "model.minimax_base_url",
|
||||||
|
"minimax_api_key": "model.minimax_api_key",
|
||||||
|
"minimax_model": "model.minimax_model",
|
||||||
|
"minimax_temperature": "model.minimax_temperature",
|
||||||
|
"minimax_max_tokens": "model.minimax_max_tokens",
|
||||||
|
"minimax_enable_thinking": "model.minimax_enable_thinking",
|
||||||
"codex_exec_path": "model.codex_exec_path",
|
"codex_exec_path": "model.codex_exec_path",
|
||||||
"codex_exec_sandbox": "model.codex_exec_sandbox",
|
"codex_exec_sandbox": "model.codex_exec_sandbox",
|
||||||
"codex_exec_profile": "model.codex_exec_profile",
|
"codex_exec_profile": "model.codex_exec_profile",
|
||||||
@@ -324,6 +364,9 @@ _LEGACY_TO_STRUCTURED: dict[str, str] = {
|
|||||||
"slow_update_samples": "optimizer.slow_update_samples",
|
"slow_update_samples": "optimizer.slow_update_samples",
|
||||||
"longitudinal_pair_policy": "optimizer.longitudinal_pair_policy",
|
"longitudinal_pair_policy": "optimizer.longitudinal_pair_policy",
|
||||||
"use_meta_skill": "optimizer.use_meta_skill",
|
"use_meta_skill": "optimizer.use_meta_skill",
|
||||||
|
"use_skill_aware_reflection": "optimizer.use_skill_aware_reflection",
|
||||||
|
"skill_aware_appendix_source": "optimizer.skill_aware_appendix_source",
|
||||||
|
"skill_aware_consolidate_threshold": "optimizer.skill_aware_consolidate_threshold",
|
||||||
"use_gate": "evaluation.use_gate",
|
"use_gate": "evaluation.use_gate",
|
||||||
"sel_env_num": "evaluation.sel_env_num",
|
"sel_env_num": "evaluation.sel_env_num",
|
||||||
"test_env_num": "evaluation.test_env_num",
|
"test_env_num": "evaluation.test_env_num",
|
||||||
@@ -403,6 +446,9 @@ def load_config(args: argparse.Namespace) -> dict:
|
|||||||
elif backend in {"qwen", "qwen_chat"}:
|
elif backend in {"qwen", "qwen_chat"}:
|
||||||
flat.setdefault("optimizer_backend", "openai_chat")
|
flat.setdefault("optimizer_backend", "openai_chat")
|
||||||
flat.setdefault("target_backend", "qwen_chat")
|
flat.setdefault("target_backend", "qwen_chat")
|
||||||
|
elif backend in {"minimax", "minimax_chat"}:
|
||||||
|
flat.setdefault("optimizer_backend", "openai_chat")
|
||||||
|
flat.setdefault("target_backend", "minimax_chat")
|
||||||
else:
|
else:
|
||||||
flat.setdefault("optimizer_backend", "openai_chat")
|
flat.setdefault("optimizer_backend", "openai_chat")
|
||||||
flat.setdefault("target_backend", "openai_chat")
|
flat.setdefault("target_backend", "openai_chat")
|
||||||
@@ -416,6 +462,12 @@ def load_config(args: argparse.Namespace) -> dict:
|
|||||||
and not _has_model_override("model.optimizer", "optimizer_model")
|
and not _has_model_override("model.optimizer", "optimizer_model")
|
||||||
):
|
):
|
||||||
flat["optimizer_model"] = default_model_for_backend("claude_chat")
|
flat["optimizer_model"] = default_model_for_backend("claude_chat")
|
||||||
|
if flat.get("optimizer_backend") == "qwen_chat":
|
||||||
|
if (
|
||||||
|
str(flat.get("optimizer_model", "") or "").strip() in _OPENAI_DEFAULT_MODEL_SENTINELS
|
||||||
|
and not _has_model_override("model.optimizer", "optimizer_model")
|
||||||
|
):
|
||||||
|
flat["optimizer_model"] = default_model_for_backend("qwen_chat")
|
||||||
if flat.get("target_backend") == "claude_chat":
|
if flat.get("target_backend") == "claude_chat":
|
||||||
if (
|
if (
|
||||||
str(flat.get("target_model", "") or "").strip() in _OPENAI_DEFAULT_MODEL_SENTINELS
|
str(flat.get("target_model", "") or "").strip() in _OPENAI_DEFAULT_MODEL_SENTINELS
|
||||||
@@ -434,6 +486,15 @@ def load_config(args: argparse.Namespace) -> dict:
|
|||||||
and not _has_model_override("model.target", "target_model")
|
and not _has_model_override("model.target", "target_model")
|
||||||
):
|
):
|
||||||
flat["target_model"] = default_model_for_backend("qwen_chat")
|
flat["target_model"] = default_model_for_backend("qwen_chat")
|
||||||
|
if flat.get("target_backend") == "minimax_chat":
|
||||||
|
if (
|
||||||
|
str(flat.get("target_model", "") or "").strip() in _OPENAI_DEFAULT_MODEL_SENTINELS
|
||||||
|
and not _has_model_override("model.target", "target_model")
|
||||||
|
):
|
||||||
|
flat["target_model"] = (
|
||||||
|
flat.get("minimax_model")
|
||||||
|
or default_model_for_backend("minimax_chat")
|
||||||
|
)
|
||||||
|
|
||||||
# Auto-generate output root
|
# Auto-generate output root
|
||||||
if not flat.get("out_root"):
|
if not flat.get("out_root"):
|
||||||
@@ -473,6 +534,7 @@ def main() -> None:
|
|||||||
print(f" minibatch_size: {cfg.get('minibatch_size')}")
|
print(f" minibatch_size: {cfg.get('minibatch_size')}")
|
||||||
print(f" seed: {cfg.get('seed')}")
|
print(f" seed: {cfg.get('seed')}")
|
||||||
print(f" meta_skill: {cfg.get('use_meta_skill', False)}")
|
print(f" meta_skill: {cfg.get('use_meta_skill', False)}")
|
||||||
|
print(f" skill_aware_reflection: {cfg.get('use_skill_aware_reflection', False)}")
|
||||||
print(f" slow_update: {cfg.get('use_slow_update', False)}")
|
print(f" slow_update: {cfg.get('use_slow_update', False)}")
|
||||||
print(f" out_root: {cfg.get('out_root')}")
|
print(f" out_root: {cfg.get('out_root')}")
|
||||||
print(f"{'='*60}\n")
|
print(f"{'='*60}\n")
|
||||||
|
|||||||
+21
-7
@@ -79,6 +79,24 @@ _FLATTEN_MAP: dict[str, str] = {
|
|||||||
"model.qwen_chat_timeout_seconds": "qwen_chat_timeout_seconds",
|
"model.qwen_chat_timeout_seconds": "qwen_chat_timeout_seconds",
|
||||||
"model.qwen_chat_max_tokens": "qwen_chat_max_tokens",
|
"model.qwen_chat_max_tokens": "qwen_chat_max_tokens",
|
||||||
"model.qwen_chat_enable_thinking": "qwen_chat_enable_thinking",
|
"model.qwen_chat_enable_thinking": "qwen_chat_enable_thinking",
|
||||||
|
"model.optimizer_qwen_chat_base_url": "optimizer_qwen_chat_base_url",
|
||||||
|
"model.optimizer_qwen_chat_api_key": "optimizer_qwen_chat_api_key",
|
||||||
|
"model.optimizer_qwen_chat_temperature": "optimizer_qwen_chat_temperature",
|
||||||
|
"model.optimizer_qwen_chat_timeout_seconds": "optimizer_qwen_chat_timeout_seconds",
|
||||||
|
"model.optimizer_qwen_chat_max_tokens": "optimizer_qwen_chat_max_tokens",
|
||||||
|
"model.optimizer_qwen_chat_enable_thinking": "optimizer_qwen_chat_enable_thinking",
|
||||||
|
"model.target_qwen_chat_base_url": "target_qwen_chat_base_url",
|
||||||
|
"model.target_qwen_chat_api_key": "target_qwen_chat_api_key",
|
||||||
|
"model.target_qwen_chat_temperature": "target_qwen_chat_temperature",
|
||||||
|
"model.target_qwen_chat_timeout_seconds": "target_qwen_chat_timeout_seconds",
|
||||||
|
"model.target_qwen_chat_max_tokens": "target_qwen_chat_max_tokens",
|
||||||
|
"model.target_qwen_chat_enable_thinking": "target_qwen_chat_enable_thinking",
|
||||||
|
"model.minimax_base_url": "minimax_base_url",
|
||||||
|
"model.minimax_api_key": "minimax_api_key",
|
||||||
|
"model.minimax_model": "minimax_model",
|
||||||
|
"model.minimax_temperature": "minimax_temperature",
|
||||||
|
"model.minimax_max_tokens": "minimax_max_tokens",
|
||||||
|
"model.minimax_enable_thinking": "minimax_enable_thinking",
|
||||||
"train.num_epochs": "num_epochs",
|
"train.num_epochs": "num_epochs",
|
||||||
"train.train_size": "train_size",
|
"train.train_size": "train_size",
|
||||||
"train.steps_per_epoch": "steps_per_epoch",
|
"train.steps_per_epoch": "steps_per_epoch",
|
||||||
@@ -101,6 +119,9 @@ _FLATTEN_MAP: dict[str, str] = {
|
|||||||
"optimizer.slow_update_gate_with_selection": "slow_update_gate_with_selection",
|
"optimizer.slow_update_gate_with_selection": "slow_update_gate_with_selection",
|
||||||
"optimizer.longitudinal_pair_policy": "longitudinal_pair_policy",
|
"optimizer.longitudinal_pair_policy": "longitudinal_pair_policy",
|
||||||
"optimizer.use_meta_skill": "use_meta_skill",
|
"optimizer.use_meta_skill": "use_meta_skill",
|
||||||
|
"optimizer.use_skill_aware_reflection": "use_skill_aware_reflection",
|
||||||
|
"optimizer.skill_aware_appendix_source": "skill_aware_appendix_source",
|
||||||
|
"optimizer.skill_aware_consolidate_threshold": "skill_aware_consolidate_threshold",
|
||||||
"evaluation.use_gate": "use_gate",
|
"evaluation.use_gate": "use_gate",
|
||||||
"evaluation.gate_metric": "gate_metric",
|
"evaluation.gate_metric": "gate_metric",
|
||||||
"evaluation.gate_mixed_weight": "gate_mixed_weight",
|
"evaluation.gate_mixed_weight": "gate_mixed_weight",
|
||||||
@@ -171,13 +192,6 @@ def flatten_config(cfg: dict) -> dict:
|
|||||||
|
|
||||||
flat: dict[str, Any] = {}
|
flat: dict[str, Any] = {}
|
||||||
|
|
||||||
evaluation_section = cfg.get("evaluation", {})
|
|
||||||
if isinstance(evaluation_section, dict) and evaluation_section.get("use_gate") is False:
|
|
||||||
raise ValueError(
|
|
||||||
"Gate validation is mandatory in this branch. Remove "
|
|
||||||
"`evaluation.use_gate: false` from the config."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply the explicit mapping
|
# Apply the explicit mapping
|
||||||
for dotted, flat_key in _FLATTEN_MAP.items():
|
for dotted, flat_key in _FLATTEN_MAP.items():
|
||||||
section, key = dotted.split(".", 1)
|
section, key = dotted.split(".", 1)
|
||||||
|
|||||||
+349
-33
@@ -24,7 +24,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
from skillopt.datasets.base import BatchSpec
|
from skillopt.datasets.base import BatchSpec
|
||||||
from skillopt.envs.base import EnvAdapter
|
from skillopt.envs.base import EnvAdapter
|
||||||
from skillopt.evaluation.gate import evaluate_gate, select_gate_score
|
from skillopt.evaluation.gate import GateResult, evaluate_gate, select_gate_score
|
||||||
from skillopt.gradient.aggregate import merge_patches
|
from skillopt.gradient.aggregate import merge_patches
|
||||||
from skillopt.optimizer.meta_skill import run_meta_skill
|
from skillopt.optimizer.meta_skill import run_meta_skill
|
||||||
from skillopt.optimizer.clip import rank_and_select
|
from skillopt.optimizer.clip import rank_and_select
|
||||||
@@ -32,6 +32,17 @@ from skillopt.optimizer.lr_autonomous import decide_autonomous_learning_rate
|
|||||||
from skillopt.optimizer.rewrite import rewrite_skill_from_suggestions
|
from skillopt.optimizer.rewrite import rewrite_skill_from_suggestions
|
||||||
from skillopt.optimizer.scheduler import build_scheduler
|
from skillopt.optimizer.scheduler import build_scheduler
|
||||||
from skillopt.optimizer.skill import apply_patch_with_report
|
from skillopt.optimizer.skill import apply_patch_with_report
|
||||||
|
from skillopt.optimizer.appendix import (
|
||||||
|
append_to_appendix_field,
|
||||||
|
extract_appendix_notes as extract_appendix_notes_from_skill,
|
||||||
|
inject_empty_appendix_field,
|
||||||
|
_strip_all_appendix_fields,
|
||||||
|
)
|
||||||
|
from skillopt.optimizer.skill_aware import (
|
||||||
|
configure_skill_aware_reflection,
|
||||||
|
consolidate_appendix_notes,
|
||||||
|
extract_appendix_notes as extract_appendix_notes_from_result,
|
||||||
|
)
|
||||||
from skillopt.optimizer.slow_update import (
|
from skillopt.optimizer.slow_update import (
|
||||||
build_comparison_pairs,
|
build_comparison_pairs,
|
||||||
extract_slow_update_field,
|
extract_slow_update_field,
|
||||||
@@ -48,9 +59,11 @@ from skillopt.optimizer.update_modes import (
|
|||||||
short_item_summary,
|
short_item_summary,
|
||||||
)
|
)
|
||||||
from skillopt.model import (
|
from skillopt.model import (
|
||||||
|
chat_optimizer,
|
||||||
configure_azure_openai,
|
configure_azure_openai,
|
||||||
configure_claude_code_exec,
|
configure_claude_code_exec,
|
||||||
configure_codex_exec,
|
configure_codex_exec,
|
||||||
|
configure_minimax_chat,
|
||||||
configure_qwen_chat,
|
configure_qwen_chat,
|
||||||
get_token_summary,
|
get_token_summary,
|
||||||
reset_token_tracker,
|
reset_token_tracker,
|
||||||
@@ -63,6 +76,74 @@ from skillopt.model import (
|
|||||||
from skillopt.utils import compute_score, skill_hash
|
from skillopt.utils import compute_score, skill_hash
|
||||||
|
|
||||||
|
|
||||||
|
# ── Skill-aware reflection: appendix flush ───────────────────────────────────
|
||||||
|
|
||||||
|
def _flush_skill_aware_appendix(
|
||||||
|
current_skill: str,
|
||||||
|
all_raw_patches: list,
|
||||||
|
step_rec: dict,
|
||||||
|
step_dir: str,
|
||||||
|
cfg: dict,
|
||||||
|
) -> str:
|
||||||
|
"""Append this step's EXECUTION_LAPSE notes into the protected appendix.
|
||||||
|
|
||||||
|
Returns the (possibly) updated skill. Must be called on BOTH the normal
|
||||||
|
update path and the skip branches: a lapse-only step yields no body
|
||||||
|
patches by design (analysts return ``edits: []`` carriers), so the skip
|
||||||
|
paths would otherwise silently drop every note of the step.
|
||||||
|
"""
|
||||||
|
step_appendix_notes: list[str] = []
|
||||||
|
for rp in all_raw_patches:
|
||||||
|
if isinstance(rp, dict):
|
||||||
|
step_appendix_notes.extend(extract_appendix_notes_from_result(rp))
|
||||||
|
if not step_appendix_notes:
|
||||||
|
return current_skill
|
||||||
|
|
||||||
|
before_notes = extract_appendix_notes_from_skill(current_skill)
|
||||||
|
current_skill = append_to_appendix_field(
|
||||||
|
current_skill, step_appendix_notes,
|
||||||
|
)
|
||||||
|
after_notes = extract_appendix_notes_from_skill(current_skill)
|
||||||
|
n_added = len(after_notes) - len(before_notes)
|
||||||
|
step_rec["n_execution_lapse_notes"] = len(step_appendix_notes)
|
||||||
|
step_rec["n_appendix_notes_added"] = n_added
|
||||||
|
step_rec["n_appendix_notes_total"] = len(after_notes)
|
||||||
|
with open(os.path.join(step_dir, "appendix_notes.json"), "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"step_notes": step_appendix_notes,
|
||||||
|
"appendix_after": after_notes,
|
||||||
|
},
|
||||||
|
f, indent=2, ensure_ascii=False,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" [skill-aware] +{n_added} appendix note(s) "
|
||||||
|
f"(total {len(after_notes)}) from {len(step_appendix_notes)} lapse signal(s)"
|
||||||
|
)
|
||||||
|
# Threshold-gated LLM consolidation (paper Eq.11): when the
|
||||||
|
# appendix grows past N notes, compact it with one optimizer
|
||||||
|
# call (dedupe / merge / shorten). 0 disables it. Any failure
|
||||||
|
# leaves the appendix unchanged.
|
||||||
|
consolidate_threshold = int(
|
||||||
|
cfg.get("skill_aware_consolidate_threshold", 0) or 0
|
||||||
|
)
|
||||||
|
if consolidate_threshold > 0 and len(after_notes) > consolidate_threshold:
|
||||||
|
compacted = consolidate_appendix_notes(
|
||||||
|
after_notes, chat_fn=chat_optimizer,
|
||||||
|
)
|
||||||
|
if compacted and len(compacted) < len(after_notes):
|
||||||
|
current_skill = append_to_appendix_field(
|
||||||
|
_strip_all_appendix_fields(current_skill), compacted,
|
||||||
|
)
|
||||||
|
step_rec["n_appendix_notes_consolidated"] = len(compacted)
|
||||||
|
step_rec["n_appendix_notes_total"] = len(compacted)
|
||||||
|
print(
|
||||||
|
f" [skill-aware] consolidated appendix "
|
||||||
|
f"{len(after_notes)} -> {len(compacted)} notes"
|
||||||
|
)
|
||||||
|
return current_skill
|
||||||
|
|
||||||
|
|
||||||
# ── Patch normalization ───────────────────────────────────────────────────────
|
# ── Patch normalization ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _normalise_patches(
|
def _normalise_patches(
|
||||||
@@ -466,7 +547,7 @@ def _format_step_buffer(buffer: list[dict]) -> str:
|
|||||||
|
|
||||||
# Failure patterns
|
# Failure patterns
|
||||||
for p in entry.get("failure_patterns", []):
|
for p in entry.get("failure_patterns", []):
|
||||||
ids = ", ".join(p["task_ids"][:3])
|
ids = ", ".join(p["task_ids"])
|
||||||
parts.append(f' - "{p["pattern"]}" (×{p["count"]}, tasks: {ids})')
|
parts.append(f' - "{p["pattern"]}" (×{p["count"]}, tasks: {ids})')
|
||||||
|
|
||||||
# Rejected edits (only present on reject)
|
# Rejected edits (only present on reject)
|
||||||
@@ -483,7 +564,7 @@ def _format_step_buffer(buffer: list[dict]) -> str:
|
|||||||
content = e.get("content", "")
|
content = e.get("content", "")
|
||||||
target = e.get("target", "")
|
target = e.get("target", "")
|
||||||
if target:
|
if target:
|
||||||
parts.append(f' {i}. [{op}] target="{target[:80]}" → "{content}"')
|
parts.append(f' {i}. [{op}] target="{target}" → "{content}"')
|
||||||
else:
|
else:
|
||||||
parts.append(f' {i}. [{op}] "{content}"')
|
parts.append(f' {i}. [{op}] "{content}"')
|
||||||
else:
|
else:
|
||||||
@@ -628,14 +709,36 @@ class ReflACTTrainer:
|
|||||||
effort=cfg.get("claude_code_exec_effort", cfg.get("reasoning_effort", "medium")),
|
effort=cfg.get("claude_code_exec_effort", cfg.get("reasoning_effort", "medium")),
|
||||||
max_thinking_tokens=cfg.get("claude_code_exec_max_thinking_tokens", 16384),
|
max_thinking_tokens=cfg.get("claude_code_exec_max_thinking_tokens", 16384),
|
||||||
)
|
)
|
||||||
configure_qwen_chat(
|
configure_qwen_chat(
|
||||||
base_url=cfg.get("qwen_chat_base_url") or None,
|
base_url=cfg.get("qwen_chat_base_url") or None,
|
||||||
api_key=cfg.get("qwen_chat_api_key") or None,
|
api_key=cfg.get("qwen_chat_api_key") or None,
|
||||||
temperature=cfg.get("qwen_chat_temperature"),
|
temperature=cfg.get("qwen_chat_temperature"),
|
||||||
timeout_seconds=cfg.get("qwen_chat_timeout_seconds"),
|
timeout_seconds=cfg.get("qwen_chat_timeout_seconds"),
|
||||||
max_tokens=cfg.get("qwen_chat_max_tokens"),
|
max_tokens=cfg.get("qwen_chat_max_tokens"),
|
||||||
enable_thinking=cfg.get("qwen_chat_enable_thinking"),
|
enable_thinking=cfg.get("qwen_chat_enable_thinking"),
|
||||||
|
optimizer_base_url=cfg.get("optimizer_qwen_chat_base_url") or None,
|
||||||
|
optimizer_api_key=cfg.get("optimizer_qwen_chat_api_key") or None,
|
||||||
|
optimizer_temperature=cfg.get("optimizer_qwen_chat_temperature"),
|
||||||
|
optimizer_timeout_seconds=cfg.get("optimizer_qwen_chat_timeout_seconds"),
|
||||||
|
optimizer_max_tokens=cfg.get("optimizer_qwen_chat_max_tokens"),
|
||||||
|
optimizer_enable_thinking=cfg.get("optimizer_qwen_chat_enable_thinking"),
|
||||||
|
target_base_url=cfg.get("target_qwen_chat_base_url") or None,
|
||||||
|
target_api_key=cfg.get("target_qwen_chat_api_key") or None,
|
||||||
|
target_temperature=cfg.get("target_qwen_chat_temperature"),
|
||||||
|
target_timeout_seconds=cfg.get("target_qwen_chat_timeout_seconds"),
|
||||||
|
target_max_tokens=cfg.get("target_qwen_chat_max_tokens"),
|
||||||
|
target_enable_thinking=cfg.get("target_qwen_chat_enable_thinking"),
|
||||||
|
)
|
||||||
|
configure_minimax_chat(
|
||||||
|
base_url=cfg.get("minimax_base_url") or None,
|
||||||
|
api_key=cfg.get("minimax_api_key") or None,
|
||||||
|
temperature=cfg.get("minimax_temperature"),
|
||||||
|
max_tokens=cfg.get("minimax_max_tokens"),
|
||||||
|
enable_thinking=cfg.get("minimax_enable_thinking"),
|
||||||
)
|
)
|
||||||
|
minimax_model_cfg = cfg.get("minimax_model")
|
||||||
|
if minimax_model_cfg and cfg.get("target_backend") == "minimax_chat":
|
||||||
|
set_target_deployment(str(minimax_model_cfg))
|
||||||
os.environ["REFLACT_CODEX_TRACE_TO_OPTIMIZER"] = (
|
os.environ["REFLACT_CODEX_TRACE_TO_OPTIMIZER"] = (
|
||||||
"1"
|
"1"
|
||||||
if target_backend == "codex_exec" and cfg.get("codex_trace_to_optimizer", False)
|
if target_backend == "codex_exec" and cfg.get("codex_trace_to_optimizer", False)
|
||||||
@@ -815,6 +918,16 @@ class ReflACTTrainer:
|
|||||||
|
|
||||||
_save_skill(out_root, 0, skill_init)
|
_save_skill(out_root, 0, skill_init)
|
||||||
|
|
||||||
|
use_skill_aware = cfg.get("use_skill_aware_reflection", False)
|
||||||
|
# Publish the toggle process-wide so run_minibatch_reflect resolves it
|
||||||
|
# from config for EVERY env adapter — no per-benchmark wiring needed.
|
||||||
|
configure_skill_aware_reflection(
|
||||||
|
use_skill_aware,
|
||||||
|
cfg.get("skill_aware_appendix_source", "both"),
|
||||||
|
)
|
||||||
|
if use_skill_aware:
|
||||||
|
current_skill = inject_empty_appendix_field(current_skill)
|
||||||
|
|
||||||
def _persist_runtime_state(last_completed_step: int) -> None:
|
def _persist_runtime_state(last_completed_step: int) -> None:
|
||||||
_save_runtime_state(
|
_save_runtime_state(
|
||||||
out_root,
|
out_root,
|
||||||
@@ -840,11 +953,10 @@ class ReflACTTrainer:
|
|||||||
sel_cache[sh] = (rec["selection_hard"], rec["selection_soft"])
|
sel_cache[sh] = (rec["selection_hard"], rec["selection_soft"])
|
||||||
|
|
||||||
# ── Baseline evaluation on selection set ─────────────────────────
|
# ── Baseline evaluation on selection set ─────────────────────────
|
||||||
if cfg.get("use_gate") is False:
|
# `use_gate=False` keeps validation running (selection rollout +
|
||||||
raise ValueError(
|
# scoring are unconditional below) but force-accepts every candidate
|
||||||
"Gate validation is mandatory in this branch. Remove "
|
# instead of gating it; final skill is chosen manually afterwards.
|
||||||
"`evaluation.use_gate=false` from the config."
|
use_gate = cfg.get("use_gate", True) is not False
|
||||||
)
|
|
||||||
gate_metric = str(cfg.get("gate_metric", "hard")).strip().lower()
|
gate_metric = str(cfg.get("gate_metric", "hard")).strip().lower()
|
||||||
if gate_metric not in {"hard", "soft", "mixed"}:
|
if gate_metric not in {"hard", "soft", "mixed"}:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -864,6 +976,8 @@ class ReflACTTrainer:
|
|||||||
if gate_metric == "mixed"
|
if gate_metric == "mixed"
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
|
+ ("" if use_gate
|
||||||
|
else " (DISABLED → validation runs, candidates force-accepted)")
|
||||||
)
|
)
|
||||||
slow_gate_with_selection = bool(
|
slow_gate_with_selection = bool(
|
||||||
cfg.get("slow_update_gate_with_selection", False)
|
cfg.get("slow_update_gate_with_selection", False)
|
||||||
@@ -1081,6 +1195,13 @@ class ReflACTTrainer:
|
|||||||
|
|
||||||
# ── No patches? Skip ─────────────────────────────────────
|
# ── No patches? Skip ─────────────────────────────────────
|
||||||
if not all_failure_patches and not all_success_patches:
|
if not all_failure_patches and not all_success_patches:
|
||||||
|
# Skill-aware: a lapse-only step has no body patches but
|
||||||
|
# may still carry appendix notes — flush them BEFORE
|
||||||
|
# skipping, or they would be silently dropped.
|
||||||
|
if use_skill_aware:
|
||||||
|
current_skill = _flush_skill_aware_appendix(
|
||||||
|
current_skill, all_raw_patches, step_rec, step_dir, cfg,
|
||||||
|
)
|
||||||
step_rec["action"] = "skip_no_patches"
|
step_rec["action"] = "skip_no_patches"
|
||||||
step_rec["current_score"] = current_score
|
step_rec["current_score"] = current_score
|
||||||
step_rec["best_score"] = best_score
|
step_rec["best_score"] = best_score
|
||||||
@@ -1269,6 +1390,12 @@ class ReflACTTrainer:
|
|||||||
is_full_rewrite_minibatch_mode(update_mode)
|
is_full_rewrite_minibatch_mode(update_mode)
|
||||||
and rewrite_result is None
|
and rewrite_result is None
|
||||||
):
|
):
|
||||||
|
# Skill-aware: flush appendix notes before skipping (see
|
||||||
|
# the skip_no_patches branch above).
|
||||||
|
if use_skill_aware:
|
||||||
|
current_skill = _flush_skill_aware_appendix(
|
||||||
|
current_skill, all_raw_patches, step_rec, step_dir, cfg,
|
||||||
|
)
|
||||||
step_rec["action"] = "skip_no_rewrite"
|
step_rec["action"] = "skip_no_rewrite"
|
||||||
step_rec["current_score"] = current_score
|
step_rec["current_score"] = current_score
|
||||||
step_rec["best_score"] = best_score
|
step_rec["best_score"] = best_score
|
||||||
@@ -1323,10 +1450,31 @@ class ReflACTTrainer:
|
|||||||
cand_soft=cand_soft,
|
cand_soft=cand_soft,
|
||||||
metric=gate_metric,
|
metric=gate_metric,
|
||||||
mixed_weight=gate_mixed_weight,
|
mixed_weight=gate_mixed_weight,
|
||||||
)
|
) if use_gate else None
|
||||||
cand_gate_score = select_gate_score(
|
cand_gate_score = select_gate_score(
|
||||||
cand_hard, cand_soft, gate_metric, gate_mixed_weight,
|
cand_hard, cand_soft, gate_metric, gate_mixed_weight,
|
||||||
)
|
)
|
||||||
|
if not use_gate:
|
||||||
|
# Validation ran (scores recorded above) but the gate is
|
||||||
|
# disabled: force-accept the candidate as the new current
|
||||||
|
# skill. Best-so-far is still tracked for convenience; the
|
||||||
|
# final skill is selected manually from the trajectory.
|
||||||
|
if cand_gate_score > best_score:
|
||||||
|
fa_best_skill = candidate_skill
|
||||||
|
fa_best_score = cand_gate_score
|
||||||
|
fa_best_step = global_step
|
||||||
|
else:
|
||||||
|
fa_best_skill = best_skill
|
||||||
|
fa_best_score = best_score
|
||||||
|
fa_best_step = best_step
|
||||||
|
gate = GateResult(
|
||||||
|
action="force_accept",
|
||||||
|
current_skill=candidate_skill,
|
||||||
|
current_score=cand_gate_score,
|
||||||
|
best_skill=fa_best_skill,
|
||||||
|
best_score=fa_best_score,
|
||||||
|
best_step=fa_best_step,
|
||||||
|
)
|
||||||
step_rec["gate_metric"] = gate_metric
|
step_rec["gate_metric"] = gate_metric
|
||||||
step_rec["candidate_gate_score"] = cand_gate_score
|
step_rec["candidate_gate_score"] = cand_gate_score
|
||||||
step_rec["action"] = gate.action
|
step_rec["action"] = gate.action
|
||||||
@@ -1337,11 +1485,18 @@ class ReflACTTrainer:
|
|||||||
best_skill = gate.best_skill
|
best_skill = gate.best_skill
|
||||||
best_score = gate.best_score
|
best_score = gate.best_score
|
||||||
best_step = gate.best_step
|
best_step = gate.best_step
|
||||||
if gate.action in {"accept", "accept_new_best"}:
|
if gate.action in {"accept", "accept_new_best", "force_accept"}:
|
||||||
current_origin = f"step_{global_step:04d}"
|
current_origin = f"step_{global_step:04d}"
|
||||||
if gate.action == "accept_new_best":
|
if gate.action == "accept_new_best" or (
|
||||||
|
gate.action == "force_accept" and best_step == global_step
|
||||||
|
):
|
||||||
best_origin = current_origin
|
best_origin = current_origin
|
||||||
|
|
||||||
|
if use_skill_aware:
|
||||||
|
current_skill = _flush_skill_aware_appendix(
|
||||||
|
current_skill, all_raw_patches, step_rec, step_dir, cfg,
|
||||||
|
)
|
||||||
|
|
||||||
if gate_metric == "hard":
|
if gate_metric == "hard":
|
||||||
score_label = f"hard={cand_hard:.4f}"
|
score_label = f"hard={cand_hard:.4f}"
|
||||||
elif gate_metric == "soft":
|
elif gate_metric == "soft":
|
||||||
@@ -1361,6 +1516,11 @@ class ReflACTTrainer:
|
|||||||
f" [6/6 EVALUATE] ACCEPT "
|
f" [6/6 EVALUATE] ACCEPT "
|
||||||
f"{score_label} > current={prev_current:.4f}"
|
f"{score_label} > current={prev_current:.4f}"
|
||||||
)
|
)
|
||||||
|
elif gate.action == "force_accept":
|
||||||
|
print(
|
||||||
|
f" [6/6 EVALUATE] FORCE-ACCEPT (gate disabled) "
|
||||||
|
f"{score_label}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f" [6/6 EVALUATE] REJECT "
|
f" [6/6 EVALUATE] REJECT "
|
||||||
@@ -1491,13 +1651,13 @@ class ReflACTTrainer:
|
|||||||
elif action in {
|
elif action in {
|
||||||
"accept", "accept_new_best", "force_accept",
|
"accept", "accept_new_best", "force_accept",
|
||||||
}:
|
}:
|
||||||
# Force-accept mode: re-apply to both current & best.
|
# Force-accept mode: re-apply guidance to
|
||||||
|
# current_skill only. best_skill must remain a
|
||||||
|
# faithful snapshot of the val-best step and must
|
||||||
|
# NOT receive force-injected slow-update content.
|
||||||
current_skill = replace_slow_update_field(
|
current_skill = replace_slow_update_field(
|
||||||
current_skill, slow_saved["slow_update_content"],
|
current_skill, slow_saved["slow_update_content"],
|
||||||
)
|
)
|
||||||
best_skill = replace_slow_update_field(
|
|
||||||
best_skill, slow_saved["slow_update_content"],
|
|
||||||
)
|
|
||||||
elif epoch == 1:
|
elif epoch == 1:
|
||||||
# Epoch 1: inject empty placeholder
|
# Epoch 1: inject empty placeholder
|
||||||
os.makedirs(slow_dir, exist_ok=True)
|
os.makedirs(slow_dir, exist_ok=True)
|
||||||
@@ -1505,7 +1665,7 @@ class ReflACTTrainer:
|
|||||||
current_origin = f"slow_update_placeholder_epoch_{epoch:02d}"
|
current_origin = f"slow_update_placeholder_epoch_{epoch:02d}"
|
||||||
_save_skill(out_root, global_step, current_skill)
|
_save_skill(out_root, global_step, current_skill)
|
||||||
with open(os.path.join(out_root, "best_skill.md"), "w") as f:
|
with open(os.path.join(out_root, "best_skill.md"), "w") as f:
|
||||||
f.write(best_skill if best_score > current_score else current_skill)
|
f.write(best_skill)
|
||||||
with open(slow_done_path, "w") as f:
|
with open(slow_done_path, "w") as f:
|
||||||
json.dump({"action": "inject_placeholder", "epoch": epoch}, f, indent=2)
|
json.dump({"action": "inject_placeholder", "epoch": epoch}, f, indent=2)
|
||||||
_persist_runtime_state(global_step)
|
_persist_runtime_state(global_step)
|
||||||
@@ -1726,16 +1886,15 @@ class ReflACTTrainer:
|
|||||||
else:
|
else:
|
||||||
# ── Force-accept mode (default) ──────────────────
|
# ── Force-accept mode (default) ──────────────────
|
||||||
# The epoch-level longitudinal guidance is injected
|
# The epoch-level longitudinal guidance is injected
|
||||||
# into both current_skill and best_skill
|
# into current_skill ONLY, so training continues
|
||||||
# unconditionally — it must not be gated by
|
# with the accumulated slow memory. best_skill is
|
||||||
# step-level selection scores.
|
# left untouched: it must remain a faithful snapshot
|
||||||
|
# of the val-best step (which may be a pre-slow step
|
||||||
|
# such as S_0 carrying no slow_update field at all).
|
||||||
slow_content = slow_result["slow_update_content"]
|
slow_content = slow_result["slow_update_content"]
|
||||||
current_skill = replace_slow_update_field(
|
current_skill = replace_slow_update_field(
|
||||||
current_skill, slow_content,
|
current_skill, slow_content,
|
||||||
)
|
)
|
||||||
best_skill = replace_slow_update_field(
|
|
||||||
best_skill, slow_content,
|
|
||||||
)
|
|
||||||
# Update caches so downstream steps use the
|
# Update caches so downstream steps use the
|
||||||
# slow-update-injected skill for hashing.
|
# slow-update-injected skill for hashing.
|
||||||
slow_candidate_hash = skill_hash(current_skill)
|
slow_candidate_hash = skill_hash(current_skill)
|
||||||
@@ -1746,7 +1905,7 @@ class ReflACTTrainer:
|
|||||||
|
|
||||||
print(
|
print(
|
||||||
f" [slow update] force-injected into "
|
f" [slow update] force-injected into "
|
||||||
f"current & best "
|
f"current only "
|
||||||
f"({len(slow_content)} chars), "
|
f"({len(slow_content)} chars), "
|
||||||
f"{slow_time}s"
|
f"{slow_time}s"
|
||||||
)
|
)
|
||||||
@@ -1899,10 +2058,70 @@ class ReflACTTrainer:
|
|||||||
baseline_test_soft = None
|
baseline_test_soft = None
|
||||||
test_hard = None
|
test_hard = None
|
||||||
test_soft = None
|
test_soft = None
|
||||||
|
final_test_hard = None
|
||||||
|
final_test_soft = None
|
||||||
|
final_selection_hard = None
|
||||||
|
final_selection_soft = None
|
||||||
|
|
||||||
if cfg["eval_test"]:
|
if cfg["eval_test"]:
|
||||||
task_types = adapter.get_task_types()
|
task_types = adapter.get_task_types()
|
||||||
|
|
||||||
|
# ── Final skill validation (valid_seen) + best promotion ─────
|
||||||
|
# The final (last) skill may carry an epoch-end slow_update that
|
||||||
|
# was force-injected WITHOUT a val pass (use_gate=false or
|
||||||
|
# slow_update_gate_with_selection=false), so it never competed for
|
||||||
|
# best. Run one real val on the final skill; if its gate score
|
||||||
|
# beats the incumbent best, PROMOTE it to best so that best is the
|
||||||
|
# true val-argmax over all skills (including the final slow_update).
|
||||||
|
# When final == best, reuse the existing val score (no rollout).
|
||||||
|
try:
|
||||||
|
if skill_hash(current_skill) == skill_hash(best_skill):
|
||||||
|
final_selection_hard, final_selection_soft = best_score, None
|
||||||
|
print(
|
||||||
|
"\n [final skill == best skill] "
|
||||||
|
f"final_selection_hard={best_score:.4f} (reused)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fval_env, fval_n = _build_eval_env(
|
||||||
|
split="valid_seen",
|
||||||
|
env_num=cfg["sel_env_num"],
|
||||||
|
seed=seed,
|
||||||
|
)
|
||||||
|
fval_dir = os.path.join(out_root, "final_selection_eval")
|
||||||
|
fval_results = adapter.rollout(fval_env, current_skill, fval_dir)
|
||||||
|
final_selection_hard, final_selection_soft = compute_score(fval_results)
|
||||||
|
final_gate_score = select_gate_score(
|
||||||
|
final_selection_hard, final_selection_soft,
|
||||||
|
gate_metric, gate_mixed_weight,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"\n [final skill val] items={fval_n} "
|
||||||
|
f"final_selection_hard={final_selection_hard:.4f} "
|
||||||
|
f"gate={final_gate_score:.4f} "
|
||||||
|
f"(best={best_score:.4f})"
|
||||||
|
)
|
||||||
|
if final_gate_score > best_score:
|
||||||
|
# Promote: the final (slow-updated) skill is val-better
|
||||||
|
# than the incumbent best. Make it the new best so the
|
||||||
|
# subsequent BEST-skill test rollout evaluates it and
|
||||||
|
# best/final test scores coincide.
|
||||||
|
print(
|
||||||
|
f" [promote] final {final_gate_score:.4f} > "
|
||||||
|
f"best {best_score:.4f} → final becomes new best "
|
||||||
|
f"(step {global_step}, origin {current_origin})"
|
||||||
|
)
|
||||||
|
best_skill = current_skill
|
||||||
|
best_score = final_gate_score
|
||||||
|
best_step = global_step
|
||||||
|
best_origin = current_origin
|
||||||
|
with open(os.path.join(out_root, "best_skill.md"), "w") as f:
|
||||||
|
f.write(best_skill)
|
||||||
|
_persist_runtime_state(global_step)
|
||||||
|
except Exception as _e: # noqa: BLE001
|
||||||
|
final_selection_hard = None
|
||||||
|
final_selection_soft = None
|
||||||
|
print(f"\n [final skill val FAILED: {_e!r}]")
|
||||||
|
|
||||||
# Baseline: S_0 on test set (valid_unseen)
|
# Baseline: S_0 on test set (valid_unseen)
|
||||||
print(f"\n{'='*60}")
|
print(f"\n{'='*60}")
|
||||||
print(" BASELINE TEST — evaluate initial skill on Test set (valid_unseen)")
|
print(" BASELINE TEST — evaluate initial skill on Test set (valid_unseen)")
|
||||||
@@ -1971,13 +2190,87 @@ class ReflACTTrainer:
|
|||||||
f, indent=2, ensure_ascii=False,
|
f, indent=2, ensure_ascii=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Final skill (last skill in trajectory) on test set.
|
||||||
|
# Distinct from best_skill: with use_gate=False every candidate is
|
||||||
|
# force-accepted so the final skill is whatever the last step
|
||||||
|
# produced; with use_gate=True it is the last accepted skill, which
|
||||||
|
# may differ from the best-on-val skill. We always evaluate it so
|
||||||
|
# every run reports baseline / best-on-val / final on test.
|
||||||
|
# Guarded so a failure here never prevents summary.json from being
|
||||||
|
# written (the orchestrator's post-hoc safety net fills it in).
|
||||||
|
try:
|
||||||
|
if skill_hash(current_skill) == skill_hash(best_skill):
|
||||||
|
# Final == best: reuse results, skip a redundant rollout.
|
||||||
|
final_test_hard, final_test_soft = test_hard, test_soft
|
||||||
|
final_test_dir = os.path.join(out_root, "test_eval_final")
|
||||||
|
os.makedirs(final_test_dir, exist_ok=True)
|
||||||
|
with open(os.path.join(final_test_dir, "summary.json"), "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
k: {
|
||||||
|
"total": b["total"],
|
||||||
|
"hard_acc": b["hard"] / max(b["total"], 1),
|
||||||
|
}
|
||||||
|
for k, b in best_buckets.items()
|
||||||
|
},
|
||||||
|
f, indent=2, ensure_ascii=False,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"\n [final skill == best skill] "
|
||||||
|
f"final_test_hard={final_test_hard:.4f} (reused)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(" FINAL SKILL TEST — evaluate last skill on Test set (valid_unseen)")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
test_env3, test_n3 = _build_eval_env(
|
||||||
|
split="valid_unseen",
|
||||||
|
env_num=cfg["test_env_num"],
|
||||||
|
seed=seed,
|
||||||
|
)
|
||||||
|
print(f" Test items: {test_n3}")
|
||||||
|
final_test_dir = os.path.join(out_root, "test_eval_final")
|
||||||
|
final_test_results = adapter.rollout(test_env3, current_skill, final_test_dir)
|
||||||
|
final_test_hard, final_test_soft = compute_score(final_test_results)
|
||||||
|
final_buckets = _compute_task_type_buckets(final_test_results, task_types)
|
||||||
|
print("\n === Final Skill Test Results ===")
|
||||||
|
for task_type in task_types + ["overall"]:
|
||||||
|
b = final_buckets.get(task_type, {"total": 0, "hard": 0})
|
||||||
|
t = max(b["total"], 1)
|
||||||
|
print(
|
||||||
|
f" {task_type:<40s}: "
|
||||||
|
f"hard={b['hard']}/{b['total']}={b['hard']/t:.4f}"
|
||||||
|
)
|
||||||
|
with open(os.path.join(final_test_dir, "summary.json"), "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
k: {
|
||||||
|
"total": b["total"],
|
||||||
|
"hard_acc": b["hard"] / max(b["total"], 1),
|
||||||
|
}
|
||||||
|
for k, b in final_buckets.items()
|
||||||
|
},
|
||||||
|
f, indent=2, ensure_ascii=False,
|
||||||
|
)
|
||||||
|
except Exception as _e: # noqa: BLE001
|
||||||
|
final_test_hard = None
|
||||||
|
final_test_soft = None
|
||||||
|
print(f"\n [final skill test FAILED: {_e!r}] "
|
||||||
|
"— will be filled by post-hoc eval")
|
||||||
|
|
||||||
# Comparison
|
# Comparison
|
||||||
delta_hard = (test_hard or 0) - (baseline_test_hard or 0)
|
delta_hard = (test_hard or 0) - (baseline_test_hard or 0)
|
||||||
print(f"\n === Improvement (best vs baseline) ===")
|
print(f"\n === Improvement vs baseline (init S_0) ===")
|
||||||
print(
|
print(
|
||||||
f" hard: {baseline_test_hard:.4f} -> {test_hard:.4f} "
|
f" [2] best-on-val hard: {baseline_test_hard:.4f} -> {test_hard:.4f} "
|
||||||
f"(delta={delta_hard:+.4f})"
|
f"(delta={delta_hard:+.4f})"
|
||||||
)
|
)
|
||||||
|
if final_test_hard is not None:
|
||||||
|
final_delta_hard = (final_test_hard or 0) - (baseline_test_hard or 0)
|
||||||
|
print(
|
||||||
|
f" [3] final/last hard: {baseline_test_hard:.4f} -> {final_test_hard:.4f} "
|
||||||
|
f"(delta={final_delta_hard:+.4f})"
|
||||||
|
)
|
||||||
|
|
||||||
# ── Global summary ───────────────────────────────────────────────
|
# ── Global summary ───────────────────────────────────────────────
|
||||||
total_wall = time.time() - t_loop_start
|
total_wall = time.time() - t_loop_start
|
||||||
@@ -2009,6 +2302,8 @@ class ReflACTTrainer:
|
|||||||
skill_hash(skill_init), (None, None),
|
skill_hash(skill_init), (None, None),
|
||||||
)[0],
|
)[0],
|
||||||
"best_selection_hard": best_score,
|
"best_selection_hard": best_score,
|
||||||
|
"final_selection_hard": final_selection_hard,
|
||||||
|
"final_selection_soft": final_selection_soft,
|
||||||
"best_step": best_step,
|
"best_step": best_step,
|
||||||
"current_origin": current_origin,
|
"current_origin": current_origin,
|
||||||
"best_origin": best_origin,
|
"best_origin": best_origin,
|
||||||
@@ -2021,11 +2316,18 @@ class ReflACTTrainer:
|
|||||||
"baseline_test_soft": baseline_test_soft,
|
"baseline_test_soft": baseline_test_soft,
|
||||||
"test_hard": test_hard,
|
"test_hard": test_hard,
|
||||||
"test_soft": test_soft,
|
"test_soft": test_soft,
|
||||||
|
"final_test_hard": final_test_hard,
|
||||||
|
"final_test_soft": final_test_soft,
|
||||||
"test_delta_hard": (
|
"test_delta_hard": (
|
||||||
(test_hard or 0) - (baseline_test_hard or 0)
|
(test_hard or 0) - (baseline_test_hard or 0)
|
||||||
if test_hard is not None
|
if test_hard is not None
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
"final_test_delta_hard": (
|
||||||
|
(final_test_hard or 0) - (baseline_test_hard or 0)
|
||||||
|
if final_test_hard is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
"total_wall_time_s": round(total_wall, 1),
|
"total_wall_time_s": round(total_wall, 1),
|
||||||
"token_summary": token_summary,
|
"token_summary": token_summary,
|
||||||
}
|
}
|
||||||
@@ -2046,8 +2348,22 @@ class ReflACTTrainer:
|
|||||||
f" epoch {es['epoch']}: accept={es['accepts']} reject={es['rejects']} "
|
f" epoch {es['epoch']}: accept={es['accepts']} reject={es['rejects']} "
|
||||||
f"best={es['best_score_at_epoch_end']:.4f}"
|
f"best={es['best_score_at_epoch_end']:.4f}"
|
||||||
)
|
)
|
||||||
|
if baseline_test_hard is not None:
|
||||||
|
print("\n === TEST scores (3 skills, split=valid_unseen) ===")
|
||||||
|
print(
|
||||||
|
f" [1] init/baseline (S_0) : "
|
||||||
|
f"test_hard={baseline_test_hard:.4f}"
|
||||||
|
)
|
||||||
if test_hard is not None:
|
if test_hard is not None:
|
||||||
print(f" test_hard={test_hard:.4f} test_soft={test_soft:.4f}")
|
print(
|
||||||
|
f" [2] best-on-val (step {best_step})".ljust(37)
|
||||||
|
+ f": test_hard={test_hard:.4f} test_soft={test_soft:.4f}"
|
||||||
|
)
|
||||||
|
if final_test_hard is not None:
|
||||||
|
print(
|
||||||
|
f" [3] final/last skill : "
|
||||||
|
f"test_hard={final_test_hard:.4f} test_soft={final_test_soft:.4f}"
|
||||||
|
)
|
||||||
if token_summary.get("_total"):
|
if token_summary.get("_total"):
|
||||||
t = token_summary["_total"]
|
t = token_summary["_total"]
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -4,16 +4,40 @@ This directory provides scaffold files for adding a new benchmark to SkillOpt.
|
|||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `env_template.py` — Environment adapter template
|
- `env_template.py` — Environment adapter template (subclasses
|
||||||
- `loader_template.py` — Data loader template
|
`EnvAdapter`; implements the 5 abstract methods so the file is
|
||||||
- `config_template.yaml` — Config file template
|
instantiable out of the box).
|
||||||
|
- `loader_template.py` — Data loader template (subclasses
|
||||||
|
`SplitDataLoader`; implements `load_split_items` for `.json`/`.jsonl`).
|
||||||
|
- `config_template.yaml` — Config file template.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Copy this directory: `cp -r skillopt/envs/_template skillopt/envs/your_benchmark`
|
1. **Copy the directory:**
|
||||||
2. Rename files: remove `_template` suffix
|
```bash
|
||||||
3. Implement the `TODO` sections
|
cp -r skillopt/envs/_template skillopt/envs/your_benchmark
|
||||||
4. Register in `skillopt/envs/__init__.py`
|
```
|
||||||
5. Create config at `configs/your_benchmark/default.yaml`
|
2. **Rename the files** (drop the `_template` suffix):
|
||||||
|
```bash
|
||||||
|
cd skillopt/envs/your_benchmark
|
||||||
|
mv env_template.py adapter.py
|
||||||
|
mv loader_template.py loader.py
|
||||||
|
```
|
||||||
|
…and inside each file rename the classes
|
||||||
|
(`TemplateBenchmarkEnv → YourBenchmarkAdapter`,
|
||||||
|
`TemplateBenchmarkLoader → YourBenchmarkLoader`)
|
||||||
|
and fix the cross-import in `adapter.py`.
|
||||||
|
3. **Implement the TODO blocks** inside `adapter.py:rollout` and the
|
||||||
|
`_normalize_item` helper in `loader.py`. If you want real reflection,
|
||||||
|
uncomment the `run_minibatch_reflect` block in `adapter.py:reflect`.
|
||||||
|
4. **Register** the adapter — add a `try / except ImportError` block in
|
||||||
|
`scripts/train.py`'s `_register_builtins()` mapping the registry key
|
||||||
|
to your `YourBenchmarkAdapter` class. There is no
|
||||||
|
`BENCHMARK_REGISTRY` dict in `skillopt/envs/__init__.py`; the live
|
||||||
|
registry is `_ENV_REGISTRY` in `scripts/train.py`.
|
||||||
|
5. **Create the config** at `configs/your_benchmark/default.yaml`
|
||||||
|
(start from `config_template.yaml`). `_base_` is a **string path**,
|
||||||
|
not a list.
|
||||||
|
|
||||||
See the [documentation](../../docs/guide/new-benchmark.md) for the full guide.
|
See the [Add a New Benchmark guide](../../../docs/guide/new-benchmark.md)
|
||||||
|
for the full step-by-step with a worked `docfaithful` example.
|
||||||
|
|||||||
@@ -4,27 +4,36 @@
|
|||||||
# Copy this file to configs/<your_benchmark>/default.yaml
|
# Copy this file to configs/<your_benchmark>/default.yaml
|
||||||
# and customize the values below.
|
# and customize the values below.
|
||||||
|
|
||||||
# Inherit global defaults
|
# Inherit global defaults.
|
||||||
_base_: ['../_base_/default.yaml']
|
# NOTE: `_base_` is a string path, not a list.
|
||||||
|
_base_: ../_base_/default.yaml
|
||||||
|
|
||||||
# ── Environment ──────────────────────────────────
|
# ── Environment ──────────────────────────────────
|
||||||
env:
|
env:
|
||||||
name: your_benchmark # Must match registry key
|
name: your_benchmark # Must match the key registered in scripts/train.py
|
||||||
data_path: data/your_benchmark # Path to your data
|
# Optional: a seed skill document. Create this file yourself before the
|
||||||
|
# first run, or omit the key to start from an empty skill.
|
||||||
|
# skill_init: skillopt/envs/your_benchmark/skills/initial.md
|
||||||
|
data_path: data/your_benchmark # Path to your data (for split_mode: ratio)
|
||||||
|
split_dir: "" # Set this and use split_mode: split_dir for pre-split data
|
||||||
split_mode: ratio # "ratio" or "split_dir"
|
split_mode: ratio # "ratio" or "split_dir"
|
||||||
split_ratio: "2:1:7" # train:val:test
|
split_ratio: "2:1:7" # train:val:test (used when split_mode: ratio)
|
||||||
exec_timeout: 120 # Per-task timeout (seconds)
|
workers: 4 # Parallel rollout workers
|
||||||
|
max_completion_tokens: 4096 # Cap per target-model call
|
||||||
|
limit: 0 # 0 = no limit; small int = debug sample
|
||||||
|
|
||||||
# ── Training ─────────────────────────────────────
|
# ── Training ─────────────────────────────────────
|
||||||
train:
|
train:
|
||||||
num_epochs: 4 # Number of epochs
|
num_epochs: 4
|
||||||
batch_size: 40 # Tasks per step (batch size)
|
batch_size: 40
|
||||||
|
accumulation: 1
|
||||||
seed: 42
|
seed: 42
|
||||||
|
|
||||||
# ── Gradient (Reflection) ───────────────────────
|
# ── Gradient (Reflection) ───────────────────────
|
||||||
gradient:
|
gradient:
|
||||||
analyst_workers: 16 # Parallel reflection workers
|
analyst_workers: 16 # Parallel reflection workers
|
||||||
minibatch_size: 8
|
minibatch_size: 8
|
||||||
|
merge_batch_size: 8
|
||||||
|
|
||||||
# ── Optimizer ────────────────────────────────────
|
# ── Optimizer ────────────────────────────────────
|
||||||
optimizer:
|
optimizer:
|
||||||
@@ -39,7 +48,8 @@ evaluation:
|
|||||||
eval_test: true # Run test eval after training
|
eval_test: true # Run test eval after training
|
||||||
|
|
||||||
# ── Model ────────────────────────────────────────
|
# ── Model ────────────────────────────────────────
|
||||||
|
# Override only what differs from the inherited defaults.
|
||||||
model:
|
model:
|
||||||
backend: azure_openai # azure_openai | openai_chat | claude_code_exec | qwen
|
optimizer_backend: openai_chat # openai_chat | claude_chat | qwen_chat | minimax_chat
|
||||||
optimizer: gpt-4o
|
target_backend: openai_chat # … plus codex_exec / claude_code_exec for target only
|
||||||
target: gpt-4o
|
reasoning_effort: medium
|
||||||
|
|||||||
@@ -4,89 +4,193 @@ Benchmark Environment Template
|
|||||||
Copy this file and implement the TODO sections to add a new benchmark.
|
Copy this file and implement the TODO sections to add a new benchmark.
|
||||||
|
|
||||||
The EnvAdapter is responsible for:
|
The EnvAdapter is responsible for:
|
||||||
1. Executing tasks using the target model + current skill document
|
1. Building per-batch environment managers (train and eval splits).
|
||||||
2. Evaluating predictions against ground truth
|
2. Running rollouts under the current skill document.
|
||||||
3. Returning structured results for the training loop
|
3. Reflecting on those rollouts into raw patch dicts.
|
||||||
|
4. Reporting the distinct task types in your data (for stratified
|
||||||
|
sampling).
|
||||||
|
|
||||||
|
For a fully worked example see ``skillopt/envs/officeqa/``.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from skillopt.datasets.base import BatchSpec
|
||||||
from skillopt.envs.base import EnvAdapter
|
from skillopt.envs.base import EnvAdapter
|
||||||
|
from skillopt.envs._template.loader_template import TemplateBenchmarkLoader
|
||||||
|
# When you wire in real reflection, also import:
|
||||||
|
# from skillopt.gradient.reflect import run_minibatch_reflect
|
||||||
|
|
||||||
|
|
||||||
class TemplateBenchmarkEnv(EnvAdapter):
|
class TemplateBenchmarkEnv(EnvAdapter):
|
||||||
"""
|
"""
|
||||||
Environment adapter for <Your Benchmark Name>.
|
Environment adapter for <Your Benchmark Name>.
|
||||||
|
|
||||||
Rename this class and implement the abstract methods below.
|
Rename this class. Each abstract method below is required by
|
||||||
|
:class:`skillopt.envs.base.EnvAdapter`. The template implementations
|
||||||
|
are minimal so this file is importable and instantiable; replace the
|
||||||
|
TODOs with real logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cfg: dict):
|
def __init__(
|
||||||
super().__init__(cfg)
|
self,
|
||||||
# TODO: Initialize benchmark-specific state
|
split_dir: str = "",
|
||||||
# Example: self.tools = load_tools(cfg)
|
data_path: str = "",
|
||||||
|
split_mode: str = "split_dir",
|
||||||
|
split_ratio: str = "2:1:7",
|
||||||
|
split_seed: int = 42,
|
||||||
|
split_output_dir: str = "",
|
||||||
|
workers: int = 4,
|
||||||
|
analyst_workers: int = 4,
|
||||||
|
failure_only: bool = False,
|
||||||
|
minibatch_size: int = 8,
|
||||||
|
edit_budget: int = 4,
|
||||||
|
seed: int = 42,
|
||||||
|
limit: int = 0,
|
||||||
|
max_completion_tokens: int = 4096,
|
||||||
|
) -> None:
|
||||||
|
self.workers = workers
|
||||||
|
self.analyst_workers = analyst_workers
|
||||||
|
self.failure_only = failure_only
|
||||||
|
self.minibatch_size = minibatch_size
|
||||||
|
self.edit_budget = edit_budget
|
||||||
|
self.max_completion_tokens = int(max_completion_tokens)
|
||||||
|
self.dataloader = TemplateBenchmarkLoader(
|
||||||
|
split_dir=split_dir,
|
||||||
|
data_path=data_path,
|
||||||
|
split_mode=split_mode,
|
||||||
|
split_ratio=split_ratio,
|
||||||
|
split_seed=split_seed,
|
||||||
|
split_output_dir=split_output_dir,
|
||||||
|
seed=seed,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
|
||||||
async def execute(self, item, skill: str, model):
|
# ── Lifecycle hooks ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def setup(self, cfg: dict) -> None:
|
||||||
|
super().setup(cfg)
|
||||||
|
self.dataloader.setup(cfg)
|
||||||
|
|
||||||
|
def get_dataloader(self):
|
||||||
|
return self.dataloader
|
||||||
|
|
||||||
|
# ── Batch → env manager ────────────────────────────────────────────
|
||||||
|
|
||||||
|
def build_env_from_batch(self, batch: BatchSpec, **kwargs):
|
||||||
|
# Dataset-backed envs typically just pass items straight through.
|
||||||
|
return list(batch.payload or [])
|
||||||
|
|
||||||
|
def build_train_env(self, batch_size: int, seed: int, **kwargs):
|
||||||
|
batch = self.dataloader.build_train_batch(
|
||||||
|
batch_size=batch_size, seed=seed, **kwargs
|
||||||
|
)
|
||||||
|
return self.build_env_from_batch(batch, **kwargs)
|
||||||
|
|
||||||
|
def build_eval_env(self, env_num: int, split: str, seed: int, **kwargs):
|
||||||
|
batch = self.dataloader.build_eval_batch(
|
||||||
|
env_num=env_num, split=split, seed=seed, **kwargs
|
||||||
|
)
|
||||||
|
return self.build_env_from_batch(batch, **kwargs)
|
||||||
|
|
||||||
|
# ── Rollout: run episodes under current skill ──────────────────────
|
||||||
|
|
||||||
|
def rollout(
|
||||||
|
self,
|
||||||
|
env_manager,
|
||||||
|
skill_content: str,
|
||||||
|
out_dir: str,
|
||||||
|
**kwargs,
|
||||||
|
) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Execute a single task with the target model.
|
Run a batch of episodes under the current skill.
|
||||||
|
|
||||||
Args:
|
TODO: replace this loop with your real rollout. For each item:
|
||||||
item: DataItem with .id, .input, .ground_truth, .metadata
|
1. Build the prompt using `skill_content` as the system message.
|
||||||
skill: Current skill document content (Markdown string)
|
2. Call your target model.
|
||||||
model: Target model backend instance
|
3. Score the prediction.
|
||||||
|
4. Return a dict with at minimum: ``id`` (str), ``hard`` (0|1),
|
||||||
Returns:
|
``soft`` (float in [0, 1]). Add any env-specific extras you
|
||||||
TaskResult with prediction, score, and trajectory
|
need for reflect() — they will be preserved on
|
||||||
|
``RolloutResult.extras``.
|
||||||
"""
|
"""
|
||||||
# Step 1: Build the prompt combining skill + task input
|
items: list[dict] = env_manager
|
||||||
prompt = self.build_prompt(item, skill)
|
results: list[dict] = []
|
||||||
|
for item in items:
|
||||||
|
# ── REPLACE THIS BLOCK WITH YOUR REAL ROLLOUT ──
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"id": str(item.get("id", "")),
|
||||||
|
"hard": 0,
|
||||||
|
"soft": 0.0,
|
||||||
|
"predicted_answer": "",
|
||||||
|
"question": item.get("question", ""),
|
||||||
|
"fail_reason": "template rollout — not implemented",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
||||||
# Step 2: Call the target model
|
# ── Reflect: turn rollout results into patch dicts ─────────────────
|
||||||
# TODO: Customize the message format for your benchmark
|
|
||||||
messages = [
|
|
||||||
{"role": "system", "content": skill},
|
|
||||||
{"role": "user", "content": item.input},
|
|
||||||
]
|
|
||||||
response = await model.generate(messages)
|
|
||||||
|
|
||||||
# Step 3: Parse the model response into a prediction
|
def reflect(
|
||||||
prediction = self.parse_response(response.content)
|
self,
|
||||||
|
results: list[dict],
|
||||||
# Step 4: Score the prediction
|
skill_content: str,
|
||||||
score = self.evaluate(prediction, item.ground_truth)
|
out_dir: str,
|
||||||
|
**kwargs,
|
||||||
# Step 5: Return structured result
|
) -> list[dict | None]:
|
||||||
return {
|
|
||||||
"item_id": item.id,
|
|
||||||
"prediction": prediction,
|
|
||||||
"score": score,
|
|
||||||
"trajectory": messages + [{"role": "assistant", "content": response.content}],
|
|
||||||
}
|
|
||||||
|
|
||||||
def evaluate(self, prediction: str, ground_truth: str) -> float:
|
|
||||||
"""
|
"""
|
||||||
Score a prediction against the ground truth.
|
Turn rollouts into a list of raw patch dicts (or None to drop).
|
||||||
|
|
||||||
Returns:
|
Each non-None dict MUST have:
|
||||||
Float between 0.0 (wrong) and 1.0 (correct)
|
- "patch": {"edits": [...]} a Patch.to_dict() payload
|
||||||
|
- "source_type": "failure" | "success"
|
||||||
TODO: Implement your scoring metric. Common options:
|
|
||||||
- Exact match: float(pred.strip().lower() == gt.strip().lower())
|
|
||||||
- F1 score: compute token overlap
|
|
||||||
- ANLS: for document QA tasks
|
|
||||||
- Custom: any float in [0, 1]
|
|
||||||
"""
|
|
||||||
# Placeholder — exact match
|
|
||||||
return float(prediction.strip().lower() == ground_truth.strip().lower())
|
|
||||||
|
|
||||||
def build_prompt(self, item, skill: str) -> str:
|
Most benchmarks delegate to
|
||||||
"""Combine skill document with task input."""
|
:func:`skillopt.gradient.reflect.run_minibatch_reflect` which
|
||||||
return f"{skill}\n\n---\n\nQuestion: {item.input}"
|
will call the optimizer model with the
|
||||||
|
``analyst_error_*`` / ``analyst_success_*`` prompts. To enable it,
|
||||||
|
uncomment the import above and call:
|
||||||
|
|
||||||
def parse_response(self, response: str) -> str:
|
from skillopt.gradient.reflect import run_minibatch_reflect
|
||||||
|
return run_minibatch_reflect(
|
||||||
|
results=results,
|
||||||
|
skill_content=skill_content,
|
||||||
|
prediction_dir=kwargs.get(
|
||||||
|
"prediction_dir", os.path.join(out_dir, "predictions")
|
||||||
|
),
|
||||||
|
patches_dir=kwargs.get(
|
||||||
|
"patches_dir", os.path.join(out_dir, "patches")
|
||||||
|
),
|
||||||
|
workers=self.analyst_workers,
|
||||||
|
failure_only=self.failure_only,
|
||||||
|
minibatch_size=self.minibatch_size,
|
||||||
|
edit_budget=self.edit_budget,
|
||||||
|
random_seed=kwargs.get("random_seed"),
|
||||||
|
error_system=self.get_error_minibatch_prompt(),
|
||||||
|
success_system=self.get_success_minibatch_prompt(),
|
||||||
|
step_buffer_context=kwargs.get("step_buffer_context", ""),
|
||||||
|
update_mode=getattr(self, "_cfg", {}).get(
|
||||||
|
"skill_update_mode", "patch"
|
||||||
|
),
|
||||||
|
)
|
||||||
"""
|
"""
|
||||||
Extract the answer from the model's raw response.
|
# Template default: produce no patches (no-op trainer step).
|
||||||
|
return [None for _ in results]
|
||||||
TODO: Implement extraction logic. For example:
|
|
||||||
- Extract text after "Answer:"
|
# ── Stratification hint ────────────────────────────────────────────
|
||||||
- Parse JSON output
|
|
||||||
- Extract from code blocks
|
def get_task_types(self) -> list[str]:
|
||||||
"""
|
"""Distinct task-type strings used for stratified sampling."""
|
||||||
return response.strip()
|
seen: list[str] = []
|
||||||
|
all_items = (
|
||||||
|
self.dataloader.train_items
|
||||||
|
+ self.dataloader.val_items
|
||||||
|
+ self.dataloader.test_items
|
||||||
|
)
|
||||||
|
for item in all_items:
|
||||||
|
tt = str(item.get("task_type") or "template")
|
||||||
|
if tt not in seen:
|
||||||
|
seen.append(tt)
|
||||||
|
return seen or ["template"]
|
||||||
|
|||||||
@@ -1,103 +1,87 @@
|
|||||||
"""
|
"""
|
||||||
Benchmark Data Loader Template
|
Benchmark Data Loader Template
|
||||||
================================
|
================================
|
||||||
Copy this file and implement the TODO sections to load your benchmark data.
|
Copy this file and implement ``load_split_items`` to load your benchmark
|
||||||
|
data. The loader is a :class:`skillopt.datasets.base.SplitDataLoader`
|
||||||
|
subclass — the base class handles both ``split_mode="split_dir"`` (read
|
||||||
|
an existing train/val/test layout) and ``split_mode="ratio"`` (build the
|
||||||
|
splits from a single raw file deterministically).
|
||||||
|
|
||||||
The DataLoader is responsible for:
|
For a fully worked example see
|
||||||
1. Loading raw data from disk
|
``skillopt/envs/officeqa/dataloader.py``.
|
||||||
2. Splitting into train / validation / test sets
|
|
||||||
3. Providing DataItem objects to the training loop
|
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from skillopt.datasets.base import SplitDataLoader
|
||||||
|
|
||||||
class TemplateBenchmarkLoader:
|
|
||||||
|
def _normalize_item(raw: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Normalise one raw entry into the dict shape SkillOpt expects.
|
||||||
|
|
||||||
|
The only **hard** requirement is ``"id"`` (str). Add whatever extra
|
||||||
|
fields your :class:`TemplateBenchmarkEnv.rollout` needs.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"id": str(raw.get("uid") or raw.get("id") or ""),
|
||||||
|
"question": str(raw.get("question") or raw.get("prompt") or ""),
|
||||||
|
"ground_truth": str(raw.get("ground_truth") or raw.get("answer") or ""),
|
||||||
|
"task_type": str(raw.get("category") or raw.get("task_type") or "template"),
|
||||||
|
# ── add benchmark-specific keys here ──
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateBenchmarkLoader(SplitDataLoader):
|
||||||
"""
|
"""
|
||||||
Data loader for <Your Benchmark Name>.
|
Data loader for <Your Benchmark Name>.
|
||||||
|
|
||||||
Rename this class and implement the methods below.
|
Subclass note: you usually only need to implement
|
||||||
|
:meth:`load_split_items`. The base class drives ``setup(cfg)``,
|
||||||
|
materialises ratio-mode splits, exposes ``train_items``,
|
||||||
|
``val_items``, ``test_items``, and builds ``BatchSpec`` objects on
|
||||||
|
demand.
|
||||||
|
|
||||||
|
If you want to support ``split_mode="ratio"`` (auto-split a single
|
||||||
|
file into train/val/test), also implement
|
||||||
|
:meth:`load_raw_items(data_path)` returning the full list of items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data_dir: str = "data/your_benchmark", **kwargs):
|
def load_split_items(self, split_path: str) -> list[dict]:
|
||||||
self.data_dir = Path(data_dir)
|
"""Load all items for one split directory.
|
||||||
self.items = []
|
|
||||||
self.splits = {}
|
|
||||||
|
|
||||||
def setup(self, cfg: dict):
|
``split_path`` is e.g. ``data/your_benchmark/train/``. Return a
|
||||||
|
list of dicts, each shaped like :func:`_normalize_item`'s output.
|
||||||
"""
|
"""
|
||||||
Initialize the loader with config.
|
path = Path(split_path)
|
||||||
|
|
||||||
Called once before training starts.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cfg: Dict with keys like 'split_mode', 'train_ratio', 'val_ratio', etc.
|
|
||||||
"""
|
|
||||||
# Step 1: Load raw data
|
|
||||||
self.items = self._load_items()
|
|
||||||
|
|
||||||
# Step 2: Create splits
|
json_files = sorted(path.glob("*.json"))
|
||||||
split_mode = cfg.get("split_mode", "ratio")
|
if json_files:
|
||||||
if split_mode == "ratio":
|
with json_files[0].open(encoding="utf-8") as f:
|
||||||
self._split_by_ratio(
|
payload = json.load(f)
|
||||||
train_ratio=cfg.get("train_ratio", 0.7),
|
if not isinstance(payload, list):
|
||||||
val_ratio=cfg.get("val_ratio", 0.15),
|
raise ValueError(
|
||||||
)
|
f"Expected JSON array at top level of {json_files[0]}"
|
||||||
elif split_mode == "split_dir":
|
)
|
||||||
self._load_predefined_splits(cfg.get("split_dir", self.data_dir))
|
return [_normalize_item(row) for row in payload]
|
||||||
|
|
||||||
def _load_items(self) -> list:
|
jsonl_files = sorted(path.glob("*.jsonl"))
|
||||||
"""
|
if jsonl_files:
|
||||||
Load raw data into structured items.
|
items: list[dict] = []
|
||||||
|
with jsonl_files[0].open(encoding="utf-8") as f:
|
||||||
TODO: Implement data loading. Each item should have at minimum:
|
for line in f:
|
||||||
- id: unique identifier
|
line = line.strip()
|
||||||
- input: the task input (question, instruction, etc.)
|
if not line:
|
||||||
- ground_truth: the expected answer
|
continue
|
||||||
- metadata: optional dict with extra info
|
items.append(_normalize_item(json.loads(line)))
|
||||||
|
|
||||||
Example:
|
|
||||||
items = []
|
|
||||||
for path in self.data_dir.glob("*.json"):
|
|
||||||
data = json.loads(path.read_text())
|
|
||||||
for entry in data:
|
|
||||||
items.append({
|
|
||||||
"id": entry["id"],
|
|
||||||
"input": entry["question"],
|
|
||||||
"ground_truth": entry["answer"],
|
|
||||||
"metadata": {"source": path.name},
|
|
||||||
})
|
|
||||||
return items
|
return items
|
||||||
"""
|
|
||||||
raise NotImplementedError("Implement _load_items() for your benchmark")
|
|
||||||
|
|
||||||
def _split_by_ratio(self, train_ratio: float, val_ratio: float):
|
raise FileNotFoundError(
|
||||||
"""Split items by ratio."""
|
f"No .json or .jsonl file found in {split_path}"
|
||||||
import random
|
)
|
||||||
random.shuffle(self.items)
|
|
||||||
n = len(self.items)
|
|
||||||
n_train = int(n * train_ratio)
|
|
||||||
n_val = int(n * val_ratio)
|
|
||||||
self.splits = {
|
|
||||||
"train": self.items[:n_train],
|
|
||||||
"valid": self.items[n_train:n_train + n_val],
|
|
||||||
"test": self.items[n_train + n_val:],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _load_predefined_splits(self, split_dir):
|
# Optional — only needed if you intend to use ``split_mode='ratio'``.
|
||||||
"""Load from pre-split directories."""
|
# def load_raw_items(self, data_path: str) -> list[dict]:
|
||||||
# TODO: Implement if your benchmark has pre-defined splits
|
# ...
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_split_items(self, split: str) -> list:
|
|
||||||
"""
|
|
||||||
Return items for a given split.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
split: One of "train", "valid", "test"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of data items for the requested split
|
|
||||||
"""
|
|
||||||
if split not in self.splits:
|
|
||||||
raise ValueError(f"Unknown split '{split}'. Available: {list(self.splits.keys())}")
|
|
||||||
return self.splits[split]
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ def _build_eval_feedback(verify_report: str) -> str:
|
|||||||
output and whether each cell is correct or wrong.
|
output and whether each cell is correct or wrong.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
lines = ["Your code executed successfully but produced incorrect results.",
|
wrong_lines = []
|
||||||
"The following cells have wrong values:"]
|
n_correct = 0
|
||||||
for raw_line in verify_report.splitlines():
|
for raw_line in verify_report.splitlines():
|
||||||
raw_line = raw_line.strip()
|
raw_line = raw_line.strip()
|
||||||
if not raw_line:
|
if not raw_line:
|
||||||
@@ -68,9 +68,14 @@ def _build_eval_feedback(verify_report: str) -> str:
|
|||||||
if m:
|
if m:
|
||||||
cell, got_val, mark = m.groups()
|
cell, got_val, mark = m.groups()
|
||||||
if mark == "✗":
|
if mark == "✗":
|
||||||
lines.append(f" {cell}: your output = {got_val} (WRONG)")
|
wrong_lines.append(f" {cell}: your output = {got_val} (WRONG)")
|
||||||
else:
|
else:
|
||||||
lines.append(f" {cell}: correct ✓")
|
n_correct += 1
|
||||||
|
lines = ["Your code executed successfully but produced incorrect results.",
|
||||||
|
"The following cells have wrong values:"]
|
||||||
|
lines.extend(wrong_lines)
|
||||||
|
if n_correct:
|
||||||
|
lines.append(f" ({n_correct} other cells are correct.)")
|
||||||
lines.append(
|
lines.append(
|
||||||
"\nPlease analyze the spreadsheet data more carefully and fix the code. "
|
"\nPlease analyze the spreadsheet data more carefully and fix the code. "
|
||||||
"Return a complete corrected Python script inside a ```python``` block."
|
"Return a complete corrected Python script inside a ```python``` block."
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ from concurrent.futures import (
|
|||||||
import openpyxl
|
import openpyxl
|
||||||
|
|
||||||
from skillopt.envs.spreadsheetbench.react_agent import run_react
|
from skillopt.envs.spreadsheetbench.react_agent import run_react
|
||||||
from skillopt.envs.spreadsheetbench.evaluator import evaluate, _generate_cell_names
|
from skillopt.envs.spreadsheetbench.evaluator import (
|
||||||
|
evaluate, _generate_cell_names, _compare_cell_value,
|
||||||
|
)
|
||||||
from skillopt.envs.spreadsheetbench.executor import run_generated_code
|
from skillopt.envs.spreadsheetbench.executor import run_generated_code
|
||||||
|
|
||||||
|
|
||||||
@@ -87,6 +89,21 @@ def _find_test_cases(task_dir: str) -> list[tuple[str, str, str]]:
|
|||||||
|
|
||||||
# ── Auto-verify helper ──────────────────────────────────────────────────────
|
# ── Auto-verify helper ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# The official SpreadsheetBench evaluator never serialises cells to text — it
|
||||||
|
# compares in memory and returns only a pass/fail bool. The per-cell report
|
||||||
|
# below is a repo-local training aid (fed back to the model on retry and saved
|
||||||
|
# into the trajectory for reflection). On most tasks the answer range is a
|
||||||
|
# handful of cells, so the full report is tiny. But a few tasks have answer
|
||||||
|
# ranges spanning tens of thousands of cells (e.g. 80-42 =
|
||||||
|
# 'Consolidate_ALL'!A2:L8000 ≈ 96k cells); dumping every cell explodes the
|
||||||
|
# report to several MB, floods the model's context and bloats conversation
|
||||||
|
# files. We therefore apply the same head+tail character truncation the rest of
|
||||||
|
# the codebase uses for oversized trajectory text (cf. reflect.py / slow_update.py
|
||||||
|
# `text[:half] + "...[truncated]...\n" + text[-half:]`): keep the first and last
|
||||||
|
# `_MAX_REPORT_CHARS // 2` chars so both the leading and trailing wrong cells
|
||||||
|
# stay visible. Small reports are unchanged.
|
||||||
|
_MAX_REPORT_CHARS = 12000 # head+tail char budget (~6000 head + 6000 tail)
|
||||||
|
|
||||||
|
|
||||||
def _auto_verify_output(
|
def _auto_verify_output(
|
||||||
pred_path: str,
|
pred_path: str,
|
||||||
@@ -97,7 +114,8 @@ def _auto_verify_output(
|
|||||||
|
|
||||||
Returns a human-readable verification report that can be appended to the
|
Returns a human-readable verification report that can be appended to the
|
||||||
trajectory so the error analyst can see exactly what went wrong (e.g.
|
trajectory so the error analyst can see exactly what went wrong (e.g.
|
||||||
``cell A1: got=None, expected=420``).
|
``cell A1: got=None, expected=420``). Oversized reports are head+tail
|
||||||
|
truncated to `_MAX_REPORT_CHARS` chars, matching the rest of the codebase.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(pred_path):
|
if not os.path.exists(pred_path):
|
||||||
return "Verification: output file does not exist."
|
return "Verification: output file does not exist."
|
||||||
@@ -129,11 +147,28 @@ def _auto_verify_output(
|
|||||||
lines.append(f" Sheet '{sheet_name}' NOT FOUND in output.")
|
lines.append(f" Sheet '{sheet_name}' NOT FOUND in output.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
n_empty_correct = 0 # empty-on-both correct cells collapsed to a count
|
||||||
for cn in cell_names:
|
for cn in cell_names:
|
||||||
gv = ws_gold[cn].value if ws_gold else "N/A"
|
gv = ws_gold[cn].value if ws_gold else "N/A"
|
||||||
pv = ws_pred[cn].value
|
pv = ws_pred[cn].value
|
||||||
match = "✓" if repr(gv) == repr(pv) else "✗"
|
# Use the official cell comparator so this report's ✓/✗ agrees
|
||||||
|
# with the real scorer (evaluate). repr() equality would wrongly
|
||||||
|
# flag e.g. 5 vs 5.0 or None vs "" as mismatches and mislead the
|
||||||
|
# model into "fixing" cells that already pass scoring.
|
||||||
|
ok_cell = ws_gold is not None and _compare_cell_value(gv, pv)
|
||||||
|
# Collapse only cells that are correct AND empty on both sides
|
||||||
|
# (got=None, expected=None ✓): pure noise. Every other cell —
|
||||||
|
# including non-empty correct cells — is listed in full; the
|
||||||
|
# final head+tail char cap keeps the report bounded.
|
||||||
|
if ok_cell and gv in (None, "") and pv in (None, ""):
|
||||||
|
n_empty_correct += 1
|
||||||
|
continue
|
||||||
|
match = "✓" if ok_cell else "✗"
|
||||||
lines.append(f" {sheet_name}!{cn}: got={pv!r}, expected={gv!r} {match}")
|
lines.append(f" {sheet_name}!{cn}: got={pv!r}, expected={gv!r} {match}")
|
||||||
|
if n_empty_correct:
|
||||||
|
lines.append(
|
||||||
|
f" (+{n_empty_correct} empty cells correct, omitted)"
|
||||||
|
)
|
||||||
|
|
||||||
# Also check if any cells in the output contain formula strings
|
# Also check if any cells in the output contain formula strings
|
||||||
formula_cells = []
|
formula_cells = []
|
||||||
@@ -159,7 +194,17 @@ def _auto_verify_output(
|
|||||||
wb_pred.close()
|
wb_pred.close()
|
||||||
wb_gold.close()
|
wb_gold.close()
|
||||||
|
|
||||||
return "\n".join(lines)
|
report = "\n".join(lines)
|
||||||
|
# Head+tail truncation, matching reflect.py / slow_update.py: keep the first
|
||||||
|
# and last half so both leading and trailing wrong cells remain visible.
|
||||||
|
if len(report) > _MAX_REPORT_CHARS:
|
||||||
|
half = _MAX_REPORT_CHARS // 2
|
||||||
|
report = (
|
||||||
|
report[:half]
|
||||||
|
+ f"\n ...[verification report truncated, {len(report)} chars total]...\n"
|
||||||
|
+ report[-half:]
|
||||||
|
)
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
# ── Per-task worker ──────────────────────────────────────────────────────────
|
# ── Per-task worker ──────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def _merge_batch(
|
|||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=system_prompt,
|
system=system_prompt,
|
||||||
user=user,
|
user=user,
|
||||||
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 4096,
|
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 16384,
|
||||||
retries=3,
|
retries=3,
|
||||||
stage="merge",
|
stage="merge",
|
||||||
)
|
)
|
||||||
@@ -231,7 +231,7 @@ def merge_patches(
|
|||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=merge_final_prompt,
|
system=merge_final_prompt,
|
||||||
user=user,
|
user=user,
|
||||||
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 4096,
|
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 16384,
|
||||||
retries=3,
|
retries=3,
|
||||||
stage="merge",
|
stage="merge",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|||||||
|
|
||||||
from skillopt.model import chat_optimizer
|
from skillopt.model import chat_optimizer
|
||||||
from skillopt.optimizer.meta_skill import format_meta_skill_context
|
from skillopt.optimizer.meta_skill import format_meta_skill_context
|
||||||
|
from skillopt.optimizer.skill_aware import (
|
||||||
|
augment_error_prompt,
|
||||||
|
augment_success_prompt,
|
||||||
|
extract_appendix_notes,
|
||||||
|
get_skill_aware_appendix_source,
|
||||||
|
is_skill_aware_enabled,
|
||||||
|
)
|
||||||
from skillopt.optimizer.update_modes import (
|
from skillopt.optimizer.update_modes import (
|
||||||
get_payload_items,
|
get_payload_items,
|
||||||
is_full_rewrite_minibatch_mode,
|
is_full_rewrite_minibatch_mode,
|
||||||
@@ -43,19 +50,21 @@ from skillopt.utils import extract_json
|
|||||||
|
|
||||||
# ── Trajectory formatting ────────────────────────────────────────────────────
|
# ── Trajectory formatting ────────────────────────────────────────────────────
|
||||||
|
|
||||||
_MAX_TRAJ_CHARS = 12_000
|
|
||||||
|
|
||||||
|
def _clip_text(value, limit: int | None = None) -> str:
|
||||||
|
"""Render optional trajectory fields. Truncation is disabled: the optimizer
|
||||||
|
is given the full content so it can see exactly what the agent saw/did.
|
||||||
|
|
||||||
def _clip_text(value, limit: int) -> str:
|
``limit`` is accepted for backward compatibility but ignored.
|
||||||
"""Render optional trajectory fields safely before truncation."""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return ""
|
return ""
|
||||||
return str(value)[:limit]
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
def fmt_trajectory(
|
def fmt_trajectory(
|
||||||
conversation: list[dict],
|
conversation: list[dict],
|
||||||
max_chars: int = _MAX_TRAJ_CHARS,
|
max_chars: int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Format a conversation list into analyst-readable text.
|
"""Format a conversation list into analyst-readable text.
|
||||||
|
|
||||||
@@ -69,37 +78,32 @@ def fmt_trajectory(
|
|||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
for item in conversation:
|
for item in conversation:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
lines.append(f"[agent] {_clip_text(item, 500)}")
|
lines.append(f"[agent] {_clip_text(item)}")
|
||||||
continue
|
continue
|
||||||
if item.get("type") == "tool_call":
|
if item.get("type") == "tool_call":
|
||||||
cmd = _clip_text(item.get("cmd"), 500)
|
cmd = _clip_text(item.get("cmd"))
|
||||||
obs = _clip_text(item.get("obs"), 800)
|
obs = _clip_text(item.get("obs"))
|
||||||
lines.append(f"[action] {cmd}")
|
lines.append(f"[action] {cmd}")
|
||||||
lines.append(f"[obs] {obs}")
|
lines.append(f"[obs] {obs}")
|
||||||
elif "action" in item and "env_feedback" in item:
|
elif "action" in item and "env_feedback" in item:
|
||||||
step = item.get("step", "?")
|
step = item.get("step", "?")
|
||||||
reasoning = _clip_text(item.get("reasoning"), 300)
|
reasoning = _clip_text(item.get("reasoning"))
|
||||||
action = _clip_text(item.get("action"), 200)
|
action = _clip_text(item.get("action"))
|
||||||
feedback = _clip_text(item.get("env_feedback"), 500)
|
feedback = _clip_text(item.get("env_feedback"))
|
||||||
if reasoning:
|
if reasoning:
|
||||||
lines.append(f"[step {step} think] {reasoning}")
|
lines.append(f"[step {step} think] {reasoning}")
|
||||||
lines.append(f"[step {step} action] {action}")
|
lines.append(f"[step {step} action] {action}")
|
||||||
lines.append(f"[step {step} obs] {feedback}")
|
lines.append(f"[step {step} obs] {feedback}")
|
||||||
elif item.get("role") == "system":
|
elif item.get("role") == "system":
|
||||||
# Post-execution verification / enrichment info
|
# Post-execution verification / enrichment info
|
||||||
msg = _clip_text(item.get("content"), 2000)
|
msg = _clip_text(item.get("content"))
|
||||||
lines.append(f"[verification] {msg}")
|
lines.append(f"[verification] {msg}")
|
||||||
else:
|
else:
|
||||||
msg = _clip_text(item.get("content"), 500)
|
msg = _clip_text(item.get("content"))
|
||||||
role = item.get("role", "agent")
|
role = item.get("role", "agent")
|
||||||
lines.append(f"[{role}] {msg}")
|
lines.append(f"[{role}] {msg}")
|
||||||
|
|
||||||
text = "\n".join(lines)
|
return "\n".join(lines)
|
||||||
if len(text) > max_chars:
|
|
||||||
head = text[: max_chars // 2]
|
|
||||||
tail = text[-max_chars // 2 :]
|
|
||||||
text = head + "\n...[middle truncated]...\n" + tail
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
# ── Minibatch trajectory formatting ──────────────────────────────────────────
|
# ── Minibatch trajectory formatting ──────────────────────────────────────────
|
||||||
@@ -157,7 +161,7 @@ def fmt_minibatch_trajectories(
|
|||||||
if reference_text:
|
if reference_text:
|
||||||
header += (
|
header += (
|
||||||
f"\n#### Hidden Reference\n"
|
f"\n#### Hidden Reference\n"
|
||||||
f"{reference_text[:4000]}\n"
|
f"{reference_text}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Append target context (what the agent saw) ──────────────
|
# ── Append target context (what the agent saw) ──────────────
|
||||||
@@ -170,7 +174,7 @@ def fmt_minibatch_trajectories(
|
|||||||
if target_prompt:
|
if target_prompt:
|
||||||
header += (
|
header += (
|
||||||
f"\n#### Target System Prompt\n"
|
f"\n#### Target System Prompt\n"
|
||||||
f"{target_prompt[:3000]}\n"
|
f"{target_prompt}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
user_prompt = item.get("target_user_prompt", "")
|
user_prompt = item.get("target_user_prompt", "")
|
||||||
@@ -182,7 +186,7 @@ def fmt_minibatch_trajectories(
|
|||||||
if user_prompt:
|
if user_prompt:
|
||||||
header += (
|
header += (
|
||||||
f"\n#### Target User Prompt\n"
|
f"\n#### Target User Prompt\n"
|
||||||
f"{user_prompt[:3000]}\n"
|
f"{user_prompt}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if os.environ.get("REFLACT_CODEX_TRACE_TO_OPTIMIZER", "0") == "1":
|
if os.environ.get("REFLACT_CODEX_TRACE_TO_OPTIMIZER", "0") == "1":
|
||||||
@@ -214,7 +218,7 @@ def fmt_minibatch_trajectories(
|
|||||||
if preview:
|
if preview:
|
||||||
header += (
|
header += (
|
||||||
f"\n#### Spreadsheet Preview\n"
|
f"\n#### Spreadsheet Preview\n"
|
||||||
f"{preview[:3000]}\n"
|
f"{preview}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
parts.append(header + "\n" + traj_text)
|
parts.append(header + "\n" + traj_text)
|
||||||
@@ -261,6 +265,7 @@ def run_error_analyst_minibatch(
|
|||||||
step_buffer_context: str = "",
|
step_buffer_context: str = "",
|
||||||
meta_skill_context: str = "",
|
meta_skill_context: str = "",
|
||||||
update_mode: str = "patch",
|
update_mode: str = "patch",
|
||||||
|
skill_aware_reflection: bool = False,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Analyze a minibatch of failed trajectories in one optimizer call.
|
"""Analyze a minibatch of failed trajectories in one optimizer call.
|
||||||
|
|
||||||
@@ -290,6 +295,11 @@ def run_error_analyst_minibatch(
|
|||||||
"""
|
"""
|
||||||
mode = normalize_update_mode(update_mode)
|
mode = normalize_update_mode(update_mode)
|
||||||
actual_system = _resolve_prompt(system_prompt, "analyst_error", mode)
|
actual_system = _resolve_prompt(system_prompt, "analyst_error", mode)
|
||||||
|
# Skill-aware reflection: augment the resolved prompt at runtime so both
|
||||||
|
# env-specific and generic analyst prompts get the defect/lapse instruction.
|
||||||
|
# When the toggle is off this is a no-op (prompt byte-identical to baseline).
|
||||||
|
if skill_aware_reflection and not is_full_rewrite_minibatch_mode(mode):
|
||||||
|
actual_system = augment_error_prompt(actual_system)
|
||||||
|
|
||||||
trajectories_text = fmt_minibatch_trajectories(items, prediction_dir)
|
trajectories_text = fmt_minibatch_trajectories(items, prediction_dir)
|
||||||
if not trajectories_text.strip():
|
if not trajectories_text.strip():
|
||||||
@@ -323,16 +333,31 @@ def run_error_analyst_minibatch(
|
|||||||
try:
|
try:
|
||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=actual_system, user=user,
|
system=actual_system, user=user,
|
||||||
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(mode) else 4096,
|
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(mode) else 16384,
|
||||||
retries=3,
|
retries=3,
|
||||||
stage="analyst",
|
stage="analyst",
|
||||||
)
|
)
|
||||||
result = extract_json(response)
|
result = extract_json(response)
|
||||||
if result and "patch" in result:
|
if not result:
|
||||||
|
return None
|
||||||
|
notes = extract_appendix_notes(result) if skill_aware_reflection else []
|
||||||
|
if "patch" in result:
|
||||||
result["source_type"] = "failure"
|
result["source_type"] = "failure"
|
||||||
if not is_full_rewrite_minibatch_mode(mode):
|
if not is_full_rewrite_minibatch_mode(mode):
|
||||||
truncate_payload(result["patch"], edit_budget, mode)
|
truncate_payload(result["patch"], edit_budget, mode)
|
||||||
|
if skill_aware_reflection:
|
||||||
|
result["appendix_notes"] = notes
|
||||||
return result
|
return result
|
||||||
|
# Skill-aware: a batch may legitimately yield ONLY execution-lapse notes
|
||||||
|
# (no body edit). Return a no-op patch so the notes still reach the
|
||||||
|
# trainer via all_raw_patches; empty edits are dropped from the body
|
||||||
|
# pipeline by _normalise_patches, so body behavior is unchanged.
|
||||||
|
if skill_aware_reflection and notes:
|
||||||
|
return {
|
||||||
|
"source_type": "failure",
|
||||||
|
"patch": {"reasoning": "execution-lapse only", "edits": []},
|
||||||
|
"appendix_notes": notes,
|
||||||
|
}
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
@@ -349,6 +374,8 @@ def run_success_analyst_minibatch(
|
|||||||
step_buffer_context: str = "",
|
step_buffer_context: str = "",
|
||||||
meta_skill_context: str = "",
|
meta_skill_context: str = "",
|
||||||
update_mode: str = "patch",
|
update_mode: str = "patch",
|
||||||
|
skill_aware_reflection: bool = False,
|
||||||
|
emit_appendix_notes: bool = True,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Analyze a minibatch of successful trajectories in one optimizer call.
|
"""Analyze a minibatch of successful trajectories in one optimizer call.
|
||||||
|
|
||||||
@@ -368,6 +395,11 @@ def run_success_analyst_minibatch(
|
|||||||
"""
|
"""
|
||||||
mode = normalize_update_mode(update_mode)
|
mode = normalize_update_mode(update_mode)
|
||||||
actual_system = _resolve_prompt(system_prompt, "analyst_success", mode)
|
actual_system = _resolve_prompt(system_prompt, "analyst_success", mode)
|
||||||
|
# Only augment + parse appendix notes on the success side when allowed.
|
||||||
|
# failure_only mode (paper-faithful S_app) suppresses success-side notes.
|
||||||
|
sa_emit = skill_aware_reflection and emit_appendix_notes
|
||||||
|
if sa_emit and not is_full_rewrite_minibatch_mode(mode):
|
||||||
|
actual_system = augment_success_prompt(actual_system)
|
||||||
|
|
||||||
trajectories_text = fmt_minibatch_trajectories(items, prediction_dir)
|
trajectories_text = fmt_minibatch_trajectories(items, prediction_dir)
|
||||||
if not trajectories_text.strip():
|
if not trajectories_text.strip():
|
||||||
@@ -398,7 +430,7 @@ def run_success_analyst_minibatch(
|
|||||||
try:
|
try:
|
||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=actual_system, user=user,
|
system=actual_system, user=user,
|
||||||
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(mode) else 4096,
|
max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(mode) else 16384,
|
||||||
retries=3,
|
retries=3,
|
||||||
stage="analyst",
|
stage="analyst",
|
||||||
)
|
)
|
||||||
@@ -407,6 +439,8 @@ def run_success_analyst_minibatch(
|
|||||||
result["source_type"] = "success"
|
result["source_type"] = "success"
|
||||||
if not is_full_rewrite_minibatch_mode(mode):
|
if not is_full_rewrite_minibatch_mode(mode):
|
||||||
truncate_payload(result["patch"], edit_budget, mode)
|
truncate_payload(result["patch"], edit_budget, mode)
|
||||||
|
if sa_emit:
|
||||||
|
result["appendix_notes"] = extract_appendix_notes(result)
|
||||||
return result
|
return result
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -453,6 +487,8 @@ def run_minibatch_reflect(
|
|||||||
step_buffer_context: str = "",
|
step_buffer_context: str = "",
|
||||||
meta_skill_context: str = "",
|
meta_skill_context: str = "",
|
||||||
update_mode: str = "patch",
|
update_mode: str = "patch",
|
||||||
|
skill_aware_reflection: bool | None = None,
|
||||||
|
skill_aware_appendix_source: str | None = None,
|
||||||
) -> list[dict | None]:
|
) -> list[dict | None]:
|
||||||
"""Full minibatch reflect stage: group → parallel optimizer calls → patches.
|
"""Full minibatch reflect stage: group → parallel optimizer calls → patches.
|
||||||
|
|
||||||
@@ -487,6 +523,14 @@ def run_minibatch_reflect(
|
|||||||
list[dict | None]
|
list[dict | None]
|
||||||
Patch dicts (with ``source_type`` "failure" or "success").
|
Patch dicts (with ``source_type`` "failure" or "success").
|
||||||
"""
|
"""
|
||||||
|
# Resolve the skill-aware toggle: explicit kwargs win; otherwise fall back
|
||||||
|
# to the process-wide config switch set by the trainer, so the feature is
|
||||||
|
# env-independent and adapters need no per-benchmark wiring.
|
||||||
|
if skill_aware_reflection is None:
|
||||||
|
skill_aware_reflection = is_skill_aware_enabled()
|
||||||
|
if skill_aware_appendix_source is None:
|
||||||
|
skill_aware_appendix_source = get_skill_aware_appendix_source()
|
||||||
|
|
||||||
os.makedirs(patches_dir, exist_ok=True)
|
os.makedirs(patches_dir, exist_ok=True)
|
||||||
|
|
||||||
# Separate failure / success
|
# Separate failure / success
|
||||||
@@ -542,6 +586,7 @@ def run_minibatch_reflect(
|
|||||||
trajectory_memory_context=trajectory_memory_context,
|
trajectory_memory_context=trajectory_memory_context,
|
||||||
meta_skill_context=meta_skill_context,
|
meta_skill_context=meta_skill_context,
|
||||||
update_mode=update_mode,
|
update_mode=update_mode,
|
||||||
|
skill_aware_reflection=skill_aware_reflection,
|
||||||
)
|
)
|
||||||
return f"minibatch_fail_{idx:03d}", patch
|
return f"minibatch_fail_{idx:03d}", patch
|
||||||
|
|
||||||
@@ -554,6 +599,8 @@ def run_minibatch_reflect(
|
|||||||
trajectory_memory_context=trajectory_memory_context,
|
trajectory_memory_context=trajectory_memory_context,
|
||||||
meta_skill_context=meta_skill_context,
|
meta_skill_context=meta_skill_context,
|
||||||
update_mode=update_mode,
|
update_mode=update_mode,
|
||||||
|
skill_aware_reflection=skill_aware_reflection,
|
||||||
|
emit_appendix_notes=(skill_aware_appendix_source != "failure_only"),
|
||||||
)
|
)
|
||||||
return f"minibatch_succ_{idx:03d}", patch
|
return f"minibatch_succ_{idx:03d}", patch
|
||||||
|
|
||||||
|
|||||||
+113
-2
@@ -6,6 +6,7 @@ from typing import Any
|
|||||||
|
|
||||||
from skillopt.model import azure_openai as _openai
|
from skillopt.model import azure_openai as _openai
|
||||||
from skillopt.model import claude_backend as _claude
|
from skillopt.model import claude_backend as _claude
|
||||||
|
from skillopt.model import minimax_backend as _minimax
|
||||||
from skillopt.model import qwen_backend as _qwen
|
from skillopt.model import qwen_backend as _qwen
|
||||||
from skillopt.model.backend_config import ( # noqa: F401
|
from skillopt.model.backend_config import ( # noqa: F401
|
||||||
configure_claude_code_exec,
|
configure_claude_code_exec,
|
||||||
@@ -50,6 +51,10 @@ def set_backend(name: str | None) -> str:
|
|||||||
set_optimizer_backend("openai_chat")
|
set_optimizer_backend("openai_chat")
|
||||||
set_target_backend("qwen_chat")
|
set_target_backend("qwen_chat")
|
||||||
return "qwen_chat"
|
return "qwen_chat"
|
||||||
|
if normalized in {"minimax", "minimax_chat"}:
|
||||||
|
set_optimizer_backend("openai_chat")
|
||||||
|
set_target_backend("minimax_chat")
|
||||||
|
return "minimax_chat"
|
||||||
raise ValueError(f"Unsupported legacy backend: {name!r}")
|
raise ValueError(f"Unsupported legacy backend: {name!r}")
|
||||||
|
|
||||||
|
|
||||||
@@ -59,12 +64,16 @@ def get_backend_name() -> str:
|
|||||||
target = get_target_backend()
|
target = get_target_backend()
|
||||||
if optimizer == "claude_chat" and target == "claude_chat":
|
if optimizer == "claude_chat" and target == "claude_chat":
|
||||||
return "claude_chat"
|
return "claude_chat"
|
||||||
|
if optimizer == "qwen_chat" and target == "qwen_chat":
|
||||||
|
return "qwen_chat"
|
||||||
if optimizer == "openai_chat" and target == "openai_chat":
|
if optimizer == "openai_chat" and target == "openai_chat":
|
||||||
return "azure_openai"
|
return "azure_openai"
|
||||||
if optimizer == "openai_chat" and target == "codex_exec":
|
if optimizer == "openai_chat" and target == "codex_exec":
|
||||||
return "codex"
|
return "codex"
|
||||||
if optimizer == "openai_chat" and target == "qwen_chat":
|
if optimizer == "openai_chat" and target == "qwen_chat":
|
||||||
return "qwen_chat"
|
return "qwen_chat"
|
||||||
|
if optimizer == "openai_chat" and target == "minimax_chat":
|
||||||
|
return "minimax_chat"
|
||||||
return f"{optimizer}+{target}"
|
return f"{optimizer}+{target}"
|
||||||
|
|
||||||
|
|
||||||
@@ -86,6 +95,16 @@ def chat_optimizer(
|
|||||||
stage=stage,
|
stage=stage,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
if get_optimizer_backend() == "qwen_chat":
|
||||||
|
return _qwen.chat_optimizer(
|
||||||
|
system=system,
|
||||||
|
user=user,
|
||||||
|
max_completion_tokens=max_completion_tokens,
|
||||||
|
retries=retries,
|
||||||
|
stage=stage,
|
||||||
|
reasoning_effort=reasoning_effort,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
return _openai.chat_optimizer(
|
return _openai.chat_optimizer(
|
||||||
system=system,
|
system=system,
|
||||||
user=user,
|
user=user,
|
||||||
@@ -123,10 +142,20 @@ def chat_target(
|
|||||||
retries=retries,
|
retries=retries,
|
||||||
stage=stage,
|
stage=stage,
|
||||||
reasoning_effort=reasoning_effort,
|
reasoning_effort=reasoning_effort,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
if get_target_backend() == "minimax_chat":
|
||||||
|
return _minimax.chat_target(
|
||||||
|
system=system,
|
||||||
|
user=user,
|
||||||
|
max_completion_tokens=max_completion_tokens,
|
||||||
|
retries=retries,
|
||||||
|
stage=stage,
|
||||||
|
reasoning_effort=reasoning_effort,
|
||||||
)
|
)
|
||||||
if not is_target_chat_backend():
|
if not is_target_chat_backend():
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"chat_target is only supported with target_backend=openai_chat, claude_chat, or qwen_chat. "
|
"chat_target is only supported with target_backend=openai_chat, claude_chat, qwen_chat, or minimax_chat. "
|
||||||
"Exec backends are handled in environment-specific rollout code."
|
"Exec backends are handled in environment-specific rollout code."
|
||||||
)
|
)
|
||||||
return _openai.chat_target(
|
return _openai.chat_target(
|
||||||
@@ -163,6 +192,18 @@ def chat_optimizer_messages(
|
|||||||
return_message=return_message,
|
return_message=return_message,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
if get_optimizer_backend() == "qwen_chat":
|
||||||
|
return _qwen.chat_optimizer_messages(
|
||||||
|
messages=messages,
|
||||||
|
max_completion_tokens=max_completion_tokens,
|
||||||
|
retries=retries,
|
||||||
|
stage=stage,
|
||||||
|
reasoning_effort=reasoning_effort,
|
||||||
|
tools=tools,
|
||||||
|
tool_choice=tool_choice,
|
||||||
|
return_message=return_message,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
return _openai.chat_optimizer_messages(
|
return _openai.chat_optimizer_messages(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
max_completion_tokens=max_completion_tokens,
|
max_completion_tokens=max_completion_tokens,
|
||||||
@@ -209,10 +250,22 @@ def chat_target_messages(
|
|||||||
tools=tools,
|
tools=tools,
|
||||||
tool_choice=tool_choice,
|
tool_choice=tool_choice,
|
||||||
return_message=return_message,
|
return_message=return_message,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
if get_target_backend() == "minimax_chat":
|
||||||
|
return _minimax.chat_target_messages(
|
||||||
|
messages=messages,
|
||||||
|
max_completion_tokens=max_completion_tokens,
|
||||||
|
retries=retries,
|
||||||
|
stage=stage,
|
||||||
|
reasoning_effort=reasoning_effort,
|
||||||
|
tools=tools,
|
||||||
|
tool_choice=tool_choice,
|
||||||
|
return_message=return_message,
|
||||||
)
|
)
|
||||||
if not is_target_chat_backend():
|
if not is_target_chat_backend():
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"chat_target_messages is only supported with target_backend=openai_chat, claude_chat, or qwen_chat. "
|
"chat_target_messages is only supported with target_backend=openai_chat, claude_chat, qwen_chat, or minimax_chat. "
|
||||||
"Exec backends are handled in environment-specific rollout code."
|
"Exec backends are handled in environment-specific rollout code."
|
||||||
)
|
)
|
||||||
return _openai.chat_target_messages(
|
return _openai.chat_target_messages(
|
||||||
@@ -301,6 +354,17 @@ def get_token_summary() -> dict:
|
|||||||
summary[stage]["prompt_tokens"] += values["prompt_tokens"]
|
summary[stage]["prompt_tokens"] += values["prompt_tokens"]
|
||||||
summary[stage]["completion_tokens"] += values["completion_tokens"]
|
summary[stage]["completion_tokens"] += values["completion_tokens"]
|
||||||
summary[stage]["total_tokens"] += values["total_tokens"]
|
summary[stage]["total_tokens"] += values["total_tokens"]
|
||||||
|
minimax_summary = _minimax.get_token_summary()
|
||||||
|
for stage, values in minimax_summary.items():
|
||||||
|
if stage == "_total":
|
||||||
|
continue
|
||||||
|
if stage not in summary:
|
||||||
|
summary[stage] = values
|
||||||
|
continue
|
||||||
|
summary[stage]["calls"] += values["calls"]
|
||||||
|
summary[stage]["prompt_tokens"] += values["prompt_tokens"]
|
||||||
|
summary[stage]["completion_tokens"] += values["completion_tokens"]
|
||||||
|
summary[stage]["total_tokens"] += values["total_tokens"]
|
||||||
total = {
|
total = {
|
||||||
"calls": 0,
|
"calls": 0,
|
||||||
"prompt_tokens": 0,
|
"prompt_tokens": 0,
|
||||||
@@ -322,6 +386,7 @@ def reset_token_tracker() -> None:
|
|||||||
_openai.reset_token_tracker()
|
_openai.reset_token_tracker()
|
||||||
_claude.reset_token_tracker()
|
_claude.reset_token_tracker()
|
||||||
_qwen.reset_token_tracker()
|
_qwen.reset_token_tracker()
|
||||||
|
_minimax.reset_token_tracker()
|
||||||
|
|
||||||
|
|
||||||
def configure_azure_openai(
|
def configure_azure_openai(
|
||||||
@@ -375,6 +440,18 @@ def configure_qwen_chat(
|
|||||||
timeout_seconds: float | str | None = None,
|
timeout_seconds: float | str | None = None,
|
||||||
max_tokens: int | str | None = None,
|
max_tokens: int | str | None = None,
|
||||||
enable_thinking: bool | str | None = None,
|
enable_thinking: bool | str | None = None,
|
||||||
|
optimizer_base_url: str | None = None,
|
||||||
|
optimizer_api_key: str | None = None,
|
||||||
|
optimizer_temperature: float | str | None = None,
|
||||||
|
optimizer_timeout_seconds: float | str | None = None,
|
||||||
|
optimizer_max_tokens: int | str | None = None,
|
||||||
|
optimizer_enable_thinking: bool | str | None = None,
|
||||||
|
target_base_url: str | None = None,
|
||||||
|
target_api_key: str | None = None,
|
||||||
|
target_temperature: float | str | None = None,
|
||||||
|
target_timeout_seconds: float | str | None = None,
|
||||||
|
target_max_tokens: int | str | None = None,
|
||||||
|
target_enable_thinking: bool | str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
_qwen.configure_qwen_chat(
|
_qwen.configure_qwen_chat(
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
@@ -383,6 +460,37 @@ def configure_qwen_chat(
|
|||||||
timeout_seconds=timeout_seconds,
|
timeout_seconds=timeout_seconds,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
enable_thinking=enable_thinking,
|
enable_thinking=enable_thinking,
|
||||||
|
optimizer_base_url=optimizer_base_url,
|
||||||
|
optimizer_api_key=optimizer_api_key,
|
||||||
|
optimizer_temperature=optimizer_temperature,
|
||||||
|
optimizer_timeout_seconds=optimizer_timeout_seconds,
|
||||||
|
optimizer_max_tokens=optimizer_max_tokens,
|
||||||
|
optimizer_enable_thinking=optimizer_enable_thinking,
|
||||||
|
target_base_url=target_base_url,
|
||||||
|
target_api_key=target_api_key,
|
||||||
|
target_temperature=target_temperature,
|
||||||
|
target_timeout_seconds=target_timeout_seconds,
|
||||||
|
target_max_tokens=target_max_tokens,
|
||||||
|
target_enable_thinking=target_enable_thinking,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_minimax_chat(
|
||||||
|
*,
|
||||||
|
base_url: str | None = None,
|
||||||
|
api_key: str | None = None,
|
||||||
|
temperature: float | str | None = None,
|
||||||
|
timeout_seconds: float | str | None = None,
|
||||||
|
max_tokens: int | str | None = None,
|
||||||
|
enable_thinking: bool | str | None = None,
|
||||||
|
) -> None:
|
||||||
|
_minimax.configure_minimax_chat(
|
||||||
|
base_url=base_url,
|
||||||
|
api_key=api_key,
|
||||||
|
temperature=temperature,
|
||||||
|
timeout_seconds=timeout_seconds,
|
||||||
|
max_tokens=max_tokens,
|
||||||
|
enable_thinking=enable_thinking,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -390,14 +498,17 @@ def set_reasoning_effort(effort: str | None) -> None:
|
|||||||
_openai.set_reasoning_effort(effort)
|
_openai.set_reasoning_effort(effort)
|
||||||
_claude.set_reasoning_effort(effort)
|
_claude.set_reasoning_effort(effort)
|
||||||
_qwen.set_reasoning_effort(effort)
|
_qwen.set_reasoning_effort(effort)
|
||||||
|
_minimax.set_reasoning_effort(effort)
|
||||||
|
|
||||||
|
|
||||||
def set_target_deployment(deployment: str) -> None:
|
def set_target_deployment(deployment: str) -> None:
|
||||||
_openai.set_target_deployment(deployment)
|
_openai.set_target_deployment(deployment)
|
||||||
_claude.set_target_deployment(deployment)
|
_claude.set_target_deployment(deployment)
|
||||||
_qwen.set_target_deployment(deployment)
|
_qwen.set_target_deployment(deployment)
|
||||||
|
_minimax.set_target_deployment(deployment)
|
||||||
|
|
||||||
|
|
||||||
def set_optimizer_deployment(deployment: str) -> None:
|
def set_optimizer_deployment(deployment: str) -> None:
|
||||||
_openai.set_optimizer_deployment(deployment)
|
_openai.set_optimizer_deployment(deployment)
|
||||||
_claude.set_optimizer_deployment(deployment)
|
_claude.set_optimizer_deployment(deployment)
|
||||||
|
_qwen.set_optimizer_deployment(deployment)
|
||||||
|
|||||||
@@ -336,9 +336,10 @@ def get_target_client() -> AzureOpenAI | OpenAI:
|
|||||||
from skillopt.model.backend_config import get_target_backend
|
from skillopt.model.backend_config import get_target_backend
|
||||||
if get_target_backend() == "qwen_chat":
|
if get_target_backend() == "qwen_chat":
|
||||||
from skillopt.model import qwen_backend as _qwen
|
from skillopt.model import qwen_backend as _qwen
|
||||||
|
target_config = _qwen.TARGET_CONFIG
|
||||||
_target_client = OpenAI(
|
_target_client = OpenAI(
|
||||||
base_url=_qwen.BASE_URL,
|
base_url=target_config.base_url,
|
||||||
api_key=_qwen.API_KEY or "dummy",
|
api_key=target_config.api_key or "dummy",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_target_client = _make_client("target")
|
_target_client = _make_client("target")
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS = max(
|
|||||||
def set_optimizer_backend(backend: str) -> None:
|
def set_optimizer_backend(backend: str) -> None:
|
||||||
global OPTIMIZER_BACKEND
|
global OPTIMIZER_BACKEND
|
||||||
OPTIMIZER_BACKEND = normalize_backend_name(backend or "openai_chat")
|
OPTIMIZER_BACKEND = normalize_backend_name(backend or "openai_chat")
|
||||||
if OPTIMIZER_BACKEND not in {"openai_chat", "claude_chat"}:
|
if OPTIMIZER_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported optimizer backend: {OPTIMIZER_BACKEND!r}. "
|
f"Unsupported optimizer backend: {OPTIMIZER_BACKEND!r}. "
|
||||||
"Supported values are 'openai_chat' and 'claude_chat'."
|
"Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', and 'minimax_chat'."
|
||||||
)
|
)
|
||||||
os.environ["OPTIMIZER_BACKEND"] = OPTIMIZER_BACKEND
|
os.environ["OPTIMIZER_BACKEND"] = OPTIMIZER_BACKEND
|
||||||
|
|
||||||
@@ -64,10 +64,10 @@ def get_optimizer_backend() -> str:
|
|||||||
def set_target_backend(backend: str) -> None:
|
def set_target_backend(backend: str) -> None:
|
||||||
global TARGET_BACKEND
|
global TARGET_BACKEND
|
||||||
TARGET_BACKEND = normalize_backend_name(backend or "openai_chat")
|
TARGET_BACKEND = normalize_backend_name(backend or "openai_chat")
|
||||||
if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "codex_exec", "claude_code_exec"}:
|
if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "codex_exec", "claude_code_exec"}:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported target backend: {TARGET_BACKEND!r}. "
|
f"Unsupported target backend: {TARGET_BACKEND!r}. "
|
||||||
"Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'codex_exec', and 'claude_code_exec'."
|
"Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'minimax_chat', 'codex_exec', and 'claude_code_exec'."
|
||||||
)
|
)
|
||||||
os.environ["TARGET_BACKEND"] = TARGET_BACKEND
|
os.environ["TARGET_BACKEND"] = TARGET_BACKEND
|
||||||
|
|
||||||
@@ -81,11 +81,11 @@ def is_target_exec_backend() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def is_optimizer_chat_backend() -> bool:
|
def is_optimizer_chat_backend() -> bool:
|
||||||
return OPTIMIZER_BACKEND in {"openai_chat", "claude_chat"}
|
return OPTIMIZER_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}
|
||||||
|
|
||||||
|
|
||||||
def is_target_chat_backend() -> bool:
|
def is_target_chat_backend() -> bool:
|
||||||
return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat"}
|
return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}
|
||||||
|
|
||||||
|
|
||||||
def configure_codex_exec(
|
def configure_codex_exec(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ _BACKEND_DEFAULT_MODELS = {
|
|||||||
"claude_chat": "claude-sonnet-4-6",
|
"claude_chat": "claude-sonnet-4-6",
|
||||||
"claude_code_exec": "claude-sonnet-4-6",
|
"claude_code_exec": "claude-sonnet-4-6",
|
||||||
"qwen_chat": "Qwen/Qwen3.5-4B",
|
"qwen_chat": "Qwen/Qwen3.5-4B",
|
||||||
|
"minimax_chat": "MiniMax-M2.7",
|
||||||
}
|
}
|
||||||
|
|
||||||
_BACKEND_ALIASES = {
|
_BACKEND_ALIASES = {
|
||||||
@@ -41,6 +42,8 @@ _BACKEND_ALIASES = {
|
|||||||
"anthropic": "claude_chat",
|
"anthropic": "claude_chat",
|
||||||
"qwen": "qwen_chat",
|
"qwen": "qwen_chat",
|
||||||
"qwen_chat": "qwen_chat",
|
"qwen_chat": "qwen_chat",
|
||||||
|
"minimax": "minimax_chat",
|
||||||
|
"minimax_chat": "minimax_chat",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
"""OpenAI-compatible MiniMax chat backend for the target path."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from skillopt.model.common import (
|
||||||
|
CompatAssistantMessage,
|
||||||
|
CompatToolCall,
|
||||||
|
CompatToolFunction,
|
||||||
|
TokenTracker,
|
||||||
|
default_model_for_backend,
|
||||||
|
)
|
||||||
|
|
||||||
|
BASE_URL = os.environ.get("MINIMAX_BASE_URL", "https://api.minimax.io/v1")
|
||||||
|
API_KEY = os.environ.get("MINIMAX_API_KEY", "")
|
||||||
|
TIMEOUT_SECONDS = float(os.environ.get("MINIMAX_TIMEOUT_SECONDS", "300") or 300)
|
||||||
|
MAX_TOKENS = int(os.environ.get("MINIMAX_MAX_TOKENS", "8000") or 8000)
|
||||||
|
TEMPERATURE: float | None = None
|
||||||
|
_raw_temperature = os.environ.get("MINIMAX_TEMPERATURE", "0.7").strip()
|
||||||
|
if _raw_temperature:
|
||||||
|
TEMPERATURE = float(_raw_temperature)
|
||||||
|
ENABLE_THINKING = os.environ.get("MINIMAX_ENABLE_THINKING", "false").strip().lower() in {
|
||||||
|
"1",
|
||||||
|
"true",
|
||||||
|
"yes",
|
||||||
|
"on",
|
||||||
|
}
|
||||||
|
|
||||||
|
TARGET_DEPLOYMENT = os.environ.get(
|
||||||
|
"TARGET_DEPLOYMENT",
|
||||||
|
default_model_for_backend("minimax_chat"),
|
||||||
|
)
|
||||||
|
|
||||||
|
_config_lock = threading.Lock()
|
||||||
|
tracker = TokenTracker()
|
||||||
|
|
||||||
|
|
||||||
|
def _chat_url() -> str:
|
||||||
|
base = BASE_URL.rstrip("/")
|
||||||
|
if base.endswith("/chat/completions"):
|
||||||
|
return base
|
||||||
|
return f"{base}/chat/completions"
|
||||||
|
|
||||||
|
|
||||||
|
def _json_safe(value: Any) -> Any:
|
||||||
|
if value is None or isinstance(value, (str, int, float, bool)):
|
||||||
|
return value
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [_json_safe(item) for item in value]
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return {str(key): _json_safe(val) for key, val in value.items()}
|
||||||
|
model_dump = getattr(value, "model_dump", None)
|
||||||
|
if callable(model_dump):
|
||||||
|
try:
|
||||||
|
return model_dump(mode="json")
|
||||||
|
except TypeError:
|
||||||
|
return model_dump()
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _usage_from_payload(payload: dict[str, Any]) -> dict[str, int]:
|
||||||
|
usage = payload.get("usage") or {}
|
||||||
|
prompt_tokens = int(usage.get("prompt_tokens") or usage.get("input_tokens") or 0)
|
||||||
|
completion_tokens = int(usage.get("completion_tokens") or usage.get("output_tokens") or 0)
|
||||||
|
total_tokens = int(usage.get("total_tokens") or (prompt_tokens + completion_tokens))
|
||||||
|
return {
|
||||||
|
"prompt_tokens": prompt_tokens,
|
||||||
|
"completion_tokens": completion_tokens,
|
||||||
|
"total_tokens": total_tokens,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _compat_message_from_payload(message: dict[str, Any], choice: dict[str, Any]) -> CompatAssistantMessage:
|
||||||
|
content = message.get("content") or ""
|
||||||
|
if not isinstance(content, str):
|
||||||
|
content = json.dumps(content, ensure_ascii=False)
|
||||||
|
tool_calls: list[CompatToolCall] = []
|
||||||
|
for index, tool_call in enumerate(message.get("tool_calls") or [], start=1):
|
||||||
|
function = tool_call.get("function") or {}
|
||||||
|
tool_calls.append(
|
||||||
|
CompatToolCall(
|
||||||
|
id=str(tool_call.get("id") or f"minimax_tool_{index}"),
|
||||||
|
type=str(tool_call.get("type") or "function"),
|
||||||
|
function=CompatToolFunction(
|
||||||
|
name=str(function.get("name") or ""),
|
||||||
|
arguments=str(function.get("arguments") or "{}"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return CompatAssistantMessage(
|
||||||
|
content=content,
|
||||||
|
tool_calls=tool_calls,
|
||||||
|
metadata={
|
||||||
|
"finish_reason": choice.get("finish_reason"),
|
||||||
|
"choice0": _json_safe(choice),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _post_chat_completion(payload: dict[str, Any], timeout: float | None) -> dict[str, Any]:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if API_KEY:
|
||||||
|
headers["Authorization"] = f"Bearer {API_KEY}"
|
||||||
|
req = urllib.request.Request(
|
||||||
|
_chat_url(),
|
||||||
|
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
||||||
|
headers=headers,
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout or TIMEOUT_SECONDS) as resp:
|
||||||
|
raw = resp.read().decode("utf-8")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode("utf-8", errors="replace")
|
||||||
|
raise RuntimeError(f"MiniMax chat API returned HTTP {e.code}: {body}") from e
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
raise RuntimeError(f"MiniMax chat API request failed: {e}") from e
|
||||||
|
try:
|
||||||
|
return json.loads(raw)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise RuntimeError(f"MiniMax chat API returned non-JSON response: {raw[:1000]}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def _chat_messages_impl(
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
max_completion_tokens: int,
|
||||||
|
retries: int,
|
||||||
|
stage: str,
|
||||||
|
*,
|
||||||
|
tools: list[dict[str, Any]] | None = None,
|
||||||
|
tool_choice: str | dict[str, Any] | None = None,
|
||||||
|
return_message: bool = False,
|
||||||
|
deployment: str | None = None,
|
||||||
|
timeout: float | None = None,
|
||||||
|
) -> tuple[Any, dict[str, int]]:
|
||||||
|
payload: dict[str, Any] = {
|
||||||
|
"model": deployment or TARGET_DEPLOYMENT,
|
||||||
|
"messages": _json_safe(messages),
|
||||||
|
"max_tokens": min(max_completion_tokens, MAX_TOKENS),
|
||||||
|
}
|
||||||
|
payload["chat_template_kwargs"] = {"enable_thinking": ENABLE_THINKING}
|
||||||
|
if TEMPERATURE is not None:
|
||||||
|
payload["temperature"] = TEMPERATURE
|
||||||
|
if tools:
|
||||||
|
payload["tools"] = _json_safe(tools)
|
||||||
|
if tool_choice is not None:
|
||||||
|
payload["tool_choice"] = _json_safe(tool_choice)
|
||||||
|
|
||||||
|
last_err: Exception | None = None
|
||||||
|
for attempt in range(retries):
|
||||||
|
try:
|
||||||
|
data = _post_chat_completion(payload, timeout)
|
||||||
|
choices = data.get("choices") or []
|
||||||
|
if not choices:
|
||||||
|
raise RuntimeError(f"MiniMax chat API returned no choices: {data}")
|
||||||
|
choice0 = choices[0]
|
||||||
|
message = choice0.get("message") or {}
|
||||||
|
text = message.get("content") or ""
|
||||||
|
if not isinstance(text, str):
|
||||||
|
text = json.dumps(text, ensure_ascii=False)
|
||||||
|
usage_info = _usage_from_payload(data)
|
||||||
|
tracker.record(stage, usage_info["prompt_tokens"], usage_info["completion_tokens"])
|
||||||
|
if return_message:
|
||||||
|
return _compat_message_from_payload(message, choice0), usage_info
|
||||||
|
return text, usage_info
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
last_err = e
|
||||||
|
time.sleep(min(2 ** attempt, 30))
|
||||||
|
raise RuntimeError(f"MiniMax chat call failed after {retries} retries: {last_err}")
|
||||||
|
|
||||||
|
|
||||||
|
def configure_minimax_chat(
|
||||||
|
*,
|
||||||
|
base_url: str | None = None,
|
||||||
|
api_key: str | None = None,
|
||||||
|
temperature: float | str | None = None,
|
||||||
|
timeout_seconds: float | str | None = None,
|
||||||
|
max_tokens: int | str | None = None,
|
||||||
|
enable_thinking: bool | str | None = None,
|
||||||
|
) -> None:
|
||||||
|
global BASE_URL, API_KEY, TEMPERATURE, TIMEOUT_SECONDS, MAX_TOKENS, ENABLE_THINKING
|
||||||
|
with _config_lock:
|
||||||
|
if base_url is not None:
|
||||||
|
BASE_URL = str(base_url).strip() or BASE_URL
|
||||||
|
os.environ["MINIMAX_BASE_URL"] = BASE_URL
|
||||||
|
if api_key is not None:
|
||||||
|
API_KEY = str(api_key).strip()
|
||||||
|
os.environ["MINIMAX_API_KEY"] = API_KEY
|
||||||
|
if temperature is not None:
|
||||||
|
raw = str(temperature).strip()
|
||||||
|
TEMPERATURE = float(raw) if raw else None
|
||||||
|
os.environ["MINIMAX_TEMPERATURE"] = raw
|
||||||
|
if timeout_seconds is not None:
|
||||||
|
TIMEOUT_SECONDS = float(timeout_seconds)
|
||||||
|
os.environ["MINIMAX_TIMEOUT_SECONDS"] = str(timeout_seconds)
|
||||||
|
if max_tokens is not None:
|
||||||
|
MAX_TOKENS = int(max_tokens)
|
||||||
|
os.environ["MINIMAX_MAX_TOKENS"] = str(max_tokens)
|
||||||
|
if enable_thinking is not None:
|
||||||
|
if isinstance(enable_thinking, str):
|
||||||
|
ENABLE_THINKING = enable_thinking.strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
else:
|
||||||
|
ENABLE_THINKING = bool(enable_thinking)
|
||||||
|
os.environ["MINIMAX_ENABLE_THINKING"] = "true" if ENABLE_THINKING else "false"
|
||||||
|
|
||||||
|
|
||||||
|
def get_max_tokens() -> int:
|
||||||
|
return MAX_TOKENS
|
||||||
|
|
||||||
|
|
||||||
|
def chat_target(
|
||||||
|
system: str,
|
||||||
|
user: str,
|
||||||
|
max_completion_tokens: int = 16384,
|
||||||
|
retries: int = 5,
|
||||||
|
stage: str = "target",
|
||||||
|
reasoning_effort: str | None = None,
|
||||||
|
timeout: float | None = None,
|
||||||
|
) -> tuple[str, dict[str, int]]:
|
||||||
|
del reasoning_effort
|
||||||
|
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||||||
|
return _chat_messages_impl(
|
||||||
|
messages,
|
||||||
|
max_completion_tokens,
|
||||||
|
retries,
|
||||||
|
stage,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def chat_target_messages(
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
max_completion_tokens: int = 16384,
|
||||||
|
retries: int = 5,
|
||||||
|
stage: str = "target",
|
||||||
|
reasoning_effort: str | None = None,
|
||||||
|
*,
|
||||||
|
tools: list[dict[str, Any]] | None = None,
|
||||||
|
tool_choice: str | dict[str, Any] | None = None,
|
||||||
|
return_message: bool = False,
|
||||||
|
timeout: float | None = None,
|
||||||
|
) -> tuple[Any, dict[str, int]]:
|
||||||
|
del reasoning_effort
|
||||||
|
return _chat_messages_impl(
|
||||||
|
messages,
|
||||||
|
max_completion_tokens,
|
||||||
|
retries,
|
||||||
|
stage,
|
||||||
|
tools=tools,
|
||||||
|
tool_choice=tool_choice,
|
||||||
|
return_message=return_message,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_summary() -> dict[str, dict[str, int]]:
|
||||||
|
return tracker.summary()
|
||||||
|
|
||||||
|
|
||||||
|
def reset_token_tracker() -> None:
|
||||||
|
tracker.reset()
|
||||||
|
|
||||||
|
|
||||||
|
def set_reasoning_effort(effort: str | None) -> None:
|
||||||
|
del effort
|
||||||
|
|
||||||
|
|
||||||
|
def set_target_deployment(deployment: str) -> None:
|
||||||
|
global TARGET_DEPLOYMENT
|
||||||
|
TARGET_DEPLOYMENT = deployment or default_model_for_backend("minimax_chat")
|
||||||
|
os.environ["TARGET_DEPLOYMENT"] = TARGET_DEPLOYMENT
|
||||||
+230
-51
@@ -1,6 +1,7 @@
|
|||||||
"""OpenAI-compatible Qwen chat backend for the target path."""
|
"""OpenAI-compatible Qwen chat backend for optimizer and target paths."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
@@ -17,32 +18,72 @@ from skillopt.model.common import (
|
|||||||
default_model_for_backend,
|
default_model_for_backend,
|
||||||
)
|
)
|
||||||
|
|
||||||
BASE_URL = os.environ.get("QWEN_CHAT_BASE_URL", "http://localhost:8000/v1")
|
|
||||||
API_KEY = os.environ.get("QWEN_CHAT_API_KEY", "")
|
|
||||||
TIMEOUT_SECONDS = float(os.environ.get("QWEN_CHAT_TIMEOUT_SECONDS", "300") or 300)
|
|
||||||
MAX_TOKENS = int(os.environ.get("QWEN_CHAT_MAX_TOKENS", "8000") or 8000)
|
|
||||||
TEMPERATURE: float | None = None
|
|
||||||
_raw_temperature = os.environ.get("QWEN_CHAT_TEMPERATURE", "0.7").strip()
|
|
||||||
if _raw_temperature:
|
|
||||||
TEMPERATURE = float(_raw_temperature)
|
|
||||||
ENABLE_THINKING = os.environ.get("QWEN_CHAT_ENABLE_THINKING", "false").strip().lower() in {
|
|
||||||
"1",
|
|
||||||
"true",
|
|
||||||
"yes",
|
|
||||||
"on",
|
|
||||||
}
|
|
||||||
|
|
||||||
TARGET_DEPLOYMENT = os.environ.get(
|
@dataclass
|
||||||
"TARGET_DEPLOYMENT",
|
class QwenChatConfig:
|
||||||
default_model_for_backend("qwen_chat"),
|
base_url: str
|
||||||
)
|
api_key: str
|
||||||
|
timeout_seconds: float
|
||||||
|
max_tokens: int
|
||||||
|
temperature: float | None
|
||||||
|
enable_thinking: bool
|
||||||
|
deployment: str
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_bool(value: Any, default: bool = False) -> bool:
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_optional_float(value: Any) -> float | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
raw = str(value).strip()
|
||||||
|
return float(raw) if raw else None
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_int(value: Any, default: int) -> int:
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
raw = str(value).strip()
|
||||||
|
return int(raw) if raw else default
|
||||||
|
|
||||||
|
|
||||||
|
def _role_env(role: str, key: str, default: str) -> str:
|
||||||
|
role_key = f"{role.upper()}_QWEN_CHAT_{key}"
|
||||||
|
generic_key = f"QWEN_CHAT_{key}"
|
||||||
|
return os.environ.get(role_key) or os.environ.get(generic_key) or default
|
||||||
|
|
||||||
|
|
||||||
|
def _initial_config(role: str) -> QwenChatConfig:
|
||||||
|
role_upper = role.upper()
|
||||||
|
deployment_env = "OPTIMIZER_DEPLOYMENT" if role == "optimizer" else "TARGET_DEPLOYMENT"
|
||||||
|
return QwenChatConfig(
|
||||||
|
base_url=_role_env(role, "BASE_URL", "http://localhost:8000/v1"),
|
||||||
|
api_key=_role_env(role, "API_KEY", ""),
|
||||||
|
timeout_seconds=float(_role_env(role, "TIMEOUT_SECONDS", "300") or 300),
|
||||||
|
max_tokens=_parse_int(_role_env(role, "MAX_TOKENS", "8000"), 8000),
|
||||||
|
temperature=_parse_optional_float(_role_env(role, "TEMPERATURE", "0.7")),
|
||||||
|
enable_thinking=_parse_bool(_role_env(role, "ENABLE_THINKING", "false")),
|
||||||
|
deployment=(
|
||||||
|
os.environ.get(f"{role_upper}_QWEN_CHAT_MODEL")
|
||||||
|
or os.environ.get("QWEN_CHAT_MODEL")
|
||||||
|
or os.environ.get(deployment_env)
|
||||||
|
or default_model_for_backend("qwen_chat")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
OPTIMIZER_CONFIG = _initial_config("optimizer")
|
||||||
|
TARGET_CONFIG = _initial_config("target")
|
||||||
|
|
||||||
_config_lock = threading.Lock()
|
_config_lock = threading.Lock()
|
||||||
tracker = TokenTracker()
|
tracker = TokenTracker()
|
||||||
|
|
||||||
|
|
||||||
def _chat_url() -> str:
|
def _chat_url(config: QwenChatConfig) -> str:
|
||||||
base = BASE_URL.rstrip("/")
|
base = config.base_url.rstrip("/")
|
||||||
if base.endswith("/chat/completions"):
|
if base.endswith("/chat/completions"):
|
||||||
return base
|
return base
|
||||||
return f"{base}/chat/completions"
|
return f"{base}/chat/completions"
|
||||||
@@ -103,18 +144,22 @@ def _compat_message_from_payload(message: dict[str, Any], choice: dict[str, Any]
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _post_chat_completion(payload: dict[str, Any], timeout: float | None) -> dict[str, Any]:
|
def _post_chat_completion(
|
||||||
|
payload: dict[str, Any],
|
||||||
|
timeout: float | None,
|
||||||
|
config: QwenChatConfig,
|
||||||
|
) -> dict[str, Any]:
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
if API_KEY:
|
if config.api_key:
|
||||||
headers["Authorization"] = f"Bearer {API_KEY}"
|
headers["Authorization"] = f"Bearer {config.api_key}"
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
_chat_url(),
|
_chat_url(config),
|
||||||
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
method="POST",
|
method="POST",
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=timeout or TIMEOUT_SECONDS) as resp:
|
with urllib.request.urlopen(req, timeout=timeout or config.timeout_seconds) as resp:
|
||||||
raw = resp.read().decode("utf-8")
|
raw = resp.read().decode("utf-8")
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
body = e.read().decode("utf-8", errors="replace")
|
body = e.read().decode("utf-8", errors="replace")
|
||||||
@@ -133,20 +178,23 @@ def _chat_messages_impl(
|
|||||||
retries: int,
|
retries: int,
|
||||||
stage: str,
|
stage: str,
|
||||||
*,
|
*,
|
||||||
|
role: str,
|
||||||
tools: list[dict[str, Any]] | None = None,
|
tools: list[dict[str, Any]] | None = None,
|
||||||
tool_choice: str | dict[str, Any] | None = None,
|
tool_choice: str | dict[str, Any] | None = None,
|
||||||
return_message: bool = False,
|
return_message: bool = False,
|
||||||
deployment: str | None = None,
|
deployment: str | None = None,
|
||||||
timeout: float | None = None,
|
timeout: float | None = None,
|
||||||
) -> tuple[Any, dict[str, int]]:
|
) -> tuple[Any, dict[str, int]]:
|
||||||
|
config = OPTIMIZER_CONFIG if role == "optimizer" else TARGET_CONFIG
|
||||||
payload: dict[str, Any] = {
|
payload: dict[str, Any] = {
|
||||||
"model": deployment or TARGET_DEPLOYMENT,
|
"model": deployment or config.deployment,
|
||||||
"messages": _json_safe(messages),
|
"messages": _json_safe(messages),
|
||||||
"max_tokens": min(max_completion_tokens, MAX_TOKENS),
|
"max_tokens": min(max_completion_tokens, config.max_tokens),
|
||||||
}
|
}
|
||||||
payload["chat_template_kwargs"] = {"enable_thinking": ENABLE_THINKING}
|
if config.enable_thinking:
|
||||||
if TEMPERATURE is not None:
|
payload["chat_template_kwargs"] = {"enable_thinking": True}
|
||||||
payload["temperature"] = TEMPERATURE
|
if config.temperature is not None:
|
||||||
|
payload["temperature"] = config.temperature
|
||||||
if tools:
|
if tools:
|
||||||
payload["tools"] = _json_safe(tools)
|
payload["tools"] = _json_safe(tools)
|
||||||
if tool_choice is not None:
|
if tool_choice is not None:
|
||||||
@@ -155,7 +203,7 @@ def _chat_messages_impl(
|
|||||||
last_err: Exception | None = None
|
last_err: Exception | None = None
|
||||||
for attempt in range(retries):
|
for attempt in range(retries):
|
||||||
try:
|
try:
|
||||||
data = _post_chat_completion(payload, timeout)
|
data = _post_chat_completion(payload, timeout, config)
|
||||||
choices = data.get("choices") or []
|
choices = data.get("choices") or []
|
||||||
if not choices:
|
if not choices:
|
||||||
raise RuntimeError(f"Qwen chat API returned no choices: {data}")
|
raise RuntimeError(f"Qwen chat API returned no choices: {data}")
|
||||||
@@ -183,35 +231,134 @@ def configure_qwen_chat(
|
|||||||
timeout_seconds: float | str | None = None,
|
timeout_seconds: float | str | None = None,
|
||||||
max_tokens: int | str | None = None,
|
max_tokens: int | str | None = None,
|
||||||
enable_thinking: bool | str | None = None,
|
enable_thinking: bool | str | None = None,
|
||||||
|
optimizer_base_url: str | None = None,
|
||||||
|
optimizer_api_key: str | None = None,
|
||||||
|
optimizer_temperature: float | str | None = None,
|
||||||
|
optimizer_timeout_seconds: float | str | None = None,
|
||||||
|
optimizer_max_tokens: int | str | None = None,
|
||||||
|
optimizer_enable_thinking: bool | str | None = None,
|
||||||
|
target_base_url: str | None = None,
|
||||||
|
target_api_key: str | None = None,
|
||||||
|
target_temperature: float | str | None = None,
|
||||||
|
target_timeout_seconds: float | str | None = None,
|
||||||
|
target_max_tokens: int | str | None = None,
|
||||||
|
target_enable_thinking: bool | str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
global BASE_URL, API_KEY, TEMPERATURE, TIMEOUT_SECONDS, MAX_TOKENS, ENABLE_THINKING
|
|
||||||
with _config_lock:
|
with _config_lock:
|
||||||
if base_url is not None:
|
if base_url is not None:
|
||||||
BASE_URL = str(base_url).strip() or BASE_URL
|
os.environ["QWEN_CHAT_BASE_URL"] = str(base_url).strip()
|
||||||
os.environ["QWEN_CHAT_BASE_URL"] = BASE_URL
|
|
||||||
if api_key is not None:
|
if api_key is not None:
|
||||||
API_KEY = str(api_key).strip()
|
os.environ["QWEN_CHAT_API_KEY"] = str(api_key).strip()
|
||||||
os.environ["QWEN_CHAT_API_KEY"] = API_KEY
|
|
||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
raw = str(temperature).strip()
|
os.environ["QWEN_CHAT_TEMPERATURE"] = str(temperature).strip()
|
||||||
TEMPERATURE = float(raw) if raw else None
|
|
||||||
os.environ["QWEN_CHAT_TEMPERATURE"] = raw
|
|
||||||
if timeout_seconds is not None:
|
if timeout_seconds is not None:
|
||||||
TIMEOUT_SECONDS = float(timeout_seconds)
|
|
||||||
os.environ["QWEN_CHAT_TIMEOUT_SECONDS"] = str(timeout_seconds)
|
os.environ["QWEN_CHAT_TIMEOUT_SECONDS"] = str(timeout_seconds)
|
||||||
if max_tokens is not None:
|
if max_tokens is not None:
|
||||||
MAX_TOKENS = int(max_tokens)
|
|
||||||
os.environ["QWEN_CHAT_MAX_TOKENS"] = str(max_tokens)
|
os.environ["QWEN_CHAT_MAX_TOKENS"] = str(max_tokens)
|
||||||
if enable_thinking is not None:
|
if enable_thinking is not None:
|
||||||
if isinstance(enable_thinking, str):
|
os.environ["QWEN_CHAT_ENABLE_THINKING"] = (
|
||||||
ENABLE_THINKING = enable_thinking.strip().lower() in {"1", "true", "yes", "on"}
|
"true" if _parse_bool(enable_thinking) else "false"
|
||||||
else:
|
)
|
||||||
ENABLE_THINKING = bool(enable_thinking)
|
_update_config(
|
||||||
os.environ["QWEN_CHAT_ENABLE_THINKING"] = "true" if ENABLE_THINKING else "false"
|
OPTIMIZER_CONFIG,
|
||||||
|
"optimizer",
|
||||||
|
base_url=optimizer_base_url if optimizer_base_url is not None else base_url,
|
||||||
|
api_key=optimizer_api_key if optimizer_api_key is not None else api_key,
|
||||||
|
temperature=(
|
||||||
|
optimizer_temperature
|
||||||
|
if optimizer_temperature is not None
|
||||||
|
else temperature
|
||||||
|
),
|
||||||
|
timeout_seconds=(
|
||||||
|
optimizer_timeout_seconds
|
||||||
|
if optimizer_timeout_seconds is not None
|
||||||
|
else timeout_seconds
|
||||||
|
),
|
||||||
|
max_tokens=optimizer_max_tokens if optimizer_max_tokens is not None else max_tokens,
|
||||||
|
enable_thinking=(
|
||||||
|
optimizer_enable_thinking
|
||||||
|
if optimizer_enable_thinking is not None
|
||||||
|
else enable_thinking
|
||||||
|
),
|
||||||
|
)
|
||||||
|
_update_config(
|
||||||
|
TARGET_CONFIG,
|
||||||
|
"target",
|
||||||
|
base_url=target_base_url if target_base_url is not None else base_url,
|
||||||
|
api_key=target_api_key if target_api_key is not None else api_key,
|
||||||
|
temperature=target_temperature if target_temperature is not None else temperature,
|
||||||
|
timeout_seconds=(
|
||||||
|
target_timeout_seconds
|
||||||
|
if target_timeout_seconds is not None
|
||||||
|
else timeout_seconds
|
||||||
|
),
|
||||||
|
max_tokens=target_max_tokens if target_max_tokens is not None else max_tokens,
|
||||||
|
enable_thinking=(
|
||||||
|
target_enable_thinking
|
||||||
|
if target_enable_thinking is not None
|
||||||
|
else enable_thinking
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _update_config(
|
||||||
|
config: QwenChatConfig,
|
||||||
|
role: str,
|
||||||
|
*,
|
||||||
|
base_url: str | None = None,
|
||||||
|
api_key: str | None = None,
|
||||||
|
temperature: float | str | None = None,
|
||||||
|
timeout_seconds: float | str | None = None,
|
||||||
|
max_tokens: int | str | None = None,
|
||||||
|
enable_thinking: bool | str | None = None,
|
||||||
|
) -> None:
|
||||||
|
env_prefix = role.upper()
|
||||||
|
if base_url is not None:
|
||||||
|
config.base_url = str(base_url).strip() or config.base_url
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_BASE_URL"] = config.base_url
|
||||||
|
if api_key is not None:
|
||||||
|
config.api_key = str(api_key).strip()
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_API_KEY"] = config.api_key
|
||||||
|
if temperature is not None:
|
||||||
|
raw = str(temperature).strip()
|
||||||
|
config.temperature = float(raw) if raw else None
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_TEMPERATURE"] = raw
|
||||||
|
if timeout_seconds is not None:
|
||||||
|
config.timeout_seconds = float(timeout_seconds)
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_TIMEOUT_SECONDS"] = str(timeout_seconds)
|
||||||
|
if max_tokens is not None:
|
||||||
|
config.max_tokens = int(max_tokens)
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_MAX_TOKENS"] = str(max_tokens)
|
||||||
|
if enable_thinking is not None:
|
||||||
|
config.enable_thinking = _parse_bool(enable_thinking)
|
||||||
|
os.environ[f"{env_prefix}_QWEN_CHAT_ENABLE_THINKING"] = (
|
||||||
|
"true" if config.enable_thinking else "false"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_max_tokens() -> int:
|
def get_max_tokens() -> int:
|
||||||
return MAX_TOKENS
|
return TARGET_CONFIG.max_tokens
|
||||||
|
|
||||||
|
|
||||||
|
def chat_optimizer(
|
||||||
|
system: str,
|
||||||
|
user: str,
|
||||||
|
max_completion_tokens: int = 16384,
|
||||||
|
retries: int = 5,
|
||||||
|
stage: str = "optimizer",
|
||||||
|
reasoning_effort: str | None = None,
|
||||||
|
timeout: float | None = None,
|
||||||
|
) -> tuple[str, dict[str, int]]:
|
||||||
|
del reasoning_effort
|
||||||
|
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||||||
|
return _chat_messages_impl(
|
||||||
|
messages,
|
||||||
|
max_completion_tokens,
|
||||||
|
retries,
|
||||||
|
stage,
|
||||||
|
role="optimizer",
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def chat_target(
|
def chat_target(
|
||||||
@@ -230,6 +377,33 @@ def chat_target(
|
|||||||
max_completion_tokens,
|
max_completion_tokens,
|
||||||
retries,
|
retries,
|
||||||
stage,
|
stage,
|
||||||
|
role="target",
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def chat_optimizer_messages(
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
max_completion_tokens: int = 16384,
|
||||||
|
retries: int = 5,
|
||||||
|
stage: str = "optimizer",
|
||||||
|
reasoning_effort: str | None = None,
|
||||||
|
*,
|
||||||
|
tools: list[dict[str, Any]] | None = None,
|
||||||
|
tool_choice: str | dict[str, Any] | None = None,
|
||||||
|
return_message: bool = False,
|
||||||
|
timeout: float | None = None,
|
||||||
|
) -> tuple[Any, dict[str, int]]:
|
||||||
|
del reasoning_effort
|
||||||
|
return _chat_messages_impl(
|
||||||
|
messages,
|
||||||
|
max_completion_tokens,
|
||||||
|
retries,
|
||||||
|
stage,
|
||||||
|
role="optimizer",
|
||||||
|
tools=tools,
|
||||||
|
tool_choice=tool_choice,
|
||||||
|
return_message=return_message,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -252,6 +426,7 @@ def chat_target_messages(
|
|||||||
max_completion_tokens,
|
max_completion_tokens,
|
||||||
retries,
|
retries,
|
||||||
stage,
|
stage,
|
||||||
|
role="target",
|
||||||
tools=tools,
|
tools=tools,
|
||||||
tool_choice=tool_choice,
|
tool_choice=tool_choice,
|
||||||
return_message=return_message,
|
return_message=return_message,
|
||||||
@@ -272,6 +447,10 @@ def set_reasoning_effort(effort: str | None) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def set_target_deployment(deployment: str) -> None:
|
def set_target_deployment(deployment: str) -> None:
|
||||||
global TARGET_DEPLOYMENT
|
TARGET_CONFIG.deployment = deployment or default_model_for_backend("qwen_chat")
|
||||||
TARGET_DEPLOYMENT = deployment or default_model_for_backend("qwen_chat")
|
os.environ["TARGET_DEPLOYMENT"] = TARGET_CONFIG.deployment
|
||||||
os.environ["TARGET_DEPLOYMENT"] = TARGET_DEPLOYMENT
|
|
||||||
|
|
||||||
|
def set_optimizer_deployment(deployment: str) -> None:
|
||||||
|
OPTIMIZER_CONFIG.deployment = deployment or default_model_for_backend("qwen_chat")
|
||||||
|
os.environ["OPTIMIZER_DEPLOYMENT"] = OPTIMIZER_CONFIG.deployment
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
"""Skill-Aware Reflection — protected appendix field (EmbodiSkill S_app).
|
||||||
|
|
||||||
|
EmbodiSkill (paper 2605.10332v1) splits a skill into ``S = (S_body, S_app)``:
|
||||||
|
the body holds the main prescriptive rules; the appendix only *emphasizes*
|
||||||
|
existing valid rules that the executor failed to follow (EXECUTION_LAPSE), and
|
||||||
|
**never introduces new rules**.
|
||||||
|
|
||||||
|
This module owns the appendix region of the skill document. It mirrors the
|
||||||
|
protected-field pattern of :mod:`skillopt.optimizer.slow_update`, with two
|
||||||
|
differences:
|
||||||
|
|
||||||
|
1. **Append semantics** (not replace): execution-lapse reminders accumulate
|
||||||
|
across steps within a run, so new notes are merged into the existing
|
||||||
|
appendix rather than overwriting it.
|
||||||
|
2. **Lightweight dedup**: near-duplicate reminders are collapsed (inspired by
|
||||||
|
GMemory's ``_dedupe_preserve_order``) so the appendix stays compact.
|
||||||
|
|
||||||
|
The appendix lives **inside** the skill markdown, between dedicated markers, so
|
||||||
|
it is persisted by the normal ``_save_skill`` path and is resume-safe. Step-level
|
||||||
|
analyst edits cannot modify it (enforced by the shared protected-region check in
|
||||||
|
:mod:`skillopt.optimizer.skill`).
|
||||||
|
|
||||||
|
Public API
|
||||||
|
----------
|
||||||
|
- :func:`has_appendix_field` — check if markers are present
|
||||||
|
- :func:`inject_empty_appendix_field` — add empty placeholder (skill init)
|
||||||
|
- :func:`extract_appendix_notes` — read current notes as a list
|
||||||
|
- :func:`append_to_appendix_field` — merge new notes (dedup) into the region
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# ── Protected field markers ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
APPENDIX_START = "<!-- APPENDIX_START -->"
|
||||||
|
APPENDIX_END = "<!-- APPENDIX_END -->"
|
||||||
|
|
||||||
|
# Heading shown inside the rendered appendix block (human-readable only).
|
||||||
|
APPENDIX_HEADING = "## Execution Notes Appendix"
|
||||||
|
|
||||||
|
# Each note is rendered as a markdown bullet so the target model reads it as
|
||||||
|
# ordinary guidance.
|
||||||
|
_NOTE_BULLET_PREFIX = "- "
|
||||||
|
|
||||||
|
|
||||||
|
# ── Dedup helpers ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _canonicalize(text: str) -> str:
|
||||||
|
"""Normalize a note for duplicate detection (whitespace/punct/case-insensitive)."""
|
||||||
|
normalized = re.sub(r"\s+", " ", str(text or "").strip())
|
||||||
|
normalized = normalized.rstrip(" .;:,_-")
|
||||||
|
return normalized.casefold()
|
||||||
|
|
||||||
|
|
||||||
|
def _dedupe_preserve_order(notes: list[str]) -> list[str]:
|
||||||
|
"""Drop blanks and near-duplicates, preserving first-seen order."""
|
||||||
|
seen: set[str] = set()
|
||||||
|
deduped: list[str] = []
|
||||||
|
for note in notes:
|
||||||
|
text = re.sub(r"\s+", " ", str(note).strip())
|
||||||
|
if not text:
|
||||||
|
continue
|
||||||
|
key = _canonicalize(text)
|
||||||
|
if not key or key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
deduped.append(text)
|
||||||
|
return deduped
|
||||||
|
|
||||||
|
|
||||||
|
# ── Field manipulation ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def has_appendix_field(skill: str) -> bool:
|
||||||
|
return APPENDIX_START in skill and APPENDIX_END in skill
|
||||||
|
|
||||||
|
|
||||||
|
def _render_block(notes: list[str]) -> str:
|
||||||
|
"""Render the full marker-delimited appendix block for *notes*."""
|
||||||
|
lines = [APPENDIX_START, APPENDIX_HEADING]
|
||||||
|
for note in notes:
|
||||||
|
lines.append(f"{_NOTE_BULLET_PREFIX}{note}")
|
||||||
|
lines.append(APPENDIX_END)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_empty_appendix_field(skill: str) -> str:
|
||||||
|
"""Add an empty appendix placeholder at the end of *skill* (idempotent).
|
||||||
|
|
||||||
|
Mirrors ``inject_empty_slow_update_field``: called once at skill init so the
|
||||||
|
protected region exists before any note is written.
|
||||||
|
"""
|
||||||
|
if has_appendix_field(skill):
|
||||||
|
return skill
|
||||||
|
block = f"\n\n{APPENDIX_START}\n{APPENDIX_HEADING}\n{APPENDIX_END}\n"
|
||||||
|
return skill.rstrip() + block
|
||||||
|
|
||||||
|
|
||||||
|
def extract_appendix_notes(skill: str) -> list[str]:
|
||||||
|
"""Return the current appendix notes as a list of strings (no markers/heading)."""
|
||||||
|
start = skill.find(APPENDIX_START)
|
||||||
|
end = skill.find(APPENDIX_END)
|
||||||
|
if start == -1 or end == -1:
|
||||||
|
return []
|
||||||
|
inner = skill[start + len(APPENDIX_START):end].strip()
|
||||||
|
notes: list[str] = []
|
||||||
|
for raw_line in inner.splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line == APPENDIX_HEADING or line.lstrip("#").strip() == APPENDIX_HEADING.lstrip("#").strip():
|
||||||
|
continue
|
||||||
|
if line.startswith(_NOTE_BULLET_PREFIX):
|
||||||
|
line = line[len(_NOTE_BULLET_PREFIX):].strip()
|
||||||
|
elif line.startswith("-") or line.startswith("*"):
|
||||||
|
line = line[1:].strip()
|
||||||
|
if line:
|
||||||
|
notes.append(line)
|
||||||
|
return notes
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_all_appendix_fields(skill: str) -> str:
|
||||||
|
"""Remove every appendix marker pair (and content between) from *skill*."""
|
||||||
|
while True:
|
||||||
|
start = skill.find(APPENDIX_START)
|
||||||
|
if start == -1:
|
||||||
|
break
|
||||||
|
end = skill.find(APPENDIX_END, start)
|
||||||
|
if end == -1:
|
||||||
|
skill = skill[:start] + skill[start + len(APPENDIX_START):]
|
||||||
|
break
|
||||||
|
skill = skill[:end + len(APPENDIX_END)].rsplit(APPENDIX_START, 1)[0] + skill[end + len(APPENDIX_END):]
|
||||||
|
skill = skill.replace(APPENDIX_END, "")
|
||||||
|
while "\n\n\n" in skill:
|
||||||
|
skill = skill.replace("\n\n\n", "\n\n")
|
||||||
|
return skill.rstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def append_to_appendix_field(skill: str, new_notes: list[str]) -> str:
|
||||||
|
"""Merge *new_notes* into the appendix region (dedup), returning updated skill.
|
||||||
|
|
||||||
|
- If no appendix region exists yet, one is created.
|
||||||
|
- Existing notes are preserved; new ones are appended after dedup against the
|
||||||
|
combined set, so order is stable and duplicates are dropped.
|
||||||
|
- Empty / whitespace-only notes are ignored. If the merged set is empty, an
|
||||||
|
empty placeholder region is still ensured.
|
||||||
|
"""
|
||||||
|
incoming = _dedupe_preserve_order(list(new_notes or []))
|
||||||
|
existing = extract_appendix_notes(skill)
|
||||||
|
merged = _dedupe_preserve_order(existing + incoming)
|
||||||
|
|
||||||
|
base = _strip_all_appendix_fields(skill)
|
||||||
|
block = _render_block(merged)
|
||||||
|
return f"{base}\n\n{block}\n"
|
||||||
@@ -57,7 +57,7 @@ def rank_and_select(
|
|||||||
# Build the edit pool description for the optimizer
|
# Build the edit pool description for the optimizer
|
||||||
edits_desc = []
|
edits_desc = []
|
||||||
for i, edit in enumerate(edits):
|
for i, edit in enumerate(edits):
|
||||||
edits_desc.append(f"[{i}] {describe_item(edit, update_mode, max_chars=500)}")
|
edits_desc.append(f"[{i}] {describe_item(edit, update_mode)}")
|
||||||
|
|
||||||
user = (
|
user = (
|
||||||
f"## Current Skill\n{skill_content}\n\n"
|
f"## Current Skill\n{skill_content}\n\n"
|
||||||
@@ -74,7 +74,7 @@ def rank_and_select(
|
|||||||
try:
|
try:
|
||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=load_prompt(prompt_name), user=user,
|
system=load_prompt(prompt_name), user=user,
|
||||||
max_completion_tokens=2048, retries=3, stage="ranking",
|
max_completion_tokens=16384, retries=3, stage="ranking",
|
||||||
)
|
)
|
||||||
result = extract_json(response)
|
result = extract_json(response)
|
||||||
if result and "selected_indices" in result:
|
if result and "selected_indices" in result:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ def decide_autonomous_learning_rate(
|
|||||||
items = get_payload_items(merged_patch, update_mode)
|
items = get_payload_items(merged_patch, update_mode)
|
||||||
available = len(items)
|
available = len(items)
|
||||||
item_lines = [
|
item_lines = [
|
||||||
f"[{idx}] {describe_item(item, update_mode, max_chars=700)}"
|
f"[{idx}] {describe_item(item, update_mode)}"
|
||||||
for idx, item in enumerate(items)
|
for idx, item in enumerate(items)
|
||||||
]
|
]
|
||||||
user = (
|
user = (
|
||||||
@@ -76,7 +76,7 @@ def decide_autonomous_learning_rate(
|
|||||||
response, _ = chat_optimizer(
|
response, _ = chat_optimizer(
|
||||||
system=load_prompt("lr_autonomous"),
|
system=load_prompt("lr_autonomous"),
|
||||||
user=user,
|
user=user,
|
||||||
max_completion_tokens=2048,
|
max_completion_tokens=16384,
|
||||||
retries=3,
|
retries=3,
|
||||||
stage="lr_autonomous",
|
stage="lr_autonomous",
|
||||||
)
|
)
|
||||||
|
|||||||
+57
-20
@@ -14,25 +14,62 @@ if TYPE_CHECKING:
|
|||||||
SLOW_UPDATE_START = "<!-- SLOW_UPDATE_START -->"
|
SLOW_UPDATE_START = "<!-- SLOW_UPDATE_START -->"
|
||||||
SLOW_UPDATE_END = "<!-- SLOW_UPDATE_END -->"
|
SLOW_UPDATE_END = "<!-- SLOW_UPDATE_END -->"
|
||||||
|
|
||||||
|
# Skill-aware reflection (EmbodiSkill S_app) appendix region. Like the slow
|
||||||
|
# update region, it is protected: step-level analyst edits must not modify it.
|
||||||
|
APPENDIX_START = "<!-- APPENDIX_START -->"
|
||||||
|
APPENDIX_END = "<!-- APPENDIX_END -->"
|
||||||
|
|
||||||
def _is_in_slow_update_region(skill: str, target: str) -> bool:
|
# All protected (start, end) marker pairs. Step-level edits cannot target text
|
||||||
"""Check if *target* text falls within the protected slow update region."""
|
# inside any of these regions, and `append` / `insert_after`-fallback ops are
|
||||||
start_idx = skill.find(SLOW_UPDATE_START)
|
# inserted before the earliest-occurring region so protected blocks stay at the
|
||||||
end_idx = skill.find(SLOW_UPDATE_END)
|
# document tail. With only the slow-update region present, every helper reduces
|
||||||
if start_idx == -1 or end_idx == -1:
|
# to the original slow-update-only behavior (byte-identical skill output).
|
||||||
|
_PROTECTED_REGIONS: tuple[tuple[str, str], ...] = (
|
||||||
|
(SLOW_UPDATE_START, SLOW_UPDATE_END),
|
||||||
|
(APPENDIX_START, APPENDIX_END),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _earliest_protected_start(skill: str) -> int:
|
||||||
|
"""Index of the earliest protected-region start marker, or -1 if none."""
|
||||||
|
positions = [
|
||||||
|
idx
|
||||||
|
for idx in (skill.find(start) for start, _ in _PROTECTED_REGIONS)
|
||||||
|
if idx != -1
|
||||||
|
]
|
||||||
|
return min(positions) if positions else -1
|
||||||
|
|
||||||
|
|
||||||
|
def _is_in_protected_region(skill: str, target: str) -> bool:
|
||||||
|
"""Check if *target* text falls within any protected region."""
|
||||||
|
if not target:
|
||||||
return False
|
return False
|
||||||
target_idx = skill.find(target)
|
target_idx = skill.find(target)
|
||||||
if target_idx == -1:
|
if target_idx == -1:
|
||||||
return False
|
return False
|
||||||
region_end = end_idx + len(SLOW_UPDATE_END)
|
for start_marker, end_marker in _PROTECTED_REGIONS:
|
||||||
return start_idx <= target_idx < region_end
|
start_idx = skill.find(start_marker)
|
||||||
|
end_idx = skill.find(end_marker)
|
||||||
|
if start_idx == -1 or end_idx == -1:
|
||||||
|
continue
|
||||||
|
region_end = end_idx + len(end_marker)
|
||||||
|
if start_idx <= target_idx < region_end:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _is_in_slow_update_region(skill: str, target: str) -> bool:
|
||||||
|
"""Backward-compatible alias kept for any external callers/tests."""
|
||||||
|
return _is_in_protected_region(skill, target)
|
||||||
|
|
||||||
|
|
||||||
def _strip_slow_update_markers(text: str) -> str:
|
def _strip_slow_update_markers(text: str) -> str:
|
||||||
"""Remove any SLOW_UPDATE markers from edit content to prevent duplication."""
|
"""Remove any protected-region markers from edit content to prevent duplication."""
|
||||||
return (
|
return (
|
||||||
text.replace(SLOW_UPDATE_START, "")
|
text.replace(SLOW_UPDATE_START, "")
|
||||||
.replace(SLOW_UPDATE_END, "")
|
.replace(SLOW_UPDATE_END, "")
|
||||||
|
.replace(APPENDIX_START, "")
|
||||||
|
.replace(APPENDIX_END, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,27 +91,27 @@ def _apply_edit_with_report(skill: str, edit: EditType | dict) -> tuple[str, dic
|
|||||||
"status": "unknown",
|
"status": "unknown",
|
||||||
}
|
}
|
||||||
|
|
||||||
if target and _is_in_slow_update_region(skill, target):
|
if target and _is_in_protected_region(skill, target):
|
||||||
report["status"] = "skipped_protected_slow_update_region"
|
report["status"] = "skipped_protected_region"
|
||||||
return skill, report
|
return skill, report
|
||||||
|
|
||||||
if op == "append":
|
if op == "append":
|
||||||
su_start = skill.find(SLOW_UPDATE_START)
|
prot_start = _earliest_protected_start(skill)
|
||||||
if su_start != -1:
|
if prot_start != -1:
|
||||||
before = skill[:su_start].rstrip()
|
before = skill[:prot_start].rstrip()
|
||||||
after = skill[su_start:]
|
after = skill[prot_start:]
|
||||||
report["status"] = "applied_append_before_slow_update"
|
report["status"] = "applied_append_before_protected_region"
|
||||||
return before + "\n\n" + content + "\n\n" + after, report
|
return before + "\n\n" + content + "\n\n" + after, report
|
||||||
report["status"] = "applied_append"
|
report["status"] = "applied_append"
|
||||||
return skill.rstrip() + "\n\n" + content + "\n", report
|
return skill.rstrip() + "\n\n" + content + "\n", report
|
||||||
|
|
||||||
if op == "insert_after":
|
if op == "insert_after":
|
||||||
if not target or target not in skill:
|
if not target or target not in skill:
|
||||||
su_start = skill.find(SLOW_UPDATE_START)
|
prot_start = _earliest_protected_start(skill)
|
||||||
if su_start != -1:
|
if prot_start != -1:
|
||||||
before = skill[:su_start].rstrip()
|
before = skill[:prot_start].rstrip()
|
||||||
after = skill[su_start:]
|
after = skill[prot_start:]
|
||||||
report["status"] = "applied_insert_after_fallback_before_slow_update"
|
report["status"] = "applied_insert_after_fallback_before_protected_region"
|
||||||
return before + "\n\n" + content + "\n\n" + after, report
|
return before + "\n\n" + content + "\n\n" + after, report
|
||||||
report["status"] = "applied_insert_after_fallback_append"
|
report["status"] = "applied_insert_after_fallback_append"
|
||||||
return skill.rstrip() + "\n\n" + content + "\n", report
|
return skill.rstrip() + "\n\n" + content + "\n", report
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
"""Skill-Aware Reflection — analyst prompt augmentation (EmbodiSkill).
|
||||||
|
|
||||||
|
When ``use_skill_aware_reflection`` is enabled, the failure/success analysts are
|
||||||
|
asked to additionally classify each reflection by EmbodiSkill type and to route
|
||||||
|
**EXECUTION_LAPSE** reflections (the skill rule is correct, the executor just
|
||||||
|
failed to follow it) into a separate ``appendix_notes`` list instead of the body
|
||||||
|
patch. This module owns:
|
||||||
|
|
||||||
|
1. the instruction text appended to the resolved analyst system prompt, and
|
||||||
|
2. extraction of ``appendix_notes`` from the analyst JSON response.
|
||||||
|
|
||||||
|
Design notes
|
||||||
|
------------
|
||||||
|
- The suffix is appended **at runtime, gated by the toggle**, so env-specific and
|
||||||
|
generic analyst prompts are augmented uniformly and — when the toggle is off —
|
||||||
|
remain byte-identical to baseline.
|
||||||
|
- Discrimination follows the paper / GMemory: ``SKILL_DEFECT`` = the skill rule is
|
||||||
|
wrong / missing / underspecified (→ body edit); ``EXECUTION_LAPSE`` = the rule
|
||||||
|
is valid but the agent didn't follow it (→ appendix reminder, body untouched).
|
||||||
|
**When unsure, default to EXECUTION_LAPSE** (protect the body — never delete a
|
||||||
|
valid rule over a one-off execution slip).
|
||||||
|
- Success reflections are labeled DISCOVERY / OPTIMIZATION for logging only; their
|
||||||
|
edit behavior is unchanged.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
# ── Runtime switch (config-driven, env-independent) ─────────────────────────
|
||||||
|
#
|
||||||
|
# The trainer calls :func:`configure_skill_aware_reflection` once at startup
|
||||||
|
# from the resolved config. ``run_minibatch_reflect`` then picks these values
|
||||||
|
# up automatically, so env adapters never need to thread the toggle through —
|
||||||
|
# the feature is controlled purely by ``optimizer.use_skill_aware_reflection``
|
||||||
|
# regardless of benchmark. Mirrors the ``configure_azure_openai`` pattern in
|
||||||
|
# :mod:`skillopt.model`. Explicit kwargs at a call site still take precedence
|
||||||
|
# (backward compatible).
|
||||||
|
|
||||||
|
_RUNTIME: dict = {"enabled": False, "appendix_source": "both"}
|
||||||
|
|
||||||
|
|
||||||
|
def configure_skill_aware_reflection(
|
||||||
|
enabled: bool,
|
||||||
|
appendix_source: str = "both",
|
||||||
|
) -> None:
|
||||||
|
"""Set the process-wide skill-aware reflection switch from config."""
|
||||||
|
_RUNTIME["enabled"] = bool(enabled)
|
||||||
|
_RUNTIME["appendix_source"] = str(appendix_source or "both")
|
||||||
|
|
||||||
|
|
||||||
|
def is_skill_aware_enabled() -> bool:
|
||||||
|
return bool(_RUNTIME["enabled"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_skill_aware_appendix_source() -> str:
|
||||||
|
return str(_RUNTIME["appendix_source"])
|
||||||
|
|
||||||
|
|
||||||
|
# ── Prompt suffixes ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Appended to the FAILURE analyst system prompt when the toggle is on.
|
||||||
|
ERROR_SUFFIX = """
|
||||||
|
|
||||||
|
## Skill-Aware Reflection (EmbodiSkill)
|
||||||
|
|
||||||
|
Before proposing body edits, classify EACH failure pattern as one of:
|
||||||
|
|
||||||
|
- **SKILL_DEFECT**: the current skill is wrong, missing, or underspecified for
|
||||||
|
this situation — i.e. an agent that *followed the skill* would still fail, or
|
||||||
|
the skill gives no relevant guidance. These become normal body `edits`.
|
||||||
|
- **EXECUTION_LAPSE**: the skill ALREADY contains a relevant, correct rule that
|
||||||
|
would have avoided the failure, but the agent did not follow it (e.g. ignored a
|
||||||
|
rule, malformed output, copied the feedback text verbatim, emitted a non-action
|
||||||
|
token like "stop", or otherwise broke execution unrelated to skill content).
|
||||||
|
|
||||||
|
Discrimination test: "Is there a rule in the current skill that, if followed,
|
||||||
|
prevents this failure?" If yes → EXECUTION_LAPSE. If no (rule absent/wrong) →
|
||||||
|
SKILL_DEFECT. **When genuinely unsure, choose EXECUTION_LAPSE** — do not edit or
|
||||||
|
delete a valid rule over a one-off execution slip.
|
||||||
|
|
||||||
|
Routing:
|
||||||
|
- SKILL_DEFECT → put the fix in `patch.edits` (body), as usual.
|
||||||
|
- EXECUTION_LAPSE → put a concise reminder in `appendix_notes` (a flat list of
|
||||||
|
strings). DO NOT add a body edit for it. Each note should re-emphasize the
|
||||||
|
existing valid rule the agent failed to follow; it must NOT introduce a new
|
||||||
|
rule. Keep notes short, concrete, and reusable.
|
||||||
|
|
||||||
|
Add `appendix_notes` as a TOP-LEVEL key of your JSON output (a sibling of
|
||||||
|
`patch`), e.g. `"appendix_notes": ["Follow the existing X rule before Y."]`.
|
||||||
|
Use `[]` when there is no execution lapse. Body edits and appendix notes are
|
||||||
|
independent: a batch may yield only edits, only notes, both, or neither.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Appended to the SUCCESS analyst system prompt when the toggle is on.
|
||||||
|
SUCCESS_SUFFIX = """
|
||||||
|
|
||||||
|
## Skill-Aware Reflection (EmbodiSkill)
|
||||||
|
|
||||||
|
For each proposed edit, optionally label its `reflection_type` for logging:
|
||||||
|
- **DISCOVERY**: a useful new rule not yet in the skill (typically an `append`).
|
||||||
|
- **OPTIMIZATION**: a better way to perform an existing rule (typically a
|
||||||
|
`replace` of that rule).
|
||||||
|
|
||||||
|
This labeling does not change edit behavior. You may also add a top-level
|
||||||
|
`appendix_notes` list (flat strings) if a successful trajectory reveals an
|
||||||
|
existing valid rule worth re-emphasizing; otherwise use `[]`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def augment_error_prompt(system_prompt: str) -> str:
|
||||||
|
"""Append the failure-analyst skill-aware instruction."""
|
||||||
|
return system_prompt.rstrip() + "\n" + ERROR_SUFFIX
|
||||||
|
|
||||||
|
|
||||||
|
def augment_success_prompt(system_prompt: str) -> str:
|
||||||
|
"""Append the success-analyst skill-aware instruction."""
|
||||||
|
return system_prompt.rstrip() + "\n" + SUCCESS_SUFFIX
|
||||||
|
|
||||||
|
|
||||||
|
# ── Response parsing ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def extract_appendix_notes(result: dict | None) -> list[str]:
|
||||||
|
"""Pull a clean list of appendix-note strings from an analyst JSON result.
|
||||||
|
|
||||||
|
Tolerant of shape: accepts a top-level ``appendix_notes`` list, a single
|
||||||
|
string, or items wrapped in dicts with a ``note``/``content`` field. Returns
|
||||||
|
``[]`` for anything missing or malformed (so a non-compliant model degrades
|
||||||
|
gracefully to baseline body-only behavior).
|
||||||
|
"""
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
return []
|
||||||
|
raw = result.get("appendix_notes")
|
||||||
|
if raw is None:
|
||||||
|
return []
|
||||||
|
if isinstance(raw, str):
|
||||||
|
raw = [raw]
|
||||||
|
if not isinstance(raw, list):
|
||||||
|
return []
|
||||||
|
notes: list[str] = []
|
||||||
|
for item in raw:
|
||||||
|
if isinstance(item, str):
|
||||||
|
text = item.strip()
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
text = str(item.get("note") or item.get("content") or "").strip()
|
||||||
|
else:
|
||||||
|
text = ""
|
||||||
|
if text:
|
||||||
|
notes.append(text)
|
||||||
|
return notes
|
||||||
|
|
||||||
|
|
||||||
|
# ── Appendix consolidation (threshold-gated, paper Eq.11 UpdateSkillAppendix) ──
|
||||||
|
|
||||||
|
_CONSOLIDATE_SYSTEM = (
|
||||||
|
"You compact the Execution Notes Appendix of an agent skill. Each note "
|
||||||
|
"re-emphasizes an existing skill rule the agent failed to follow. Your job "
|
||||||
|
"is a periodic compaction pass: remove duplicates and redundant overlap, "
|
||||||
|
"merge near-identical reminders into one, and simplify phrasing while keeping "
|
||||||
|
"each note concrete and operational. Do not invent new rules. Preserve the "
|
||||||
|
"distinct actionable content. Return valid JSON only."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def consolidate_appendix_notes(
|
||||||
|
notes: list[str],
|
||||||
|
*,
|
||||||
|
chat_fn,
|
||||||
|
max_completion_tokens: int = 4096,
|
||||||
|
) -> list[str]:
|
||||||
|
"""LLM-consolidate appendix notes: dedupe / merge / compact.
|
||||||
|
|
||||||
|
Mirrors GMemory ``_maybe_refactor_execution_notes`` and paper Eq.11. ``chat_fn``
|
||||||
|
is the optimizer chat callable ``(system, user, max_completion_tokens, retries,
|
||||||
|
stage) -> (text, meta)``. On ANY failure (parse, empty, exception) the original
|
||||||
|
notes are returned unchanged, so consolidation can never lose the appendix.
|
||||||
|
"""
|
||||||
|
from skillopt.utils import extract_json # local import to avoid cycles
|
||||||
|
|
||||||
|
clean = [str(n).strip() for n in (notes or []) if str(n).strip()]
|
||||||
|
if len(clean) < 2:
|
||||||
|
return clean
|
||||||
|
|
||||||
|
numbered = "\n".join(f"{i}. {n}" for i, n in enumerate(clean, 1))
|
||||||
|
user = (
|
||||||
|
f"## Current Execution Notes ({len(clean)} total)\n{numbered}\n\n"
|
||||||
|
"Compact these into a shorter list without losing distinct actionable "
|
||||||
|
"information. Merge duplicates and near-duplicates; keep each note short, "
|
||||||
|
"concrete, and reusable. Return valid JSON only with this schema:\n"
|
||||||
|
'{ "appendix_notes": ["compacted note 1", "compacted note 2"] }'
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response, _ = chat_fn(
|
||||||
|
system=_CONSOLIDATE_SYSTEM,
|
||||||
|
user=user,
|
||||||
|
max_completion_tokens=max_completion_tokens,
|
||||||
|
retries=2,
|
||||||
|
stage="appendix_consolidate",
|
||||||
|
)
|
||||||
|
result = extract_json(response)
|
||||||
|
compacted = extract_appendix_notes(result)
|
||||||
|
# Guard: only accept a non-empty result that actually shrinks the set.
|
||||||
|
if compacted and len(compacted) <= len(clean):
|
||||||
|
return compacted
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
return clean
|
||||||
@@ -91,18 +91,21 @@ def replace_slow_update_field(skill: str, new_content: str) -> str:
|
|||||||
# ── Comparison text builder ─────────────────────────────────────────────────
|
# ── Comparison text builder ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
# NOTE: The character limits below (whole-trajectory cap + the per-field caps in
|
# NOTE: Character-length limits on the comparison samples fed to the slow-update /
|
||||||
# _read_trajectory and the comparison metadata) only trim the comparison samples
|
# meta-skill optimizer have been REMOVED. Previously a whole-trajectory cap plus
|
||||||
# fed to the slow-update optimizer. They exist to cut token usage and speed up the
|
# per-field caps (cmd/obs/reasoning/etc.) and comparison-metadata caps
|
||||||
# call; they do NOT affect what gets written into the skill. If you need richer
|
# (task/answer/fail_reason) trimmed this context to save optimizer tokens and
|
||||||
# context for the longitudinal comparison, feel free to raise them.
|
# speed up the call. They never affected what gets written into the skill — only
|
||||||
_MAX_TRAJ_CHARS = 3000
|
# how much longitudinal context the optimizer sees. We now pass everything through
|
||||||
|
# at full length: the comparison input is as long as the source data is.
|
||||||
|
|
||||||
|
|
||||||
def _clip_text(value, limit: int) -> str:
|
def _clip_text(value, limit: int | None = None) -> str:
|
||||||
|
# Truncation disabled: return the full text. The `limit` argument is kept only
|
||||||
|
# for call-site compatibility and is intentionally ignored (see NOTE above).
|
||||||
if value is None:
|
if value is None:
|
||||||
return ""
|
return ""
|
||||||
return str(value)[:limit]
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
def _read_trajectory(rollout_dir: str, task_id: str) -> str:
|
def _read_trajectory(rollout_dir: str, task_id: str) -> str:
|
||||||
@@ -122,35 +125,32 @@ def _read_trajectory(rollout_dir: str, task_id: str) -> str:
|
|||||||
for entry in conversation:
|
for entry in conversation:
|
||||||
if not isinstance(entry, dict):
|
if not isinstance(entry, dict):
|
||||||
continue
|
continue
|
||||||
# Per-field caps (cmd/obs/reasoning/etc.) keep each trajectory compact to
|
# Per-field truncation removed: feed each step's full cmd/obs/reasoning/
|
||||||
# save tokens / time; raise them if you want fuller step detail.
|
# action/feedback/content (see NOTE above).
|
||||||
if entry.get("type") == "tool_call":
|
if entry.get("type") == "tool_call":
|
||||||
cmd = _clip_text(entry.get("cmd"), 500)
|
cmd = _clip_text(entry.get("cmd"))
|
||||||
obs = _clip_text(entry.get("obs"), 800)
|
obs = _clip_text(entry.get("obs"))
|
||||||
lines.append(f"[action] {cmd}")
|
lines.append(f"[action] {cmd}")
|
||||||
lines.append(f"[obs] {obs}")
|
lines.append(f"[obs] {obs}")
|
||||||
elif "action" in entry and "env_feedback" in entry:
|
elif "action" in entry and "env_feedback" in entry:
|
||||||
step = entry.get("step", "?")
|
step = entry.get("step", "?")
|
||||||
reasoning = _clip_text(entry.get("reasoning"), 300)
|
reasoning = _clip_text(entry.get("reasoning"))
|
||||||
action = _clip_text(entry.get("action"), 200)
|
action = _clip_text(entry.get("action"))
|
||||||
feedback = _clip_text(entry.get("env_feedback"), 500)
|
feedback = _clip_text(entry.get("env_feedback"))
|
||||||
if reasoning:
|
if reasoning:
|
||||||
lines.append(f"[step {step} think] {reasoning}")
|
lines.append(f"[step {step} think] {reasoning}")
|
||||||
lines.append(f"[step {step} action] {action}")
|
lines.append(f"[step {step} action] {action}")
|
||||||
lines.append(f"[step {step} obs] {feedback}")
|
lines.append(f"[step {step} obs] {feedback}")
|
||||||
elif entry.get("role") == "system":
|
elif entry.get("role") == "system":
|
||||||
msg = _clip_text(entry.get("content"), 1000)
|
msg = _clip_text(entry.get("content"))
|
||||||
lines.append(f"[verification] {msg}")
|
lines.append(f"[verification] {msg}")
|
||||||
else:
|
else:
|
||||||
msg = _clip_text(entry.get("content"), 500)
|
msg = _clip_text(entry.get("content"))
|
||||||
role = entry.get("role", "agent")
|
role = entry.get("role", "agent")
|
||||||
lines.append(f"[{role}] {msg}")
|
lines.append(f"[{role}] {msg}")
|
||||||
|
|
||||||
text = "\n".join(lines)
|
# Whole-trajectory truncation removed: return the full formatted trajectory.
|
||||||
if len(text) > _MAX_TRAJ_CHARS:
|
return "\n".join(lines)
|
||||||
half = _MAX_TRAJ_CHARS // 2
|
|
||||||
text = text[:half] + "\n...[truncated]...\n" + text[-half:]
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
# ── Structured comparison pairs ─────────────────────────────────────────────
|
# ── Structured comparison pairs ─────────────────────────────────────────────
|
||||||
@@ -228,7 +228,7 @@ def save_comparison_pairs(pairs: list[dict], out_path: str) -> None:
|
|||||||
for p in pairs:
|
for p in pairs:
|
||||||
slim.append({
|
slim.append({
|
||||||
"id": p["id"],
|
"id": p["id"],
|
||||||
"task": p["task"][:300],
|
"task": p["task"],
|
||||||
"category": p["category"],
|
"category": p["category"],
|
||||||
"prev": p["prev"],
|
"prev": p["prev"],
|
||||||
"curr": p["curr"],
|
"curr": p["curr"],
|
||||||
@@ -276,16 +276,16 @@ def format_comparison_text(pairs: list[dict]) -> str:
|
|||||||
prev = e["prev"]
|
prev = e["prev"]
|
||||||
curr = e["curr"]
|
curr = e["curr"]
|
||||||
lines.append(
|
lines.append(
|
||||||
f"\n#### Task {e['id']}: {e['task'][:300]}\n"
|
f"\n#### Task {e['id']}: {e['task']}\n"
|
||||||
f"- Prev epoch: {'PASS' if prev['hard'] else 'FAIL'} "
|
f"- Prev epoch: {'PASS' if prev['hard'] else 'FAIL'} "
|
||||||
f"(soft={prev['soft']:.2f}) — answer: {str(prev['predicted_answer'])[:200]}\n"
|
f"(soft={prev['soft']:.2f}) — answer: {str(prev['predicted_answer'])}\n"
|
||||||
f"- Curr epoch: {'PASS' if curr['hard'] else 'FAIL'} "
|
f"- Curr epoch: {'PASS' if curr['hard'] else 'FAIL'} "
|
||||||
f"(soft={curr['soft']:.2f}) — answer: {str(curr['predicted_answer'])[:200]}"
|
f"(soft={curr['soft']:.2f}) — answer: {str(curr['predicted_answer'])}"
|
||||||
)
|
)
|
||||||
if curr.get("fail_reason"):
|
if curr.get("fail_reason"):
|
||||||
lines.append(f"- Curr fail reason: {curr['fail_reason'][:300]}")
|
lines.append(f"- Curr fail reason: {curr['fail_reason']}")
|
||||||
if prev.get("fail_reason") and not prev["hard"]:
|
if prev.get("fail_reason") and not prev["hard"]:
|
||||||
lines.append(f"- Prev fail reason: {prev['fail_reason'][:300]}")
|
lines.append(f"- Prev fail reason: {prev['fail_reason']}")
|
||||||
|
|
||||||
if show_traj:
|
if show_traj:
|
||||||
if e.get("prev_trajectory"):
|
if e.get("prev_trajectory"):
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def truncate_payload(container: dict, max_items: int, mode: str | None) -> dict:
|
|||||||
return container
|
return container
|
||||||
|
|
||||||
|
|
||||||
def describe_item(item: dict, mode: str | None, *, max_chars: int = 240) -> str:
|
def describe_item(item: dict, mode: str | None, *, max_chars: int | None = None) -> str:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
return ""
|
return ""
|
||||||
if is_full_rewrite_minibatch_mode(mode):
|
if is_full_rewrite_minibatch_mode(mode):
|
||||||
@@ -84,7 +84,7 @@ def describe_item(item: dict, mode: str | None, *, max_chars: int = 240) -> str:
|
|||||||
parts.append(f"support={item.get('support_count')}")
|
parts.append(f"support={item.get('support_count')}")
|
||||||
new_skill = str(item.get("new_skill", "")).strip()
|
new_skill = str(item.get("new_skill", "")).strip()
|
||||||
if new_skill:
|
if new_skill:
|
||||||
parts.append(f"new_skill_preview={new_skill[:120]!r}")
|
parts.append(f"new_skill_preview={new_skill!r}")
|
||||||
text = " ".join(parts)
|
text = " ".join(parts)
|
||||||
elif is_rewrite_mode(mode):
|
elif is_rewrite_mode(mode):
|
||||||
parts = [
|
parts = [
|
||||||
@@ -109,28 +109,27 @@ def describe_item(item: dict, mode: str | None, *, max_chars: int = 240) -> str:
|
|||||||
if item.get("support_count") is not None:
|
if item.get("support_count") is not None:
|
||||||
parts.append(f"support={item.get('support_count')}")
|
parts.append(f"support={item.get('support_count')}")
|
||||||
text = " ".join(parts)
|
text = " ".join(parts)
|
||||||
if len(text) <= max_chars:
|
# Truncation disabled: the optimizer is given the full item description.
|
||||||
return text
|
return text
|
||||||
return text[: max_chars - 3].rstrip() + "..."
|
|
||||||
|
|
||||||
|
|
||||||
def short_item_summary(item: dict, mode: str | None, *, max_chars: int = 200) -> dict[str, Any]:
|
def short_item_summary(item: dict, mode: str | None, *, max_chars: int | None = None) -> dict[str, Any]:
|
||||||
if is_full_rewrite_minibatch_mode(mode):
|
if is_full_rewrite_minibatch_mode(mode):
|
||||||
return {
|
return {
|
||||||
"title": str(item.get("title", ""))[:max_chars],
|
"title": str(item.get("title", "")),
|
||||||
"change_summary": [
|
"change_summary": [
|
||||||
str(x)[:max_chars] for x in item.get("change_summary", [])[:3]
|
str(x) for x in item.get("change_summary", [])
|
||||||
] if isinstance(item.get("change_summary"), list) else [],
|
] if isinstance(item.get("change_summary"), list) else [],
|
||||||
"source_type": item.get("source_type", ""),
|
"source_type": item.get("source_type", ""),
|
||||||
}
|
}
|
||||||
if is_rewrite_mode(mode):
|
if is_rewrite_mode(mode):
|
||||||
return {
|
return {
|
||||||
"type": item.get("type", "?"),
|
"type": item.get("type", "?"),
|
||||||
"title": str(item.get("title", ""))[:max_chars],
|
"title": str(item.get("title", "")),
|
||||||
"instruction": str(item.get("instruction", ""))[:max_chars],
|
"instruction": str(item.get("instruction", "")),
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"op": item.get("op", "?"),
|
"op": item.get("op", "?"),
|
||||||
"content": str(item.get("content", ""))[:max_chars],
|
"content": str(item.get("content", "")),
|
||||||
"target": item.get("target", ""),
|
"target": item.get("target", ""),
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user