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,306 @@
|
|
|
1
|
+
"""Standardized I/O types for the ReflACT pipeline.
|
|
2
|
+
|
|
3
|
+
Shared dataclass definitions for the 6-stage per-step pipeline
|
|
4
|
+
and the 2 epoch-level stages. All types support round-trip
|
|
5
|
+
conversion to/from plain dicts for incremental adoption.
|
|
6
|
+
|
|
7
|
+
Re-exports
|
|
8
|
+
----------
|
|
9
|
+
GateResult, GateAction — from skillopt.evaluation.gate
|
|
10
|
+
BatchSpec — from skillopt.datasets.base
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field, fields as dc_fields
|
|
15
|
+
from typing import Any, Literal
|
|
16
|
+
|
|
17
|
+
from skillopt.evaluation.gate import GateAction, GateResult # noqa: F401
|
|
18
|
+
from skillopt.datasets.base import BatchSpec # noqa: F401
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── Atomic types ─────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
EditOp = Literal["append", "insert_after", "replace", "delete"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Edit:
|
|
28
|
+
"""A single edit operation on a skill document.
|
|
29
|
+
|
|
30
|
+
Used across Reflect → Aggregate → Select → Update → MetaReflect.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
op: EditOp
|
|
34
|
+
content: str = ""
|
|
35
|
+
target: str = ""
|
|
36
|
+
support_count: int | None = None
|
|
37
|
+
source_type: Literal["failure", "success"] | None = None
|
|
38
|
+
merge_level: int | None = None
|
|
39
|
+
update_origin: str = ""
|
|
40
|
+
update_target: str = ""
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_dict(cls, d: dict) -> Edit:
|
|
44
|
+
return cls(
|
|
45
|
+
op=d.get("op", "append"),
|
|
46
|
+
content=d.get("content", ""),
|
|
47
|
+
target=d.get("target", ""),
|
|
48
|
+
support_count=d.get("support_count"),
|
|
49
|
+
source_type=d.get("source_type"),
|
|
50
|
+
merge_level=d.get("merge_level"),
|
|
51
|
+
update_origin=d.get("update_origin", ""),
|
|
52
|
+
update_target=d.get("update_target", ""),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def to_dict(self) -> dict:
|
|
56
|
+
d: dict[str, Any] = {"op": self.op, "content": self.content}
|
|
57
|
+
if self.target:
|
|
58
|
+
d["target"] = self.target
|
|
59
|
+
if self.support_count is not None:
|
|
60
|
+
d["support_count"] = self.support_count
|
|
61
|
+
if self.source_type is not None:
|
|
62
|
+
d["source_type"] = self.source_type
|
|
63
|
+
if self.merge_level is not None:
|
|
64
|
+
d["merge_level"] = self.merge_level
|
|
65
|
+
if self.update_origin:
|
|
66
|
+
d["update_origin"] = self.update_origin
|
|
67
|
+
if self.update_target:
|
|
68
|
+
d["update_target"] = self.update_target
|
|
69
|
+
return d
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Patch:
|
|
74
|
+
"""A set of edits with reasoning.
|
|
75
|
+
|
|
76
|
+
Output of Aggregate (③), Select (④); input to Update (⑤).
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
edits: list[Edit] = field(default_factory=list)
|
|
80
|
+
reasoning: str = ""
|
|
81
|
+
ranking_details: dict[str, Any] | None = None
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_dict(cls, d: dict) -> Patch:
|
|
85
|
+
edits_raw = d.get("edits", [])
|
|
86
|
+
return cls(
|
|
87
|
+
edits=[Edit.from_dict(e) if isinstance(e, dict) else e for e in edits_raw],
|
|
88
|
+
reasoning=d.get("reasoning", ""),
|
|
89
|
+
ranking_details=d.get("ranking_details"),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def to_dict(self) -> dict:
|
|
93
|
+
d: dict[str, Any] = {
|
|
94
|
+
"reasoning": self.reasoning,
|
|
95
|
+
"edits": [e.to_dict() if isinstance(e, Edit) else e for e in self.edits],
|
|
96
|
+
}
|
|
97
|
+
if self.ranking_details is not None:
|
|
98
|
+
d["ranking_details"] = self.ranking_details
|
|
99
|
+
return d
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── Stage ① ROLLOUT ──────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class RolloutResult:
|
|
106
|
+
"""Result of a single episode/task rollout.
|
|
107
|
+
|
|
108
|
+
Universal fields are required; env-specific fields live in ``extras``.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
id: str
|
|
112
|
+
hard: int
|
|
113
|
+
soft: float
|
|
114
|
+
n_turns: int = 0
|
|
115
|
+
fail_reason: str = ""
|
|
116
|
+
task_type: str = ""
|
|
117
|
+
task_description: str = ""
|
|
118
|
+
predicted_answer: str = ""
|
|
119
|
+
question: str = ""
|
|
120
|
+
reference_text: str = ""
|
|
121
|
+
target_system_prompt: str = ""
|
|
122
|
+
target_user_prompt: str = ""
|
|
123
|
+
spreadsheet_preview: str = ""
|
|
124
|
+
extras: dict[str, Any] = field(default_factory=dict)
|
|
125
|
+
|
|
126
|
+
_KNOWN_FIELDS: frozenset[str] | None = field(
|
|
127
|
+
default=None, init=False, repr=False, compare=False, # type: ignore[assignment]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def _get_known_fields(cls) -> frozenset[str]:
|
|
132
|
+
if cls._KNOWN_FIELDS is None:
|
|
133
|
+
cls._KNOWN_FIELDS = frozenset(
|
|
134
|
+
f.name for f in dc_fields(cls)
|
|
135
|
+
if f.name != "_KNOWN_FIELDS"
|
|
136
|
+
)
|
|
137
|
+
return cls._KNOWN_FIELDS
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_dict(cls, d: dict) -> RolloutResult:
|
|
141
|
+
known = cls._get_known_fields()
|
|
142
|
+
extras = {k: v for k, v in d.items() if k not in known}
|
|
143
|
+
return cls(
|
|
144
|
+
id=str(d.get("id", "")),
|
|
145
|
+
hard=int(d.get("hard", 0)),
|
|
146
|
+
soft=float(d.get("soft", 0.0)),
|
|
147
|
+
n_turns=int(d.get("n_turns", 0)),
|
|
148
|
+
fail_reason=str(d.get("fail_reason", "")),
|
|
149
|
+
task_type=str(d.get("task_type", "")),
|
|
150
|
+
task_description=str(d.get("task_description", "")),
|
|
151
|
+
predicted_answer=str(d.get("predicted_answer", "")),
|
|
152
|
+
question=str(d.get("question", "")),
|
|
153
|
+
reference_text=str(d.get("reference_text", "")),
|
|
154
|
+
target_system_prompt=str(d.get("target_system_prompt", "")),
|
|
155
|
+
target_user_prompt=str(d.get("target_user_prompt", "")),
|
|
156
|
+
spreadsheet_preview=str(d.get("spreadsheet_preview", "")),
|
|
157
|
+
extras=extras,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def to_dict(self) -> dict:
|
|
161
|
+
d: dict[str, Any] = {
|
|
162
|
+
"id": self.id,
|
|
163
|
+
"hard": self.hard,
|
|
164
|
+
"soft": self.soft,
|
|
165
|
+
}
|
|
166
|
+
for attr in (
|
|
167
|
+
"n_turns", "fail_reason", "task_type", "task_description",
|
|
168
|
+
"predicted_answer", "question", "reference_text",
|
|
169
|
+
"target_system_prompt", "target_user_prompt",
|
|
170
|
+
"spreadsheet_preview",
|
|
171
|
+
):
|
|
172
|
+
val = getattr(self, attr)
|
|
173
|
+
if val:
|
|
174
|
+
d[attr] = val
|
|
175
|
+
d.update(self.extras)
|
|
176
|
+
return d
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ── Stage ② REFLECT ──────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class FailureSummaryEntry:
|
|
183
|
+
"""One entry in the failure summary produced by error analysts."""
|
|
184
|
+
|
|
185
|
+
failure_type: str
|
|
186
|
+
count: int = 0
|
|
187
|
+
description: str = ""
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def from_dict(cls, d: dict) -> FailureSummaryEntry:
|
|
191
|
+
return cls(
|
|
192
|
+
failure_type=d.get("failure_type", ""),
|
|
193
|
+
count=int(d.get("count", 0)),
|
|
194
|
+
description=d.get("description", ""),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def to_dict(self) -> dict:
|
|
198
|
+
return {
|
|
199
|
+
"failure_type": self.failure_type,
|
|
200
|
+
"count": self.count,
|
|
201
|
+
"description": self.description,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class RawPatch:
|
|
207
|
+
"""Analyst output from the Reflect stage — a patch with provenance.
|
|
208
|
+
|
|
209
|
+
Wraps the dict produced by ``run_error_analyst_minibatch``
|
|
210
|
+
and ``run_success_analyst_minibatch``.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
patch: Patch
|
|
214
|
+
source_type: Literal["failure", "success"] = "failure"
|
|
215
|
+
batch_size: int = 0
|
|
216
|
+
failure_summary: list[FailureSummaryEntry] = field(default_factory=list)
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def from_dict(cls, d: dict | None) -> RawPatch | None:
|
|
220
|
+
if d is None:
|
|
221
|
+
return None
|
|
222
|
+
inner = d.get("patch", d)
|
|
223
|
+
if not isinstance(inner, dict):
|
|
224
|
+
return None
|
|
225
|
+
patch = Patch.from_dict(inner)
|
|
226
|
+
return cls(
|
|
227
|
+
patch=patch,
|
|
228
|
+
source_type=d.get("source_type", "failure"),
|
|
229
|
+
batch_size=int(d.get("batch_size", 0)),
|
|
230
|
+
failure_summary=[
|
|
231
|
+
FailureSummaryEntry.from_dict(fs)
|
|
232
|
+
for fs in d.get("failure_summary", [])
|
|
233
|
+
],
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def to_dict(self) -> dict:
|
|
237
|
+
d: dict[str, Any] = {
|
|
238
|
+
"patch": self.patch.to_dict(),
|
|
239
|
+
"source_type": self.source_type,
|
|
240
|
+
"batch_size": self.batch_size,
|
|
241
|
+
}
|
|
242
|
+
if self.failure_summary:
|
|
243
|
+
d["failure_summary"] = [fs.to_dict() for fs in self.failure_summary]
|
|
244
|
+
return d
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ── Epoch-level: SLOW_UPDATE ─────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
@dataclass
|
|
250
|
+
class SlowUpdateResult:
|
|
251
|
+
"""Output of the epoch-level slow update stage (EMA / regularization)."""
|
|
252
|
+
|
|
253
|
+
reasoning: str = ""
|
|
254
|
+
slow_update_content: str = ""
|
|
255
|
+
action: str = ""
|
|
256
|
+
time_s: float | None = None
|
|
257
|
+
prev_hard: float | None = None
|
|
258
|
+
curr_hard: float | None = None
|
|
259
|
+
selection_hard: float | None = None
|
|
260
|
+
selection_soft: float | None = None
|
|
261
|
+
candidate_hash: str = ""
|
|
262
|
+
update_origin: str = ""
|
|
263
|
+
update_target: str = ""
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def from_dict(cls, d: dict | None) -> SlowUpdateResult | None:
|
|
267
|
+
if d is None:
|
|
268
|
+
return None
|
|
269
|
+
return cls(
|
|
270
|
+
reasoning=d.get("reasoning", ""),
|
|
271
|
+
slow_update_content=d.get("slow_update_content", ""),
|
|
272
|
+
action=d.get("action", ""),
|
|
273
|
+
time_s=d.get("time_s"),
|
|
274
|
+
prev_hard=d.get("prev_hard"),
|
|
275
|
+
curr_hard=d.get("curr_hard"),
|
|
276
|
+
selection_hard=d.get("selection_hard"),
|
|
277
|
+
selection_soft=d.get("selection_soft"),
|
|
278
|
+
candidate_hash=d.get("candidate_hash", ""),
|
|
279
|
+
update_origin=d.get("update_origin", ""),
|
|
280
|
+
update_target=d.get("update_target", ""),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def to_dict(self) -> dict:
|
|
284
|
+
d: dict[str, Any] = {
|
|
285
|
+
"reasoning": self.reasoning,
|
|
286
|
+
"slow_update_content": self.slow_update_content,
|
|
287
|
+
}
|
|
288
|
+
if self.action:
|
|
289
|
+
d["action"] = self.action
|
|
290
|
+
if self.time_s is not None:
|
|
291
|
+
d["time_s"] = self.time_s
|
|
292
|
+
if self.prev_hard is not None:
|
|
293
|
+
d["prev_hard"] = self.prev_hard
|
|
294
|
+
if self.curr_hard is not None:
|
|
295
|
+
d["curr_hard"] = self.curr_hard
|
|
296
|
+
if self.selection_hard is not None:
|
|
297
|
+
d["selection_hard"] = self.selection_hard
|
|
298
|
+
if self.selection_soft is not None:
|
|
299
|
+
d["selection_soft"] = self.selection_soft
|
|
300
|
+
if self.candidate_hash:
|
|
301
|
+
d["candidate_hash"] = self.candidate_hash
|
|
302
|
+
if self.update_origin:
|
|
303
|
+
d["update_origin"] = self.update_origin
|
|
304
|
+
if self.update_target:
|
|
305
|
+
d["update_target"] = self.update_target
|
|
306
|
+
return d
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""JSON extraction helpers for LLM responses."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _top_level_brace_objects(text: str) -> list[str]:
|
|
10
|
+
"""Return every balanced *top-level* ``{...}`` span in ``text``.
|
|
11
|
+
|
|
12
|
+
Fully string/escape aware: braces inside quoted strings are ignored both
|
|
13
|
+
when scanning for an object start AND while tracking depth inside one, so a
|
|
14
|
+
``{`` that appears in prose (e.g. ``'set it to {x}'``) is never mistaken for
|
|
15
|
+
the start of a JSON object. Used to detect ambiguity: when a response carries
|
|
16
|
+
more than one top-level object we must not let a repair pass silently pick
|
|
17
|
+
one — it may pick the wrong (discarded) edit, strictly worse than None.
|
|
18
|
+
"""
|
|
19
|
+
spans: list[str] = []
|
|
20
|
+
i, n = 0, len(text)
|
|
21
|
+
outer_in_str = False
|
|
22
|
+
outer_esc = False
|
|
23
|
+
while i < n:
|
|
24
|
+
ch = text[i]
|
|
25
|
+
# Skip over braces that live *inside* a quoted string before any object
|
|
26
|
+
# has started — otherwise a `{` in prose like '"set it to {x}"' is wrongly
|
|
27
|
+
# treated as an object start, and the repair pass below turns non-JSON
|
|
28
|
+
# prose into a bogus dict (strictly worse than returning None).
|
|
29
|
+
if outer_in_str:
|
|
30
|
+
if outer_esc:
|
|
31
|
+
outer_esc = False
|
|
32
|
+
elif ch == "\\":
|
|
33
|
+
outer_esc = True
|
|
34
|
+
elif ch == '"':
|
|
35
|
+
outer_in_str = False
|
|
36
|
+
i += 1
|
|
37
|
+
continue
|
|
38
|
+
if ch == '"':
|
|
39
|
+
outer_in_str = True
|
|
40
|
+
i += 1
|
|
41
|
+
continue
|
|
42
|
+
if ch != "{":
|
|
43
|
+
i += 1
|
|
44
|
+
continue
|
|
45
|
+
depth = 0
|
|
46
|
+
in_str = False
|
|
47
|
+
esc = False
|
|
48
|
+
start = i
|
|
49
|
+
while i < n:
|
|
50
|
+
ch = text[i]
|
|
51
|
+
if in_str:
|
|
52
|
+
if esc:
|
|
53
|
+
esc = False
|
|
54
|
+
elif ch == "\\":
|
|
55
|
+
esc = True
|
|
56
|
+
elif ch == '"':
|
|
57
|
+
in_str = False
|
|
58
|
+
elif ch == '"':
|
|
59
|
+
in_str = True
|
|
60
|
+
elif ch == "{":
|
|
61
|
+
depth += 1
|
|
62
|
+
elif ch == "}":
|
|
63
|
+
depth -= 1
|
|
64
|
+
if depth == 0:
|
|
65
|
+
spans.append(text[start:i + 1])
|
|
66
|
+
i += 1
|
|
67
|
+
break
|
|
68
|
+
i += 1
|
|
69
|
+
else:
|
|
70
|
+
break # unterminated final object
|
|
71
|
+
return spans
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _looks_json_like(span: str) -> bool:
|
|
75
|
+
"""Heuristic: does ``span`` look like an intended JSON object (vs. prose)?
|
|
76
|
+
|
|
77
|
+
A genuine JSON object's first non-space character after ``{`` is either ``"``
|
|
78
|
+
(a string key) or ``}`` (an empty object). Prose pseudo-objects that the
|
|
79
|
+
repair pass would otherwise fabricate into bogus dicts — ``{op: delete}``,
|
|
80
|
+
``{x: 1}`` quoted in single quotes or backticks, etc. — start with a bare
|
|
81
|
+
word and are rejected. This complements the string-aware scan, which only
|
|
82
|
+
skips *double*-quoted prose; single-quoted / backticked / unquoted prose
|
|
83
|
+
braces are caught here instead. Legitimate repair targets (trailing commas,
|
|
84
|
+
unescaped quotes inside string values) all begin with ``"`` and pass.
|
|
85
|
+
"""
|
|
86
|
+
inner = span.strip()
|
|
87
|
+
if not (inner.startswith("{") and inner.endswith("}")):
|
|
88
|
+
return False
|
|
89
|
+
after_brace = inner[1:].lstrip()
|
|
90
|
+
return after_brace[:1] in ('"', '}')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def extract_json(text: str) -> dict | None:
|
|
94
|
+
"""Extract a JSON object from LLM response text.
|
|
95
|
+
|
|
96
|
+
Tries ```json fences first, then bare {...} patterns.
|
|
97
|
+
"""
|
|
98
|
+
m = re.search(r"```json\s*(.*?)```", text, re.DOTALL)
|
|
99
|
+
if m:
|
|
100
|
+
try:
|
|
101
|
+
return json.loads(m.group(1))
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
pass
|
|
104
|
+
m = re.search(r"\{.*\}", text, re.DOTALL)
|
|
105
|
+
if m:
|
|
106
|
+
try:
|
|
107
|
+
return json.loads(m.group(0))
|
|
108
|
+
except json.JSONDecodeError:
|
|
109
|
+
pass
|
|
110
|
+
# Tolerant fallback for non-OpenAI backends (Claude/Qwen, …) whose free-form
|
|
111
|
+
# JSON strict json.loads rejects — unescaped ASCII quotes inside CJK string
|
|
112
|
+
# values, trailing commas, etc. Repair so the analyst's edits aren't silently
|
|
113
|
+
# dropped, but ONLY a single unambiguous object: never feed the greedy `{.*}`
|
|
114
|
+
# span or the raw text, or json_repair would quietly return one of several
|
|
115
|
+
# objects (empirically the wrong/last one) — strictly worse than None, which
|
|
116
|
+
# the caller can detect and retry/skip.
|
|
117
|
+
#
|
|
118
|
+
# Pick the candidate FIRST, before importing json_repair, so the optional
|
|
119
|
+
# dependency only matters (and only warns) when there is genuinely a single
|
|
120
|
+
# malformed object we could have repaired. Ordinary no-JSON / prose replies
|
|
121
|
+
# have no candidate and return None silently.
|
|
122
|
+
candidate = None
|
|
123
|
+
fenced = re.search(r"```json\s*(.*?)```", text, re.DOTALL)
|
|
124
|
+
if fenced and len(_top_level_brace_objects(fenced.group(1))) == 1:
|
|
125
|
+
candidate = fenced.group(1)
|
|
126
|
+
else:
|
|
127
|
+
objs = _top_level_brace_objects(text)
|
|
128
|
+
if len(objs) == 1:
|
|
129
|
+
candidate = objs[0]
|
|
130
|
+
# 0 or >1 top-level objects → too ambiguous to repair safely → None
|
|
131
|
+
if not candidate:
|
|
132
|
+
return None
|
|
133
|
+
# Final guard: only repair spans that actually look like an intended JSON
|
|
134
|
+
# object. Prose pseudo-objects in single quotes / backticks / bare text
|
|
135
|
+
# (e.g. `{op: delete}`) reach here because the scan only skips double-quoted
|
|
136
|
+
# prose; repairing them would fabricate a wrong dict (worse than None).
|
|
137
|
+
if not _looks_json_like(candidate):
|
|
138
|
+
return None
|
|
139
|
+
try:
|
|
140
|
+
from json_repair import repair_json
|
|
141
|
+
except ModuleNotFoundError:
|
|
142
|
+
warnings.warn(
|
|
143
|
+
"json_repair not installed; malformed-JSON recovery disabled — "
|
|
144
|
+
"a non-OpenAI analyst edit may be silently dropped. pip install json_repair",
|
|
145
|
+
RuntimeWarning,
|
|
146
|
+
stacklevel=2,
|
|
147
|
+
)
|
|
148
|
+
return None
|
|
149
|
+
try:
|
|
150
|
+
repaired = repair_json(candidate, return_objects=True)
|
|
151
|
+
if isinstance(repaired, dict) and repaired:
|
|
152
|
+
return repaired
|
|
153
|
+
except Exception: # noqa: BLE001 — repair is best-effort
|
|
154
|
+
pass
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def extract_json_array(text: str) -> list | None:
|
|
159
|
+
"""Extract a JSON array from LLM response text."""
|
|
160
|
+
m = re.search(r"```json\s*(.*?)```", text, re.DOTALL)
|
|
161
|
+
if m:
|
|
162
|
+
try:
|
|
163
|
+
return json.loads(m.group(1))
|
|
164
|
+
except json.JSONDecodeError:
|
|
165
|
+
pass
|
|
166
|
+
m = re.search(r"\[.*\]", text, re.DOTALL)
|
|
167
|
+
if m:
|
|
168
|
+
try:
|
|
169
|
+
return json.loads(m.group(0))
|
|
170
|
+
except json.JSONDecodeError:
|
|
171
|
+
pass
|
|
172
|
+
return None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Scoring and hashing utilities."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def compute_score(results: list) -> tuple[float, float]:
|
|
8
|
+
"""Compute hard and soft accuracy from a list of episode results.
|
|
9
|
+
|
|
10
|
+
Accepts both plain dicts and :class: instances. hard may be continuous (0.0-1.0) when using smoothed reward.
|
|
11
|
+
"""
|
|
12
|
+
if not results:
|
|
13
|
+
return 0.0, 0.0
|
|
14
|
+
|
|
15
|
+
def _hard(r: object) -> float:
|
|
16
|
+
return float(r.hard if hasattr(r, "hard") else r.get("hard", 0))
|
|
17
|
+
|
|
18
|
+
def _soft(r: object) -> float:
|
|
19
|
+
return float(r.soft if hasattr(r, "soft") else r.get("soft", 0.0))
|
|
20
|
+
|
|
21
|
+
hard = sum(_hard(r) for r in results) / len(results)
|
|
22
|
+
soft = sum(_soft(r) for r in results) / len(results)
|
|
23
|
+
return hard, soft
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def skill_hash(content: str) -> str:
|
|
27
|
+
"""Return a short deterministic hash of skill content (for caching)."""
|
|
28
|
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|