amana / src /llm.py
developer
Scaffold YouTube Channel Knowledge Base (RAG)
f4d9ca1
Raw
History Blame Contribute Delete
2.31 kB
"""Pluggable LLM provider: Ollama (local) or Anthropic (Claude).
Selected via LLM_PROVIDER. Both expose the same .complete(system, user) API so
the rest of the app never needs to know which backend is in use.
"""
from __future__ import annotations
from typing import Protocol
import requests
from .config import CONFIG
class LLMProvider(Protocol):
name: str
def complete(self, system: str, user: str) -> str: ...
class OllamaProvider:
name = "ollama"
def __init__(self, model: str | None = None, host: str | None = None):
self.model = model or CONFIG.ollama_model
self.host = (host or CONFIG.ollama_host).rstrip("/")
def complete(self, system: str, user: str) -> str:
resp = requests.post(
f"{self.host}/api/chat",
json={
"model": self.model,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
"stream": False,
"options": {"temperature": 0.2},
},
timeout=180,
)
resp.raise_for_status()
return resp.json()["message"]["content"].strip()
class AnthropicProvider:
name = "anthropic"
def __init__(self, model: str | None = None, api_key: str | None = None):
from anthropic import Anthropic
key = api_key or CONFIG.anthropic_api_key
if not key:
raise RuntimeError("ANTHROPIC_API_KEY is not set but LLM_PROVIDER=anthropic.")
self.model = model or CONFIG.anthropic_model
self.client = Anthropic(api_key=key)
def complete(self, system: str, user: str) -> str:
msg = self.client.messages.create(
model=self.model,
max_tokens=1024,
temperature=0.2,
system=system,
messages=[{"role": "user", "content": user}],
)
return "".join(block.text for block in msg.content if block.type == "text").strip()
def get_provider(name: str | None = None) -> LLMProvider:
name = (name or CONFIG.llm_provider).lower()
if name == "anthropic":
return AnthropicProvider()
if name == "ollama":
return OllamaProvider()
raise ValueError(f"Unknown LLM_PROVIDER: {name!r}")