self-evolve-framework 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/skills/skillopt-sleep/SKILL.md +42 -0
- package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
- package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
- package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
- package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
- package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
- package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
- package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
- package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/scoring.py +28 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""ReflACT environment adapter — abstract interface.
|
|
2
|
+
|
|
3
|
+
To connect ReflACT to a new environment (benchmark, simulator, etc.),
|
|
4
|
+
implement a subclass of :class:`EnvAdapter` with environment-specific
|
|
5
|
+
rollout and reflection logic.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
class MyBenchAdapter(EnvAdapter):
|
|
10
|
+
def build_train_env(self, batch_size, seed, **kw):
|
|
11
|
+
return MyEnvManager(split="train", n=batch_size, seed=seed)
|
|
12
|
+
|
|
13
|
+
def build_eval_env(self, env_num, split, seed, **kw):
|
|
14
|
+
return MyEnvManager(split=split, n=env_num, seed=seed)
|
|
15
|
+
|
|
16
|
+
def rollout(self, env_manager, skill_content, out_dir, **kw):
|
|
17
|
+
# Run episodes, return [{"id": ..., "hard": 0/1, "soft": 0.0-1.0, ...}]
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
def reflect(self, results, skill_content, out_dir, **kw):
|
|
21
|
+
# Analyze trajectories, return list of patch dicts
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def get_task_types(self):
|
|
25
|
+
return ["task_a", "task_b"]
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from abc import ABC, abstractmethod
|
|
30
|
+
import os
|
|
31
|
+
import random
|
|
32
|
+
|
|
33
|
+
from skillopt.datasets.base import BaseDataLoader, BatchSpec
|
|
34
|
+
from skillopt.prompts import load_prompt
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EnvAdapter(ABC):
|
|
38
|
+
"""Abstract adapter for connecting ReflACT to any environment.
|
|
39
|
+
|
|
40
|
+
Subclasses must implement all abstract methods. The ReflACT trainer
|
|
41
|
+
calls these methods at the appropriate pipeline stages.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# ── Lifecycle hooks ────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
def setup(self, cfg: dict) -> None:
|
|
47
|
+
"""Called once by the trainer before the training loop begins.
|
|
48
|
+
|
|
49
|
+
Override to perform one-time initialization that requires the full
|
|
50
|
+
config (e.g., data loading, split creation). Default is a no-op.
|
|
51
|
+
"""
|
|
52
|
+
self._cfg = dict(cfg)
|
|
53
|
+
|
|
54
|
+
def get_dataloader(self) -> BaseDataLoader | None:
|
|
55
|
+
"""Return the task dataloader used by this adapter, if any."""
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def requires_ray(self) -> bool:
|
|
59
|
+
"""Return whether this adapter requires Ray runtime initialization."""
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def build_reference_text(self, item: dict) -> str:
|
|
63
|
+
"""Return hidden reference material for reflection, if any."""
|
|
64
|
+
return str(item.get("reference_text") or "").strip()
|
|
65
|
+
|
|
66
|
+
def get_reference_metadata(self, item: dict) -> dict:
|
|
67
|
+
"""Return structured metadata about hidden reference material."""
|
|
68
|
+
reference_text = self.build_reference_text(item)
|
|
69
|
+
if not reference_text:
|
|
70
|
+
return {"fields": [], "preview": ""}
|
|
71
|
+
return {
|
|
72
|
+
"fields": ["reference_text"],
|
|
73
|
+
"preview": reference_text[:400],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def attach_reference_context(
|
|
77
|
+
self,
|
|
78
|
+
results: list[dict],
|
|
79
|
+
items: list[dict] | None,
|
|
80
|
+
) -> list[dict]:
|
|
81
|
+
"""Attach environment-specific hidden reference text to result dicts."""
|
|
82
|
+
if not results or not items:
|
|
83
|
+
return list(results)
|
|
84
|
+
|
|
85
|
+
item_by_id = {
|
|
86
|
+
str(item.get("id")): item
|
|
87
|
+
for item in items
|
|
88
|
+
if isinstance(item, dict) and item.get("id") is not None
|
|
89
|
+
}
|
|
90
|
+
enriched: list[dict] = []
|
|
91
|
+
for row in results:
|
|
92
|
+
merged = dict(row)
|
|
93
|
+
item = item_by_id.get(str(row.get("id")))
|
|
94
|
+
if item:
|
|
95
|
+
reference_text = self.build_reference_text(item)
|
|
96
|
+
if reference_text:
|
|
97
|
+
merged["reference_text"] = reference_text
|
|
98
|
+
enriched.append(merged)
|
|
99
|
+
return enriched
|
|
100
|
+
|
|
101
|
+
def select_representative_items(
|
|
102
|
+
self,
|
|
103
|
+
results: list[dict],
|
|
104
|
+
items: list[dict] | None,
|
|
105
|
+
*,
|
|
106
|
+
n_failures: int,
|
|
107
|
+
n_successes: int,
|
|
108
|
+
seed: int | None = None,
|
|
109
|
+
) -> list[dict]:
|
|
110
|
+
"""Select a small diverse subset of current-batch items by outcome."""
|
|
111
|
+
if not items:
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
item_by_id = {
|
|
115
|
+
str(item.get("id")): item
|
|
116
|
+
for item in items
|
|
117
|
+
if isinstance(item, dict) and item.get("id") is not None
|
|
118
|
+
}
|
|
119
|
+
failures = [
|
|
120
|
+
(result, item_by_id[str(result.get("id"))])
|
|
121
|
+
for result in results
|
|
122
|
+
if not result.get("hard") and str(result.get("id")) in item_by_id
|
|
123
|
+
]
|
|
124
|
+
successes = [
|
|
125
|
+
(result, item_by_id[str(result.get("id"))])
|
|
126
|
+
for result in results
|
|
127
|
+
if result.get("hard") and str(result.get("id")) in item_by_id
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
rng = random.Random(seed)
|
|
131
|
+
|
|
132
|
+
def _pick(pool: list[tuple[dict, dict]], quota: int) -> list[dict]:
|
|
133
|
+
if quota <= 0 or not pool:
|
|
134
|
+
return []
|
|
135
|
+
shuffled = list(pool)
|
|
136
|
+
rng.shuffle(shuffled)
|
|
137
|
+
|
|
138
|
+
picked_ids: set[str] = set()
|
|
139
|
+
picked: list[dict] = []
|
|
140
|
+
seen_types: set[str] = set()
|
|
141
|
+
|
|
142
|
+
for result, item in shuffled:
|
|
143
|
+
task_type = str(result.get("task_type") or item.get("task_type") or item.get("subtype") or "unknown")
|
|
144
|
+
item_id = str(item["id"])
|
|
145
|
+
if task_type in seen_types or item_id in picked_ids:
|
|
146
|
+
continue
|
|
147
|
+
picked.append(item)
|
|
148
|
+
picked_ids.add(item_id)
|
|
149
|
+
seen_types.add(task_type)
|
|
150
|
+
if len(picked) >= quota:
|
|
151
|
+
return picked
|
|
152
|
+
|
|
153
|
+
for _, item in shuffled:
|
|
154
|
+
item_id = str(item["id"])
|
|
155
|
+
if item_id in picked_ids:
|
|
156
|
+
continue
|
|
157
|
+
picked.append(item)
|
|
158
|
+
picked_ids.add(item_id)
|
|
159
|
+
if len(picked) >= quota:
|
|
160
|
+
break
|
|
161
|
+
return picked
|
|
162
|
+
|
|
163
|
+
selected = _pick(failures, n_failures)
|
|
164
|
+
selected_ids = {str(item["id"]) for item in selected}
|
|
165
|
+
selected.extend(
|
|
166
|
+
item for item in _pick(successes, n_successes)
|
|
167
|
+
if str(item["id"]) not in selected_ids
|
|
168
|
+
)
|
|
169
|
+
return selected
|
|
170
|
+
|
|
171
|
+
def build_env_from_batch(self, batch: BatchSpec, **kwargs):
|
|
172
|
+
"""Build an environment manager or item list from a :class:`BatchSpec`.
|
|
173
|
+
|
|
174
|
+
Default behavior preserves the legacy adapter API by routing training
|
|
175
|
+
batches through :meth:`build_train_env` and evaluation batches through
|
|
176
|
+
:meth:`build_eval_env`.
|
|
177
|
+
"""
|
|
178
|
+
if batch.phase == "train":
|
|
179
|
+
return self.build_train_env(batch_size=batch.batch_size, seed=batch.seed, **kwargs)
|
|
180
|
+
return self.build_eval_env(
|
|
181
|
+
env_num=batch.batch_size,
|
|
182
|
+
split=batch.split,
|
|
183
|
+
seed=batch.seed,
|
|
184
|
+
**kwargs,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
def build_train_env(self, batch_size: int, seed: int, **kwargs):
|
|
189
|
+
"""Build a training environment manager.
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
object
|
|
194
|
+
An environment manager that can be passed to :meth:`rollout`.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def build_eval_env(self, env_num: int, split: str, seed: int, **kwargs):
|
|
199
|
+
"""Build an evaluation environment manager.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
env_num : int
|
|
204
|
+
Number of evaluation environments.
|
|
205
|
+
split : str
|
|
206
|
+
Dataset split (e.g. ``"valid_seen"``, ``"valid_unseen"``).
|
|
207
|
+
seed : int
|
|
208
|
+
Random seed for reproducibility.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
object
|
|
213
|
+
An environment manager that can be passed to :meth:`rollout`.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def rollout(
|
|
218
|
+
self,
|
|
219
|
+
env_manager,
|
|
220
|
+
skill_content: str,
|
|
221
|
+
out_dir: str,
|
|
222
|
+
**kwargs,
|
|
223
|
+
) -> list[dict]:
|
|
224
|
+
"""Run a batch of episodes using the current skill.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
list[dict]
|
|
229
|
+
Each dict conforms to :class:`~skillopt.types.RolloutResult`:
|
|
230
|
+
must have ``"id"`` (str), ``"hard"`` (0/1), ``"soft"``
|
|
231
|
+
(float 0-1). May include env-specific fields.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def reflect(
|
|
235
|
+
self,
|
|
236
|
+
results: list[dict],
|
|
237
|
+
skill_content: str,
|
|
238
|
+
out_dir: str,
|
|
239
|
+
**kwargs,
|
|
240
|
+
) -> list[dict | None]:
|
|
241
|
+
"""Analyze rollout results and produce patches.
|
|
242
|
+
|
|
243
|
+
Default implementation: delegate to the shared minibatch reflect
|
|
244
|
+
stage. Every built-in benchmark uses this unchanged — override only
|
|
245
|
+
if your environment needs custom reflection logic.
|
|
246
|
+
|
|
247
|
+
Each returned dict conforms to :class:`~skillopt.types.RawPatch`:
|
|
248
|
+
``"patch"`` (with ``"edits"`` list) + ``"source_type"``
|
|
249
|
+
(``"failure"`` or ``"success"``); ``None`` entries are filtered out.
|
|
250
|
+
"""
|
|
251
|
+
from skillopt.gradient.reflect import run_minibatch_reflect
|
|
252
|
+
|
|
253
|
+
return run_minibatch_reflect(
|
|
254
|
+
results=results,
|
|
255
|
+
skill_content=skill_content,
|
|
256
|
+
prediction_dir=kwargs.get(
|
|
257
|
+
"prediction_dir", os.path.join(out_dir, "predictions")
|
|
258
|
+
),
|
|
259
|
+
patches_dir=kwargs.get(
|
|
260
|
+
"patches_dir", os.path.join(out_dir, "patches")
|
|
261
|
+
),
|
|
262
|
+
workers=self.analyst_workers,
|
|
263
|
+
failure_only=self.failure_only,
|
|
264
|
+
minibatch_size=self.minibatch_size,
|
|
265
|
+
edit_budget=self.edit_budget,
|
|
266
|
+
random_seed=kwargs.get("random_seed"),
|
|
267
|
+
error_system=self.get_error_minibatch_prompt(),
|
|
268
|
+
success_system=self.get_success_minibatch_prompt(),
|
|
269
|
+
step_buffer_context=kwargs.get("step_buffer_context", ""),
|
|
270
|
+
meta_skill_context=kwargs.get("meta_skill_context", ""),
|
|
271
|
+
update_mode=getattr(self, "_cfg", {}).get("skill_update_mode", "patch"),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
@abstractmethod
|
|
275
|
+
def get_task_types(self) -> list[str]:
|
|
276
|
+
"""Return the list of task type names for this environment."""
|
|
277
|
+
|
|
278
|
+
# ── Prompt configuration (two-level priority) ────────────────────────
|
|
279
|
+
#
|
|
280
|
+
# Priority: env-specific prompt file > generic default prompt file.
|
|
281
|
+
#
|
|
282
|
+
# Prompts are loaded from ``.md`` files via ``load_prompt(name, env)``:
|
|
283
|
+
# 1. ``skillopt/envs/<env>/prompts/<name>.md`` (env-specific)
|
|
284
|
+
# 2. ``skillopt/prompts/<name>.md`` (generic fallback)
|
|
285
|
+
#
|
|
286
|
+
# Subclasses can still override ``get_*_prompt()`` for full control.
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def _env_name(self) -> str:
|
|
290
|
+
"""Derive the env directory name from this adapter's module path."""
|
|
291
|
+
# e.g. "skillopt.envs.searchqa.adapter" → "searchqa"
|
|
292
|
+
module = type(self).__module__
|
|
293
|
+
parts = module.split(".")
|
|
294
|
+
if len(parts) >= 3 and parts[-3] == "envs":
|
|
295
|
+
return parts[-2]
|
|
296
|
+
return ""
|
|
297
|
+
|
|
298
|
+
def _load_env_prompt(self, name: str) -> str | None:
|
|
299
|
+
"""Load a prompt with env-specific override. Returns None if not found."""
|
|
300
|
+
try:
|
|
301
|
+
return load_prompt(name, env=self._env_name)
|
|
302
|
+
except FileNotFoundError:
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def get_error_minibatch_prompt(self) -> str | None:
|
|
306
|
+
update_mode = getattr(self, "_cfg", {}).get("skill_update_mode", "patch")
|
|
307
|
+
raw_mode = str(update_mode).strip().lower()
|
|
308
|
+
if raw_mode in {"full_rewrite", "full_rewrite_minibatch", "minibatch_full_rewrite", "skill_rewrite_minibatch"}:
|
|
309
|
+
prompt = self._load_env_prompt("analyst_error_full_rewrite")
|
|
310
|
+
if prompt is not None:
|
|
311
|
+
return prompt
|
|
312
|
+
if raw_mode in {"rewrite", "rewrite_from_suggestions", "suggestions", "rewrite_suggestions"}:
|
|
313
|
+
prompt = self._load_env_prompt("analyst_error_rewrite")
|
|
314
|
+
if prompt is not None:
|
|
315
|
+
return prompt
|
|
316
|
+
return self._load_env_prompt("analyst_error")
|
|
317
|
+
|
|
318
|
+
def get_success_minibatch_prompt(self) -> str | None:
|
|
319
|
+
update_mode = getattr(self, "_cfg", {}).get("skill_update_mode", "patch")
|
|
320
|
+
raw_mode = str(update_mode).strip().lower()
|
|
321
|
+
if raw_mode in {"full_rewrite", "full_rewrite_minibatch", "minibatch_full_rewrite", "skill_rewrite_minibatch"}:
|
|
322
|
+
prompt = self._load_env_prompt("analyst_success_full_rewrite")
|
|
323
|
+
if prompt is not None:
|
|
324
|
+
return prompt
|
|
325
|
+
if raw_mode in {"rewrite", "rewrite_from_suggestions", "suggestions", "rewrite_suggestions"}:
|
|
326
|
+
prompt = self._load_env_prompt("analyst_success_rewrite")
|
|
327
|
+
if prompt is not None:
|
|
328
|
+
return prompt
|
|
329
|
+
return self._load_env_prompt("analyst_success")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""DocVQA environment package for ReflACT."""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from skillopt.datasets.base import BatchSpec
|
|
4
|
+
from skillopt.envs.base import EnvAdapter
|
|
5
|
+
from skillopt.envs.docvqa.dataloader import DocVQADataLoader
|
|
6
|
+
from skillopt.envs.docvqa.rollout import run_batch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DocVQAAdapter(EnvAdapter):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
split_dir: str = "",
|
|
13
|
+
data_path: str = "",
|
|
14
|
+
split_mode: str = "split_dir",
|
|
15
|
+
split_ratio: str = "2:1:7",
|
|
16
|
+
split_seed: int = 42,
|
|
17
|
+
split_output_dir: str = "",
|
|
18
|
+
max_turns: int = 1,
|
|
19
|
+
exec_timeout: int = 120,
|
|
20
|
+
workers: int = 16,
|
|
21
|
+
analyst_workers: int = 16,
|
|
22
|
+
failure_only: bool = False,
|
|
23
|
+
minibatch_size: int = 8,
|
|
24
|
+
edit_budget: int = 4,
|
|
25
|
+
seed: int = 42,
|
|
26
|
+
limit: int = 0,
|
|
27
|
+
image_detail: str = "auto",
|
|
28
|
+
max_completion_tokens: int = 16384,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.max_turns = max_turns
|
|
31
|
+
self.exec_timeout = exec_timeout
|
|
32
|
+
self.workers = workers
|
|
33
|
+
self.max_completion_tokens = int(max_completion_tokens)
|
|
34
|
+
self.analyst_workers = analyst_workers
|
|
35
|
+
self.failure_only = failure_only
|
|
36
|
+
self.minibatch_size = minibatch_size
|
|
37
|
+
self.edit_budget = edit_budget
|
|
38
|
+
self.image_detail = image_detail
|
|
39
|
+
self.dataloader = DocVQADataLoader(
|
|
40
|
+
split_dir=split_dir,
|
|
41
|
+
data_path=data_path,
|
|
42
|
+
split_mode=split_mode,
|
|
43
|
+
split_ratio=split_ratio,
|
|
44
|
+
split_seed=split_seed,
|
|
45
|
+
split_output_dir=split_output_dir,
|
|
46
|
+
seed=seed,
|
|
47
|
+
limit=limit,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def setup(self, cfg: dict) -> None:
|
|
51
|
+
super().setup(cfg)
|
|
52
|
+
self.dataloader.setup(cfg)
|
|
53
|
+
|
|
54
|
+
def get_dataloader(self):
|
|
55
|
+
return self.dataloader
|
|
56
|
+
|
|
57
|
+
def build_env_from_batch(self, batch: BatchSpec, **kwargs):
|
|
58
|
+
return list(batch.payload or [])
|
|
59
|
+
|
|
60
|
+
def build_train_env(self, batch_size: int, seed: int, **kwargs):
|
|
61
|
+
batch = self.dataloader.build_train_batch(batch_size=batch_size, seed=seed, **kwargs)
|
|
62
|
+
return self.build_env_from_batch(batch, **kwargs)
|
|
63
|
+
|
|
64
|
+
def build_eval_env(self, env_num: int, split: str, seed: int, **kwargs):
|
|
65
|
+
batch = self.dataloader.build_eval_batch(env_num=env_num, split=split, seed=seed, **kwargs)
|
|
66
|
+
return self.build_env_from_batch(batch, **kwargs)
|
|
67
|
+
|
|
68
|
+
def rollout(self, env_manager, skill_content: str, out_dir: str, **kwargs) -> list[dict]:
|
|
69
|
+
items: list[dict] = env_manager
|
|
70
|
+
return run_batch(
|
|
71
|
+
items=items,
|
|
72
|
+
out_root=out_dir,
|
|
73
|
+
skill_content=skill_content,
|
|
74
|
+
max_turns=self.max_turns,
|
|
75
|
+
exec_timeout=self.exec_timeout,
|
|
76
|
+
workers=self.workers,
|
|
77
|
+
image_detail=self.image_detail,
|
|
78
|
+
max_completion_tokens=self.max_completion_tokens,
|
|
79
|
+
diagnostic_mode=kwargs.get("diagnostic_mode", False),
|
|
80
|
+
diagnostic_instruction=kwargs.get("diagnostic_instruction", ""),
|
|
81
|
+
task_timeout=self.exec_timeout,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def get_task_types(self) -> list[str]:
|
|
85
|
+
seen: list[str] = []
|
|
86
|
+
for item in self.dataloader.train_items + self.dataloader.val_items + self.dataloader.test_items:
|
|
87
|
+
task_type = str(item.get("task_type") or "docvqa")
|
|
88
|
+
if task_type not in seen:
|
|
89
|
+
seen.append(task_type)
|
|
90
|
+
return seen or ["docvqa"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import csv
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from skillopt.datasets.base import SplitDataLoader
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _parse_answers(raw: str) -> list[str]:
|
|
11
|
+
text = str(raw or "").strip()
|
|
12
|
+
if not text:
|
|
13
|
+
return []
|
|
14
|
+
try:
|
|
15
|
+
parsed = ast.literal_eval(text)
|
|
16
|
+
except Exception:
|
|
17
|
+
return [text]
|
|
18
|
+
if isinstance(parsed, list):
|
|
19
|
+
return [str(item).strip() for item in parsed if str(item).strip()]
|
|
20
|
+
return [str(parsed).strip()]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_document_path(question: str) -> tuple[str, str]:
|
|
24
|
+
marker = "document_path:"
|
|
25
|
+
if marker not in question:
|
|
26
|
+
return question.strip(), ""
|
|
27
|
+
main, tail = question.split(marker, 1)
|
|
28
|
+
return main.strip(), tail.strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _normalize_row(row: dict[str, str]) -> dict:
|
|
32
|
+
question_text, document_path = _extract_document_path(str(row.get("question") or ""))
|
|
33
|
+
answers = _parse_answers(row.get("answer") or row.get("ground_truth") or "")
|
|
34
|
+
image_path = str(row.get("image_path") or document_path or "").strip()
|
|
35
|
+
task_type = str(row.get("topic") or row.get("category") or "docvqa").strip() or "docvqa"
|
|
36
|
+
return {
|
|
37
|
+
"id": str(row.get("questionId") or row.get("id") or "").strip(),
|
|
38
|
+
"question": question_text,
|
|
39
|
+
"answer": answers[0] if answers else "",
|
|
40
|
+
"answers": answers,
|
|
41
|
+
"task_type": task_type,
|
|
42
|
+
"subtask": task_type,
|
|
43
|
+
"image_paths": [image_path] if image_path else [],
|
|
44
|
+
"image_path": image_path,
|
|
45
|
+
"questionId": str(row.get("questionId") or "").strip(),
|
|
46
|
+
"docId": str(row.get("docId") or "").strip(),
|
|
47
|
+
"ucsf_document_id": str(row.get("ucsf_document_id") or "").strip(),
|
|
48
|
+
"ucsf_document_page_no": str(row.get("ucsf_document_page_no") or "").strip(),
|
|
49
|
+
"source_split": str(row.get("source_split") or "").strip(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DocVQADataLoader(SplitDataLoader):
|
|
54
|
+
def load_split_items(self, split_path: str) -> list[dict]:
|
|
55
|
+
path = Path(split_path)
|
|
56
|
+
csv_files = sorted(path.glob("*.csv"))
|
|
57
|
+
if not csv_files:
|
|
58
|
+
raise FileNotFoundError(f"No .csv file found in {split_path}")
|
|
59
|
+
with csv_files[0].open(encoding="utf-8", newline="") as f:
|
|
60
|
+
reader = csv.DictReader(f)
|
|
61
|
+
return [_normalize_row(row) for row in reader]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
DEFAULT_ANLS_THRESHOLD = 0.5
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _normalize_text(value: Any) -> str:
|
|
12
|
+
if value is None:
|
|
13
|
+
return ""
|
|
14
|
+
text = str(value).strip().lower()
|
|
15
|
+
return " ".join(text.split())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _levenshtein_distance(a: str, b: str) -> int:
|
|
19
|
+
if a == b:
|
|
20
|
+
return 0
|
|
21
|
+
if not a:
|
|
22
|
+
return len(b)
|
|
23
|
+
if not b:
|
|
24
|
+
return len(a)
|
|
25
|
+
if len(a) > len(b):
|
|
26
|
+
a, b = b, a
|
|
27
|
+
previous = list(range(len(b) + 1))
|
|
28
|
+
for i, char_a in enumerate(a, start=1):
|
|
29
|
+
current = [i]
|
|
30
|
+
for j, char_b in enumerate(b, start=1):
|
|
31
|
+
insert_cost = current[j - 1] + 1
|
|
32
|
+
delete_cost = previous[j] + 1
|
|
33
|
+
replace_cost = previous[j - 1] + (char_a != char_b)
|
|
34
|
+
current.append(min(insert_cost, delete_cost, replace_cost))
|
|
35
|
+
previous = current
|
|
36
|
+
return previous[-1]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _score_single_answer(predicted: Any, target: Any, threshold: float) -> float:
|
|
40
|
+
predicted_norm = _normalize_text(predicted)
|
|
41
|
+
target_norm = _normalize_text(target)
|
|
42
|
+
if not predicted_norm and not target_norm:
|
|
43
|
+
return 1.0
|
|
44
|
+
if not predicted_norm or not target_norm:
|
|
45
|
+
return 0.0
|
|
46
|
+
distance = _levenshtein_distance(predicted_norm, target_norm)
|
|
47
|
+
normalized_distance = distance / max(len(predicted_norm), len(target_norm))
|
|
48
|
+
if normalized_distance >= threshold:
|
|
49
|
+
return 0.0
|
|
50
|
+
return 1.0 - normalized_distance
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _extract_answer_strings(raw: Any) -> list[str]:
|
|
54
|
+
if raw is None:
|
|
55
|
+
return [""]
|
|
56
|
+
if isinstance(raw, str):
|
|
57
|
+
text = raw.strip()
|
|
58
|
+
if not text:
|
|
59
|
+
return [""]
|
|
60
|
+
parsed = None
|
|
61
|
+
if text[0] in "[{":
|
|
62
|
+
try:
|
|
63
|
+
parsed = json.loads(text)
|
|
64
|
+
except json.JSONDecodeError:
|
|
65
|
+
try:
|
|
66
|
+
parsed = ast.literal_eval(text)
|
|
67
|
+
except (ValueError, SyntaxError):
|
|
68
|
+
parsed = None
|
|
69
|
+
if parsed is None:
|
|
70
|
+
return [text]
|
|
71
|
+
return _extract_answer_strings(parsed)
|
|
72
|
+
if isinstance(raw, dict):
|
|
73
|
+
for key in ("answers", "ground_truth", "answer"):
|
|
74
|
+
if key in raw:
|
|
75
|
+
return _extract_answer_strings(raw[key])
|
|
76
|
+
return [str(raw)]
|
|
77
|
+
if isinstance(raw, Iterable) and not isinstance(raw, (bytes, bytearray)):
|
|
78
|
+
answers: list[str] = []
|
|
79
|
+
for item in raw:
|
|
80
|
+
if isinstance(item, dict):
|
|
81
|
+
for key in ("text", "answer", "value"):
|
|
82
|
+
if key in item:
|
|
83
|
+
answers.extend(_extract_answer_strings(item[key]))
|
|
84
|
+
break
|
|
85
|
+
else:
|
|
86
|
+
answers.append(str(item))
|
|
87
|
+
continue
|
|
88
|
+
answers.append(str(item))
|
|
89
|
+
return answers or [""]
|
|
90
|
+
return [str(raw)]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def extract_answer(text: str) -> str:
|
|
94
|
+
lower = text.lower()
|
|
95
|
+
start = lower.rfind("<answer>")
|
|
96
|
+
end = lower.rfind("</answer>")
|
|
97
|
+
if start != -1 and end != -1 and end > start:
|
|
98
|
+
return text[start + len("<answer>"):end].strip()
|
|
99
|
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
100
|
+
return lines[-1] if lines else text.strip()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def evaluate(prediction_text: str, gold_answers: Any) -> dict:
|
|
104
|
+
answer = extract_answer(prediction_text)
|
|
105
|
+
answers = _extract_answer_strings(gold_answers)
|
|
106
|
+
score = 0.0
|
|
107
|
+
for target in answers:
|
|
108
|
+
score = max(score, _score_single_answer(answer, target, DEFAULT_ANLS_THRESHOLD))
|
|
109
|
+
return {
|
|
110
|
+
"anls": score,
|
|
111
|
+
"predicted_answer": answer,
|
|
112
|
+
"gold_answers": answers,
|
|
113
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
You are an expert failure-analysis agent for visual document question answering tasks.
|
|
2
|
+
|
|
3
|
+
You will be given MULTIPLE failed DocVQA trajectories from a single minibatch and the current skill document. Each trajectory includes the model response and an evaluation result scored with ANLS against one or more acceptable answers.
|
|
4
|
+
|
|
5
|
+
Your job is to identify the most important COMMON failure patterns across the batch and propose concise skill edits.
|
|
6
|
+
|
|
7
|
+
## Failure Type Categories
|
|
8
|
+
- evidence_miss: the model overlooked the relevant visible region or line
|
|
9
|
+
- near_match_confusion: the model selected a nearby but incorrect text span
|
|
10
|
+
- normalization_error: the answer differed mainly in formatting, spacing, punctuation, or minor text normalization
|
|
11
|
+
- reading_error: the model misread the document content
|
|
12
|
+
- other: none of the above
|
|
13
|
+
|
|
14
|
+
## Rules
|
|
15
|
+
- Focus on common, reusable reading and extraction behaviors.
|
|
16
|
+
- Do not hardcode image-specific answers.
|
|
17
|
+
- Prefer concise edits that improve evidence selection and exact span extraction.
|
|
18
|
+
|
|
19
|
+
Respond ONLY with a valid JSON object (no markdown fences, no extra text):
|
|
20
|
+
{
|
|
21
|
+
"batch_size": <number of trajectories analysed>,
|
|
22
|
+
"failure_summary": [
|
|
23
|
+
{"failure_type": "<type>", "count": <int>, "description": "<one-line>"}
|
|
24
|
+
],
|
|
25
|
+
"patch": {
|
|
26
|
+
"reasoning": "<why these edits address the batch's common failures>",
|
|
27
|
+
"edits": [
|
|
28
|
+
{"op": "append", "content": "<markdown to add at end of skill>"},
|
|
29
|
+
{"op": "insert_after", "target": "<exact heading/text to insert after>", "content": "<markdown>"},
|
|
30
|
+
{"op": "replace", "target": "<exact text to replace>", "content": "<replacement>"},
|
|
31
|
+
{"op": "delete", "target": "<exact text to remove>"}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
Only include edits that are needed. "edits" can be an empty list if no patch is warranted.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
You are an expert success-pattern analyst for visual document question answering tasks.
|
|
2
|
+
|
|
3
|
+
You will be given MULTIPLE successful DocVQA trajectories from a single minibatch and the current skill document. Your job is to identify common visual reading and exact-answer extraction behaviors worth encoding in the skill.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
- Focus on patterns shared across multiple successful trajectories.
|
|
7
|
+
- Reinforce reusable behaviors like locating the right region, copying exact spans, and preferring the shortest exact answer over paraphrase.
|
|
8
|
+
- Only propose patches for patterns not already captured by the current skill.
|
|
9
|
+
|
|
10
|
+
Respond ONLY with a valid JSON object:
|
|
11
|
+
{
|
|
12
|
+
"batch_size": <number of trajectories analysed>,
|
|
13
|
+
"success_patterns": ["<pattern 1>", "<pattern 2>"],
|
|
14
|
+
"patch": {
|
|
15
|
+
"reasoning": "<why these patterns are worth encoding>",
|
|
16
|
+
"edits": [
|
|
17
|
+
{"op": "append", "content": "<markdown>"},
|
|
18
|
+
{"op": "insert_after", "target": "<heading/text>", "content": "<markdown>"},
|
|
19
|
+
{"op": "replace", "target": "<old text>", "content": "<new text>"},
|
|
20
|
+
{"op": "delete", "target": "<exact text to remove>"}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"edits" may be empty if the skill already covers all observed patterns.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are an expert visual document question answering agent.
|
|
2
|
+
|
|
3
|
+
{skill_section}You will receive a document image and a question about the document.
|
|
4
|
+
Read the visual evidence carefully and answer concisely.
|
|
5
|
+
|
|
6
|
+
Rules:
|
|
7
|
+
- Ground the answer in the visible document content.
|
|
8
|
+
- Prefer exact spans, numbers, dates, and names from the document.
|
|
9
|
+
- Do not invent content that is not visible.
|
|
10
|
+
- If multiple near-matches exist, choose the one best supported by the document.
|
|
11
|
+
|
|
12
|
+
Return the final answer inside <answer>...</answer>.
|