From c215b9bf0c37afc4cac03549b3202b7e6cedd4de Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Sun, 26 Apr 2026 11:32:35 -0400 Subject: [PATCH] fix(addon): require LLM backend reachability for bot online state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit action_ping was hitting /health, which returns 200 as long as the FastAPI app responds — even when Ollama is down, dispatch fails. So the bot showed online while every DM errored. Switch to /health/detailed and gate 'online' on db, master_agent and the active LLM backend (ollama for local privacy mode, claude otherwise) all reporting 'ok'. Anything else flips the bot to error or offline, which propagates through _sync_bot_user_presence to a grey dot in Discuss. --- addons/activeblue_ai/models/ab_ai_bot.py | 29 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/addons/activeblue_ai/models/ab_ai_bot.py b/addons/activeblue_ai/models/ab_ai_bot.py index f436d83..8719695 100644 --- a/addons/activeblue_ai/models/ab_ai_bot.py +++ b/addons/activeblue_ai/models/ab_ai_bot.py @@ -57,17 +57,34 @@ class AbAiBot(models.Model): def action_ping(self): self.ensure_one() - url = self._get_service_url() + '/health' + url = self._get_service_url() + '/health/detailed' try: resp = requests.get(url, timeout=5, headers=self._build_headers()) - if resp.status_code == 200: - self.write({'status': 'online', 'last_ping': fields.Datetime.now()}) - return {'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': {'message': _('AI service is online'), 'type': 'success'}} - else: + if resp.status_code != 200: self.write({'status': 'error'}) return {'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {'message': _('AI service returned %s') % resp.status_code, 'type': 'warning'}} + data = resp.json() if resp.content else {} + # Bot is only "online" when every backend the LLM router needs is ok. + # Local privacy mode requires Ollama; cloud requires Claude. We treat + # any backend whose status is not 'ok' as a hard failure for the + # privacy mode in use, plus DB and master agent are always required. + db_ok = data.get('db') == 'ok' + master_ok = data.get('master_agent') == 'ok' + mode = data.get('privacy_mode') or self.privacy_mode + ollama_ok = data.get('ollama') == 'ok' + llm_ok = ollama_ok if mode == 'local' else True + if db_ok and master_ok and llm_ok: + self.write({'status': 'online', 'last_ping': fields.Datetime.now()}) + return {'type': 'ir.actions.client', 'tag': 'display_notification', + 'params': {'message': _('AI service is online'), 'type': 'success'}} + self.write({'status': 'error', 'last_ping': fields.Datetime.now()}) + reason = ', '.join( + f'{k}={data.get(k)}' for k in ('db', 'master_agent', 'ollama') + if data.get(k) and data.get(k) != 'ok' + ) or 'degraded' + return {'type': 'ir.actions.client', 'tag': 'display_notification', + 'params': {'message': _('AI service degraded: %s') % reason, 'type': 'warning'}} except Exception as exc: self.write({'status': 'offline'}) return {'type': 'ir.actions.client', 'tag': 'display_notification',