← Blog

Lokalny asystent głosowy z Ollamą

Mikrofon → rozpoznawanie mowy → lokalny LLM → głos. Cały asystent na własnym komputerze, bez wysyłania niczego do chmury.

Kacper Walczak · 6 marca 2026 · 3 min czytania

W tym wpisie
  1. Klocki
  2. 1. Słuchanie: kiedy użytkownik skończył mówić?
  3. 2. Rozumienie: faster-whisper
  4. 3. Myślenie: Ollama
  5. 4. Mówienie: piper
  6. Pętla
  7. Co dalej

Asystent głosowy to cztery klocki: słuchanie, rozumienie, myślenie, mówienie. Każdy z nich da się dziś uruchomić lokalnie — na laptopie, bez klucza API i bez wysyłania nagrań na cudze serwery. Ten wpis składa te klocki w działający prototyp w Pythonie. Z tego samego pnia wyrósł później nasz Jarvis.

Klocki

EtapNarzędzieDlaczego
Słuchaniesounddevice + prosty VADzero zależności natywnych poza PortAudio
Rozumienie (STT)faster-whisperWhisper na CPU/GPU, model small wystarcza do rozmowy
Myślenie (LLM)Ollamajeden ollama run, lokalne API na :11434
Mówienie (TTS)piperszybkie, lekkie głosy, także polskie

1. Słuchanie: kiedy użytkownik skończył mówić?

Najprostszy detektor ciszy: nagrywaj w blokach po 30 ms, licz energię (RMS) i uznaj koniec wypowiedzi po ~800 ms poniżej progu.

import numpy as np, sounddevice as sd

SR = 16000
def record_utterance(threshold=0.01, silence_ms=800):
    chunks, silent = [], 0
    with sd.InputStream(samplerate=SR, channels=1, dtype="float32") as stream:
        while True:
            block, _ = stream.read(int(SR * 0.03))
            chunks.append(block)
            rms = float(np.sqrt(np.mean(block ** 2)))
            silent = silent + 30 if rms < threshold else 0
            if chunks and silent >= silence_ms and len(chunks) > 20:
                break
    return np.concatenate(chunks).flatten()

Ten sam pomiar RMS przydaje się później do animowania „ust” postaci — tak działa lipsync u Simlokatorów.

2. Rozumienie: faster-whisper

from faster_whisper import WhisperModel
stt = WhisperModel("small", device="cpu", compute_type="int8")

def transcribe(audio):
    segments, _ = stt.transcribe(audio, language="pl", vad_filter=True)
    return " ".join(s.text for s in segments).strip()

Model small w int8 transkrybuje zdanie w ok. sekundę na zwykłym laptopie. Dla angielskiego można zejść do base.

3. Myślenie: Ollama

Uruchom raz ollama pull llama3.1 (lub dowolny model, który mieści się w Twojej pamięci). Ollama wystawia lokalne HTTP API, do którego wystarczy requests:

import requests

SYSTEM = "Jesteś zwięzłym asystentem. Odpowiadasz po polsku, maksymalnie dwoma zdaniami."
history = [{"role": "system", "content": SYSTEM}]

def think(user_text):
    history.append({"role": "user", "content": user_text})
    r = requests.post("http://localhost:11434/api/chat", json={
        "model": "llama3.1", "messages": history, "stream": False,
    })
    answer = r.json()["message"]["content"]
    history.append({"role": "assistant", "content": answer})
    return answer

Lista history to cała „pamięć” rozmowy. Przy dłuższych sesjach przycinaj ją do ostatnich N wiadomości albo streszczaj — model ma ograniczone okno kontekstu.

4. Mówienie: piper

Piper generuje mowę z pliku modelu .onnx, bez internetu:

import subprocess

def speak(text, voice="pl_PL-gosia-medium.onnx"):
    subprocess.run(
        ["piper", "--model", voice, "--output-raw"],
        input=text.encode(), stdout=subprocess.PIPE, check=True,
    )

Surowe PCM z --output-raw możesz od razu odtworzyć przez sounddevice, bez zapisu do pliku — mniejsze opóźnienie.

Pętla

while True:
    audio = record_utterance()
    text = transcribe(audio)
    if not text:
        continue
    print("Ty:", text)
    reply = think(text)
    print("Asystent:", reply)
    speak(reply)

Cały cykl (od końca mówienia do początku odpowiedzi) na laptopie bez GPU to 3–5 sekund. Największy koszt to LLM; mniejszy model albo streaming odpowiedzi z Ollamy ("stream": True i podawanie zdań do TTS na bieżąco) skraca to wyraźnie.

Co dalej

  • Słowo wybudzające: uruchamiaj record_utterance() dopiero po „hej, Jarvis” (np. openwakeword).
  • Narzędzia: zamiast zwykłej odpowiedzi poproś model o JSON {"action": "...", "args": {...}} i wykonuj akcje — to pierwszy krok od czatu do agenta.
  • Przerywanie: nasłuchuj mikrofonu także podczas mówienia i ucinaj TTS, gdy użytkownik wejdzie w słowo.

Wszystko powyższe działa offline. Twoje nagrania nie opuszczają komputera — a to dziś rzadki luksus.