The hidden reflection call behind the critic verdict (CORE-403).
One prompt, one model round-trip, one parsed verdict -- ported from
MetacognitiveMiddleware's reflection half. The prompt text and the
approve-on-malformed rule are verbatim; the provider arrives injected and
the rules arrive through :class:ProceduralRulesSource, so this module
never touches the orchestrator.
CriticPreference
dataclass
CriticPreference(priority: float, statement: str, sensitive_tags: frozenset[str] = frozenset())
One rule the draft must respect, as values (no domain import).
ProceduralRulesSource
Bases: Protocol
Per-turn procedural rules, supplied by the composition root.
retrieve
async
retrieve(query: str, top_k: int) -> Sequence[str]
Return up to top_k rule contents for query.
Source code in src/symfonic/capabilities/critic/reflection.py
| async def retrieve(self, query: str, top_k: int) -> Sequence[str]:
"""Return up to ``top_k`` rule contents for ``query``."""
...
|
parse_reflection_output
parse_reflection_output(raw: str) -> tuple[str, str | None, list[str]]
Ported verbatim: structured verdict, approve on malformed output.
Source code in src/symfonic/capabilities/critic/reflection.py
| def parse_reflection_output(raw: str) -> tuple[str, str | None, list[str]]:
"""Ported verbatim: structured verdict, approve on malformed output."""
try:
cleaned = raw.strip()
if cleaned.startswith("```"):
lines = cleaned.split("\n")
lines = [ln for ln in lines if not ln.strip().startswith("```")]
cleaned = "\n".join(lines).strip()
data = json.loads(cleaned)
verdict_raw = data.get("verdict", "approve")
if verdict_raw not in ("approve", "revise", "refuse"):
return "approve", None, []
text = None
if verdict_raw == "revise":
text = data.get("revised_draft")
elif verdict_raw == "refuse":
text = data.get("refusal_message")
matched = data.get("matched_preferences", [])
if not isinstance(matched, list):
matched = []
return verdict_raw, text, matched
except (json.JSONDecodeError, AttributeError, TypeError):
return "approve", None, []
|
reflect_on_draft
async
reflect_on_draft(provider: Any, model_config: Any, template: str, draft: str, preferences: tuple[CriticPreference, ...], rules: list[str], hit: str | None) -> tuple[str, str | None, list[str]]
One hidden reflection call; approve on any failure (legacy rule).
Source code in src/symfonic/capabilities/critic/reflection.py
| async def reflect_on_draft(
provider: Any,
model_config: Any,
template: str,
draft: str,
preferences: tuple[CriticPreference, ...],
rules: list[str],
hit: str | None,
) -> tuple[str, str | None, list[str]]:
"""One hidden reflection call; approve on any failure (legacy rule)."""
try:
prefs_text = "\n".join(
f"- [{p.priority}] {p.statement}"
for p in sorted(preferences, key=lambda p: p.priority, reverse=True)
) or "(none)"
rules_text = "\n".join(f"- {rule}" for rule in rules) or "(none)"
prompt = template.format(
draft=draft,
core_preferences=prefs_text,
procedural_rules=rules_text,
sensitive_tag_hit=hit or "none",
)
model = provider.get_chat_model(model_config)
result = await model.ainvoke([HumanMessage(content=prompt)])
raw = result.content if hasattr(result, "content") else str(result)
return parse_reflection_output(str(raw))
except Exception:
logger.warning("Critic reflection failed, falling back to approve", exc_info=True)
return "approve", None, []
|