Files
odoo-ai/agent_service/config.py
Carlos Garcia c769fca79f fix: resolve all 5 startup constructor errors + add DB retry
Fixes all errors reported in docker compose logs agent-service:

1. config.py: add ollama_max_concurrent, claude_timeout, claude_max_concurrent
   fields so LLMRouter(config=settings) can read them without AttributeError.

2. main.py - LLM router: drop manual OllamaBackend/ClaudeBackend construction;
   call LLMRouter(config=settings, pg_pool=pool) to match class signature.
   Fixes: OllamaBackend.__init__() unexpected kwarg 'base_url'.

3. main.py - DB: add 5-attempt retry with 2s backoff and redacted DSN logging.
   Fixes: connection refused race on startup before Postgres accepts connections.

4. main.py - AgentRegistry: call AgentRegistry() with no args (class takes none),
   then await agent_registry.load_from_odoo(odoo) to populate active agents.
   Fixes: AgentRegistry.__init__() unexpected kwarg 'odoo'.

5. main.py - PeerBus: pass registry=agent_registry at construction; register
   specialist agents on agent_registry (not peer_bus, which has no register()).
   peer_bus.py: make directive_id optional (default None) — bus is a singleton
   at startup; directive_id is only needed per-request.
   Fixes: PeerBus.__init__() missing positional args 'registry' and 'directive_id'.

6. main.py - MasterAgent: drop unexpected peer_bus= kwarg from constructor call.
   Fixes: MasterAgent.__init__() unexpected kwarg 'peer_bus'.

7. mcp_router.py: pass NotificationOptions() instance instead of None.
   Fixes: AttributeError 'NoneType' has no attribute 'tools_changed' (was applied
   in running container but not committed; now committed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:48:23 -04:00

96 lines
2.8 KiB
Python

from __future__ import annotations
import os
from functools import lru_cache
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# Odoo
odoo_url: str = 'http://localhost:8069'
odoo_db: str = 'odoo'
odoo_api_key: str = ''
# Ollama
ollama_url: str = 'http://localhost:11434'
ollama_model: str = 'llama3'
ollama_timeout: int = 120
ollama_max_concurrent: int = 2
# Anthropic / Claude
anthropic_api_key: str = ''
claude_model: str = 'claude-sonnet-4-6'
claude_timeout: int = 120
claude_max_concurrent: int = 2
# Privacy
llm_privacy_mode: str = 'local' # local | hybrid | cloud
# Per-agent backend overrides (env: AGENT_BACKEND_FINANCE=claude)
agent_backend_finance: str = ''
agent_backend_accounting: str = ''
agent_backend_crm: str = ''
agent_backend_sales: str = ''
agent_backend_project: str = ''
agent_backend_elearning: str = ''
agent_backend_expenses: str = ''
agent_backend_employees: str = ''
# Service
agent_service_port: int = 8001
webhook_secret: str = ''
allowed_callback_ip: str = ''
# Postgres
postgres_host: str = 'localhost'
postgres_port: int = 5432
postgres_db: str = 'activeblue_ai'
postgres_user: str = 'activeblue'
postgres_password: str = ''
postgres_min_connections: int = 2
postgres_max_connections: int = 10
# Rate limiting
dispatch_rate_limit_per_user: int = 30 # requests per minute
directive_timeout_minutes: int = 10
# Logging
log_level: str = 'INFO'
log_format: str = 'json'
loki_url: str = ''
class Config:
env_file = '.env'
env_file_encoding = 'utf-8'
@property
def postgres_dsn(self) -> str:
return (
f'postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}'
f'@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}'
)
@property
def postgres_asyncpg_dsn(self) -> str:
return (
f'asyncpg://{self.postgres_user}:{self.postgres_password}'
f'@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}'
)
def agent_backend_override(self, agent_name: str) -> str:
mapping = {
'finance_agent': self.agent_backend_finance,
'accounting_agent': self.agent_backend_accounting,
'crm_agent': self.agent_backend_crm,
'sales_agent': self.agent_backend_sales,
'project_agent': self.agent_backend_project,
'elearning_agent': self.agent_backend_elearning,
'expenses_agent': self.agent_backend_expenses,
'employees_agent': self.agent_backend_employees,
}
return mapping.get(agent_name, '')
@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()