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
45 lines
1.7 KiB
Python
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.")
|