feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands)
Closes the last functional parity gap with rust. Python now has voice-driven
Ollama↔Groq switching, persistent across restarts.
llm_backend.py (new, ~130 lines)
Singleton module:
current_client() → active OpenAI client (or None)
current_backend() → 'groq' | 'ollama' | 'none'
current_model() → active model name
swap_to(name) → hot-swap + persist to <here>/llm_backend.txt
parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama)
Backends:
- Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults)
- Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) +
OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder,
openai lib insists on a non-empty string; Ollama ignores it).
Init precedence (idempotent _ensure_init):
1. Persisted choice from llm_backend.txt
2. JARVIS_LLM env var
3. Auto-detect: Groq if token present, else Ollama
Voice commands (commands.yaml, +3 entries → 151)
llm_switch_local → ollama
llm_switch_cloud → groq
llm_status → speaks current backend
extensions.py
+ do_llm_switch / do_llm_status handlers
+ llm_backend import
do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_*
dev_handlers.py
+ llm_backend import
do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_*
vision_handler.py
Vision call documented as Groq-specific (Ollama doesn't expose vision via
OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading.
Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries.
PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE.
Only remaining rust-only feature: the Tauri GUI (python is console-only).
This commit is contained in:
parent
31ff997782
commit
8d3da4ea06
6 changed files with 254 additions and 53 deletions
|
|
@ -15,6 +15,7 @@ import re
|
|||
import subprocess
|
||||
|
||||
import memory_store
|
||||
import llm_backend
|
||||
|
||||
_speak_fn = print
|
||||
|
||||
|
|
@ -146,25 +147,11 @@ def do_codebase_ask(action, voice):
|
|||
|
||||
digest = "\n\n".join(f"--- {p} ---\n{c}" for p, c in chunks)
|
||||
|
||||
try:
|
||||
import config as cfg
|
||||
except ImportError:
|
||||
cfg = None
|
||||
token = getattr(cfg, 'GROQ_TOKEN', None) if cfg else None
|
||||
if not token:
|
||||
_speak("LLM не настроен — нужен GROQ_TOKEN.")
|
||||
client = llm_backend.current_client()
|
||||
if client is None:
|
||||
_speak("LLM не настроен.")
|
||||
return
|
||||
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
_speak("OpenAI клиент не установлен.")
|
||||
return
|
||||
|
||||
client = OpenAI(
|
||||
api_key=token,
|
||||
base_url=getattr(cfg, 'GROQ_BASE_URL', 'https://api.groq.com/openai/v1'),
|
||||
)
|
||||
model = llm_backend.current_model()
|
||||
|
||||
user_prompt = (
|
||||
f"Ты — старший разработчик. По digest проекта ответь на вопрос пользователя "
|
||||
|
|
@ -174,7 +161,7 @@ def do_codebase_ask(action, voice):
|
|||
|
||||
try:
|
||||
resp = client.chat.completions.create(
|
||||
model=getattr(cfg, 'GROQ_MODEL', 'llama-3.3-70b-versatile'),
|
||||
model=model,
|
||||
messages=[
|
||||
{'role': 'system', 'content': 'Ты — внимательный код-ревьюер. По существу, без воды.'},
|
||||
{'role': 'user', 'content': user_prompt},
|
||||
|
|
@ -291,25 +278,11 @@ def do_github_summarize_pr(action, voice):
|
|||
files = pr.get('changedFiles', '?')
|
||||
author = (pr.get('author') or {}).get('login', '(unknown)')
|
||||
|
||||
try:
|
||||
import config as cfg
|
||||
except ImportError:
|
||||
cfg = None
|
||||
token = getattr(cfg, 'GROQ_TOKEN', None) if cfg else None
|
||||
if not token:
|
||||
_speak(f"PR номер {number}: {title}. Без LLM сводки — нужен GROQ_TOKEN.")
|
||||
client = llm_backend.current_client()
|
||||
if client is None:
|
||||
_speak(f"PR номер {number}: {title}. Без LLM — настройте Groq или Ollama.")
|
||||
return
|
||||
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
_speak("OpenAI клиент не установлен.")
|
||||
return
|
||||
|
||||
client = OpenAI(
|
||||
api_key=token,
|
||||
base_url=getattr(cfg, 'GROQ_BASE_URL', 'https://api.groq.com/openai/v1'),
|
||||
)
|
||||
model = llm_backend.current_model()
|
||||
|
||||
user_prompt = (
|
||||
f"Repo: {repo}\nPR #{number}: {title}\nAuthor: {author}\n"
|
||||
|
|
@ -321,7 +294,7 @@ def do_github_summarize_pr(action, voice):
|
|||
|
||||
try:
|
||||
resp = client.chat.completions.create(
|
||||
model=getattr(cfg, 'GROQ_MODEL', 'llama-3.3-70b-versatile'),
|
||||
model=model,
|
||||
messages=[
|
||||
{'role': 'system', 'content': "Ты — старший разработчик, делаешь код-ревью. По делу, без воды."},
|
||||
{'role': 'user', 'content': user_prompt},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue