LangChain Tools with Secure Code Execution Using HopX
LangChain's built-in PythonREPL tool has a big problem: it runs code directly on your machine. One hallucinated rm -rf / and your server is gone.
This guide shows you how to replace LangChain's dangerous code execution with secure, isolated HopX sandboxes—while keeping the familiar LangChain patterns you already know.
The Problem with LangChain's Default Code Execution
LangChain includes a PythonREPLTool that lets agents execute Python code:
| 1 | # ⚠️ DANGEROUS - Don't use in production |
| 2 | from langchain_experimental.tools import PythonREPLTool |
| 3 | |
| 4 | tool = PythonREPLTool() |
| 5 | result = tool.run("import os; os.system('rm -rf /')") # Game over |
| 6 | |
This executes code directly on your host machine with full access to:
- Your filesystem
- Network connections
- Environment variables (including API keys)
- System processes
In production, this is a ticking time bomb.
The Solution: HopX Sandboxed Execution
Replace the dangerous PythonREPLTool with a custom tool that runs code in isolated HopX sandboxes:
| 1 | ┌─────────────────────────────────────────────────────────────┐ |
| 2 | │ LangChain Agent │ |
| 3 | │ │ |
| 4 | │ "I need to run this Python code to analyze the data..." │ |
| 5 | └─────────────────────────────────────────────────────────────┘ |
| 6 | │ |
| 7 | ▼ |
| 8 | ┌─────────────────────────────────────────────────────────────┐ |
| 9 | │ HopX Sandbox Tool │ |
| 10 | │ (Custom LangChain Tool) │ |
| 11 | └─────────────────────────────────────────────────────────────┘ |
| 12 | │ |
| 13 | ▼ |
| 14 | ┌─────────────────────────────────────────────────────────────┐ |
| 15 | │ HopX MicroVM │ |
| 16 | │ ┌────────────────────────────────────────────────────┐ │ |
| 17 | │ │ Isolated execution environment │ │ |
| 18 | │ │ • No access to host filesystem │ │ |
| 19 | │ │ • Network policies enforced │ │ |
| 20 | │ │ • Resource limits applied │ │ |
| 21 | │ │ • Destroyed after execution │ │ |
| 22 | │ └────────────────────────────────────────────────────┘ │ |
| 23 | └─────────────────────────────────────────────────────────────┘ |
| 24 | │ |
| 25 | ▼ |
| 26 | Results returned to agent |
| 27 | |
Prerequisites
Install the required packages:
| 1 | pip install langchain langchain-openai hopx-ai |
| 2 | |
Set your API keys:
| 1 | export OPENAI_API_KEY="sk-..." |
| 2 | export HOPX_API_KEY="your-hopx-key" |
| 3 | |
Step 1: Create a Secure Python Execution Tool
First, let's build a LangChain-compatible tool that executes code in HopX:
| 1 | from langchain.tools import BaseTool |
| 2 | from hopx import Sandbox |
| 3 | from pydantic import BaseModel, Field |
| 4 | from typing import Type, Optional |
| 5 | |
| 6 | class PythonCodeInput(BaseModel): |
| 7 | """Input schema for Python code execution.""" |
| 8 | code: str = Field(description="The Python code to execute") |
| 9 | |
| 10 | class SecurePythonTool(BaseTool): |
| 11 | """Execute Python code securely in an isolated HopX sandbox.""" |
| 12 | |
| 13 | name: str = "python_executor" |
| 14 | description: str = """Execute Python code in a secure, isolated sandbox. |
| 15 | Use this tool when you need to: |
| 16 | - Perform calculations or data analysis |
| 17 | - Process files or data structures |
| 18 | - Run any Python code safely |
| 19 | |
| 20 | The sandbox has pandas, numpy, matplotlib, requests, and standard libraries. |
| 21 | For visualizations, save to /app/output.png. |
| 22 | """ |
| 23 | args_schema: Type[BaseModel] = PythonCodeInput |
| 24 | |
| 25 | # Sandbox configuration |
| 26 | template: str = "code-interpreter" |
| 27 | timeout: int = 60 |
| 28 | |
| 29 | def _run(self, code: str) -> str: |
| 30 | """Execute code in isolated sandbox.""" |
| 31 | sandbox = None |
| 32 | try: |
| 33 | # Create isolated sandbox |
| 34 | sandbox = Sandbox.create(template=self.template) |
| 35 | |
| 36 | # Execute code with timeout |
| 37 | result = sandbox.runCode(code, language="python", timeout=self.timeout) |
| 38 | |
| 39 | # Format output |
| 40 | if result.exitCode == 0: |
| 41 | output = result.stdout or "Code executed successfully (no output)" |
| 42 | return f"✅ Execution successful:\n{output}" |
| 43 | else: |
| 44 | error = result.stderr or "Unknown error" |
| 45 | return f"❌ Execution failed:\n{error}" |
| 46 | |
| 47 | except Exception as e: |
| 48 | return f"❌ Sandbox error: {str(e)}" |
| 49 | |
| 50 | finally: |
| 51 | if sandbox: |
| 52 | sandbox.kill() |
| 53 | |
| 54 | async def _arun(self, code: str) -> str: |
| 55 | """Async version - runs sync for simplicity.""" |
| 56 | return self._run(code) |
| 57 | |
Step 2: Build an Agent with Secure Code Execution
Now create a LangChain agent using our secure tool:
| 1 | from langchain_openai import ChatOpenAI |
| 2 | from langchain.agents import AgentExecutor, create_openai_tools_agent |
| 3 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
| 4 | |
| 5 | # Initialize the LLM |
| 6 | llm = ChatOpenAI(model="gpt-4o", temperature=0) |
| 7 | |
| 8 | # Create the secure tool |
| 9 | python_tool = SecurePythonTool() |
| 10 | |
| 11 | # Define the prompt |
| 12 | prompt = ChatPromptTemplate.from_messages([ |
| 13 | ("system", """You are a helpful AI assistant with access to a Python executor. |
| 14 | |
| 15 | When users ask questions that require computation, data analysis, or code execution: |
| 16 | 1. Write clear, well-commented Python code |
| 17 | 2. Use the python_executor tool to run it |
| 18 | 3. Analyze the results and provide a helpful response |
| 19 | |
| 20 | Available libraries: pandas, numpy, matplotlib, seaborn, requests, json, csv, datetime |
| 21 | |
| 22 | Tips: |
| 23 | - Always print() results you want to see |
| 24 | - For charts, save to /app/output.png using plt.savefig() |
| 25 | - Handle potential errors in your code |
| 26 | """), |
| 27 | MessagesPlaceholder(variable_name="chat_history", optional=True), |
| 28 | ("human", "{input}"), |
| 29 | MessagesPlaceholder(variable_name="agent_scratchpad"), |
| 30 | ]) |
| 31 | |
| 32 | # Create the agent |
| 33 | agent = create_openai_tools_agent(llm, [python_tool], prompt) |
| 34 | agent_executor = AgentExecutor( |
| 35 | agent=agent, |
| 36 | tools=[python_tool], |
| 37 | verbose=True, |
| 38 | max_iterations=5 |
| 39 | ) |
| 40 | |
| 41 | # Run it |
| 42 | response = agent_executor.invoke({ |
| 43 | "input": "Calculate the first 50 prime numbers and their sum" |
| 44 | }) |
| 45 | |
| 46 | print(response["output"]) |
| 47 | |
Step 3: Add Multiple Tools
Real agents need more than just Python execution. Here's how to combine tools:
| 1 | from langchain.tools import BaseTool |
| 2 | from hopx import Sandbox |
| 3 | from pydantic import BaseModel, Field |
| 4 | from typing import Type |
| 5 | |
| 6 | class BashCommandInput(BaseModel): |
| 7 | """Input for bash commands.""" |
| 8 | command: str = Field(description="The bash command to execute") |
| 9 | |
| 10 | class SecureBashTool(BaseTool): |
| 11 | """Execute bash commands in isolated sandbox.""" |
| 12 | |
| 13 | name: str = "bash_executor" |
| 14 | description: str = """Execute bash/shell commands securely. |
| 15 | Use for: file operations, system commands, package installation. |
| 16 | Example: ls -la, cat file.txt, pip install package |
| 17 | """ |
| 18 | args_schema: Type[BaseModel] = BashCommandInput |
| 19 | template: str = "code-interpreter" |
| 20 | |
| 21 | def _run(self, command: str) -> str: |
| 22 | sandbox = None |
| 23 | try: |
| 24 | sandbox = Sandbox.create(template=self.template) |
| 25 | result = sandbox.runCode(command, language="bash", timeout=60) |
| 26 | |
| 27 | if result.exitCode == 0: |
| 28 | return f"✅ Command succeeded:\n{result.stdout}" |
| 29 | else: |
| 30 | return f"❌ Command failed (exit {result.exitCode}):\n{result.stderr}" |
| 31 | except Exception as e: |
| 32 | return f"❌ Error: {str(e)}" |
| 33 | finally: |
| 34 | if sandbox: |
| 35 | sandbox.kill() |
| 36 | |
| 37 | class FileReadInput(BaseModel): |
| 38 | """Input for reading files.""" |
| 39 | path: str = Field(description="Path to the file to read") |
| 40 | |
| 41 | class SecureFileReadTool(BaseTool): |
| 42 | """Read files from the sandbox.""" |
| 43 | |
| 44 | name: str = "read_file" |
| 45 | description: str = "Read the contents of a file. Use after creating or downloading files." |
| 46 | args_schema: Type[BaseModel] = FileReadInput |
| 47 | |
| 48 | def _run(self, path: str) -> str: |
| 49 | sandbox = None |
| 50 | try: |
| 51 | sandbox = Sandbox.create(template="code-interpreter") |
| 52 | content = sandbox.files.read(path) |
| 53 | return f"File contents:\n{content[:10000]}" # Truncate large files |
| 54 | except Exception as e: |
| 55 | return f"❌ Could not read file: {str(e)}" |
| 56 | finally: |
| 57 | if sandbox: |
| 58 | sandbox.kill() |
| 59 | |
| 60 | # Create multi-tool agent |
| 61 | tools = [ |
| 62 | SecurePythonTool(), |
| 63 | SecureBashTool(), |
| 64 | SecureFileReadTool(), |
| 65 | ] |
| 66 | |
| 67 | agent = create_openai_tools_agent(llm, tools, prompt) |
| 68 | agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) |
| 69 | |
Step 4: Persistent Sandbox for Multi-Step Tasks
For complex tasks that require multiple code executions, reuse the same sandbox:
| 1 | from langchain.tools import BaseTool |
| 2 | from hopx import Sandbox |
| 3 | from typing import Optional |
| 4 | import threading |
| 5 | |
| 6 | class PersistentSandboxManager: |
| 7 | """Manage a persistent sandbox for multi-step execution.""" |
| 8 | |
| 9 | _instance: Optional['PersistentSandboxManager'] = None |
| 10 | _lock = threading.Lock() |
| 11 | |
| 12 | def __init__(self): |
| 13 | self.sandbox: Optional[Sandbox] = None |
| 14 | self.ttl = 300 # 5 minutes |
| 15 | |
| 16 | @classmethod |
| 17 | def get_instance(cls) -> 'PersistentSandboxManager': |
| 18 | if cls._instance is None: |
| 19 | with cls._lock: |
| 20 | if cls._instance is None: |
| 21 | cls._instance = cls() |
| 22 | return cls._instance |
| 23 | |
| 24 | def get_sandbox(self) -> Sandbox: |
| 25 | """Get or create sandbox.""" |
| 26 | if self.sandbox is None: |
| 27 | self.sandbox = Sandbox.create( |
| 28 | template="code-interpreter", |
| 29 | ttl=self.ttl |
| 30 | ) |
| 31 | return self.sandbox |
| 32 | |
| 33 | def reset(self): |
| 34 | """Destroy and recreate sandbox.""" |
| 35 | if self.sandbox: |
| 36 | try: |
| 37 | self.sandbox.kill() |
| 38 | except: |
| 39 | pass |
| 40 | self.sandbox = None |
| 41 | |
| 42 | |
| 43 | class PersistentPythonTool(BaseTool): |
| 44 | """Python tool with persistent sandbox state.""" |
| 45 | |
| 46 | name: str = "python" |
| 47 | description: str = """Execute Python code with persistent state. |
| 48 | Variables and imports persist between calls. |
| 49 | Use for multi-step data analysis where you need to build on previous results. |
| 50 | """ |
| 51 | |
| 52 | def _run(self, code: str) -> str: |
| 53 | manager = PersistentSandboxManager.get_instance() |
| 54 | |
| 55 | try: |
| 56 | sandbox = manager.get_sandbox() |
| 57 | result = sandbox.runCode(code, language="python", timeout=60) |
| 58 | |
| 59 | if result.exitCode == 0: |
| 60 | return result.stdout or "Executed (no output)" |
| 61 | else: |
| 62 | return f"Error: {result.stderr}" |
| 63 | |
| 64 | except Exception as e: |
| 65 | # Sandbox might have expired, reset and retry |
| 66 | manager.reset() |
| 67 | return f"Sandbox error (will retry with fresh sandbox): {str(e)}" |
| 68 | |
Step 5: Data Analysis Agent with File Handling
Here's a complete example for data analysis tasks:
| 1 | from langchain_openai import ChatOpenAI |
| 2 | from langchain.agents import AgentExecutor, create_openai_tools_agent |
| 3 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
| 4 | from langchain.tools import BaseTool |
| 5 | from hopx import Sandbox |
| 6 | from pydantic import BaseModel, Field |
| 7 | from typing import Type, Optional |
| 8 | import base64 |
| 9 | |
| 10 | class DataAnalysisTool(BaseTool): |
| 11 | """Comprehensive data analysis tool with file support.""" |
| 12 | |
| 13 | name: str = "analyze_data" |
| 14 | description: str = """Analyze data using Python in a secure sandbox. |
| 15 | |
| 16 | Capabilities: |
| 17 | - Load CSV, JSON, Excel files |
| 18 | - Statistical analysis with pandas |
| 19 | - Visualizations with matplotlib/seaborn |
| 20 | - Machine learning with scikit-learn |
| 21 | |
| 22 | Input your Python code. For charts, save to /app/chart.png. |
| 23 | Uploaded files are available at /app/data/ |
| 24 | """ |
| 25 | |
| 26 | # Keep sandbox alive for the session |
| 27 | sandbox: Optional[Sandbox] = None |
| 28 | |
| 29 | def get_sandbox(self) -> Sandbox: |
| 30 | if self.sandbox is None: |
| 31 | self.sandbox = Sandbox.create( |
| 32 | template="code-interpreter", |
| 33 | ttl=300 # 5 minute TTL |
| 34 | ) |
| 35 | return self.sandbox |
| 36 | |
| 37 | def _run(self, code: str) -> str: |
| 38 | try: |
| 39 | sandbox = self.get_sandbox() |
| 40 | result = sandbox.runCode(code, language="python", timeout=120) |
| 41 | |
| 42 | output_parts = [] |
| 43 | |
| 44 | if result.stdout: |
| 45 | output_parts.append(f"Output:\n{result.stdout}") |
| 46 | |
| 47 | if result.stderr and result.exitCode != 0: |
| 48 | output_parts.append(f"Error:\n{result.stderr}") |
| 49 | |
| 50 | # Check if a chart was created |
| 51 | try: |
| 52 | chart_data = sandbox.files.read("/app/chart.png") |
| 53 | output_parts.append("\n📊 Chart saved to /app/chart.png") |
| 54 | except: |
| 55 | pass |
| 56 | |
| 57 | return "\n\n".join(output_parts) or "Code executed successfully" |
| 58 | |
| 59 | except Exception as e: |
| 60 | self.sandbox = None # Reset on error |
| 61 | return f"Execution error: {str(e)}" |
| 62 | |
| 63 | def upload_data(self, filename: str, content: bytes): |
| 64 | """Upload data file to sandbox.""" |
| 65 | sandbox = self.get_sandbox() |
| 66 | sandbox.files.write(f"/app/data/{filename}", content) |
| 67 | |
| 68 | def cleanup(self): |
| 69 | """Destroy sandbox when done.""" |
| 70 | if self.sandbox: |
| 71 | self.sandbox.kill() |
| 72 | self.sandbox = None |
| 73 | |
| 74 | |
| 75 | # Create the analysis agent |
| 76 | llm = ChatOpenAI(model="gpt-4o", temperature=0) |
| 77 | data_tool = DataAnalysisTool() |
| 78 | |
| 79 | analysis_prompt = ChatPromptTemplate.from_messages([ |
| 80 | ("system", """You are an expert data analyst assistant. |
| 81 | |
| 82 | When users provide data or ask analytical questions: |
| 83 | 1. First explore the data structure (head, info, describe) |
| 84 | 2. Perform the requested analysis |
| 85 | 3. Create visualizations when appropriate |
| 86 | 4. Explain your findings clearly |
| 87 | |
| 88 | Always show your work with code. Use pandas for data manipulation. |
| 89 | Save visualizations to /app/chart.png using plt.savefig('/app/chart.png', dpi=150, bbox_inches='tight') |
| 90 | """), |
| 91 | ("human", "{input}"), |
| 92 | MessagesPlaceholder(variable_name="agent_scratchpad"), |
| 93 | ]) |
| 94 | |
| 95 | agent = create_openai_tools_agent(llm, [data_tool], analysis_prompt) |
| 96 | data_agent = AgentExecutor(agent=agent, tools=[data_tool], verbose=True) |
| 97 | |
| 98 | # Example: Multi-step analysis |
| 99 | response = data_agent.invoke({ |
| 100 | "input": """Create a sample sales dataset with: |
| 101 | - 500 rows |
| 102 | - Columns: date, product, region, quantity, revenue |
| 103 | - Random but realistic data |
| 104 | |
| 105 | Then: |
| 106 | 1. Show basic statistics |
| 107 | 2. Find top 5 products by revenue |
| 108 | 3. Create a bar chart of revenue by region |
| 109 | """ |
| 110 | }) |
| 111 | |
| 112 | print(response["output"]) |
| 113 | |
| 114 | # Cleanup |
| 115 | data_tool.cleanup() |
| 116 | |
LangChain Expression Language (LCEL) Integration
For more complex chains, integrate with LCEL:
| 1 | from langchain_core.runnables import RunnablePassthrough, RunnableLambda |
| 2 | from langchain_core.output_parsers import StrOutputParser |
| 3 | from langchain_openai import ChatOpenAI |
| 4 | from hopx import Sandbox |
| 5 | |
| 6 | def execute_code_safely(code: str) -> str: |
| 7 | """Execute code in sandbox and return result.""" |
| 8 | sandbox = Sandbox.create(template="code-interpreter") |
| 9 | try: |
| 10 | result = sandbox.runCode(code, language="python", timeout=60) |
| 11 | return result.stdout if result.exitCode == 0 else f"Error: {result.stderr}" |
| 12 | finally: |
| 13 | sandbox.kill() |
| 14 | |
| 15 | # Build an LCEL chain that generates and executes code |
| 16 | generate_code_prompt = ChatPromptTemplate.from_messages([ |
| 17 | ("system", "You are a Python expert. Generate ONLY executable Python code, no explanations."), |
| 18 | ("human", "Write Python code to: {task}") |
| 19 | ]) |
| 20 | |
| 21 | explain_result_prompt = ChatPromptTemplate.from_messages([ |
| 22 | ("system", "Explain the following code execution result in plain English."), |
| 23 | ("human", "Task: {task}\n\nCode result:\n{result}") |
| 24 | ]) |
| 25 | |
| 26 | llm = ChatOpenAI(model="gpt-4o") |
| 27 | |
| 28 | # Chain: Generate code → Execute → Explain |
| 29 | chain = ( |
| 30 | {"task": RunnablePassthrough()} |
| 31 | | RunnablePassthrough.assign( |
| 32 | code=generate_code_prompt | llm | StrOutputParser() |
| 33 | ) |
| 34 | | RunnablePassthrough.assign( |
| 35 | result=lambda x: execute_code_safely(x["code"]) |
| 36 | ) |
| 37 | | explain_result_prompt |
| 38 | | llm |
| 39 | | StrOutputParser() |
| 40 | ) |
| 41 | |
| 42 | # Run it |
| 43 | result = chain.invoke("Calculate the factorial of 20 and check if it's divisible by 7") |
| 44 | print(result) |
| 45 | |
Error Handling and Retry Logic
Production agents need robust error handling:
| 1 | from langchain.tools import BaseTool |
| 2 | from hopx import Sandbox |
| 3 | from typing import Optional |
| 4 | import time |
| 5 | |
| 6 | class RobustPythonTool(BaseTool): |
| 7 | """Python execution with retry logic and error recovery.""" |
| 8 | |
| 9 | name: str = "python" |
| 10 | description: str = "Execute Python code with automatic error recovery" |
| 11 | |
| 12 | max_retries: int = 3 |
| 13 | retry_delay: float = 1.0 |
| 14 | |
| 15 | def _run(self, code: str) -> str: |
| 16 | last_error = None |
| 17 | |
| 18 | for attempt in range(self.max_retries): |
| 19 | sandbox = None |
| 20 | try: |
| 21 | sandbox = Sandbox.create(template="code-interpreter") |
| 22 | result = sandbox.runCode(code, language="python", timeout=60) |
| 23 | |
| 24 | if result.exitCode == 0: |
| 25 | return result.stdout or "Success (no output)" |
| 26 | else: |
| 27 | # Code error - don't retry, return error for LLM to fix |
| 28 | return f"Code error:\n{result.stderr}" |
| 29 | |
| 30 | except Exception as e: |
| 31 | last_error = str(e) |
| 32 | if attempt < self.max_retries - 1: |
| 33 | time.sleep(self.retry_delay) |
| 34 | continue |
| 35 | |
| 36 | finally: |
| 37 | if sandbox: |
| 38 | try: |
| 39 | sandbox.kill() |
| 40 | except: |
| 41 | pass |
| 42 | |
| 43 | return f"Sandbox failed after {self.max_retries} attempts: {last_error}" |
| 44 | |
Comparing with Built-in PythonREPL
| Feature | LangChain PythonREPL | HopX Secure Tool |
|---|---|---|
| Isolation | ❌ Runs on host | ✅ Isolated microVM |
| Security | ❌ Full system access | ✅ No host access |
| Resource Limits | ❌ Unlimited | ✅ CPU/memory limits |
| Network Control | ❌ Open | ✅ Configurable policies |
| Cleanup | ❌ Artifacts persist | ✅ VM destroyed |
| Speed | ✅ Instant | ✅ ~100ms startup |
| State Persistence | ✅ Session state | ✅ With persistent sandbox |
Best Practices
1. Always Set Timeouts
| 1 | result = sandbox.runCode(code, language="python", timeout=60) |
| 2 | |
2. Limit Output Size
| 1 | def _run(self, code: str) -> str: |
| 2 | result = sandbox.runCode(code, language="python", timeout=60) |
| 3 | output = result.stdout[:10000] # Truncate large outputs |
| 4 | return output |
| 5 | |
3. Use Custom Templates for Specialized Tasks
| 1 | # For data science tasks |
| 2 | sandbox = Sandbox.create(template="data-science") |
| 3 | |
| 4 | # For web scraping |
| 5 | sandbox = Sandbox.create(template="web-scraper") |
| 6 | |
4. Implement Conversation Memory
| 1 | from langchain.memory import ConversationBufferMemory |
| 2 | |
| 3 | memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) |
| 4 | |
| 5 | agent_executor = AgentExecutor( |
| 6 | agent=agent, |
| 7 | tools=tools, |
| 8 | memory=memory, |
| 9 | verbose=True |
| 10 | ) |
| 11 | |
5. Log All Executions
| 1 | import logging |
| 2 | |
| 3 | logger = logging.getLogger("secure_python_tool") |
| 4 | |
| 5 | def _run(self, code: str) -> str: |
| 6 | logger.info(f"Executing code: {code[:100]}...") |
| 7 | result = self._execute(code) |
| 8 | logger.info(f"Result: {result[:100]}...") |
| 9 | return result |
| 10 | |
Complete Working Example
Here's a production-ready implementation:
| 1 | """ |
| 2 | Secure LangChain Agent with HopX Code Execution |
| 3 | """ |
| 4 | |
| 5 | from langchain_openai import ChatOpenAI |
| 6 | from langchain.agents import AgentExecutor, create_openai_tools_agent |
| 7 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
| 8 | from langchain.tools import BaseTool |
| 9 | from langchain.memory import ConversationBufferMemory |
| 10 | from hopx import Sandbox |
| 11 | from pydantic import BaseModel, Field |
| 12 | from typing import Type, Optional |
| 13 | import os |
| 14 | |
| 15 | # Ensure API keys are set |
| 16 | assert os.environ.get("OPENAI_API_KEY"), "Set OPENAI_API_KEY" |
| 17 | assert os.environ.get("HOPX_API_KEY"), "Set HOPX_API_KEY" |
| 18 | |
| 19 | |
| 20 | class CodeInput(BaseModel): |
| 21 | code: str = Field(description="Python code to execute") |
| 22 | |
| 23 | |
| 24 | class SecurePythonExecutor(BaseTool): |
| 25 | name: str = "execute_python" |
| 26 | description: str = """Execute Python code in a secure isolated sandbox. |
| 27 | |
| 28 | Available: pandas, numpy, matplotlib, seaborn, scikit-learn, requests. |
| 29 | For charts: plt.savefig('/app/chart.png') |
| 30 | Print results you want to see.""" |
| 31 | |
| 32 | args_schema: Type[BaseModel] = CodeInput |
| 33 | sandbox: Optional[Sandbox] = None |
| 34 | |
| 35 | def get_or_create_sandbox(self) -> Sandbox: |
| 36 | if self.sandbox is None: |
| 37 | self.sandbox = Sandbox.create(template="code-interpreter", ttl=300) |
| 38 | return self.sandbox |
| 39 | |
| 40 | def _run(self, code: str) -> str: |
| 41 | try: |
| 42 | sandbox = self.get_or_create_sandbox() |
| 43 | result = sandbox.runCode(code, language="python", timeout=60) |
| 44 | |
| 45 | if result.exitCode == 0: |
| 46 | return result.stdout or "✅ Executed successfully" |
| 47 | return f"❌ Error:\n{result.stderr}" |
| 48 | |
| 49 | except Exception as e: |
| 50 | self.sandbox = None |
| 51 | return f"❌ Sandbox error: {e}" |
| 52 | |
| 53 | def cleanup(self): |
| 54 | if self.sandbox: |
| 55 | self.sandbox.kill() |
| 56 | self.sandbox = None |
| 57 | |
| 58 | |
| 59 | def create_secure_agent(): |
| 60 | """Create a LangChain agent with secure code execution.""" |
| 61 | |
| 62 | llm = ChatOpenAI(model="gpt-4o", temperature=0) |
| 63 | tool = SecurePythonExecutor() |
| 64 | |
| 65 | prompt = ChatPromptTemplate.from_messages([ |
| 66 | ("system", """You are a helpful AI assistant that can execute Python code safely. |
| 67 | |
| 68 | When you need to compute, analyze data, or run code: |
| 69 | 1. Write clear Python code |
| 70 | 2. Use the execute_python tool |
| 71 | 3. Explain the results |
| 72 | |
| 73 | Be concise and helpful."""), |
| 74 | MessagesPlaceholder(variable_name="chat_history", optional=True), |
| 75 | ("human", "{input}"), |
| 76 | MessagesPlaceholder(variable_name="agent_scratchpad"), |
| 77 | ]) |
| 78 | |
| 79 | agent = create_openai_tools_agent(llm, [tool], prompt) |
| 80 | memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) |
| 81 | |
| 82 | return AgentExecutor( |
| 83 | agent=agent, |
| 84 | tools=[tool], |
| 85 | memory=memory, |
| 86 | verbose=True, |
| 87 | max_iterations=5 |
| 88 | ), tool |
| 89 | |
| 90 | |
| 91 | if __name__ == "__main__": |
| 92 | agent, tool = create_secure_agent() |
| 93 | |
| 94 | try: |
| 95 | # Example conversation |
| 96 | print("\n" + "="*60) |
| 97 | response = agent.invoke({"input": "What's 2^100 exactly?"}) |
| 98 | print(f"\nAgent: {response['output']}") |
| 99 | |
| 100 | print("\n" + "="*60) |
| 101 | response = agent.invoke({ |
| 102 | "input": "Create a list of the first 10 fibonacci numbers and calculate their average" |
| 103 | }) |
| 104 | print(f"\nAgent: {response['output']}") |
| 105 | |
| 106 | finally: |
| 107 | tool.cleanup() |
| 108 | |
Conclusion
By replacing LangChain's PythonREPLTool with HopX sandboxed execution, you get:
- Security: Code runs in isolated microVMs, not your host
- Same API: Drop-in replacement for existing LangChain patterns
- Production-ready: Timeouts, error handling, resource limits
- Flexibility: Custom tools for any use case
The LLM gets the power of code execution. Your infrastructure stays safe.
Ready to secure your LangChain agents? Get started with HopX — sandboxes that spin up in 100ms.
Further Reading
- Build a Code Interpreter Agent — Full tutorial with OpenAI
- Tool Use Pattern — Deep dive into LLM tools
- Why AI Agents Need Isolated Execution — Security fundamentals
- LangChain Documentation — Official LangChain docs
- Custom Templates — Pre-configure your sandbox environment