File size: 9,624 Bytes
ce8f04a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"""
Expectation quality loop: evaluate β†’ targets β†’ re-evaluate until ready or max-iter.

Pure control plane (no LLM). Callers inject evaluate/fix callables so unit tests
drive the real loop without network.
"""
from __future__ import annotations

import logging
import os
from dataclasses import asdict, dataclass, field
from typing import Any, Callable, Dict, List, Optional

logger = logging.getLogger(__name__)

DEFAULT_MAX_ITER = int(os.environ.get("ADVISOR_EXPECTATION_MAX_ITER", "3"))
DEFAULT_MIN_SCORE = int(os.environ.get("ADVISOR_EXPECTATION_MIN_SCORE", "70"))


@dataclass
class ExpectationLoopResult:
    ready: bool
    iterations: int
    max_iterations: int
    final_score: int
    regulation_grounded_pass: bool
    stop_reason: str  # ready | max_iter | blocked_grounding | evaluate_error
    history: List[Dict[str, Any]] = field(default_factory=list)
    remaining_blockers: List[str] = field(default_factory=list)
    final_report: Optional[Dict[str, Any]] = None
    state: Optional[Dict[str, Any]] = None

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


def is_regulation_ready(
    report: Any,
    *,
    min_score: int = DEFAULT_MIN_SCORE,
    require_regulation_grounded: bool = True,
) -> bool:
    """Stop condition: advisor report meets readiness criteria."""
    if report is None:
        return False
    if isinstance(report, dict):
        score = int(report.get("score") or 0)
        grounded = bool(report.get("regulation_grounded_pass"))
        passed = bool(report.get("passed"))
        blockers = list(report.get("blockers") or [])
        mode = str(report.get("grounding_mode") or "regulation")
    else:
        score = int(getattr(report, "score", 0) or 0)
        grounded = bool(getattr(report, "regulation_grounded_pass", False))
        passed = bool(getattr(report, "passed", False))
        blockers = list(getattr(report, "blockers", None) or [])
        mode = str(getattr(report, "grounding_mode", "regulation") or "regulation")

    if mode in ("structure_only", "blocked", "blind"):
        # Never regulation-ready in ungrounded modes
        return False
    if require_regulation_grounded and not grounded:
        return False
    if blockers:
        return False
    if score < min_score:
        return False
    return passed or grounded


def extract_blockers(report: Any) -> List[str]:
    if report is None:
        return ["brak raportu doradcy"]
    if isinstance(report, dict):
        blockers = list(report.get("blockers") or [])
        if blockers:
            return [str(b) for b in blockers]
        findings = report.get("findings") or []
        out = []
        for f in findings:
            if isinstance(f, dict) and (f.get("blocking") or f.get("severity") == "critical"):
                out.append(str(f.get("message") or f.get("code") or "finding"))
        return out
    blockers = list(getattr(report, "blockers", None) or [])
    if blockers:
        return [str(b) for b in blockers]
    out = []
    for f in getattr(report, "findings", None) or []:
        if getattr(f, "blocking", False) or getattr(f, "severity", "") == "critical":
            out.append(str(getattr(f, "message", "") or getattr(f, "code", "finding")))
    return out


def report_to_dict(report: Any) -> Dict[str, Any]:
    if report is None:
        return {}
    if isinstance(report, dict):
        return report
    if hasattr(report, "to_dict"):
        return report.to_dict()
    return {
        "passed": getattr(report, "passed", False),
        "score": getattr(report, "score", 0),
        "regulation_grounded_pass": getattr(report, "regulation_grounded_pass", False),
        "blockers": list(getattr(report, "blockers", None) or []),
        "grounding_mode": getattr(report, "grounding_mode", ""),
        "summary": getattr(report, "summary", ""),
    }


