Files
Odoo-Modules/fusion_accounting_ai/services/adapters/_base.py
gsinghpal 123db4219f feat(fusion_accounting_ai): add LLMProvider contract + configurable openai base_url
Phase 1 prerequisite for local LLM support. Adapters now declare
capability flags (supports_tool_calling, max_context_tokens, etc.) so
the engine can reason about what backend is available.

OpenAI adapter accepts fusion_accounting.openai_base_url config -- point
it at LM Studio (http://host.docker.internal:1234/v1) or Ollama
(http://host.docker.internal:11434/v1) and the existing OpenAI adapter
works unchanged.

Implementation note: existing Odoo AbstractModel adapters
(fusion.accounting.adapter.openai/claude) are preserved untouched to
avoid breaking the chat panel; the new plain-Python OpenAIAdapter and
ClaudeAdapter classes (LLMProvider subclasses) are added alongside them.

Made-with: Cursor
2026-04-19 10:45:30 -04:00

45 lines
1.7 KiB
Python

"""LLMProvider contract - every adapter must conform.
Phase 1 generalisation: makes local LLM (Ollama, LM Studio, vLLM, llamafile,
llama.cpp HTTP server) a one-config-line drop-in via the OpenAI-compatible
HTTP API surface that all of them expose.
"""
class LLMProvider:
"""Contract every LLM backend must satisfy. Adapters declare capabilities
as class attributes; the engine inspects them before calling optional methods."""
supports_tool_calling: bool = False
supports_streaming: bool = False
max_context_tokens: int = 4096
supports_embeddings: bool = False
def __init__(self, env):
self.env = env
def complete(self, *, system, messages, max_tokens=2048, temperature=0.0) -> dict:
"""Plain text completion. Required for ALL providers.
Returns: {'content': str, 'tokens_used': int, 'model': str}
"""
raise NotImplementedError
def complete_with_tools(self, *, system, messages, tools, max_tokens=2048) -> dict:
"""Tool-calling completion. Optional - caller checks supports_tool_calling first.
Returns: {'content': str, 'tool_calls': [{'name': str, 'arguments': dict}], ...}
"""
raise NotImplementedError(
f"{type(self).__name__} does not support tool-calling. "
f"Check supports_tool_calling before calling.")
def embed(self, texts: list[str]) -> list[list[float]]:
"""Embeddings. Optional - caller checks supports_embeddings first.
Returns: list of float vectors, one per input text.
"""
raise NotImplementedError(
f"{type(self).__name__} does not support embeddings. "
f"Check supports_embeddings before calling.")