feat: switch deck generation from Perplexity to local Ollama (gpt-oss:20b)
This commit is contained in:
1 parent
c6d02fd38c
commit
cc481bd71b
4 files changed
+40
-26
No files matched your search
@@ -56,15 +56,15 @@ class PerplexityClient:
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str, model: str = SONAR,
|
||||
timeout: int = 120):
|
||||
if not api_key:
|
||||
raise ValueError("Perplexity API key is required")
|
||||
timeout: int = 120, base_url: str = None):
|
||||
if not api_key and not base_url:
|
||||
raise ValueError("Perplexity API key is required (or provide base_url for local)")
|
||||
self.api_key = api_key
|
||||
self.model = model
|
||||
self.timeout = timeout
|
||||
self._client = OpenAI(
|
||||
api_key=api_key,
|
||||
base_url="https://api.perplexity.ai",
|
||||
base_url=base_url or "https://api.perplexity.ai",
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
||||
+6
-5
@@ -13,8 +13,8 @@ from pathlib import Path
|
||||
LAB_ROOT = Path(os.environ.get("COMMANDER_LAB_ROOT", Path(__file__).parent.parent))
|
||||
|
||||
# ── LM Studio (OpenAI-compatible API) ──────────────────────
|
||||
LM_STUDIO_URL = os.environ.get("LM_STUDIO_URL", "http://192.168.0.241:1234/v1")
|
||||
LM_STUDIO_MODEL = os.environ.get("LM_STUDIO_MODEL", "local-model")
|
||||
LM_STUDIO_URL = os.environ.get("LM_STUDIO_URL", "http://localhost:11434/v1")
|
||||
LM_STUDIO_MODEL = os.environ.get("LM_STUDIO_MODEL", "gpt-oss:20b")
|
||||
LM_STUDIO_TIMEOUT = int(os.environ.get("LM_STUDIO_TIMEOUT", "120")) # seconds
|
||||
LM_STUDIO_MAX_RETRIES = 3
|
||||
|
||||
@@ -41,9 +41,10 @@ DEFAULT_MAX_TOKENS = 8192
|
||||
|
||||
# ── Perplexity Deck Generation Settings ───────────────────────
|
||||
# Provider for deck generation: "perplexity" (V3) or "local" (V2 collection-based)
|
||||
DECK_GEN_PROVIDER = os.environ.get("DECK_GEN_PROVIDER", "perplexity")
|
||||
DECK_GEN_PROVIDER = os.environ.get("DECK_GEN_PROVIDER", "local")
|
||||
# Model for deck generation (sonar = fast/$0.004, sonar-pro = deep/$0.04)
|
||||
DECK_GEN_MODEL = os.environ.get("DECK_GEN_MODEL", "sonar")
|
||||
DECK_GEN_MODEL = os.environ.get("DECK_GEN_MODEL", "gpt-oss:20b")
|
||||
DECK_GEN_BASE_URL = os.environ.get("DECK_GEN_BASE_URL", "http://localhost:11434/v1")
|
||||
DECK_GEN_TEMPERATURE = float(os.environ.get("DECK_GEN_TEMPERATURE", "0.2"))
|
||||
DECK_GEN_MAX_TOKENS = int(os.environ.get("DECK_GEN_MAX_TOKENS", "16384"))
|
||||
|
||||
@@ -53,7 +54,7 @@ SUBSTITUTION_MIN_SIMILARITY = float(os.environ.get("SUBSTITUTION_MIN_SIMILARITY"
|
||||
# Maximum number of alternatives to suggest per missing card
|
||||
SUBSTITUTION_MAX_ALTERNATIVES = int(os.environ.get("SUBSTITUTION_MAX_ALTERNATIVES", "5"))
|
||||
# Model for substitution fallback (sonar is fine — small focused queries)
|
||||
SUBSTITUTION_MODEL = os.environ.get("SUBSTITUTION_MODEL", "sonar")
|
||||
SUBSTITUTION_MODEL = os.environ.get("SUBSTITUTION_MODEL", "gpt-oss:20b")
|
||||
# Enable Perplexity fallback for low-confidence embedding matches
|
||||
SUBSTITUTION_USE_PPLX_FALLBACK = os.environ.get("SUBSTITUTION_USE_PPLX_FALLBACK", "true").lower() == "true"
|
||||
|
||||
|
||||
+13
-7
@@ -91,18 +91,24 @@ def init_coach_service():
|
||||
|
||||
log_coach.info(" Coach: Service initialized")
|
||||
|
||||
# Initialize V3 Deck Generator (Perplexity)
|
||||
if CFG.pplx_api_key or os.environ.get('DECK_GEN_PROVIDER') == 'local':
|
||||
_deck_gen_v3_error = None
|
||||
if CFG.pplx_api_key:
|
||||
try:
|
||||
from coach.clients.perplexity_client import PerplexityClient
|
||||
from coach.services.deck_generator import DeckGeneratorV3
|
||||
from coach.config import DECK_GEN_MODEL
|
||||
from coach.config import DECK_GEN_MODEL, DECK_GEN_PROVIDER, DECK_GEN_BASE_URL
|
||||
|
||||
pplx_client = PerplexityClient(
|
||||
api_key=CFG.pplx_api_key,
|
||||
model=DECK_GEN_MODEL,
|
||||
)
|
||||
if DECK_GEN_PROVIDER == 'local':
|
||||
pplx_client = PerplexityClient(
|
||||
api_key='ollama',
|
||||
model=DECK_GEN_MODEL,
|
||||
base_url=DECK_GEN_BASE_URL,
|
||||
)
|
||||
else:
|
||||
pplx_client = PerplexityClient(
|
||||
api_key=CFG.pplx_api_key,
|
||||
model=DECK_GEN_MODEL,
|
||||
)
|
||||
_deck_gen_v3 = DeckGeneratorV3(
|
||||
pplx_client=pplx_client,
|
||||
db_conn_factory=_get_db_conn,
|
||||
|
||||
+17
-10
@@ -771,26 +771,33 @@ async def _call_pplx_api(messages: list[dict], max_tokens: int = 4096, temperatu
|
||||
Uses httpx.AsyncClient so the uvicorn event loop is never stalled.
|
||||
Returns the assistant message content.
|
||||
"""
|
||||
if not CFG.pplx_api_key:
|
||||
raise HTTPException(400, 'Perplexity API key not configured. Set PPLX_API_KEY env var or --pplx-key.')
|
||||
from coach.config import DECK_GEN_PROVIDER, DECK_GEN_BASE_URL, DECK_GEN_MODEL
|
||||
|
||||
if DECK_GEN_PROVIDER == 'local':
|
||||
base_url = DECK_GEN_BASE_URL
|
||||
model = DECK_GEN_MODEL
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
else:
|
||||
if not CFG.pplx_api_key:
|
||||
raise HTTPException(400, 'Perplexity API key not configured. Set PPLX_API_KEY env var or --pplx-key.')
|
||||
base_url = 'https://api.perplexity.ai'
|
||||
model = 'sonar'
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': f'Bearer {CFG.pplx_api_key}',
|
||||
}
|
||||
payload = {
|
||||
'model': 'sonar',
|
||||
'model': model,
|
||||
'messages': messages,
|
||||
'max_tokens': max_tokens,
|
||||
'temperature': temperature,
|
||||
'return_related_questions': False,
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': f'Bearer {CFG.pplx_api_key}',
|
||||
}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||
resp = await client.post(
|
||||
'https://api.perplexity.ai/chat/completions',
|
||||
f'{base_url}/chat/completions',
|
||||
json=payload,
|
||||
headers=headers,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
|
||||
Reference in new issue
Block a user