"""run_python — the calculator. Agents call this for ALL arithmetic instead of doing mental math (LLMs are unreliable at it). Executes in an isolated subprocess with a hard timeout and a **scrubbed environment** (no account secrets), so model-generated code can't read API keys. For heavier data/analysis work, agents use run_analysis (tools/sandbox.py). """ from __future__ import annotations import subprocess import sys import tempfile from langchain_core.tools import tool _TIMEOUT_S = 5 @tool def run_python(code: str) -> str: """Execute a short Python snippet for EXACT math and return its stdout. Use for ANY arithmetic: position sizing, percentages, P/E, returns, averages, ratios, option breakevens. STANDARD LIBRARY ONLY (`math`, `statistics`) — NO pandas, numpy, or yfinance here; for data analysis use run_analysis instead. You MUST print() the result. Example: `print(round(1000 / 23.55, 4))`. No access to secrets; 5s limit. """ from tools.sandbox import scrubbed_env try: proc = subprocess.run( [sys.executable, "-I", "-c", code], capture_output=True, text=True, timeout=_TIMEOUT_S, env=scrubbed_env(tempfile.gettempdir()), ) except subprocess.TimeoutExpired: return f"ERROR: execution timed out ({_TIMEOUT_S}s limit)." except Exception as e: # pragma: no cover - defensive return f"ERROR: {type(e).__name__}: {e}" if proc.returncode != 0: return f"ERROR: {proc.stderr.strip() or 'non-zero exit'}" out = proc.stdout.strip() return out or "(no output — did you forget to print()?)"