Spaces:
Running on Zero
Running on Zero
File size: 6,405 Bytes
c8fbdf1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | #!/usr/bin/env python3
"""Reality Layer — grounds adapter reasoning in actual artifact facts.
Problem this fixes: when asked "what would you improve in X.py?", adapters
were free-associating off the words in the question ("improve" -> talk about
self-improvement, emotional intelligence) instead of inspecting the file.
This module extracts verifiable facts from a referenced source file (line
count, classes, functions, rough complexity) BEFORE adapters reason, and
provides a cheap post-hoc check for whether a response actually engaged with
those facts. Python-only AST analysis; falls back to line counts for other
text files. No network access, read-only, reuses the same path allowlist as
codette_tools.
"""
from __future__ import annotations
import ast
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Optional
try:
from .codette_tools import _resolve_path, ALLOWED_ROOTS, READABLE_EXTENSIONS, MAX_FILE_SIZE
except ImportError:
from codette_tools import _resolve_path, ALLOWED_ROOTS, READABLE_EXTENSIONS, MAX_FILE_SIZE
_SKIP_DIR_PARTS = {".git", "__pycache__", "node_modules", ".venv", "venv"}
@dataclass
class ArtifactFacts:
path: str
line_count: int
classes: List[str] = field(default_factory=list)
functions: List[str] = field(default_factory=list)
imports: List[str] = field(default_factory=list)
branch_count: int = 0 # rough cyclomatic complexity proxy
longest_function: Optional[str] = None
longest_function_lines: int = 0
def names(self) -> List[str]:
"""All identifiers a grounded response should plausibly reference."""
return self.classes + self.functions
# Matches things like "universal_reasoning.py", "inference/codette_server.py"
_FILE_REF_RE = re.compile(
r"\b[\w./\\-]+\.(?:py|js|ts|tsx|jsx|json|yaml|yml|md)\b"
)
def find_referenced_file(query: str) -> Optional[Path]:
"""Look for a file path mentioned in the query and resolve it safely.
Handles both relative paths ("inference/foo.py") and bare filenames
("foo.py") by falling back to a recursive search under the allowed
roots when a direct join against the project root doesn't exist.
"""
for match in _FILE_REF_RE.finditer(query):
candidate = match.group(0)
resolved = _resolve_path(candidate)
if resolved is not None and resolved.is_file():
return resolved
basename = Path(candidate).name
for root in ALLOWED_ROOTS:
try:
for hit in root.rglob(basename):
if any(part in _SKIP_DIR_PARTS for part in hit.parts):
continue
if hit.is_file():
return hit
except Exception:
continue
return None
def extract_artifact_facts(query: str) -> Optional[ArtifactFacts]:
"""If the query references a real local file, extract grounded facts."""
path = find_referenced_file(query)
if path is None:
return None
if path.suffix.lower() not in READABLE_EXTENSIONS:
return None
if path.stat().st_size > MAX_FILE_SIZE:
return None
try:
source = path.read_text(encoding="utf-8", errors="replace")
except Exception:
return None
line_count = source.count("\n") + 1
facts = ArtifactFacts(path=str(path.name), line_count=line_count)
if path.suffix.lower() != ".py":
return facts # line count only for non-Python files
try:
tree = ast.parse(source)
except SyntaxError:
return facts # unparseable — still return what we know
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
facts.classes.append(node.name)
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
facts.functions.append(node.name)
fn_lines = (node.end_lineno or node.lineno) - node.lineno
if fn_lines > facts.longest_function_lines:
facts.longest_function_lines = fn_lines
facts.longest_function = node.name
elif isinstance(node, (ast.If, ast.For, ast.While, ast.Try, ast.BoolOp, ast.ExceptHandler)):
facts.branch_count += 1
elif isinstance(node, ast.Import):
facts.imports.extend(a.name for a in node.names)
elif isinstance(node, ast.ImportFrom) and node.module:
facts.imports.append(node.module)
return facts
def format_facts_block(facts: ArtifactFacts) -> str:
"""Render facts as a block to inject alongside the query."""
lines = [
f"[VERIFIED FACTS about {facts.path} — extracted directly from the file, not assumptions]",
f"- {facts.line_count} lines total",
]
if facts.classes:
lines.append(f"- Classes: {', '.join(facts.classes[:20])}")
if facts.functions:
lines.append(f"- Functions: {', '.join(facts.functions[:30])}")
if facts.longest_function:
lines.append(
f"- Longest function: {facts.longest_function} "
f"({facts.longest_function_lines} lines)"
)
if facts.branch_count:
lines.append(f"- Branch/control-flow points: {facts.branch_count} (rough complexity proxy)")
if facts.imports:
uniq = sorted(set(facts.imports))[:15]
lines.append(f"- Imports: {', '.join(uniq)}")
lines.append(
"Ground your analysis in these facts. Reference specific class/function "
"names above when making claims about the code. Do not invent structure "
"that isn't listed here."
)
return "\n".join(lines)
def grounding_score(response_text: str, facts: ArtifactFacts) -> float:
"""Fraction of known identifiers from facts that the response actually names.
A score of 0.0 with non-trivial facts available means the response never
engaged with the real artifact — a strong signal it's narrative, not analysis.
"""
names = facts.names()
if not names:
return 1.0 # nothing to ground against (e.g. non-Python file) — don't penalize
text_lower = response_text.lower()
hits = sum(1 for name in names if name.lower() in text_lower)
return hits / len(names)
|