Spaces:
Running
Running
| """ | |
| CodeInterpreterTool β local sandboxed Python execution. | |
| Replaces the crewai_tools.CodeInterpreterTool removed in crewai v1.x. | |
| Runs Python code in a subprocess with a strict timeout and whitelist. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import subprocess | |
| import sys | |
| import textwrap | |
| from pathlib import Path | |
| from crewai.tools import BaseTool | |
| from loguru import logger | |
| # βββ Allowlist of safe imports ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _ALLOWED_IMPORTS = frozenset( | |
| [ | |
| "pandas", "numpy", "math", "statistics", "json", "csv", "io", | |
| "datetime", "re", "collections", "itertools", "functools", | |
| "matplotlib", "matplotlib.pyplot", | |
| ] | |
| ) | |
| _BLOCKED_KEYWORDS = frozenset( | |
| [ | |
| "__import__", "exec", "eval", "compile", "open", "os", "sys", | |
| "subprocess", "socket", "urllib", "requests", "shutil", "glob", | |
| "importlib", "ctypes", "pickle", "marshal", | |
| ] | |
| ) | |
| def _is_safe_code(code: str) -> tuple[bool, str]: | |
| """Basic static check β not a full sandbox, just a sanity layer.""" | |
| for kw in _BLOCKED_KEYWORDS: | |
| if kw in code: | |
| return False, f"Blocked keyword in code: '{kw}'" | |
| return True, "" | |
| class CodeInterpreterTool(BaseTool): | |
| """ | |
| Sandboxed Python code execution tool for financial calculations. | |
| Implements the interface of the deprecated crewai_tools.CodeInterpreterTool. | |
| Accepts Python code as a string. Returns stdout + last expression result. | |
| """ | |
| name: str = "code_interpreter" | |
| description: str = ( | |
| "Execute Python code for financial calculations. " | |
| "Supports pandas, numpy, math, statistics, datetime. " | |
| "Input: a string of Python code. " | |
| "Output: stdout output and/or the result of the last expression. " | |
| "Use for: revenue projections, unit economics, CAC/LTV, burn rate, market sizing." | |
| ) | |
| timeout_seconds: int = 30 | |
| def _run(self, code: str) -> str: | |
| """Execute Python code in a subprocess and return the output.""" | |
| if not code or not code.strip(): | |
| return "Error: no code provided." | |
| safe, reason = _is_safe_code(code) | |
| if not safe: | |
| return f"Error: unsafe code detected β {reason}" | |
| # Wrap in a try/except and auto-print last expression | |
| wrapped = textwrap.dedent(f""" | |
| import pandas as pd | |
| import numpy as np | |
| import math | |
| import statistics | |
| import json | |
| import csv | |
| import io | |
| import datetime | |
| import re | |
| import collections | |
| import itertools | |
| import functools | |
| try: | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| except Exception: | |
| pass | |
| # βββ User code ββββββββββββββββββββββββ | |
| {code} | |
| """).strip() | |
| try: | |
| result = subprocess.run( | |
| [sys.executable, "-c", wrapped], | |
| capture_output=True, | |
| text=True, | |
| timeout=self.timeout_seconds, | |
| ) | |
| output = result.stdout.strip() | |
| err = result.stderr.strip() | |
| if result.returncode != 0: | |
| # Surface the traceback but strip local paths | |
| safe_err = "\n".join( | |
| ln for ln in err.splitlines() | |
| if "/home/" not in ln and "/tmp/" not in ln | |
| ) | |
| return f"Code execution error:\n{safe_err}" if safe_err else f"Exit code {result.returncode}" | |
| if output: | |
| return output | |
| return "(code ran successfully, no output)" | |
| except subprocess.TimeoutExpired: | |
| return f"Error: code execution timed out after {self.timeout_seconds}s." | |
| except Exception as exc: | |
| logger.error(f"CodeInterpreterTool: {exc}") | |
| return f"Error: {exc}" | |