CoolFace
Apppublic

vasiuuu/DGX_AI

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
grounder.py203 linesDownload Raw Back to codeforge
1from __future__ import annotations2 3import ast4import importlib5import importlib.util6import logging7from typing import Literal8 9from pydantic import BaseModel, ConfigDict10 11_log = logging.getLogger(__name__)12 13 14# ---------------------------------------------------------------------------15# Models16# ---------------------------------------------------------------------------17 18 19class Symbol(BaseModel):20    """A single symbol extracted from source code by AST walking."""21 22    model_config = ConfigDict(frozen=True)23    module: str24    attr: str | None25    kind: Literal["import", "attribute"]26    resolved: bool27    line: int28 29 30class GroundingReport(BaseModel):31    """Result of grounding analysis on source code."""32 33    model_config = ConfigDict(frozen=True)34    total_symbols: int35    grounded: tuple[Symbol, ...]36    ungrounded: tuple[Symbol, ...]37    groundedness: float38 39 40# ---------------------------------------------------------------------------41# Helpers42# ---------------------------------------------------------------------------43 44 45def _module_spec(name: str) -> bool:46    """Return True if the module can be found by the import system."""47    try:48        return importlib.util.find_spec(name) is not None49    except (ImportError, ValueError, ModuleNotFoundError):50        return False51 52 53def _has_attr(module_name: str, attr: str) -> bool:54    """Check if *module_name* exposes *attr*.55 56    Uses the FULL module path (e.g. ``os.path``) — not just57    the top-level package.  This is the fix for SYSTEM_DESIGN §4.8.358    bug #3.59    """60    try:61        mod = importlib.import_module(module_name)62    except Exception:63        return False64    return hasattr(mod, attr)65 66 67# ---------------------------------------------------------------------------68# Public API69# ---------------------------------------------------------------------------70 71 72def ground(73    source: str,74    *,75    local_modules: frozenset[str] = frozenset(),76) -> GroundingReport:77    """AST-parse *source*, check every import and attribute access resolves.78 79    Three fixes baked in from day one (SYSTEM_DESIGN §4.8.3):80    1. SyntaxError → groundedness=0.0  (was 1.0)81    2. Zero symbols → groundedness=0.5 (was 1.0)82    3. Attribute resolution against full module path (was top-level only)83 84    *local_modules*: set of module names (e.g. ``{"core", "main"}``) that are85    local to the agent's project and should be treated as grounded even though86    ``importlib.util.find_spec`` cannot resolve them from the grader process.87    """88    # ----- parse --------------------------------------------------------89    try:90        tree = ast.parse(source)91    except SyntaxError:92        # FIX 1: unparseable code → 0.0, not 1.093        return GroundingReport(94            total_symbols=0,95            grounded=(),96            ungrounded=(),97            groundedness=0.0,98        )99 100    symbols: list[Symbol] = []101    import_to_module: dict[str, str] = {}102 103    # ----- walk imports -------------------------------------------------104    for node in ast.walk(tree):105        if isinstance(node, ast.Import):106            for alias in node.names:107                pkg = alias.name.split(".")[0]108                # Local modules are always treated as grounded109                resolved = (110                    pkg in local_modules or _module_spec(alias.name)111                )112                symbols.append(113                    Symbol(114                        module=alias.name,115                        attr=None,116                        kind="import",117                        resolved=resolved,118                        line=node.lineno,119                    )120                )121                import_to_module[alias.asname or pkg] = alias.name122 123        elif isinstance(node, ast.ImportFrom):124            if node.level != 0 or node.module is None:125                continue126            mod_top = node.module.split(".")[0]127            is_local = mod_top in local_modules128            resolved_mod = is_local or _module_spec(node.module)129            for alias in (node.names or []):130                attr_resolved = resolved_mod if is_local else (131                    resolved_mod and _has_attr(node.module, alias.name)132                )133                symbols.append(134                    Symbol(135                        module=node.module,136                        attr=alias.name,137                        kind="import",138                        resolved=attr_resolved,139                        line=node.lineno,140                    )141                )142 143    # ----- walk attribute accesses --------------------------------------144    for node in ast.walk(tree):145        if not isinstance(node, ast.Attribute):146            continue147 148        # Resolve the chain: e.g. os.path.join → base="os", chain=["path"], attr="join"149        chain: list[str] = []150        cursor: ast.expr = node.value151        while isinstance(cursor, ast.Attribute):152            chain.append(cursor.attr)153            cursor = cursor.value154        if not isinstance(cursor, ast.Name):155            continue156 157        base = cursor.id158        mod_name = import_to_module.get(base)159        if mod_name is None:160            continue161 162        # Build the full module path for chained access:163        # import os.path → import_to_module["os"] = "os.path"164        # os.path.join → chain=["path"], we need to resolve "join" against "os.path"165        # The chain intermediates are sub-module parts already covered by mod_name.166        # We check the final attr against the deepest resolvable module.167        if chain:168            # chain was built bottom-up, reverse to get top-down order169            chain.reverse()170            # Build candidate module: mod_name + chain parts171            full_mod = mod_name + "." + ".".join(chain)172            # Try the full module first; fall back to mod_name if it doesn't exist173            check_mod = full_mod if _module_spec(full_mod) else mod_name174        else:175            check_mod = mod_name176 177        # FIX 3: resolve against full module path, not just top-level178        resolved = _has_attr(check_mod, node.attr)179        symbols.append(180            Symbol(181                module=check_mod,182                attr=node.attr,183                kind="attribute",184                resolved=resolved,185                line=node.lineno,186            )187        )188 189    # ----- compute groundedness -----------------------------------------190    grounded = tuple(s for s in symbols if s.resolved)191    ungrounded = tuple(s for s in symbols if not s.resolved)192    total = len(symbols)193 194    # FIX 2: zero symbols → 0.5 (neutral), not 1.0195    groundedness = 0.5 if total == 0 else len(grounded) / total196 197    return GroundingReport(198        total_symbols=total,199        grounded=grounded,200        ungrounded=ungrounded,201        groundedness=groundedness,202    )203