bemade-addons/openwebui_integration/models/res_company.py
2025-02-18 14:55:35 -05:00

299 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
This module extends the res.company model to add OpenWebUI configuration.
It manages OpenWebUI integration settings at the company level,
including service activation, API keys, and other configuration parameters.
"""
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, tools, _
from odoo.exceptions import UserError
class ResCompany(models.Model):
_inherit = 'res.company'
ai_provider = fields.Selection([
('openwebui', 'OpenWebUI'),
('ollama', 'Ollama')
],
string='AI Provider',
default='openwebui',
required=True,
help="Select the AI provider to use"
)
# Ollama Configuration
ollama_port = fields.Integer(
string='Ollama Port',
default=11434,
required=True,
help="Port number for the Ollama server (default: 11434)"
)
# OpenWebUI Configuration
openwebui_enabled = fields.Boolean(
string='Enable OpenWebUI',
default=False,
help="Enable integration with OpenWebUI"
)
openwebui_api_url = fields.Char(
string='OpenWebUI API URL',
help="Base URL of the OpenWebUI API (e.g. http://localhost:8080)"
)
openwebui_api_key = fields.Char(
string='OpenWebUI API Key',
help="API key for authentication with OpenWebUI"
)
openwebui_verify_ssl = fields.Boolean(
string='Verify SSL Certificate',
default=True,
help="Verify the SSL certificate when making API calls"
)
openwebui_timeout = fields.Integer(
string='Timeout (seconds)',
default=60,
help="Maximum wait time for API calls"
)
openwebui_context_size = fields.Integer(
string='Context Window Size',
default=4096,
help="Maximum number of tokens in the context window (default: 4096)"
)
openwebui_max_products = fields.Integer(
string='Maximum Products per Batch',
default=800,
help="Maximum number of products that can be processed at once (default: 800)"
def test_ollama_connection(self):
"""Test the connection to Ollama server.
Returns:
dict: A notification action with the test result
"""
self.ensure_one()
if self.ai_provider != 'ollama':
raise UserError(_("Please select Ollama as the AI provider first."))
try:
import requests
# Test connection to Ollama server
url = f'http://localhost:{self.ollama_port}/api/tags'
response = requests.get(url, timeout=5)
response.raise_for_status()
# If we get here, the connection was successful
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Successfully connected to Ollama server on port %s', self.ollama_port),
'sticky': False,
'type': 'success',
}
}
except requests.exceptions.ConnectionError:
raise UserError(_("Could not connect to Ollama server. Please check if Ollama is running and the port number is correct."))
except requests.exceptions.Timeout:
raise UserError(_("Connection to Ollama server timed out. Please check your network settings."))
except requests.exceptions.RequestException as e:
raise UserError(_("Error connecting to Ollama server: %s", str(e)))
openwebui_products_per_request = fields.Integer(
string='Products per Request',
default=10,
help="Number of products to process in a single API request"
)
openwebui_models_ids = fields.One2many(
comodel_name='openwebui.model',
inverse_name='company_id',
string='OpenWebUI Models',
)
openwebui_default_model_id = fields.Many2one(
comodel_name='openwebui.model',
string='Default Model',
domain="[('company_id', '=', id), ('is_active', '=', True)]",
help="Default OpenWebUI model to use for AI requests"
)
openwebui_context_size = fields.Integer(
string='Context Window Size',
default=2048,
help="Size of the context window in tokens (e.g., 2048, 4096, 8192)"
)
def test_openwebui_connection(self):
"""Test the connection to OpenWebUI API.
This method verifies the connection to OpenWebUI by:
1. Checking if OpenWebUI integration is enabled
2. Attempting to connect to the configured API endpoint
3. Validating the API credentials
Returns:
dict: A notification action with the test results:
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Success',
'message': str,
'type': 'success',
'sticky': False
}
}
Raises:
UserError: If any of the following conditions occur:
- OpenWebUI is not enabled for the company
- API endpoint is not reachable
- Invalid API credentials
- Connection timeout
"""
self.ensure_one()
if not self.openwebui_enabled:
raise UserError(_("OpenWebUI is not enabled for this company."))
success, result = self.env['openwebui.model'].test_connection()
if not success:
raise UserError(_("Connection test failed: %s") % result)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Connection to OpenWebUI successful!'),
'sticky': False,
'type': 'success',
}
}
def refresh_model_list(self):
"""Synchronize available AI models with Odoo.
This method performs the following operations based on the selected AI provider:
For OpenWebUI:
1. Verifies that OpenWebUI integration is enabled
2. Connects to the OpenWebUI API to fetch the latest model list
3. Updates the local database with model information
For Ollama:
1. Connects to the Ollama server
2. Fetches available models using the Ollama API
3. Updates the local database with model information
Returns:
dict: A notification action with the sync results
Raises:
UserError: If there are connection or synchronization issues
"""
self.ensure_one()
if self.ai_provider == 'ollama':
return self._refresh_ollama_models()
elif self.ai_provider == 'openwebui':
if not self.openwebui_enabled:
raise UserError(_("OpenWebUI is not enabled for this company."))
return self._refresh_openwebui_models()
else:
raise UserError(_("Unknown AI provider: %s", self.ai_provider))
def _refresh_ollama_models(self):
"""Fetch and sync available models from Ollama server."""
try:
import requests
url = f'http://localhost:{self.ollama_port}/api/tags'
response = requests.get(url, timeout=5)
response.raise_for_status()
models_data = response.json()
# Get the OpenWebUI model object
Model = self.env['openwebui.model'].with_context(sync_models=True)
# Process each model from Ollama
for model in models_data.get('models', []):
name = model.get('name', '')
if not name:
continue
# Split name and tag if present (format: name:tag)
identifier = name
if ':' in name:
name, tag = name.split(':', 1)
identifier = f"{name}:{tag}"
else:
identifier = f"{name}:latest"
# Check if model already exists
existing_model = Model.search([
('identifier', '=', identifier),
('company_id', '=', self.id)
], limit=1)
model_vals = {
'name': name,
'identifier': identifier,
'is_active': True,
'description': f"Ollama model: {name}",
'company_id': self.id
}
if existing_model:
# Update existing model
existing_model.write(model_vals)
else:
# Create new model
Model.create(model_vals)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Successfully synchronized Ollama models'),
'sticky': False,
'type': 'success',
}
}
except requests.exceptions.ConnectionError:
raise UserError(_("Could not connect to Ollama server. Please check if Ollama is running and the port number is correct."))
except requests.exceptions.Timeout:
raise UserError(_("Connection to Ollama server timed out. Please check your network settings."))
except requests.exceptions.RequestException as e:
raise UserError(_("Error connecting to Ollama server: %s", str(e)))
except Exception as e:
raise UserError(_("Error synchronizing Ollama models: %s", str(e)))
def _refresh_openwebui_models(self):
"""Fetch and sync available models from OpenWebUI."""
success, result = self.env['openwebui.model'].sync_models()
if not success:
raise UserError(_("Failed to refresh models: %s") % result)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Successfully synchronized OpenWebUI models'),
'sticky': False,
'type': 'success',
}
}