def run_expectation_loop(
    *,
    evaluate: Callable[[Dict[str, Any]], Any],
    apply_fixes: Optional[Callable[[Dict[str, Any], Any], Dict[str, Any]]] = None,
    initial_state: Optional[Dict[str, Any]] = None,
    max_iterations: int = DEFAULT_MAX_ITER,
    min_score: int = DEFAULT_MIN_SCORE,
    require_regulation_grounded: bool = True,
) -> ExpectationLoopResult:
    """
    Loop:
      evaluate(state) β†’ if ready: stop
      else apply_fixes(state, report) β†’ state' β†’ re-evaluate
      until ready or max_iterations.

    structure_only / blind never stop as regulation-ready (is_regulation_ready=False).
    """
    state: Dict[str, Any] = dict(initial_state or {})
    max_iterations = max(1, int(max_iterations))
    history: List[Dict[str, Any]] = []
    last_report: Any = None

    for i in range(1, max_iterations + 1):
        try:
            last_report = evaluate(state)
        except Exception as e:
            logger.warning("[ExpectationLoop] evaluate failed at iter %s: %s", i, e)
            return ExpectationLoopResult(
                ready=False,
                iterations=i,
                max_iterations=max_iterations,
                final_score=0,
                regulation_grounded_pass=False,
                stop_reason="evaluate_error",
                history=history,
                remaining_blockers=[str(e)],
                final_report={"error": str(e)},
                state=state,
            )

        rd = report_to_dict(last_report)
        history.append({"iteration": i, "phase": "evaluate", "report": rd})

        mode = str(rd.get("grounding_mode") or state.get("grounding_mode") or "").lower()
        ext = state.get("external_context") if isinstance(state.get("external_context"), dict) else {}
        if not mode:
            mode = str(ext.get("grounding_mode") or "regulation").lower()
            rd["grounding_mode"] = mode

        if mode in ("structure_only", "blocked", "blind"):
            # Explicit: never mark regulation pass; stop early with blockers if blocked
            if mode == "blocked":
                return ExpectationLoopResult(
                    ready=False,
                    iterations=i,
                    max_iterations=max_iterations,
                    final_score=int(rd.get("score") or 0),
                    regulation_grounded_pass=False,
                    stop_reason="blocked_grounding",
                    history=history,
                    remaining_blockers=extract_blockers(last_report)
                    or ["Generacja zablokowana β€” brak regulaminu i zgody."],
                    final_report=rd,
                    state=state,
                )
            # structure_only: continue loop for structural quality but never ready=True for regulation
            # Fall through to fix attempts, but is_regulation_ready stays False

        if is_regulation_ready(
            rd,
            min_score=min_score,
            require_regulation_grounded=require_regulation_grounded,
        ):
            return ExpectationLoopResult(
                ready=True,
                iterations=i,
                max_iterations=max_iterations,
                final_score=int(rd.get("score") or 0),
                regulation_grounded_pass=bool(rd.get("regulation_grounded_pass")),
                stop_reason="ready",
                history=history,
                remaining_blockers=[],
                final_report=rd,
                state=state,
            )

        if i >= max_iterations:
            break

        if apply_fixes is None:
            # No fixer β†’ cannot improve; stop next iteration boundary
            continue

        try:
            new_state = apply_fixes(state, last_report)
            if isinstance(new_state, dict):
                state = new_state
            history.append(
                {
                    "iteration": i,
                    "phase": "fix",
                    "fixed_sections": list(
                        (new_state or {}).get("fixed_sections")
                        or state.get("last_fixed_sections")
                        or []
                    )
                    if isinstance(new_state, dict)
                    else [],
                }
            )
        except Exception as e:
            logger.warning("[ExpectationLoop] apply_fixes failed at iter %s: %s", i, e)
            history.append({"iteration": i, "phase": "fix_error", "error": str(e)})

    rd = report_to_dict(last_report)
    return ExpectationLoopResult(
        ready=False,
        iterations=max_iterations,
        max_iterations=max_iterations,
        final_score=int(rd.get("score") or 0),
        regulation_grounded_pass=bool(rd.get("regulation_grounded_pass")),
        stop_reason="max_iter",
        history=history,
        remaining_blockers=extract_blockers(last_report),
        final_report=rd,
        state=state,
    )


def run_advisor_expectation_loop(
    state: Dict[str, Any],
    *,
    max_iterations: int = DEFAULT_MAX_ITER,
    min_score: int = DEFAULT_MIN_SCORE,
    apply_fixes: Optional[Callable[[Dict[str, Any], Any], Dict[str, Any]]] = None,
) -> ExpectationLoopResult:
    """
    Default loop using world_class_advisor.evaluate_from_generator_state.
    Optional apply_fixes for rewrite integration (LLM or pure test stub).
    """
    from agents.world_class_advisor import evaluate_from_generator_state

    def _eval(st: Dict[str, Any]):
        return evaluate_from_generator_state(st)

    return run_expectation_loop(
        evaluate=_eval,
        apply_fixes=apply_fixes,
        initial_state=state,
        max_iterations=max_iterations,
        min_score=min_score,
        require_regulation_grounded=True,
    )