mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 10:01:23 +02:00
Extends the chat-completions surface so callers can ask any provider to call named functions and get structured tool_calls back. Wired through all three provider adapters so the planner and companion can switch off the fragile JSON-parsing pathway. - Request: tools[], tool_choice, assistant tool_calls, tool-role messages with tool_call_id. - Response: MessageResponse.tool_calls, Choice.finish_reason adds "tool_calls", DeltaContent streams tool_calls. - Google provider: Tool(function_declarations=...) build, result normalised (args dict → JSON string), function_response parts on a user turn for tool-role messages. - OpenAI-compat: 1:1 passthrough of the OpenAI spec. - Ollama: /api/chat passthrough; model-level capability check via a TOOL_CAPABLE_OLLAMA_PATTERNS whitelist (llama3.1+, qwen2.5+, mistral, command-r, …) — unsupported models rejected rather than silently falling back to prose. - Router: model_supports_tools() check upfront for both streaming and non-streaming paths; ProviderCapabilityError bubbles as 400. No silent downgrade. Missing tool support = explicit error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.1 KiB
Python
76 lines
2.1 KiB
Python
"""Abstract base class for LLM providers."""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any
|
|
|
|
from src.models import (
|
|
ChatCompletionRequest,
|
|
ChatCompletionResponse,
|
|
ChatCompletionStreamResponse,
|
|
EmbeddingRequest,
|
|
EmbeddingResponse,
|
|
ModelInfo,
|
|
)
|
|
|
|
|
|
class LLMProvider(ABC):
|
|
"""Abstract base class for LLM providers."""
|
|
|
|
name: str = "base"
|
|
|
|
# Set to True if the provider supports OpenAI-style `tools` + `tool_calls`
|
|
# for chat completions. The router rejects tool-bearing requests routed
|
|
# to providers without support rather than silently dropping the tools.
|
|
# Provider adapters may further narrow this per-model if needed.
|
|
supports_tools: bool = False
|
|
|
|
def model_supports_tools(self, model: str) -> bool:
|
|
"""Check if a specific model within this provider supports tools.
|
|
|
|
Default: falls back to the provider-wide flag. Providers with a
|
|
mixed capability surface (e.g. Ollama — depends on the local
|
|
model) override this.
|
|
"""
|
|
return self.supports_tools
|
|
|
|
@abstractmethod
|
|
async def chat_completion(
|
|
self,
|
|
request: ChatCompletionRequest,
|
|
model: str,
|
|
) -> ChatCompletionResponse:
|
|
"""Generate a chat completion (non-streaming)."""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def chat_completion_stream(
|
|
self,
|
|
request: ChatCompletionRequest,
|
|
model: str,
|
|
) -> AsyncIterator[ChatCompletionStreamResponse]:
|
|
"""Generate a chat completion (streaming)."""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def list_models(self) -> list[ModelInfo]:
|
|
"""List available models."""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def embeddings(
|
|
self,
|
|
request: EmbeddingRequest,
|
|
model: str,
|
|
) -> EmbeddingResponse:
|
|
"""Generate embeddings for input text."""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def health_check(self) -> dict[str, Any]:
|
|
"""Check provider health status."""
|
|
...
|
|
|
|
async def close(self) -> None:
|
|
"""Clean up resources."""
|
|
pass
|