Source code for curate_ipsum.synthesis.models

"""
Core data models for the synthesis loop.

All models use Pydantic for validation and serialization, matching the
project's existing pattern (see models.py in the root).
"""

from __future__ import annotations

import ast
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any
from uuid import uuid4


[docs] class SynthesisStatus(StrEnum): """Outcome of a synthesis run.""" SUCCESS = "success" FAILED = "failed" TIMEOUT = "timeout" CANCELLED = "cancelled"
[docs] class PatchSource(StrEnum): """How a code patch was produced.""" LLM = "llm" CROSSOVER = "crossover" MUTATION = "mutation" SEED = "seed"
[docs] class LLMBackend(StrEnum): """Which LLM backend to use.""" CLOUD = "cloud" LOCAL = "local" MOCK = "mock"
[docs] @dataclass class SynthesisConfig: """Configuration for a synthesis run.""" # Population / GA population_size: int = 20 max_iterations: int = 100 mutation_rate: float = 0.3 crossover_rate: float = 0.7 elite_ratio: float = 0.1 entropy_threshold: float = 1.0 # LLM llm_backend: str = "mock" # "cloud", "local", "mock" llm_model: str = "codellama:7b" temperature: float = 0.8 top_k: int = 10 # Fitness weights ce_weight: float = 0.4 spec_weight: float = 0.5 complexity_weight: float = 0.1 # Timeouts test_timeout_seconds: float = 30.0 synthesis_timeout_seconds: float = 300.0 def __post_init__(self) -> None: if not 0.0 <= self.mutation_rate <= 1.0: raise ValueError(f"mutation_rate must be 0-1, got {self.mutation_rate}") if not 0.0 <= self.crossover_rate <= 1.0: raise ValueError(f"crossover_rate must be 0-1, got {self.crossover_rate}") if not 0.0 <= self.elite_ratio <= 1.0: raise ValueError(f"elite_ratio must be 0-1, got {self.elite_ratio}") if self.population_size < 2: raise ValueError(f"population_size must be >= 2, got {self.population_size}") if self.max_iterations < 1: raise ValueError(f"max_iterations must be >= 1, got {self.max_iterations}") if self.top_k < 1: raise ValueError(f"top_k must be >= 1, got {self.top_k}")
[docs] @dataclass class Individual: """A candidate patch in the genetic algorithm population.""" id: str = field(default_factory=lambda: str(uuid4())[:8]) code: str = "" fitness: float = 0.0 lineage: list[str] = field(default_factory=list) # Parent IDs generation: int = 0 source: PatchSource = PatchSource.SEED metadata: dict[str, Any] = field(default_factory=dict)
[docs] def is_valid(self) -> bool: """Check if the code is syntactically valid Python.""" try: ast.parse(self.code) return True except SyntaxError: return False
[docs] @dataclass class CodePatch: """A code patch produced by synthesis.""" code: str source: PatchSource = PatchSource.LLM diff: str = "" region_id: str = "" original_code: str = "" metadata: dict[str, Any] = field(default_factory=dict)
[docs] def to_dict(self) -> dict[str, Any]: return { "code": self.code, "source": self.source.value, "diff": self.diff, "region_id": self.region_id, "metadata": self.metadata, }
[docs] @dataclass class Specification: """What a synthesized patch must satisfy.""" target_region: str = "" # Region ID original_code: str = "" # Code being replaced surviving_mutant_ids: list[str] = field(default_factory=list) test_commands: list[str] = field(default_factory=list) mutation_command: str = "" working_directory: str = "" # Assertions from M3 belief revision assertion_ids: list[str] = field(default_factory=list) preconditions: list[str] = field(default_factory=list) postconditions: list[str] = field(default_factory=list) context_code: str = "" # Surrounding code for LLM prompt context metadata: dict[str, Any] = field(default_factory=dict)
[docs] @dataclass class Counterexample: """A counterexample that a candidate patch fails on.""" id: str = field(default_factory=lambda: str(uuid4())[:8]) input_values: dict[str, Any] = field(default_factory=dict) expected_output: Any = None actual_output: Any = None mutant_id: str = "" error_message: str = "" test_command: str = "" metadata: dict[str, Any] = field(default_factory=dict)
[docs] def to_dict(self) -> dict[str, Any]: return { "id": self.id, "input_values": self.input_values, "expected_output": str(self.expected_output), "actual_output": str(self.actual_output), "mutant_id": self.mutant_id, "error_message": self.error_message, "test_command": self.test_command, }
[docs] @dataclass class SynthesisResult: """Outcome of a synthesis run.""" id: str = field(default_factory=lambda: str(uuid4())[:8]) status: SynthesisStatus = SynthesisStatus.FAILED patch: CodePatch | None = None iterations: int = 0 counterexamples_resolved: int = 0 duration_ms: int = 0 fitness_history: list[float] = field(default_factory=list) final_entropy: float = 0.0 total_candidates_evaluated: int = 0 error_message: str = "" metadata: dict[str, Any] = field(default_factory=dict)
[docs] def to_dict(self) -> dict[str, Any]: return { "id": self.id, "status": self.status.value, "patch": self.patch.to_dict() if self.patch else None, "iterations": self.iterations, "counterexamples_resolved": self.counterexamples_resolved, "duration_ms": self.duration_ms, "fitness_history": self.fitness_history, "final_entropy": self.final_entropy, "total_candidates_evaluated": self.total_candidates_evaluated, "error_message": self.error_message, }