16 Commits

Author SHA1 Message Date
Joakim Persson 5aa47d11ea Hämtar lista över tillgängliga modeller från ollama-servrarna i yaml-konfigurationen 2024-08-02 23:10:37 +02:00
Joakim Persson 5f2b71c965 Lagt till /api/tags för att kunna få ut lista av tillgängliga modeller (LLM:er) på aktuell ollama-server 2024-08-02 23:09:15 +02:00
Joakim Persson 3659308675 Ändrat namn på metoder, *models > *endpoints 2024-08-02 23:05:49 +02:00
Joakim Persson a72a46d777 Bytt till "endpoints" från "models" för att unvika upprepning av samma namn på olika nivåer av konfigurationsfilen 2024-08-02 23:04:11 +02:00
joakimp a7ce92524a Extraherar dictionary (backend) och lista (models) direkt från smartassist.yaml 2024-08-02 00:11:45 +02:00
joakimp b52c98c6b3 FIxat syntaxiskt fel: true -> True. Rensat bortkommernterad kod. 2024-08-02 00:10:05 +02:00
joakimp c8269f3152 Rensat onödiga metoder och attribut 2024-08-02 00:07:59 +02:00
Joakim Persson d553deed13 Bara ändrat någon kommentar 2024-08-01 18:21:00 +02:00
Joakim Persson ab23585711 Testar uttökad yaml-konfiguration 2024-08-01 17:04:25 +02:00
Joakim Persson 916b6f9e52 Testar med fjärrserver för ollama. Endast tillfällig lösning. 2024-08-01 17:03:25 +02:00
Joakim Persson cfed550a3e Lagt till backend och modules från yaml-filen 2024-08-01 17:02:20 +02:00
joakimp 5ee7ae520f Förbättrat utläsning av data från konfigurationsfilen smartassist.yaml 2024-07-31 23:24:38 +02:00
joakimp 1f6f0a72d5 Lade till information för ollama-test.wara-ops.org 2024-07-31 23:23:14 +02:00
Joakim Persson 9e6e6048f3 Rensat bort utkommenterad kod 2024-07-31 17:37:47 +02:00
Joakim Persson 538f02e6a7 Fixat så att sänd-knappen faktiskt gör det den ska göra 2024-07-31 17:37:26 +02:00
Joakim Persson 810b369721 Tagit bort utkommenterad kod 2024-07-31 17:36:37 +02:00
7 changed files with 135 additions and 186 deletions
+23 -7
View File
@@ -1,22 +1,38 @@
# Frontend Configuration
frontend:
url: "http://localhost:5004"
# Backend Configuration
backend:
url: "http://localhost:5004"
api: "/api/chat"
endpoints:
- model: "AUTODETECT"
title: "Ollama"
url: "http://localhost:11434"
provider: "ollama"
# - model: "AUTODETECT"
- model: "AUTODETECT"
title: "Ollama-WARA"
url: "https://ollama-test.wara-ops.org"
requestOptions:
headers:
Authorization: "${OLLAMA_API_KEY}" # on MacOS: echo "Authorization: Basic $(echo -n 'user:password' | gbase64 -w 0)"
provider: "ollama"
# Ollama Server Configuration
ollama:
url: "http://localhost:11434"
title: "Ollama-local"
# url: "http://localhost:11434"
url: "https://ollama-test.wara-ops.org"
api_key: "${OLLAMA_API_KEY}" # Refer to environment variable
model: "phi3:mini" # Select a model supported by the Ollama server
# model: "phi3:mini" # Select a model supported by the Ollama server
# model: "llama3:70b" # Select a model supported by the Ollama server
model: "llama3.1:70b" # Select a model supported by the Ollama server
# model: "llama3.1:8b" # Select a model supported by the Ollama server
# model: "llama3:latest" # Select a model supported by the Ollama server
# model: "mannix/llama3-8b-ablitered-v3:latest" # Select a model supported by the Ollama server
# model: "mistral-nemo:latest" # Select a model supported by the Ollama server
# model: "gemma2:27b"
# model: "AUTODETECT"
# Logging comment out the whole section for default level which is INFO
logging:
@@ -27,7 +43,7 @@ logging:
# Cache Settings (Optional)
cache:
enabled: true
enabled: True
timeout: 60 # Seconds
test:
+27 -12
View File
@@ -58,15 +58,15 @@ def set_session():
resp.set_cookie('session', 'some-value', samesite='None', secure=True) # Add SameSite attribute here
return resp
@app.route('/profile')
def profile():
# Retrieve data from the session
user_id = session.get('user_id')
# @app.route('/profile')
# def profile():
# # Retrieve data from the session
# user_id = session.get('user_id')
if user_id:
return f'User ID: {user_id}'
else:
return 'No user ID found'
# if user_id:
# return f'User ID: {user_id}'
# else:
# return 'No user ID found'
@app.route('/<path:filename>')
@@ -87,9 +87,23 @@ CORS(app, resources={
}
})
@app.route('/api/tags', methods=['GET'])
def tag(url = "http://localhost:11434/api/tags", headers = None):
# def tag(url = "http://localhost:11434/api/tags", headers = {"Content-Type": "application/json"}):
"""Get a list of models for the server located at url."""
try:
logger.debug(f"url: {url} headers: {headers}")
response = requests.get(url, headers=headers)
return response.json()
# return response
except requests.exceptions.RequestException as e:
logger.error("Request Exception: %s", str(e))
return {'error': 'Failed to process request'}
@app.route('/api/chat', methods=['POST'])
def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"):
def chat(model = "phi3:mini"):
# def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"):
"""
This function handles the chat. The frontend client (web browser) calls the
backend server through this endpoint (/api/chat) that manage queries
@@ -99,8 +113,8 @@ def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"
# Get the message from the JSON in the request body
data = request.get_json()
message = data.get('query')
url_server = data.get('url_server', url_server) # Use provided URL or default
model = data.get('model', model) # Use provided model or default
url_server = data.get('url_server', "https://ollama-test.wara-ops.org/api/generate") # Use provided URL or default
model = data.get('model', model) # Use provided model or default if not provided
# Get chat history from session storage (e.g., a dictionary)
chat_history = session.get('chat_history', [])
@@ -122,8 +136,9 @@ def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"
url = url_server
headers = {
"Content-Type": "application/json",
"Authorization": "Basic ZWNzanBlcjoxM2JjMTU4ZDhmNmY5YTU4YTkzZDNmY2I="
}
logger.debug(f"url: {url} headers: {headers}")
response = requests.post(url,
headers=headers,
data=json.dumps(data_to_send))
+2 -25
View File
@@ -6,35 +6,15 @@
<link rel="stylesheet" href="/css/clientstyle.css">
<!-- <link rel="stylesheet" href="python_test/smartassist/src/css/clientstyle.css"> -->
</head>
<body>
<h1>Ollama Chat</h1>
<div id="chatbox">
<!-- messages will be rendered here -->
</div>
<textarea id="userInput" placeholder="Type your message..." rows="5"></textarea>
<button id="sendButton" onclick="sendMessage()">Send</button>
<button id="sendButton" onclick="window.frontendApi.sendMessage()">Send</button>
<!-- Get the apiEndpoint and the useModel -->
<!-- <script>
const apiEndpoint = window.apiEndpoint;
const useModel = window.useModel;
console.log("client.html - API Endpoint: ", apiEndpoint);
console.log("client.html - use model: ", useModel);
</script> -->
<!-- <script>
let apiEndpoint; // Make variable available outside of the scope of the event listener
let useModel; // Make variable available outside of the scope of the event listener
window.addEventListener('message', function(event) {
if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
const { apiEndpoint, useModel } = event.data;
console.log("client.html - API Endpoint: ", apiEndpoint);
console.log("client.html - use model: ", useModel);
window.apiEndpoint = apiEndpoint;
window.useModel = useModel;
}
});
</script> -->
<!-- Marked-it for markdown rendering -->
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
@@ -54,9 +34,6 @@
};
</script>
<script>
const chatContainer = document.getElementById('chatbox');
+55 -25
View File
@@ -5,8 +5,9 @@ import yaml
import json
import socket
import urllib.parse
from backend import run_flask
from backend import run_flask, tag
import logging
import requests
import utils
from utils import GlobalState
@@ -31,15 +32,22 @@ def configure():
return os.getenv(env_var_name, None)
return value
def update_dict_with_env_vars(d):
for key in d:
if isinstance(d[key], dict):
update_dict_with_env_vars(d[key]) # Recursively check nested dictionaries
elif isinstance(d[key], str):
d[key] = resolve_env_var(d[key])
def update_value(value):
if isinstance(value, dict): # Dictionaries need recursive check
return update_dict_with_env_vars(value)
elif isinstance(value, list): # Lists must be traversed element by element
return [update_value(item) for item in value]
elif isinstance(value, str): # If value is a string it might be an environmnet variable
return resolve_env_var(value)
else: # Anything else, just keep the old value
return value
def update_dict_with_env_vars(d): # Check all keys in d
for key in d: # Iterate over all keys in the dictionary. The keys seen are all at the top-level of d
logger.info(f"key investigated now: {key}")
d[key] = update_value(d[key])
return d
# Update the config dictionary with resolved environment variables
updated_config = update_dict_with_env_vars(config)
####################################
@@ -52,23 +60,46 @@ def configure():
logger.debug("configure(): This logger now has effective log level %s", logger.getEffectiveLevel())
####################################
# Extract and export backend API
# endpoint as global state variable
# Extract models (server url, api_key, model, et cetera)
####################################
if isinstance(updated_config.get('backend'), dict): # Look for 'backend' key
if isinstance(updated_config['backend'].get('url'), str): # Look for 'url' key
url = updated_config['backend'].get('url')
if isinstance(updated_config['backend'].get('api'), str): # Look for 'api' key
api = updated_config['backend'].get('api')
# backend_api_ep = url+api # Extract API endpoint if defined
logger.debug(f"Constructing endpoint address as url+api: {url+api}")
global_state.set_backend_api_ep(url+api) # Extract API endpoint if defined and set in global_state
logger.debug(f"Backend API endpoint is set to {global_state.get_backend_api_ep()}")
# os.environ['BE_API_ENDPOINT'] = backend_api_ep # Look into alternative way to share this with backend.py
if isinstance(updated_config.get('backend'),dict): # Extract backend info from dictionary
global_state.set_backend(backend=updated_config.get('backend'))
logger.debug("backend = \n{}".format(json.dumps(global_state.get_backend(), indent=4)))
logger.debug(f"Backend API endpoint is set to: {global_state.get_backend_api_ep()}")
####################################
# Extract Ollama parameters (url, api_key, model)
####################################
if isinstance(updated_config.get('endpoints'),list): # Extract info on endpoint, model, url, provider et cetera from list
global_state.set_endpoints(endpoints=updated_config.get('endpoints')) # Extract and set list of endpoints
logger.debug("endpoints = \n{}".format(json.dumps(global_state.get_endpoints(), indent=4)))
for endpoint in global_state.get_endpoints():
if endpoint["provider"] == "ollama":
if "requestOptions" in endpoint: # Check if authentication is needed
# headers = {
# "Content-Type": "application/json",
# "Authorization": endpoint["requestOptions"]["headers"]["Authorization"]
# }
headers = {
"Authorization": endpoint["requestOptions"]["headers"]["Authorization"]
}
else: # otherwise proceed without authentication
# headers = {"Content-Type": "application/json"}
headers = None
# models = tag(url = endpoint["url"], headers = headers) # Ask for models (LLMs) available at endpoint
try:
models = requests.get(endpoint["url"] + "/api/tags", headers=headers).json()
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
if isinstance(models, dict) and 'error' in models:
logger.error('Error fetching models from backend: %s', models['error'])
else:
endpoint["models"] = models # Update endpoint with detected models
logger.debug("models = \n{}".format(json.dumps(models, indent=4)))
if endpoint["model"] is not "AUTODETECT": # Check if specified model is available
# do something
logger.debug("Asking for specific model")
# TODO: Remove this section when not needed anymore
if isinstance(updated_config.get('ollama'), dict): # Look for 'ollama' key
if isinstance(updated_config['ollama'].get('model'), str): # Look for 'model' key
model_to_use = updated_config['ollama'].get('model')
@@ -115,7 +146,6 @@ def start_backend(config):
if __name__ == '__main__':
conf = configure() # Read config from file and set up config dict
logger.debug('conf dictionary set to {}'.format(json.dumps(conf, indent=4)))
logger.debug('conf dictionary set to \n{}'.format(json.dumps(conf, indent=4)))
# start_frontend(config=conf) # Not needed as we are using Flask for backend now
start_backend(config=conf)
+3 -3
View File
@@ -61,7 +61,7 @@ h1 {
border-color: #66afe9; /* Blue outline on focus */
}
button[onclick="sendMessage()"] {
button[onclick="window.frontendApi.sendMessage()"] {
background-color: #4CAF50; /* Green */
border: none;
color: white;
@@ -75,11 +75,11 @@ button[onclick="sendMessage()"] {
transition: background-color 0.3s; /* Smooth transition effect */
}
button[onclick="sendMessage()"]:hover {
button[onclick="window.frontendApi.sendMessage()"]:hover {
background-color: #b2b2b2; /* Light Grey on hover */
}
button[onclick="sendMessage()"]:active {
button[onclick="window.frontendApi.sendMessage()"]:active {
background-color: #6f6f6f; /* Dark Grey when clicked */
}
-108
View File
@@ -1,111 +1,3 @@
// // Get the user input element from the DOM
// const chatbox = document.getElementById('chatbox');
// const userInput = document.getElementById('userInput');
// const parser = window.markdownit({
// linkify: true,
// strikethrough: true,
// });
// parser.enable(['table']);
// // const apiEndpoint = window.apiEndpoint; // Get the API endpoint
// // const useModel = window.useModel; // Get whether to use a model or not
// // console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
// // console.log("frontend.js - Use model: ", window.useModel);
// let apiEndpoint; // Make variable available outside of the scope of the event listener
// let useModel; // Make variable available outside of the scope of the event listener
// window.addEventListener('message', function(event) {
// if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
// const { apiEndpoint, useModel } = event.data;
// console.log("client.html - API Endpoint: ", apiEndpoint);
// console.log("client.html - use model: ", useModel);
// window.apiEndpoint = apiEndpoint;
// window.useModel = useModel;
// }
// });
// console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
// console.log("frontend.js - Use model: ", window.useModel);
// // Define a function to send the user's message to the AI
// function sendMessage() {
// // Get the user's input message and trim any whitespace
// const query = userInput.value.trim();
// // Check if the message is not empty
// if (query !== '') {
// // fetch(`${apiEndpoint}`, {
// fetch(apiEndpoint, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ query, model: useModel }), // Add these parameters here
// // body: JSON.stringify({ query, url_server: "http://your-custom-url", model: "phi3:mini" }), // Add these parameters here
// })
// .then(response => response.json())
// .then(data => {
// // Get the AI's response from the API data
// const aiResponse = data.response;
// // Render the user's original message in the chatbox
// renderMessage(query, 'user-message');
// // Render the AI's response in the chatbox
// renderMessage(aiResponse, 'ai-response');
// // Clear the user input field for the next message
// userInput.value = '';
// })
// .catch(error => console.error('Error sending message:', error));
// }
// }
// // Define a function to render a message in the chatbox with a specific class name
// function renderMessage(text, className) {
// // Create a new div element to hold the message
// const messageElement = document.createElement('div');
// // Add the specified class name to the element
// messageElement.className = className;
// // // Set the text content of the element to the message text
// // messageElement.textContent = text;
// // Use the markdown-it parser
// const html = parser.render(text);
// messageElement.innerHTML = html;
// // Append the message element to the chatbox immediately
// // chatbox.appendChild(messageElement);
// // Typeset math in the message element
// MathJax.typesetPromise([messageElement]).then(() => {
// // No need to append anything here, it's already appended above
// chatbox.appendChild(messageElement);
// });
// }
// // Make the button toggle colour when user presses Enter on keyboard
// const sendButton = document.getElementById('sendButton');
// document.addEventListener('keydown', function(event) {
// if (event.key === 'Enter') {
// sendButton.style.backgroundColor = '#6f6f6f'; // Dark Grey when Enter is pressed
// }
// });
// document.addEventListener('keyup', function() {
// sendButton.style.backgroundColor = ''; // Restore the original style when any key is released
// });
// Get the user input element from the DOM
const chatbox = document.getElementById('chatbox');
const userInput = document.getElementById('userInput');
+24 -5
View File
@@ -25,7 +25,12 @@ class GlobalState:
cls._instance.logger.setLevel(getattr(logging, cls._instance.log_level)) # Initialize root logger level
cls._instance.logger.info(" __new__(cls): Logger in GlobalState created: %s", cls._instance.logger)
cls._instance.llm = "phi3:mini" # Default LLM for queries. TODO: Check with ollama server that it actually exists
cls._instance.backend_api_ep = "http://localhost:5005/api/chat" # Default backend API endpoint
# cls._instance.backend_api_ep = "http://localhost:5005/api/chat" # Default backend API endpoint
# Try making things more aligned with the outline of the yaml file
cls._instance.backend = dict() # A dictionary that holds info on which server the clients connect to
cls._instance.endpoints = [] # A list that holds info on which endpoints are available for use (server url, model name, provider et cetera)
# logging - already done in __new__, perhaps change layout later
return cls._instance
def configure_logging(self, level=None):
@@ -65,11 +70,25 @@ class GlobalState:
"""Getter for which LLM is used for queries"""
return self.llm
def set_backend_api_ep(self, be_api_ep=None):
"""Set backend API endpoint"""
self.backend_api_ep = be_api_ep
def set_backend(self, backend=None):
"""Set backend that web clients connect to"""
self.backend = backend
def get_backend(self):
"""Getter for backend that web clients connect to"""
return self.backend
def get_backend_api_ep(self):
"""Getter for backend API endpoint"""
return self.backend_api_ep
return self.backend["url"]+self.backend["api"]
def set_endpoints(self, endpoints=None):
"""Set the list of endpoints."""
if endpoints is not None:
if not isinstance(endpoints, list):
raise ValueError("Endpoints must be a list, even if there is just one model")
self.endpoints = endpoints
def get_endpoints(self):
"""Return the list of endpoints"""
return self.endpoints