Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6c27094
add ollama to docker-compose
dale-wahl Mar 5, 2026
8a8427c
give me a proper worker who can do neat stuff.
dale-wahl Mar 5, 2026
89824e2
ruff you mean
dale-wahl Mar 5, 2026
e7aa9af
add docker setup if ollama present
dale-wahl Mar 5, 2026
74e01b6
a useful frontend setting panel
dale-wahl Mar 5, 2026
baec03a
only show enabled models
dale-wahl Mar 5, 2026
36fe0ed
update docker readme so people can use ollama
dale-wahl Mar 5, 2026
eb4b49a
Cleanup: stale enabled models, refresh_items scheduling, README auto-…
Copilot Mar 10, 2026
58dd587
Merge branch 'master' into ollama_management
dale-wahl Apr 9, 2026
74f5600
Merge branch 'master' into ollama_management
dale-wahl Apr 9, 2026
26f33f5
ollama_manager: get additional info from ollama including capabilities
dale-wahl Apr 9, 2026
73b9536
Merge branch 'master' into ollama_management
dale-wahl Apr 13, 2026
f2501b9
ollama_manager: display names / ollama get your api together
dale-wahl Apr 13, 2026
c72d043
Create OllamaClient to collect model info
dale-wahl Apr 13, 2026
a79657b
list capabilities in admin panel
dale-wahl Apr 13, 2026
29f1433
Merge branch 'master' into ollama_management
dale-wahl Apr 13, 2026
43de49b
ollama_manager: check for connection first, ollama_client: accept logger
dale-wahl Apr 15, 2026
1e8cb36
Merge branch 'master' into ollama_management
stijn-uva May 18, 2026
c8da75f
Multi-form!
stijn-uva May 19, 2026
fda48b1
Merge branch 'master' into ollama_management
stijn-uva May 19, 2026
4c429df
Refactor everything
stijn-uva May 21, 2026
a6ecbc2
Formatting
stijn-uva May 21, 2026
b9b3d0a
ruff
stijn-uva May 21, 2026
bdf07e2
Reshuffle OpenAI-related clients
stijn-uva May 21, 2026
d86a309
Control panel text & names
stijn-uva May 21, 2026
feb4a84
Rework Ollama model info API request & parsing
stijn-uva May 21, 2026
9084cc5
Fix Ollama capability detection
stijn-uva May 21, 2026
c645d47
Fix model card URL for external model APIs
stijn-uva May 21, 2026
5d46691
Add notice to page if currently updating
stijn-uva May 21, 2026
3acc27b
Update setting category name
stijn-uva May 21, 2026
a281a82
Fix PromptCompass
stijn-uva May 21, 2026
4813c07
No longer need LLMAdapter in PromptCompass...
stijn-uva May 21, 2026
3f3c8d5
Fix LLMPrompter queue IDs
stijn-uva May 21, 2026
098a719
Enabled models updating
stijn-uva May 27, 2026
5d9e505
typoes
stijn-uva May 28, 2026
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
8 changes: 8 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"esversion": 11,
"undef": true,
"globals": {
"$": false,
"document": false,
}
}
83 changes: 83 additions & 0 deletions backend/workers/llm_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Manage LLM models
"""
from backend.lib.worker import BasicWorker
from common.lib.llm.llm_client import LLMProviderClient

class LLMProviderManager(BasicWorker):
"""
Manages LLM models

Periodically refreshes the list of available models from an LLM provider.
Can also pull or delete models on demand when queued with a specific task.

Job details:
- task: "refresh" (default), "pull", or "delete"
- provider: the URL of the LLM provider, as configured in the
llm.providers setting. if not given, run on all providers

Job remote_id:
- For refresh: "manage-llm-refresh" (periodic) or "manage-llm-manual" (on-demand)
- For pull/delete: the model name to pull or delete
"""
type = "manage-llm"
max_workers = 1
client = None

@classmethod
def ensure_job(cls, config=None):
"""
Ensure the daily refresh job is always scheduled

