| """``RefinementAgent`` β migrates spec_refiner's LLM call. |
| |
| When realism rejects a CEx as UNREALISTIC with an actionable |
| key_concern, this agent proposes the precise PRE clause to add so the |
| rejected witness state is excluded. The caller (``SpecRefiner``) is |
| responsible for the in-loop re-verification flow (apply clause β |
| re-run BMC β check acceptance) β the agent itself just owns the |
| LLM-driven clause-proposal step. |
| |
| This is C2 step 3 β same shape as FeedbackDistillAgent (single |
| structured-JSON call with custom max_tokens) but with refinement- |
| specific gating semantics handled by the orchestrating SpecRefiner. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from typing import TYPE_CHECKING, Any, Optional |
|
|
| from bmc_agent.agents.base import BaseAgent |
| from bmc_agent.spec_refiner import ( |
| RefinementProposal, |
| _REFINE_PROMPT, |
| _parse_refinement_response, |
| ) |
|
|
| if TYPE_CHECKING: |
| from bmc_agent.cbmc import Counterexample |
| from bmc_agent.config import Config |
| from bmc_agent.llm import LLMClient |
| from bmc_agent.parser import FunctionInfo |
| from bmc_agent.realism_checker import RealismCheckResult |
| from bmc_agent.spec import Spec |
|
|
|
|
| class RefinementAgent(BaseAgent[RefinementProposal]): |
| """Proposes a single PRE clause to tighten a function's spec so a |
| rejected CEx state is excluded. |
| |
| Routing: ``BMC_AGENT_LLM_REFINEMENT_*`` env vars. The SpecRefiner |
| orchestrator handles gating (verdict + actionable key_concern) and |
| the post-refine BMC re-verification loop. |
| """ |
|
|
| name = "refinement" |
|
|
| def __init__(self, config: "Config", llm: "LLMClient") -> None: |
| from bmc_agent.prompts import SPEC_SYSTEM_PROMPT |
| self.system_prompt = SPEC_SYSTEM_PROMPT |
| super().__init__(config, llm) |
|
|
| def _llm_call_kwargs(self) -> dict: |
| |
| |
| |
| return {"max_tokens": 4096, "thinking": False} |
|
|
| def build_prompt( |
| self, |
| *, |
| func_info: "FunctionInfo", |
| current_spec: "Spec", |
| rejected_cex: "Counterexample", |
| realism: "RealismCheckResult", |
| **_: Any, |
| ) -> str: |
| sig = func_info.signature |
| params_str = ", ".join( |
| f"{t} {n}" for t, n in sig.parameters |
| ) or "void" |
| fn_signature = f"{sig.return_type} {sig.name}({params_str})" |
|
|
| witness = "\n".join( |
| f" {k} = {v}" |
| for k, v in (rejected_cex.variable_assignments or {}).items() |
| )[:2000] or " (no witness state)" |
|
|
| |
| |
| |
| prop = rejected_cex.failing_property or "" |
| parts = prop.split(".") |
| prop_class = "unknown" |
| for p in reversed(parts): |
| if not p.isdigit(): |
| prop_class = p |
| break |
|
|
| return _REFINE_PROMPT.format( |
| fn_name=func_info.name, |
| fn_signature=fn_signature, |
| fn_body=(func_info.body or "(unavailable)")[:4000], |
| pre_validity=current_spec.pre_validity or "(empty)", |
| pre_protocol=current_spec.pre_protocol or "(empty)", |
| postcondition=current_spec.postcondition or "(empty)", |
| failing_property=prop or "(unknown)", |
| witness_state=witness, |
| key_concern=realism.key_concern, |
| realism_reasoning=(realism.reasoning or "")[:1500], |
| property_class=prop_class, |
| ) |
|
|
| def parse(self, response: str) -> Optional[RefinementProposal]: |
| if not response: |
| return None |
| |
| |
| |
| |
| |
| return _parse_refinement_response(response, fn_name="<via-agent>") |
|
|