45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
"""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.
|
|
You MUST print() the result. `math` and `statistics` are importable.
|
|
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()?)"
|