Add blueprint repair loop#382
Conversation
📝 WalkthroughWalkthroughThis PR introduces an asynchronous blueprint repair workflow. It defines the ChangesBlueprint Repair Workflow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/casecrawler/generation/blueprint_repair.py`:
- Around line 251-278: _repair_requested_attempt currently computes prompt_hash
from a local payload using _hash_payload, which diverges from the terminal
attempts that use _prompt_hash(prompt, policy); change the flow so the repair
prompt is hashed once (via _prompt_hash with the actual repair prompt and
policy) before creating/persisting the REPAIR_REQUESTED GenerationAttempt and
pass that same prompt_hash into _repair_requested_attempt (or add a prompt_hash
parameter) so both the REPAIR_REQUESTED and subsequent SUCCEEDED/FAILED attempts
use the identical prompt_hash value.
In `@src/casecrawler/storage/dataset_store.py`:
- Around line 297-333: Before performing the DB writes in
save_blueprint_with_attempt, validate that the provided GenerationAttempt
actually belongs to the ClinicalBlueprint: check attempt.artifact_id ==
blueprint.blueprint_id and attempt.dataset_id == blueprint.dataset_id (or any
other domain-specific linkage between attempt and blueprint), and raise a clear
exception (e.g., ValueError) if they do not match; perform this validation
before acquiring the write lock / before executing the INSERTs so mismatched
pairs are rejected prior to the transaction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1d6f782f-9c58-4fa0-a868-40b1fc244125
📒 Files selected for processing (4)
src/casecrawler/generation/blueprint_repair.pysrc/casecrawler/storage/dataset_store.pytests/test_blueprint_repair_loop.pytests/test_blueprint_storage.py
| def _repair_requested_attempt( | ||
| self, | ||
| *, | ||
| blueprint: ClinicalBlueprint, | ||
| policy: GenerationRolePolicy, | ||
| judge_report: JudgeReport, | ||
| repair_round: int, | ||
| ) -> GenerationAttempt: | ||
| payload = { | ||
| "artifact_id": blueprint.blueprint_id, | ||
| "judge_report_id": judge_report.report_id, | ||
| "repair_round": repair_round, | ||
| "status": GenerationAttemptStatus.REPAIR_REQUESTED.value, | ||
| } | ||
| return GenerationAttempt( | ||
| attempt_id=f"attempt-{uuid4()}", | ||
| dataset_id=blueprint.dataset_id, | ||
| role=GenerationRole.REPAIR, | ||
| status=GenerationAttemptStatus.REPAIR_REQUESTED, | ||
| provider=policy.provider, | ||
| model=policy.model, | ||
| prompt_hash=_hash_payload(payload), | ||
| artifact_id=blueprint.blueprint_id, | ||
| metadata={ | ||
| "judge_report_id": judge_report.report_id, | ||
| "repair_round": repair_round, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Use the real repair prompt hash for REPAIR_REQUESTED.
_repair_requested_attempt() hashes a local payload instead of the actual repair prompt, while the later SUCCEEDED/FAILED attempt for the same round uses _prompt_hash(prompt, policy). That makes the preflight record impossible to correlate with its terminal record via prompt_hash, which weakens the attempt lineage this PR is adding. Compute the prompt/hash once before persisting the request row, then pass that same hash through both attempt builders.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/casecrawler/generation/blueprint_repair.py` around lines 251 - 278,
_repair_requested_attempt currently computes prompt_hash from a local payload
using _hash_payload, which diverges from the terminal attempts that use
_prompt_hash(prompt, policy); change the flow so the repair prompt is hashed
once (via _prompt_hash with the actual repair prompt and policy) before
creating/persisting the REPAIR_REQUESTED GenerationAttempt and pass that same
prompt_hash into _repair_requested_attempt (or add a prompt_hash parameter) so
both the REPAIR_REQUESTED and subsequent SUCCEEDED/FAILED attempts use the
identical prompt_hash value.
| def save_blueprint_with_attempt( | ||
| self, | ||
| blueprint: ClinicalBlueprint, | ||
| attempt: GenerationAttempt, | ||
| ) -> None: | ||
| with self._write_lock: | ||
| try: | ||
| self._conn.execute( | ||
| """INSERT OR REPLACE INTO clinical_blueprints | ||
| (blueprint_id, dataset_id, cohort_plan_id, archetype_name, | ||
| blueprint_json) | ||
| VALUES (?, ?, ?, ?, ?)""", | ||
| ( | ||
| blueprint.blueprint_id, | ||
| blueprint.dataset_id, | ||
| blueprint.cohort_plan_id, | ||
| blueprint.archetype_name, | ||
| blueprint.model_dump_json(), | ||
| ), | ||
| ) | ||
| self._conn.execute( | ||
| """INSERT OR REPLACE INTO generation_attempts | ||
| (attempt_id, dataset_id, role, status, artifact_id, attempt_json) | ||
| VALUES (?, ?, ?, ?, ?, ?)""", | ||
| ( | ||
| attempt.attempt_id, | ||
| attempt.dataset_id, | ||
| attempt.role.value, | ||
| attempt.status.value, | ||
| attempt.artifact_id, | ||
| attempt.model_dump_json(), | ||
| ), | ||
| ) | ||
| self._conn.commit() | ||
| except Exception: | ||
| self._conn.rollback() | ||
| raise |
There was a problem hiding this comment.
Reject mismatched blueprint/attempt pairs before the transaction.
This method never validates that the attempt belongs to the blueprint being saved. A caller can currently commit a ClinicalBlueprint for one artifact and a GenerationAttempt for another, which silently corrupts repair lineage despite the write being "atomic".
Suggested fix
def save_blueprint_with_attempt(
self,
blueprint: ClinicalBlueprint,
attempt: GenerationAttempt,
) -> None:
+ if attempt.dataset_id != blueprint.dataset_id:
+ raise ValueError("Attempt dataset_id must match blueprint dataset_id.")
+ if attempt.artifact_id != blueprint.blueprint_id:
+ raise ValueError("Attempt artifact_id must match blueprint blueprint_id.")
with self._write_lock:
try:
self._conn.execute(
"""INSERT OR REPLACE INTO clinical_blueprints📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def save_blueprint_with_attempt( | |
| self, | |
| blueprint: ClinicalBlueprint, | |
| attempt: GenerationAttempt, | |
| ) -> None: | |
| with self._write_lock: | |
| try: | |
| self._conn.execute( | |
| """INSERT OR REPLACE INTO clinical_blueprints | |
| (blueprint_id, dataset_id, cohort_plan_id, archetype_name, | |
| blueprint_json) | |
| VALUES (?, ?, ?, ?, ?)""", | |
| ( | |
| blueprint.blueprint_id, | |
| blueprint.dataset_id, | |
| blueprint.cohort_plan_id, | |
| blueprint.archetype_name, | |
| blueprint.model_dump_json(), | |
| ), | |
| ) | |
| self._conn.execute( | |
| """INSERT OR REPLACE INTO generation_attempts | |
| (attempt_id, dataset_id, role, status, artifact_id, attempt_json) | |
| VALUES (?, ?, ?, ?, ?, ?)""", | |
| ( | |
| attempt.attempt_id, | |
| attempt.dataset_id, | |
| attempt.role.value, | |
| attempt.status.value, | |
| attempt.artifact_id, | |
| attempt.model_dump_json(), | |
| ), | |
| ) | |
| self._conn.commit() | |
| except Exception: | |
| self._conn.rollback() | |
| raise | |
| def save_blueprint_with_attempt( | |
| self, | |
| blueprint: ClinicalBlueprint, | |
| attempt: GenerationAttempt, | |
| ) -> None: | |
| if attempt.dataset_id != blueprint.dataset_id: | |
| raise ValueError("Attempt dataset_id must match blueprint dataset_id.") | |
| if attempt.artifact_id != blueprint.blueprint_id: | |
| raise ValueError("Attempt artifact_id must match blueprint blueprint_id.") | |
| with self._write_lock: | |
| try: | |
| self._conn.execute( | |
| """INSERT OR REPLACE INTO clinical_blueprints | |
| (blueprint_id, dataset_id, cohort_plan_id, archetype_name, | |
| blueprint_json) | |
| VALUES (?, ?, ?, ?, ?)""", | |
| ( | |
| blueprint.blueprint_id, | |
| blueprint.dataset_id, | |
| blueprint.cohort_plan_id, | |
| blueprint.archetype_name, | |
| blueprint.model_dump_json(), | |
| ), | |
| ) | |
| self._conn.execute( | |
| """INSERT OR REPLACE INTO generation_attempts | |
| (attempt_id, dataset_id, role, status, artifact_id, attempt_json) | |
| VALUES (?, ?, ?, ?, ?, ?)""", | |
| ( | |
| attempt.attempt_id, | |
| attempt.dataset_id, | |
| attempt.role.value, | |
| attempt.status.value, | |
| attempt.artifact_id, | |
| attempt.model_dump_json(), | |
| ), | |
| ) | |
| self._conn.commit() | |
| except Exception: | |
| self._conn.rollback() | |
| raise |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/casecrawler/storage/dataset_store.py` around lines 297 - 333, Before
performing the DB writes in save_blueprint_with_attempt, validate that the
provided GenerationAttempt actually belongs to the ClinicalBlueprint: check
attempt.artifact_id == blueprint.blueprint_id and attempt.dataset_id ==
blueprint.dataset_id (or any other domain-specific linkage between attempt and
blueprint), and raise a clear exception (e.g., ValueError) if they do not match;
perform this validation before acquiring the write lock / before executing the
INSERTs so mismatched pairs are rejected prior to the transaction.
Summary
Tests
Summary by CodeRabbit
New Features
Tests