talking to ollama
This commit is contained in:
parent
e48e6d9aef
commit
2df5651f12
35 changed files with 276 additions and 146 deletions
|
|
@ -24,12 +24,13 @@ into Odoo. It includes:
|
||||||
'security/ai_security.xml',
|
'security/ai_security.xml',
|
||||||
'security/ir_rule.xml',
|
'security/ir_rule.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/menu.xml',
|
|
||||||
'views/res_company_views.xml',
|
'views/res_company_views.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
|
'views/ai_provider_views.xml',
|
||||||
'views/ai_provider_instance_views.xml',
|
'views/ai_provider_instance_views.xml',
|
||||||
'views/ai_model_views.xml',
|
'views/ai_model_views.xml',
|
||||||
'views/ai_model_stats_views.xml',
|
'views/ai_model_stats_views.xml',
|
||||||
|
'views/menu.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': False,
|
'application': False,
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,6 @@ class AIModel(models.Model):
|
||||||
_order = 'sequence, name'
|
_order = 'sequence, name'
|
||||||
_check_company = False # Disable automatic company checks
|
_check_company = False # Disable automatic company checks
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _has_provider_modules(self):
|
|
||||||
"""Check if any AI provider modules are installed."""
|
|
||||||
modules = ['ollama_ai_integration', 'chatgpt_ai_integration']
|
|
||||||
return any(self.env['ir.module.module'].search([('name', 'in', modules), ('state', '=', 'installed')]))
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def default_get(self, fields_list):
|
|
||||||
"""Override default_get to prevent creation if no provider modules are installed."""
|
|
||||||
if not self._has_provider_modules():
|
|
||||||
raise UserError(_('No AI provider modules are installed. Please install at least one provider module (e.g., Ollama or ChatGPT) before creating an AI model.'))
|
|
||||||
return super().default_get(fields_list)
|
|
||||||
|
|
||||||
active = fields.Boolean(
|
active = fields.Boolean(
|
||||||
string='Active',
|
string='Active',
|
||||||
default=True,
|
default=True,
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,20 @@ from odoo.addons.mail.models.mail_thread import MailThread
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class AIProviderInstance(models.Model):
|
class BaseAIProviderInstance(models.Model):
|
||||||
_name = 'ai.provider.instance'
|
_name = 'ai.provider.instance'
|
||||||
_description = 'AI Provider Instance'
|
_description = 'AI Provider Instance'
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
_check_company = False # Disable automatic company checks
|
_check_company = False # Disable automatic company checks
|
||||||
_inherit = ['mail.thread', 'ai.base.mixin']
|
_inherit = ['mail.thread', 'ai.base.mixin']
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _has_provider_modules(self):
|
|
||||||
"""Check if any AI provider modules are installed."""
|
|
||||||
modules = ['ollama_ai_integration', 'chatgpt_ai_integration']
|
|
||||||
return any(self.env['ir.module.module'].search([('name', 'in', modules), ('state', '=', 'installed')]))
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields_list):
|
def default_get(self, fields_list):
|
||||||
"""Override default_get to prevent creation if no provider modules are installed."""
|
"""Override default_get to prevent creation if no provider modules are installed."""
|
||||||
if not self._has_provider_modules():
|
defaults = super().default_get(fields_list)
|
||||||
raise UserError(_('No AI provider modules are installed. Please install at least one provider module (e.g., Ollama or ChatGPT) before creating a provider instance.'))
|
if defaults.get('provider_type', 'none') == 'none':
|
||||||
return super().default_get(fields_list)
|
defaults['provider_type'] = 'ollama'
|
||||||
|
return defaults
|
||||||
|
|
||||||
active = fields.Boolean(
|
active = fields.Boolean(
|
||||||
string='Active',
|
string='Active',
|
||||||
|
|
@ -35,23 +30,13 @@ class AIProviderInstance(models.Model):
|
||||||
help='Name of this provider instance (e.g., "OpenWebUI Production", "Ollama Local")'
|
help='Name of this provider instance (e.g., "OpenWebUI Production", "Ollama Local")'
|
||||||
)
|
)
|
||||||
|
|
||||||
provider_id = fields.Many2one(
|
|
||||||
'ai.provider',
|
|
||||||
string='Provider',
|
|
||||||
required=True,
|
|
||||||
ondelete='restrict',
|
|
||||||
help='The AI provider configuration'
|
|
||||||
)
|
|
||||||
|
|
||||||
provider_type = fields.Selection(
|
provider_type = fields.Selection(
|
||||||
selection=[
|
[('none', 'None')], # Base selection, will be extended by provider modules
|
||||||
('none', 'None') # Base selection, will be extended by provider modules
|
|
||||||
],
|
|
||||||
string='Provider Type',
|
string='Provider Type',
|
||||||
required=True,
|
required=True,
|
||||||
ondelete={'none': 'set default'},
|
|
||||||
default='none',
|
default='none',
|
||||||
help='Type of AI provider'
|
help='The type of AI provider for this instance',
|
||||||
|
ondelete={'none': lambda r: r.write({'provider_type': 'none'})}
|
||||||
)
|
)
|
||||||
|
|
||||||
host = fields.Char(
|
host = fields.Char(
|
||||||
|
|
@ -62,7 +47,8 @@ class AIProviderInstance(models.Model):
|
||||||
|
|
||||||
api_key = fields.Char(
|
api_key = fields.Char(
|
||||||
string='API Key',
|
string='API Key',
|
||||||
help='API key if required by the provider'
|
help='API key if required by the provider',
|
||||||
|
invisible=lambda self: self.provider_type == 'ollama'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -99,17 +85,13 @@ class AIProviderInstance(models.Model):
|
||||||
def test_connection(self):
|
def test_connection(self):
|
||||||
"""Test the connection to this provider instance."""
|
"""Test the connection to this provider instance."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
provider_model = f'ai.provider.{self.provider_type}'
|
if self.provider_type == 'none':
|
||||||
if provider_model not in self.env:
|
raise UserError(_('Please select a provider type'))
|
||||||
raise UserError(_("Provider type %s is not installed", self.provider_type))
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
|
|
||||||
return self.env[provider_model].test_connection(self)
|
|
||||||
|
|
||||||
def sync_models(self):
|
def sync_models(self):
|
||||||
"""Synchronize models from this provider instance."""
|
"""Synchronize models from this provider instance."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
provider_model = f'ai.provider.{self.provider_type}'
|
if self.provider_type == 'none':
|
||||||
if provider_model not in self.env:
|
raise UserError(_('Please select a provider type'))
|
||||||
raise UserError(_("Provider type %s is not installed", self.provider_type))
|
|
||||||
|
|
||||||
return self.env[provider_model].sync_models(self)
|
|
||||||
|
|
|
||||||
|
|
@ -17,49 +17,39 @@ Le module AI Integration fournit une infrastructure flexible pour intégrer diff
|
||||||
|
|
||||||
### 2. AI Provider Instance (`ai.provider.instance`)
|
### 2. AI Provider Instance (`ai.provider.instance`)
|
||||||
- **Description**: Instance spécifique d'un fournisseur d'IA
|
- **Description**: Instance spécifique d'un fournisseur d'IA
|
||||||
|
- **Héritage**: `mail.thread`, `ai.base.mixin`
|
||||||
- **Champs principaux**:
|
- **Champs principaux**:
|
||||||
- `name`: Nom de l'instance
|
- `name`: Nom de l'instance (ex: "OpenWebUI Production", "Ollama Local")
|
||||||
- `provider_id`: Fournisseur associé
|
- `provider_id`: Fournisseur associé
|
||||||
- `provider_type`: Type de fournisseur
|
- `provider_type`: Type de fournisseur (extensible par modules)
|
||||||
- `host`: Adresse de l'hôte
|
|
||||||
- `api_key`: Clé API (si nécessaire)
|
|
||||||
- `active`: État actif/inactif
|
- `active`: État actif/inactif
|
||||||
|
- **Validation**:
|
||||||
|
- Vérifie la présence d'au moins un module fournisseur installé
|
||||||
|
|
||||||
### 3. AI Model (`ai.model`)
|
### 3. AI Model (`ai.model`)
|
||||||
- **Description**: Modèles d'IA disponibles
|
- **Description**: Modèles d'IA disponibles
|
||||||
- **Champs principaux**:
|
- **Champs principaux**:
|
||||||
- `name`: Nom du modèle
|
- `name`: Nom du modèle
|
||||||
- `identifier`: Identifiant technique
|
- `identifier`: Identifiant technique (ex: gpt-3.5-turbo, mistral-7b)
|
||||||
- `provider_instance_id`: Instance du fournisseur
|
- `provider_instance_id`: Instance du fournisseur (cascade)
|
||||||
|
- `provider_type`: Type de fournisseur (relié à l'instance)
|
||||||
- `active`: État actif/inactif
|
- `active`: État actif/inactif
|
||||||
|
- `sequence`: Ordre d'affichage
|
||||||
|
- **Validation**:
|
||||||
|
- Vérifie la présence d'au moins un module fournisseur installé
|
||||||
|
|
||||||
### 4. AI Model Stats (`ai.model.stats`)
|
### 4. AI Generation Parameters (`ai.generation.params`)
|
||||||
- **Description**: Statistiques d'utilisation des modèles
|
- **Description**: Paramètres de génération pour les modèles d'IA
|
||||||
|
- **Type**: Modèle abstrait
|
||||||
- **Champs principaux**:
|
- **Champs principaux**:
|
||||||
- `model_id`: Modèle associé
|
- `temperature`: Contrôle de l'aléatoire (défaut: 0.7)
|
||||||
- `total_tokens`: Nombre total de tokens
|
- `repeat_penalty`: Pénalité de répétition (défaut: 1.1)
|
||||||
- `total_requests`: Nombre total de requêtes
|
- `max_tokens`: Nombre maximum de tokens (défaut: 2048)
|
||||||
- `average_latency`: Latence moyenne
|
|
||||||
|
|
||||||
## Mixin de Base
|
|
||||||
|
|
||||||
### AI Base Mixin (`ai.base.mixin`)
|
|
||||||
- **Description**: Mixin unifié pour l'intégration IA et les paramètres de génération
|
|
||||||
- **Champs principaux**:
|
|
||||||
- `temperature`: Contrôle de la créativité (0.0 - 2.0)
|
|
||||||
- `top_p`: Sampling nucleus (0.0 - 1.0)
|
|
||||||
- `max_tokens`: Limite de tokens (1 - 32768)
|
|
||||||
- `stop_sequences`: Séquences d'arrêt
|
- `stop_sequences`: Séquences d'arrêt
|
||||||
- `timeout`: Délai d'attente (1 - 300s)
|
- `frequency_penalty`: Pénalité de fréquence (défaut: 0.0)
|
||||||
- `retry_count`: Nombre de tentatives (0 - 5)
|
- `presence_penalty`: Pénalité de présence (défaut: 0.0)
|
||||||
- `stream_response`: Activation du streaming
|
|
||||||
- **Méthodes principales**:
|
|
||||||
- `_get_ai_provider_instance`: Obtenir l'instance du fournisseur
|
|
||||||
- `_get_ai_model`: Obtenir le modèle à utiliser
|
|
||||||
- `send_ai_message`: Envoyer un message à l'IA
|
|
||||||
- `_get_base_generation_params`: Obtenir les paramètres de génération
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration et Interfaces
|
||||||
|
|
||||||
### 1. Res Config Settings
|
### 1. Res Config Settings
|
||||||
- **Description**: Paramètres de configuration globaux
|
- **Description**: Paramètres de configuration globaux
|
||||||
|
|
@ -73,9 +63,7 @@ Le module AI Integration fournit une infrastructure flexible pour intégrer diff
|
||||||
- **Méthodes principales**:
|
- **Méthodes principales**:
|
||||||
- `_get_default_provider_instance`: Obtenir l'instance par défaut
|
- `_get_default_provider_instance`: Obtenir l'instance par défaut
|
||||||
|
|
||||||
## Interfaces
|
### 3. AI Provider Interface (`ai.provider.interface`)
|
||||||
|
|
||||||
### AI Provider Interface (`ai.provider.interface`)
|
|
||||||
- **Description**: Interface abstraite pour les fournisseurs d'IA
|
- **Description**: Interface abstraite pour les fournisseurs d'IA
|
||||||
- **Méthodes requises**:
|
- **Méthodes requises**:
|
||||||
- `send_message`: Envoyer un message
|
- `send_message`: Envoyer un message
|
||||||
|
|
@ -84,7 +72,19 @@ Le module AI Integration fournit une infrastructure flexible pour intégrer diff
|
||||||
|
|
||||||
## Notes d'Implémentation
|
## Notes d'Implémentation
|
||||||
|
|
||||||
1. Tous les fournisseurs d'IA doivent implémenter `ai.provider.interface`
|
1. **Architecture Modulaire**:
|
||||||
2. Les instances de fournisseur héritent des paramètres de génération via `ai.generation.params`
|
- Modules fournisseurs disponibles: `ollama_ai_integration`, `chatgpt_ai_integration`
|
||||||
3. La configuration est hiérarchique : Global > Société > Instance
|
- Vérification de la présence d'au moins un module fournisseur avant création d'instances
|
||||||
4. Les statistiques sont collectées automatiquement pour chaque modèle
|
|
||||||
|
2. **Héritage et Extensions**:
|
||||||
|
- Les instances de fournisseur héritent de `mail.thread` et `ai.base.mixin`
|
||||||
|
- Les paramètres de génération sont définis dans le modèle abstrait `ai.generation.params`
|
||||||
|
|
||||||
|
3. **Configuration Hiérarchique**:
|
||||||
|
- Configuration globale > Paramètres société > Instance
|
||||||
|
- Paramètres de génération personnalisables à plusieurs niveaux
|
||||||
|
|
||||||
|
4. **Sécurité et Validation**:
|
||||||
|
- Vérifications de sécurité intégrées
|
||||||
|
- Validation des modules requis
|
||||||
|
- Gestion des paramètres de génération avec valeurs par défaut
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,3 @@ class ResConfigSettings(models.TransientModel):
|
||||||
config_parameter='ai_integration.ai_batch_size',
|
config_parameter='ai_integration.ai_batch_size',
|
||||||
default=100,
|
default=100,
|
||||||
default_model='ai.provider.instance')
|
default_model='ai.provider.instance')
|
||||||
|
|
||||||
@api.onchange('default_provider_instance_id')
|
|
||||||
def _onchange_default_provider_instance(self):
|
|
||||||
"""Reset model when provider instance changes"""
|
|
||||||
if self.default_provider_instance_id:
|
|
||||||
self.default_model_id = False
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<field name="name">ai.model.stats.list</field>
|
<field name="name">ai.model.stats.list</field>
|
||||||
<field name="model">ai.model.stats</field>
|
<field name="model">ai.model.stats</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="Model Statistics">
|
<list string="Model Statistics" create="false">
|
||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="model_id"/>
|
<field name="model_id"/>
|
||||||
<field name="provider_instance_id"/>
|
<field name="provider_instance_id"/>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<field name="model">ai.model</field>
|
<field name="model">ai.model</field>
|
||||||
<field name="type">list</field>
|
<field name="type">list</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="AI Models">
|
<list string="AI Models" create="false">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="provider_instance_id"/>
|
<field name="provider_instance_id"/>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<field name="name">ai.provider.instance.list</field>
|
<field name="name">ai.provider.instance.list</field>
|
||||||
<field name="model">ai.provider.instance</field>
|
<field name="model">ai.provider.instance</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="AI Provider Instances">
|
<list name="ai_instance" string="AI Provider Instances" create="true">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="provider_type"/>
|
<field name="provider_type"/>
|
||||||
<field name="host"/>
|
<field name="host"/>
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
<group>
|
<group>
|
||||||
<field name="provider_type"/>
|
<field name="provider_type"/>
|
||||||
<field name="host"/>
|
<field name="host"/>
|
||||||
<field name="api_key" password="True"/>
|
<field name="api_key"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="timeout"/>
|
<field name="timeout"/>
|
||||||
|
|
|
||||||
88
ai_integration/views/ai_provider_views.xml
Normal file
88
ai_integration/views/ai_provider_views.xml
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="ai_provider_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">ai.provider.tree</field>
|
||||||
|
<field name="model">ai.provider</field>
|
||||||
|
<field name="type">list</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="AI Providers" create="false">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="default_host"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="ai_provider_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">ai.provider.form</field>
|
||||||
|
<field name="model">ai.provider</field>
|
||||||
|
<field name="type">form</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="AI Provider">
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="test_connection" type="object"
|
||||||
|
string="Test Connection" class="oe_stat_button"
|
||||||
|
icon="fa-plug"/>
|
||||||
|
<button name="get_models" type="object"
|
||||||
|
string="Get Models" class="oe_stat_button"
|
||||||
|
icon="fa-list"/>
|
||||||
|
</div>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name" class="oe_edit_only"/>
|
||||||
|
<h1><field name="name" placeholder="e.g. Ollama"/></h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="default_host"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Description" name="description">
|
||||||
|
<field name="description" nolabel="1" placeholder="Enter a description..."/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Search View -->
|
||||||
|
<record id="ai_provider_view_search" model="ir.ui.view">
|
||||||
|
<field name="name">ai.provider.search</field>
|
||||||
|
<field name="model">ai.provider</field>
|
||||||
|
<field name="type">search</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search AI Providers">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
|
||||||
|
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="ai_provider_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">AI Providers</field>
|
||||||
|
<field name="res_model">ai.provider</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="context">{'search_default_active': 1}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No AI providers found
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
AI providers are automatically created when you install AI integration modules.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -16,12 +16,20 @@
|
||||||
<menuitem id="menu_ai_settings"
|
<menuitem id="menu_ai_settings"
|
||||||
name="Settings"
|
name="Settings"
|
||||||
parent="menu_ai_config"
|
parent="menu_ai_config"
|
||||||
action="action_ai_integration_configuration"
|
action="ai_integration.action_ai_integration_configuration"
|
||||||
|
sequence="10"
|
||||||
|
groups="base.group_system"/>
|
||||||
|
|
||||||
|
<!-- Provider Types menu -->
|
||||||
|
<menuitem id="menu_ai_provider_types"
|
||||||
|
name="Provider Types"
|
||||||
|
parent="menu_ai_config"
|
||||||
|
action="ai_provider_action"
|
||||||
sequence="10"
|
sequence="10"
|
||||||
groups="base.group_system"/>
|
groups="base.group_system"/>
|
||||||
|
|
||||||
<!-- Provider Instances menu -->
|
<!-- Provider Instances menu -->
|
||||||
<menuitem id="menu_ai_providers"
|
<menuitem id="menu_ai_provider_instances"
|
||||||
name="Provider Instances"
|
name="Provider Instances"
|
||||||
parent="menu_ai_config"
|
parent="menu_ai_config"
|
||||||
action="ai_provider_instance_action"
|
action="ai_provider_instance_action"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
<!-- Settings Action -->
|
||||||
|
<record id="action_ai_integration_configuration" model="ir.actions.act_window">
|
||||||
|
<field name="name">AI Integration Settings</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">res.config.settings</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">inline</field>
|
||||||
|
<field name="context">{"module" : "ai_integration"}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||||
<field name="name">res.config.settings.view.form.inherit.ai.integration</field>
|
<field name="name">res.config.settings.view.form.inherit.ai.integration</field>
|
||||||
<field name="model">res.config.settings</field>
|
<field name="model">res.config.settings</field>
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ in your Odoo instance. Features include:
|
||||||
'ai_integration'
|
'ai_integration'
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'data/ollama_provider.xml',
|
'data/ai_provider_data.xml',
|
||||||
# 'views/ollama_views.xml',
|
|
||||||
'views/ollama_stats_views.xml',
|
'views/ollama_stats_views.xml',
|
||||||
|
'views/ai_provider_instance_views.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
],
|
],
|
||||||
'external_dependencies': {
|
'external_dependencies': {
|
||||||
13
ai_integration_ollama_api/data/ai_provider_data.xml
Normal file
13
ai_integration_ollama_api/data/ai_provider_data.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<!-- Ollama Provider -->
|
||||||
|
<record id="ai_provider_ollama" model="ai.provider">
|
||||||
|
<field name="name">Ollama</field>
|
||||||
|
<field name="code">ollama</field>
|
||||||
|
<field name="description">Ollama is a local AI model provider that allows you to run various open-source models locally.</field>
|
||||||
|
<field name="default_host">http://localhost:11434</field>
|
||||||
|
<field name="active" eval="True"/>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -18,6 +18,7 @@ from . import ollama_provider_mixin
|
||||||
|
|
||||||
# Core Implementation
|
# Core Implementation
|
||||||
from . import ollama_provider
|
from . import ollama_provider
|
||||||
|
from . import ai_provider_instance
|
||||||
|
|
||||||
# Statistics and Monitoring
|
# Statistics and Monitoring
|
||||||
from . import ollama_model_stats
|
from . import ollama_model_stats
|
||||||
|
|
@ -1,56 +1,78 @@
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
import requests
|
||||||
|
|
||||||
class AIProviderInstance(models.Model):
|
class OllamaAIProviderInstance(models.Model):
|
||||||
"""Extends the AI Provider Instance model to support Ollama-specific configuration.
|
"""Extends the AI Provider Instance model to support Ollama-specific configuration.
|
||||||
|
|
||||||
This model inherits from both ai.provider.instance and ollama.provider.mixin to:
|
This model inherits from both ai.provider.instance and ollama.provider.mixin to:
|
||||||
1. Add Ollama-specific fields (num_ctx, temperature, etc.)
|
1. Add Ollama-specific fields (num_ctx, temperature, etc.)
|
||||||
2. Handle field visibility based on provider_type
|
2. Handle field visibility based on provider_type
|
||||||
3. Manage field cleanup when switching providers
|
3. Manage field cleanup when switching providers
|
||||||
|
|
||||||
Note: This extends the base ai.provider.instance model instead of creating
|
|
||||||
a new one to ensure seamless integration with the core AI framework.
|
|
||||||
"""
|
"""
|
||||||
|
_inherit = ['ai.provider.instance', 'ollama.provider.mixin']
|
||||||
_name = 'ai.provider.instance'
|
_name = 'ai.provider.instance'
|
||||||
_inherit = ['ollama.provider.mixin', 'mail.thread']
|
_description = 'Ollama AI Provider Instance'
|
||||||
_description = 'AI Provider Instance'
|
|
||||||
|
|
||||||
# Basic Fields
|
# Override provider_type to add Ollama option
|
||||||
name = fields.Char(
|
provider_type = fields.Selection(
|
||||||
string='Name',
|
selection_add=[('ollama', 'Ollama')],
|
||||||
required=True,
|
ondelete={'ollama': lambda r: r.write({'provider_type': 'none'})}
|
||||||
tracking=True,
|
)
|
||||||
help='Name of this AI provider instance')
|
|
||||||
|
|
||||||
active = fields.Boolean(
|
|
||||||
string='Active',
|
|
||||||
default=True,
|
|
||||||
tracking=True,
|
|
||||||
help='Whether this provider instance is active')
|
|
||||||
|
|
||||||
host = fields.Char(
|
|
||||||
string='Host',
|
|
||||||
required=True,
|
|
||||||
default='http://localhost:11434',
|
|
||||||
tracking=True,
|
|
||||||
help='Ollama server host URL')
|
|
||||||
|
|
||||||
company_id = fields.Many2one(
|
|
||||||
'res.company',
|
|
||||||
string='Company',
|
|
||||||
required=True,
|
|
||||||
default=lambda self: self.env.company,
|
|
||||||
help='Company this provider instance belongs to')
|
|
||||||
|
|
||||||
@api.onchange('provider_type')
|
@api.onchange('provider_type')
|
||||||
def _onchange_provider_type(self):
|
def _onchange_provider_type(self):
|
||||||
"""Automatically clear Ollama-specific fields when switching provider type.
|
"""Handle provider type changes.
|
||||||
|
|
||||||
This ensures that Ollama configuration is only kept when the provider
|
When switching to 'ollama':
|
||||||
type is 'ollama'. When switching to another provider, all Ollama-specific
|
- Set default host if empty
|
||||||
fields are reset to their default values to avoid confusion.
|
|
||||||
|
When switching away from 'ollama':
|
||||||
|
- Clear Ollama-specific fields
|
||||||
"""
|
"""
|
||||||
if self.provider_type != 'ollama':
|
if self.provider_type == 'ollama':
|
||||||
|
if not self.host:
|
||||||
|
self.host = 'http://localhost:11434'
|
||||||
|
else:
|
||||||
|
# Clear Ollama-specific fields
|
||||||
|
self.update({
|
||||||
|
'num_ctx': False,
|
||||||
|
'temperature': False,
|
||||||
|
'top_p': False,
|
||||||
|
'top_k': False,
|
||||||
|
'repeat_penalty': False,
|
||||||
|
'repeat_last_n': False,
|
||||||
|
'num_thread': False,
|
||||||
|
'num_gpu': False,
|
||||||
|
'num_batch': False,
|
||||||
|
'model_name': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Override default host for Ollama
|
||||||
|
host = fields.Char(
|
||||||
|
default='http://localhost:11434',
|
||||||
|
help='Ollama server host URL')
|
||||||
|
|
||||||
|
@api.onchange('provider_type')
|
||||||
|
def _onchange_provider_type(self):
|
||||||
|
"""Handle provider type changes.
|
||||||
|
|
||||||
|
When switching to 'ollama':
|
||||||
|
- Set the provider_id to the Ollama provider
|
||||||
|
- Set default host if empty
|
||||||
|
|
||||||
|
When switching away from 'ollama':
|
||||||
|
- Clear Ollama-specific fields
|
||||||
|
"""
|
||||||
|
if self.provider_type == 'ollama':
|
||||||
|
# Find and set the Ollama provider
|
||||||
|
ollama_provider = self.env['ai.provider'].search([('code', '=', 'ollama')], limit=1)
|
||||||
|
if ollama_provider:
|
||||||
|
self.provider_id = ollama_provider.id
|
||||||
|
if not self.host:
|
||||||
|
self.host = ollama_provider.default_host
|
||||||
|
else:
|
||||||
|
# Clear Ollama-specific fields
|
||||||
self.update({
|
self.update({
|
||||||
'num_ctx': False, # Context length
|
'num_ctx': False, # Context length
|
||||||
'temperature': False, # Sampling temperature
|
'temperature': False, # Sampling temperature
|
||||||
|
|
@ -75,7 +97,8 @@ class AIProviderInstance(models.Model):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to list models as a basic connectivity test
|
# Try to list models as a basic connectivity test
|
||||||
self.env['ai.provider.ollama']._get_models(self)
|
response = requests.get(f'{self.host}/api/tags')
|
||||||
|
response.raise_for_status()
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.client',
|
'type': 'ir.actions.client',
|
||||||
'tag': 'display_notification',
|
'tag': 'display_notification',
|
||||||
|
|
@ -104,18 +127,34 @@ class AIProviderInstance(models.Model):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
provider = self.env['ai.provider.ollama']
|
# Get models from Ollama API
|
||||||
models = provider._get_models(self)
|
response = requests.get(f'{self.host}/api/tags')
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
models = [{
|
||||||
|
'name': model['name'],
|
||||||
|
'id': model['name'],
|
||||||
|
} for model in response.json()['models']]
|
||||||
|
|
||||||
for model_data in models:
|
for model_data in models:
|
||||||
# Create or update AI model record
|
# Create or update AI model record
|
||||||
self.env['ai.model'].create_or_update({
|
vals = {
|
||||||
'name': model_data['name'],
|
'name': model_data['name'],
|
||||||
'identifier': model_data['id'],
|
'identifier': model_data['id'],
|
||||||
'provider_instance_id': self.id,
|
'provider_instance_id': self.id,
|
||||||
'model_type': 'text',
|
|
||||||
'active': True,
|
'active': True,
|
||||||
})
|
}
|
||||||
|
# Search for existing model
|
||||||
|
existing = self.env['ai.model'].search([
|
||||||
|
('identifier', '=', model_data['id']),
|
||||||
|
('provider_instance_id', '=', self.id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
existing.write(vals)
|
||||||
|
else:
|
||||||
|
self.env['ai.model'].create(vals)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.client',
|
'type': 'ir.actions.client',
|
||||||
|
|
@ -22,14 +22,6 @@ class OllamaProviderMixin(models.AbstractModel):
|
||||||
_description = 'Ollama Provider Configuration Mixin'
|
_description = 'Ollama Provider Configuration Mixin'
|
||||||
_inherit = ['ai.generation.params']
|
_inherit = ['ai.generation.params']
|
||||||
|
|
||||||
# Provider Configuration
|
|
||||||
provider_type = fields.Selection(
|
|
||||||
selection=[('ollama', 'Ollama')],
|
|
||||||
string='Provider Type',
|
|
||||||
required=True,
|
|
||||||
default='ollama',
|
|
||||||
help='Type of AI provider - Must be Ollama for this configuration')
|
|
||||||
|
|
||||||
# Model Parameters
|
# Model Parameters
|
||||||
model_name = fields.Char(
|
model_name = fields.Char(
|
||||||
string='Model Name',
|
string='Model Name',
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Inherit AI Provider Instance List View -->
|
||||||
|
<record id="ollama_ai_provider_instance_view_list" model="ir.ui.view">
|
||||||
|
<field name="name">ai.provider.instance.list.ollama</field>
|
||||||
|
<field name="model">ai.provider.instance</field>
|
||||||
|
<field name="inherit_id" ref="ai_integration.ai_provider_instance_view_list"/>
|
||||||
|
<field name="mode">primary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//list[@name='ai_instance']" position="attributes">
|
||||||
|
<attribute name="create">true</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
<!-- Menu Item -->
|
<!-- Menu Item -->
|
||||||
<menuitem id="menu_ollama_model_stats"
|
<menuitem id="menu_ollama_model_stats"
|
||||||
name="Model Statistics"
|
name="Model Statistics"
|
||||||
parent="ai_integration.menu_ai_integration"
|
parent="ai_integration.menu_ai_config"
|
||||||
action="action_ollama_model_stats"
|
action="action_ollama_model_stats"
|
||||||
sequence="30"/>
|
sequence="30"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
Loading…
Reference in a new issue