Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential fix for #388 #389

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gitignore
Binary file not shown.
2 changes: 1 addition & 1 deletion App_Function_Libraries/Gradio_UI/Audio_ingestion_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def update_prompts(preset_name):
inputs=preset_prompt,
outputs=[custom_prompt_input, system_prompt_input]
)

global_api_endpoints
api_name_input = gr.Dropdown(
choices=[None, "Local-LLM", "OpenAI", "Anthropic", "Cohere", "Groq", "DeepSeek", "Mistral", "OpenRouter",
"Llama.cpp", "Kobold", "Ooba", "Tabbyapi", "VLLM","ollama", "HuggingFace", "Custom-OpenAI-API"],
Expand Down
31 changes: 23 additions & 8 deletions App_Function_Libraries/Gradio_UI/Llamafile_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
#
# Functions:

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODELS_DIR = os.path.join(BASE_DIR, "Models")

def create_chat_with_llamafile_tab():
# Function to update model path based on selection
def on_local_model_change(selected_model: str, search_directory: str) -> str:
Expand All @@ -35,8 +38,13 @@ def update_dropdowns(search_directory: str) -> Tuple[dict, str]:
logging.debug(f"Directory does not exist: {search_directory}") # Debug print for non-existing directory
return gr.update(choices=[], value=None), "Directory does not exist."

logging.debug(f"Directory exists: {search_directory}, scanning for files...") # Confirm directory exists
model_files = get_gguf_llamafile_files(search_directory)
try:
logging.debug(f"Directory exists: {search_directory}, scanning for files...") # Confirm directory exists
model_files = get_gguf_llamafile_files(search_directory)
logging.debug("Completed scanning for model files.")
except Exception as e:
logging.error(f"Error scanning directory: {e}")
return gr.update(choices=[], value=None), f"Error scanning directory: {e}"

if not model_files:
logging.debug(f"No model files found in {search_directory}") # Debug print for no files found
Expand Down Expand Up @@ -117,15 +125,22 @@ def download_preset_model(selected_model: str) -> Tuple[str, str]:

# Option 1: Select from Local Filesystem
with gr.Row():
search_directory = gr.Textbox(label="Model Directory",
placeholder="Enter directory path(currently '.\Models')",
value=".\Models",
interactive=True)
search_directory = gr.Textbox(
label="Model Directory",
placeholder="Enter directory path (currently './Models')",
value=MODELS_DIR,
interactive=True
)

# Initial population of local models
initial_dropdown_update, _ = update_dropdowns(".\Models")
initial_dropdown_update, _ = update_dropdowns(MODELS_DIR)
logging.debug(f"Scanning directory: {MODELS_DIR}")
refresh_button = gr.Button("Refresh Models")
local_model_dropdown = gr.Dropdown(label="Select Model from Directory", choices=[])
local_model_dropdown = gr.Dropdown(
label="Select Model from Directory",
choices=initial_dropdown_update["choices"],
value=None
)
# Display selected model path
model_value = gr.Textbox(label="Selected Model File Path", value="", interactive=False)

Expand Down
1 change: 0 additions & 1 deletion App_Function_Libraries/Gradio_UI/Website_scraping_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,6 @@ async def scrape_and_summarize_wrapper(
return convert_json_to_markdown(json.dumps({"error": f"Invalid JSON format for custom cookies: {e}"}))

if scrape_method == "Individual URLs":
# FIXME modify scrape_and_summarize_multiple to accept custom_cookies
result = await scrape_and_summarize_multiple(url_input, custom_prompt, api_name, api_key, keywords,
custom_titles, system_prompt, summarize_checkbox, custom_cookies=custom_cookies_list)
elif scrape_method == "Sitemap":
Expand Down
31 changes: 17 additions & 14 deletions App_Function_Libraries/Local_LLM/Local_LLM_Inference_Engine_Lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
####################
# Import necessary libraries
#import atexit
import glob
import logging
import os
import re
import signal
import subprocess
import sys
import time
from pathlib import Path
from typing import List, Optional
#
# Import 3rd-pary Libraries
Expand Down Expand Up @@ -157,21 +156,25 @@ def get_gguf_llamafile_files(directory: str) -> List[str]:
"""
logging.debug(f"Scanning directory: {directory}") # Debug print for directory

# Print all files in the directory for debugging
all_files = os.listdir(directory)
logging.debug(f"All files in directory: {all_files}")

pattern_gguf = os.path.join(directory, "*.gguf")
pattern_llamafile = os.path.join(directory, "*.llamafile")
try:
dir_path = Path(directory)
all_files = list(dir_path.iterdir())
logging.debug(f"All files in directory: {[str(f) for f in all_files]}")
except Exception as e:
logging.error(f"Failed to list files in directory {directory}: {e}")
return []

gguf_files = glob.glob(pattern_gguf)
llamafile_files = glob.glob(pattern_llamafile)
try:
gguf_files = list(dir_path.glob("*.gguf"))
llamafile_files = list(dir_path.glob("*.llamafile"))

# Debug: Print the files found
logging.debug(f"Found .gguf files: {gguf_files}")
logging.debug(f"Found .llamafile files: {llamafile_files}")
logging.debug(f"Found .gguf files: {[str(f) for f in gguf_files]}")
logging.debug(f"Found .llamafile files: {[str(f) for f in llamafile_files]}")

return [os.path.basename(f) for f in gguf_files + llamafile_files]
return [f.name for f in gguf_files + llamafile_files]
except Exception as e:
logging.error(f"Error during glob operations in directory {directory}: {e}")
return []


# Initialize process with type annotation
Expand Down
175 changes: 140 additions & 35 deletions App_Function_Libraries/Local_LLM/Local_LLM_ollama.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,92 +5,197 @@
import psutil
import os
import signal

import logging
import threading
import shutil

# Configure Logging
# logging.basicConfig(
# level=logging.DEBUG, # Set to DEBUG to capture all levels of logs
# format='%(asctime)s - %(levelname)s - %(message)s',
# handlers=[
# logging.FileHandler("app.log"),
# logging.StreamHandler()
# ]
# )

def is_ollama_installed():
"""
Checks if the 'ollama' executable is available in the system's PATH.
Returns True if installed, False otherwise.
"""
return shutil.which('ollama') is not None

def get_ollama_models():
"""
Retrieves available Ollama models by executing 'ollama list'.
Returns a list of model names or an empty list if an error occurs.
"""
try:
result = subprocess.run(['ollama', 'list'], capture_output=True, text=True, check=True)
result = subprocess.run(['ollama', 'list'], capture_output=True, text=True, check=True, timeout=10)
models = result.stdout.strip().split('\n')[1:] # Skip header
return [model.split()[0] for model in models]
except subprocess.CalledProcessError:
model_names = [model.split()[0] for model in models if model.strip()]
logging.debug(f"Available Ollama models: {model_names}")
return model_names
except FileNotFoundError:
logging.error("Ollama executable not found. Please ensure Ollama is installed and in your PATH.")
return []
except subprocess.TimeoutExpired:
logging.error("Ollama 'list' command timed out.")
return []
except subprocess.CalledProcessError as e:
logging.error(f"Error executing Ollama 'list': {e}")
return []
except Exception as e:
logging.error(f"Unexpected error in get_ollama_models: {e}")
return []


def pull_ollama_model(model_name):
"""
Pulls the specified Ollama model if Ollama is installed.
"""
if not is_ollama_installed():
logging.error("Ollama is not installed.")
return "Failed to pull model: Ollama is not installed or not in your PATH."

try:
subprocess.run(['ollama', 'pull', model_name], check=True)
subprocess.run(['ollama', 'pull', model_name], check=True, timeout=300) # Adjust timeout as needed
logging.info(f"Successfully pulled model: {model_name}")
return f"Successfully pulled model: {model_name}"
except subprocess.TimeoutExpired:
logging.error(f"Pulling model '{model_name}' timed out.")
return f"Failed to pull model '{model_name}': Operation timed out."
except subprocess.CalledProcessError as e:
return f"Failed to pull model: {e}"

logging.error(f"Failed to pull model '{model_name}': {e}")
return f"Failed to pull model '{model_name}': {e}"
except FileNotFoundError:
logging.error("Ollama executable not found. Please ensure Ollama is installed and in your PATH.")
return "Failed to pull model: Ollama executable not found."
except Exception as e:
logging.error(f"Unexpected error in pull_ollama_model: {e}")
return f"Failed to pull model '{model_name}': {e}"

def serve_ollama_model(model_name, port):
"""
Serves the specified Ollama model on the given port if Ollama is installed.
"""
if not is_ollama_installed():
logging.error("Ollama is not installed.")
return "Error: Ollama is not installed or not in your PATH."

try:
# Check if a server is already running on the specified port
for conn in psutil.net_connections():
if conn.laddr.port == int(port):
return f"Port {port} is already in use. Please choose a different port."
logging.warning(f"Port {port} is already in use.")
return f"Error: Port {port} is already in use. Please choose a different port."

# Start the Ollama server
port = str(port)
os.environ["OLLAMA_HOST"] = port
cmd = f"ollama serve"
process = subprocess.Popen(cmd, shell=True)
return f"Started Ollama server for model {model_name} on port {port}. Process ID: {process.pid}"
cmd = ['ollama', 'serve', model_name, '--port', str(port)]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical

This command line depends on a
user-provided value
.
This command line depends on a
user-provided value
.

Copilot Autofix AI about 1 month ago

To fix the problem, we need to ensure that the model_name and port parameters are validated against a predefined allowlist of acceptable values. This will prevent arbitrary command execution by ensuring only known, safe commands are executed.

  1. Define an allowlist: Create a list of valid model names that the application supports.
  2. Validate user input: Before constructing the command, check if the model_name is in the allowlist. If not, return an error message.
  3. Sanitize the port: Ensure the port is a valid integer within an acceptable range.
Suggested changeset 1
App_Function_Libraries/Local_LLM/Local_LLM_ollama.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/App_Function_Libraries/Local_LLM/Local_LLM_ollama.py b/App_Function_Libraries/Local_LLM/Local_LLM_ollama.py
--- a/App_Function_Libraries/Local_LLM/Local_LLM_ollama.py
+++ b/App_Function_Libraries/Local_LLM/Local_LLM_ollama.py
@@ -77,2 +77,4 @@
 
+VALID_MODELS = ["model1", "model2", "model3"]  # Replace with actual model names
+
 def serve_ollama_model(model_name, port):
@@ -85,2 +87,6 @@
 
+    if model_name not in VALID_MODELS:
+        logging.error(f"Invalid model name: {model_name}")
+        return f"Error: Invalid model name '{model_name}'."
+
     try:
@@ -103,3 +109,2 @@
         return f"Error starting Ollama server: {e}"
-
 def stop_ollama_server(pid):
EOF
@@ -77,2 +77,4 @@

VALID_MODELS = ["model1", "model2", "model3"] # Replace with actual model names

def serve_ollama_model(model_name, port):
@@ -85,2 +87,6 @@

if model_name not in VALID_MODELS:
logging.error(f"Invalid model name: {model_name}")
return f"Error: Invalid model name '{model_name}'."

try:
@@ -103,3 +109,2 @@
return f"Error starting Ollama server: {e}"

def stop_ollama_server(pid):
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
logging.info(f"Started Ollama server for model '{model_name}' on port {port}. PID: {process.pid}")
return f"Started Ollama server for model '{model_name}' on port {port}. Process ID: {process.pid}"
except FileNotFoundError:
logging.error("Ollama executable not found.")
return "Error: Ollama executable not found. Please ensure Ollama is installed and in your PATH."
except Exception as e:
logging.error(f"Error starting Ollama server: {e}")
return f"Error starting Ollama server: {e}"


def stop_ollama_server(pid):
"""
Stops the Ollama server with the specified process ID if Ollama is installed.
"""
if not is_ollama_installed():
logging.error("Ollama is not installed.")
return "Error: Ollama is not installed or not in your PATH."

try:
if platform.system() == "Windows":
os.system(f"taskkill /F /PID {pid}")
return f"Stopped Ollama server with PID {pid}"
elif platform.system() == "Linux":
os.system(f"kill {pid}")
return f"Stopped Ollama server with PID {pid}"
elif platform.system() == "Darwin":
os.system("""osascript -e 'tell app "Ollama" to quit'""")
return f"(Hopefully) Stopped Ollama server using osascript..."
subprocess.run(['taskkill', '/F', '/PID', str(pid)], check=True)
elif platform.system() in ["Linux", "Darwin"]:
os.kill(pid, signal.SIGTERM)
logging.info(f"Stopped Ollama server with PID {pid}")
return f"Stopped Ollama server with PID {pid}"
except ProcessLookupError:
logging.warning(f"No process found with PID {pid}")
return f"No process found with PID {pid}"
except Exception as e:
logging.error(f"Error stopping Ollama server: {e}")
return f"Error stopping Ollama server: {e}"


def create_ollama_tab():
"""
Creates the Ollama Model Serving tab in the Gradio interface with lazy loading.
"""
ollama_installed = is_ollama_installed()

with gr.Tab("Ollama Model Serving"):
if not ollama_installed:
gr.Markdown(
"# Ollama Model Serving\n\n"
"**Ollama is not installed or not found in your PATH. Please install Ollama to use this feature.**"
)
return # Exit early, no need to add further components

gr.Markdown("# Ollama Model Serving")

with gr.Row():
model_list = gr.Dropdown(label="Available Models", choices=get_ollama_models())
# Initialize Dropdowns with placeholders
model_list = gr.Dropdown(
label="Available Models",
choices=["Click 'Refresh Model List' to load models"],
value="Click 'Refresh Model List' to load models"
)
refresh_button = gr.Button("Refresh Model List")

with gr.Row():
new_model_name = gr.Textbox(label="Model to Pull")
new_model_name = gr.Textbox(label="Model to Pull", placeholder="Enter model name")
pull_button = gr.Button("Pull Model")

pull_output = gr.Textbox(label="Pull Status")

with gr.Row():
# FIXME - Update to update config.txt file
serve_model = gr.Dropdown(label="Model to Serve", choices=get_ollama_models())
serve_model = gr.Dropdown(
label="Model to Serve",
choices=["Click 'Refresh Model List' to load models"],
value="Click 'Refresh Model List' to load models"
)
port = gr.Number(label="Port", value=11434, precision=0)
serve_button = gr.Button("Start Server")

serve_output = gr.Textbox(label="Server Status")

with gr.Row():
pid = gr.Number(label="Server Process ID", precision=0)
pid = gr.Number(label="Server Process ID (Enter the PID to stop)", precision=0)
stop_button = gr.Button("Stop Server")

stop_output = gr.Textbox(label="Stop Status")

def update_model_lists():
"""
Retrieves the list of available Ollama models and updates the dropdowns.
"""
models = get_ollama_models()
return gr.update(choices=models), gr.update(choices=models)

refresh_button.click(update_model_lists, outputs=[model_list, serve_model])
pull_button.click(pull_ollama_model, inputs=[new_model_name], outputs=[pull_output])
serve_button.click(serve_ollama_model, inputs=[serve_model, port], outputs=[serve_output])
stop_button.click(stop_ollama_server, inputs=[pid], outputs=[stop_output])
if models:
return gr.update(choices=models, value=models[0]), gr.update(choices=models, value=models[0])
else:
return gr.update(choices=["No models found"], value="No models found"), gr.update(choices=["No models found"], value="No models found")

def async_update_model_lists():
"""
Asynchronously updates the model lists to prevent blocking.
"""
def task():
choices1, choices2 = update_model_lists()
model_list.update(choices=choices1['choices'], value=choices1.get('value'))
serve_model.update(choices=choices2['choices'], value=choices2.get('value'))
threading.Thread(target=task).start()

# Bind the refresh button to the asynchronous update function
refresh_button.click(fn=async_update_model_lists, inputs=[], outputs=[])

# Bind the pull, serve, and stop buttons to their respective functions
pull_button.click(fn=pull_ollama_model, inputs=[new_model_name], outputs=[pull_output])
serve_button.click(fn=serve_ollama_model, inputs=[serve_model, port], outputs=[serve_output])
stop_button.click(fn=stop_ollama_server, inputs=[pid], outputs=[stop_output])
5 changes: 4 additions & 1 deletion App_Function_Libraries/Utils/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ def load_and_log_configs():
logging.debug(f"Loaded Tabby API IP: {tabby_api_IP}")
logging.debug(f"Loaded VLLM API URL: {vllm_api_url}")

# Retrieve default API choices from the configuration file
default_api = config.get('API', 'default_api', fallback='openai')

# Retrieve output paths from the configuration file
output_path = config.get('Paths', 'output_path', fallback='results')
Expand Down Expand Up @@ -340,7 +342,8 @@ def load_and_log_configs():
'embedding_api_key': embedding_api_key,
'chunk_size': chunk_size,
'overlap': overlap
}
},
'default_api': default_api
}

except Exception as e:
Expand Down
File renamed without changes.
Loading
Loading