feat: Add Code Interpreter Plugin (#837)

* feat: Add Code Interpreter Plugin

Adds a Simple Code Interpreter Plugin.
## Features:
- Runs code using local Python Environment

## Issues
- Code execution is not sandboxed.

* Add Docker Sandbox for Python Server
This commit is contained in:
Ronith 2023-08-28 18:43:50 +05:30 committed by GitHub
parent e2397076a2
commit 3797ec6082
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 0 deletions

View file

@ -0,0 +1,52 @@
const { Tool } = require('langchain/tools');
const WebSocket = require('ws');
const { promisify } = require('util');
const fs = require('fs');
class CodeInterpreter extends Tool {
constructor(fields) {
super();
this.name = 'code-interpreter';
this.description = `If there is plotting or any image related tasks, save the result as .png file.
No need show the image or plot. USE print(variable_name) if you need output.You can run python codes with this plugin.You have to use print function in python code to get any result from this plugin.
This does not support user input. Even if the code has input() function, change it to an appropriate value.
You can show the user the code with input() functions. But the code passed to the plug-in should not contain input().
You should provide properly formatted code to this plugin. If the code is executed successfully, the stdout will be returned to you. You have to print that to the user, and if the user had
asked for an explanation, you have to provide one. If the output is "Error From here" or any other error message,
tell the user "Python Engine Failed" and continue with whatever you are supposed to do.`;
// Create a promisified version of fs.unlink
this.unlinkAsync = promisify(fs.unlink);
}
async _call(input) {
const websocket = new WebSocket('ws://localhost:3380'); // Update with your WebSocket server URL
// Wait until the WebSocket connection is open
await new Promise((resolve) => {
websocket.onopen = resolve;
});
// Send the Python code to the server
websocket.send(input);
// Wait for the result from the server
const result = await new Promise((resolve) => {
websocket.onmessage = (event) => {
resolve(event.data);
};
// Handle WebSocket connection closed
websocket.onclose = () => {
resolve('Python Engine Failed');
};
});
// Close the WebSocket connection
websocket.close();
return result;
}
}
module.exports = CodeInterpreter;

View file

@ -10,6 +10,8 @@ const SelfReflectionTool = require('./SelfReflection');
const AzureCognitiveSearch = require('./AzureCognitiveSearch');
const StructuredACS = require('./structured/AzureCognitiveSearch');
const availableTools = require('./manifest.json');
const CodeInterpreter = require('./CodeInterpreter');
module.exports = {
availableTools,
@ -24,4 +26,5 @@ module.exports = {
SelfReflectionTool,
AzureCognitiveSearch,
StructuredACS,
CodeInterpreter,
};

View file

@ -125,5 +125,18 @@
"description": "You need to provideq your API Key for Azure Cognitive Search."
}
]
},
{
"name": "Code Interpreter",
"pluginKey": "codeinterpreter",
"description": "Analyze files and run code online with ease",
"icon": "/assets/code.png",
"authConfig": [
{
"authField": "OPENAI_API_KEY",
"label": "OpenAI API Key",
"description": "Gets Code from Open AI API"
}
]
}
]

View file

@ -7,6 +7,7 @@ const { Calculator } = require('langchain/tools/calculator');
const { WebBrowser } = require('langchain/tools/webbrowser');
const {
availableTools,
CodeInterpreter,
AIPluginTool,
GoogleSearchAPI,
WolframAlphaAPI,
@ -76,6 +77,7 @@ const loadToolWithAuth = async (user, authFields, ToolConstructor, options = {})
const loadTools = async ({ user, model, functions = null, tools = [], options = {} }) => {
const toolConstructors = {
calculator: Calculator,
codeinterpreter: CodeInterpreter,
google: GoogleSearchAPI,
wolfram: functions ? StructuredWolfram : WolframAlphaAPI,
'dall-e': OpenAICreateImage,

11
pyserver/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM python:3.9
WORKDIR /app
COPY server.py .
COPY requirements.txt .
RUN pip install -r requirements.txt
RUN rm requirements.txt
CMD ["python", "server.py"]

View file

@ -0,0 +1,4 @@
numpy
matplotlib
websockets
pandas

65
pyserver/server.py Normal file
View file

@ -0,0 +1,65 @@
import asyncio
import websockets
import io
import sys
import os as ps
import shutil
def execute_code(code):
try:
stdout_capture = io.StringIO()
sys.stdout = stdout_capture
exec(code, globals())
stdout_output = stdout_capture.getvalue()
sys.stdout = sys.__stdout__
return stdout_output
except Exception as e:
return str(e)
def move_files():
assets_path = "/app/pyassets" # Update the assets folder path
current_dir = ps.getcwd()
moved_files = []
for filename in ps.listdir(current_dir):
full_path = ps.path.join(current_dir, filename)
if ps.path.isfile(full_path) and filename != 'server.py':
new_path = ps.path.join(assets_path, filename)
shutil.move(full_path, new_path)
moved_files.append(filename)
return moved_files
def generate_links(filenames):
base_url = ps.environ['BASE_URL']
if(base_url == ''):
return
base_url = base_url + '/assets/pyassets'
links = [f"![{filename}]({base_url}/{filename})" for filename in filenames]
return links
async def handle_client(websocket, path):
try:
code = await websocket.recv()
result = execute_code(code)
filenames = move_files()
links = generate_links(filenames)
final_result = f"{result}\n"
final_result += "\n".join(links)
await websocket.send(final_result)
except websockets.exceptions.ConnectionClosed:
pass
async def server(websocket, path):
print(f"Incoming connection from {websocket.remote_address}")
while True:
await handle_client(websocket, path)
start_server = websockets.serve(server, '0.0.0.0', 3380)
print('Server started, listening on localhost:3380')
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()