mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
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:
parent
e2397076a2
commit
3797ec6082
7 changed files with 150 additions and 0 deletions
52
api/app/clients/tools/CodeInterpreter.js
Normal file
52
api/app/clients/tools/CodeInterpreter.js
Normal 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;
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
11
pyserver/Dockerfile
Normal 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"]
|
4
pyserver/requirements.txt
Normal file
4
pyserver/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
numpy
|
||||
matplotlib
|
||||
websockets
|
||||
pandas
|
65
pyserver/server.py
Normal file
65
pyserver/server.py
Normal 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"" 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()
|
Loading…
Add table
Add a link
Reference in a new issue