:return: Job parameters for the worker
"""
return {"remote_id": "manage-llm-refresh", "interval": 86400}

def work(self):
task = self.job.details.get("task", "refresh") if self.job.details else "refresh"
provider = self.job.details.get("provider", "") if self.job.details else None
model_name = self.job.data["remote_id"]
available_models = None

for provider_config in self.config.get("llm.providers", []):
if provider and provider != provider_config["url"]:
continue

try:
client = LLMProviderClient.get_client(self.config, provider_config)
except ValueError:
self.log.debug(f"{self.__class__.__name__}: invalid provider type: {provider_config['type']}, skipping")
continue

# note that technically it is possible to pull/delete a model on
# multiple providers at once (if a model_name is defined but no
# provider). may not be a problem? may be useful one day?
success = False
if task == "pull" and hasattr(client, "pull_model"):
success = client.pull_model(model_name)

elif task == "delete" and hasattr(client, "delete_model"):
success = client.delete_model(model_name)

if success or task == "refresh":
# refresh models after pulling/deleting, or when asked to
if available_models is None:
available_models = {}

for model in client.list_models():
model = client.build_model_entry(model)
available_models[model["id"]] = model

self.log.debug(f"{self.__class__.__name__}: ran task '{task}' (model name: {model_name or 'N/A'})")

elif success is None:
self.log.warning(f"{self.__class__.__name__}: task '{task}' unknown or not supported by client")
else:
self.log.warning(f"{self.__class__.__name__}: task '{task}' failed for model {model_name}")

if available_models is not None:
enabled_and_available = set(available_models.keys()) & set(self.config.get("llm.enabled_models", []))
self.config.set("llm.available_models", available_models)
self.config.set("llm.enabled_models", list(enabled_and_available))

self.job.finish()
70 changes: 9 additions & 61 deletions backend/workers/refresh_items.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,26 @@
"""
Refresh items
"""
import json

import requests

from backend.lib.worker import BasicWorker

class ItemUpdater(BasicWorker):
"""
Refresh 4CAT items

Refreshes settings that are dependent on external factors
Refreshes settings that are dependent on external factors.
LLM model refreshing is handled by the OllamaManager worker.
"""
type = "refresh-items"
max_workers = 1

@classmethod
def ensure_job(cls, config=None):
"""
Ensure that the refresher is always running

This is used to ensure that the refresher is always running, and if it is
not, it will be started by the WorkerManager.

:return: Job parameters for the worker
"""
return {"remote_id": "refresh-items", "interval": 60}
# ensure_job is intentionally disabled: this worker currently does nothing
# and would only create unnecessary job queue churn. Re-enable when work()
# has actual tasks to perform.
# @classmethod
# def ensure_job(cls, config=None):
# return {"remote_id": "refresh-items", "interval": 60}

def work(self):
# Refresh items
self.refresh_settings()

# Placeholder – no tasks implemented yet.
self.job.finish()

def refresh_settings(self):
"""
Refresh settings
"""
# LLM server settings
llm_provider = self.config.get("llm.provider_type", "none").lower()
llm_server = self.config.get("llm.server", "")

# For now we only support the Ollama API
if llm_provider == "ollama" and llm_server:
headers = {"Content-Type": "application/json"}
llm_api_key = self.config.get("llm.api_key", "")
llm_auth_type = self.config.get("llm.auth_type", "")
if llm_api_key and llm_auth_type:
headers[llm_auth_type] = llm_api_key

available_models = {}
try:
response = requests.get(f"{llm_server}/api/tags", headers=headers, timeout=10)
if response.status_code == 200:
settings = response.json()
for model in settings.get("models", []):
model = model["name"]
try:
model_metadata = requests.post(f"{llm_server}/api/show", headers=headers, json={"model": model}, timeout=10).json()
available_models[model] = {
"name": f"{model_metadata['model_info'].get('general.basename', model)} ({model_metadata['details']['parameter_size']} parameters)",
"model_card": f"https://ollama.com/library/{model}",
"provider": "local"
}

except (requests.RequestException, json.JSONDecodeError, KeyError) as e:
self.log.debug(f"Could not get metadata for model {model} from Ollama - skipping (error: {e})")

self.config.set("llm.available_models", available_models)
self.log.debug("Refreshed LLM server settings cache")
else:
self.log.warning(f"Could not refresh LLM server settings cache - server returned status code {response.status_code}")

except requests.RequestException as e:
self.log.warning(f"Could not refresh LLM server settings cache - request error: {str(e)}")

Loading
Loading