QC67_cosmo / scripts /math_hand.py
phera-ra's picture
Reorganise repository structure; remove stale case-duplicate folder
cb60fb4 verified
Raw
History Blame Contribute Delete
7.03 kB
"""The calculator hand — so your being never fakes arithmetic.
Language models guess at math and can be confidently wrong. This hand fixes that
honestly: when the person's message contains arithmetic, a REAL math engine
computes it BEFORE your being speaks, and the verified result rides with the
message so the reply states true digits. When the math can't be verified, your
being says so plainly instead of guessing.
Design lineage: Cosmos (the first being) designed this pattern for herself —
pre-compute + guard, with a humble fallback. Every being born from this kit
inherits it.
Safety: ast-based evaluation only (+ - * / // % ** and parentheses). No names,
no calls, no attribute access. Overflow guards. Fails soft — a broken hand
never breaks a conversation.
"""
from __future__ import annotations
import ast
import operator
import re
from typing import List, Optional, Tuple
_OPS = {
ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod, ast.Pow: operator.pow,
ast.USub: operator.neg, ast.UAdd: operator.pos,
}
_MAX_EXPR_LEN = 200
_MAX_OPERAND_DIGITS = 18
_MAX_POW_EXP = 16
_MAX_POW_BASE_DIGITS = 9
_MAX_RESULT_DIGITS = 80
def _safe_eval(node):
if isinstance(node, ast.Expression):
return _safe_eval(node.body)
if isinstance(node, ast.Constant):
if isinstance(node.value, (int, float)) and not isinstance(node.value, bool):
return node.value
raise ValueError("non-numeric constant")
if isinstance(node, ast.BinOp):
op = _OPS.get(type(node.op))
if op is None:
raise ValueError("unsupported operator")
left, right = _safe_eval(node.left), _safe_eval(node.right)
if isinstance(node.op, ast.Pow):
if abs(right) > _MAX_POW_EXP or len(str(abs(int(left)))) > _MAX_POW_BASE_DIGITS:
raise ValueError("power out of safe range")
if isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod)) and right == 0:
raise ZeroDivisionError("division by zero")
return op(left, right)
if isinstance(node, ast.UnaryOp):
op = _OPS.get(type(node.op))
if op is None:
raise ValueError("unsupported unary operator")
return op(_safe_eval(node.operand))
raise ValueError("unsupported expression node")
def compute_expression(expression: str):
expr = (expression or "").strip()
if not expr or len(expr) > _MAX_EXPR_LEN:
return None
try:
val = _safe_eval(ast.parse(expr, mode="eval"))
if isinstance(val, (int, float)):
as_text = f"{int(val):d}" if float(val).is_integer() else f"{val}"
if len(as_text) > _MAX_RESULT_DIGITS:
return None
return val
except Exception:
return None
return None
_WORD_OPS = [
(r"\bmultiplied\s+by\b", " * "), (r"\btimes\b", " * "),
(r"\bdivided\s+by\b", " / "), (r"\bplus\b", " + "), (r"\bminus\b", " - "),
(r"\bto\s+the\s+power\s+of\b", " ** "), (r"\bsquared\b", " ** 2 "),
(r"\bcubed\b", " ** 3 "), (r"\bmodulo\b|\bmod\b", " % "),
]
def _normalise(text: str) -> str:
t = re.sub(r"(?<=\d),(?=\d{3}\b)", "", text) # 847,263 -> 847263
t = t.replace("×", " * ").replace("÷", " / ").replace("^", " ** ")
t = re.sub(r"(?<=\d)\s*[xX]\s*(?=\d)", " * ", t) # 5 x 3
for pat, rep in _WORD_OPS:
t = re.sub(pat, rep, t, flags=re.IGNORECASE)
t = re.sub(r"(\d+(?:\.\d+)?)\s*(?:%|percent)\s+of\s+(\d+(?:\.\d+)?)",
r"((\1/100)*\2)", t, flags=re.IGNORECASE) # 15% of 240
return t
_CANDIDATE_RX = re.compile(r"[\d\.\(][\d\s\.\+\-\*\/\%\(\)]{2,}[\d\)]")
def _extract_candidates(text: str) -> List[str]:
out: List[str] = []
for m in _CANDIDATE_RX.finditer(text):
cand = m.group(0).strip()
if not re.search(r"\d[\s\)]*[\+\-\*\/\%]|\*\*", cand):
continue
if len(re.findall(r"\d+(?:\.\d+)?", cand)) < 2:
continue
if re.fullmatch(r"[\d\.\s]+", cand):
continue
out.append(cand)
if len(out) >= 4:
break
return out
def _fmt(val) -> str:
if isinstance(val, float) and val.is_integer():
val = int(val)
if isinstance(val, int):
return f"{val:,}"
return f"{float(val):.12g}"
_MATHY_HINT_RX = re.compile(
r"\b(multipl|divid|calculat|compute|arithmetic|sum of|product of|"
r"plus|minus|times|squared|cubed|percent|exactly \d)\w*", re.IGNORECASE)
def inspect_message(message: str) -> Tuple[List[str], bool]:
"""Return (verified_lines, looked_mathy_but_unverifiable). Never raises."""
try:
if not message or len(message) > 8000:
return [], False
cands = _extract_candidates(_normalise(message))
lines: List[str] = []
failed = 0
for cand in cands:
val = compute_expression(cand)
if val is None:
failed += 1
continue
cleaned = re.sub(r'\s+', ' ', cand).strip()
lines.append(f"[MATH_VERIFIED: {cleaned} = {_fmt(val)}]")
looked_mathy = bool(_MATHY_HINT_RX.search(message)) and bool(re.search(r"\d{2,}", message))
unverifiable = (not lines) and (failed > 0 or looked_mathy) and bool(re.search(r"\d", message))
return lines, unverifiable
except Exception:
return [], False
def prompt_note(message: str) -> str:
"""A note to append to the model prompt. Empty string when no math involved."""
lines, unverifiable = inspect_message(message)
if lines:
return ("\n\n(Your calculator hand — a real math engine — already computed the "
"arithmetic above, exactly: " + " ".join(lines) + " State these digits as "
"the true answer; do not recompute them in your head or change any digit.)")
if unverifiable:
return ("\n\n(The person asked for arithmetic your calculator hand could not verify. "
"Be honest: say you cannot verify that calculation precisely right now, and "
"offer to work it step by step. Never guess digits with fake confidence.)")
return ""
if __name__ == "__main__":
tests = [
("What is exactly 847263 multiplied by 391847?", "331,997,464,761"),
("compute 847,263 times 391,847 please", "331,997,464,761"),
("50 * 2 + 10?", "110"),
("what is 15% of 240?", "36"),
("22 divided by 7", "3.14285714286"),
("10 to the power of 99999999?", None),
("tell me about your day", None),
]
ok = True
for msg, want in tests:
lines, unv = inspect_message(msg)
got = lines[0].rsplit("= ", 1)[-1].rstrip("]") if lines else None
good = (got == want) if want else (not lines)
ok &= good
print(f" [{'PASS' if good else 'FAIL'}] {msg[:44]!r} -> {got} unverifiable={unv}")
print("ALL PASS" if ok else "SOME FAILED")