Практычныя прытамулкі: Запускайце корисны локальны LLM за 30 хвілін (праграмаванне, RAG, голас)
Практычныя нарады: Как запусціць корисны локальны LLM за 30 хвілін (програмаванне, RAG, голас): контракты, перакрыцці та шаблоны коду для команд, якія викорыстоўваюць гэты патерн.
Наступныя прыміткі паказваюць практычны шлях адпрацоўкі тэмы «Запуск кориснага локальнага LLM за 30 хвілін (праграмаванне, RAG, голас)». Акцэнт ставіцца на контракты, перакрычанняя і мескі для коду, а не на мотывацыйны падход. Калі працуеце над адглядам, спачатку запісайце контракт: неабяжныя вхідныя даны, сігнал успеху і тое, што вядзецца пад частым невялікім неудачам. Такі список перакрычанняя дапамагае заліцвачваць будучыя змены ў кодзе. Документавайце як шлях успеху, так і шлях вяснавання. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў є частью продукту, а не наступным этапам дапрацоўкі.
$ ollama run qwen3:8b
>>> rewrite this function to use async/await
П’яціхвілінны спольны наладжэння
Пяціхвілінны ўстановчы процес працюе найэфектывней, калі яго розглядаць як мерыябельную структуру. Запісаце адну ідеальную версію, адзін прыклад неудачы і прыметкі па поверненню да пачатковага стану пры расширэнні масштаба. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выходзіць, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг задач. Аккуратна распадзіце колькасць токенав на кожны раунд і сесію. Інструменты з агентным режымам агрэсіўна расширваюць контекст; строгі ліміты запобегаюць таму, каб дэманстрацыі ператварыліся на неспакоўныя рахункі.
brew install ollama
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8b
ollama run qwen3:8b "Write a python script to reverse a string."
Шлях А: Местны асистэнт для кодавання
Шлях А: Адміністратыўны помачнік для кодавання працюе наяўней калі яго розглядаць як мерыябельную паверхню. Зафіксавайце адна ідеальная транскрыпцыю, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць масштабы. Разглядзіце гэты этап як кантракт межа вхіднымі даннымі і паверыранымі выходамі. Дайце назвы артыфактам, задаць критэрыя успеху і адмовіцеся ад тыхоўскага частковага завершэння. Выдзеліце бюджет токенав на кожны раунд і на кожную сесію. Інструменты-агенты агрэсывна расширваюць контекст; строгі ліміты запобегаюць таму, каб дэманстрацыі ператварыліся на неспакоючыя рахункі.
# For 24GB+ VRAM or 32GB+ Mac Unified Memory
ollama pull qwen3-coder:30b
# For 16GB RAM laptops
ollama pull qwen2.5-coder:7b
Модель Ollama, налаштаваная па прынцыпах Cline
Модэль Ollama, настроеныя па методы Cline, працюе найкраща, калі яе расследжваць як меравальную паверхню. Запісайце адны ідеальны прыклад работы, адны прыклад неудачы і прыказку па вярнэнні да пачатковага стану пры розширэнні сферы застосоўвання. Запісуйце час выконання і кост токэнаў або запытаў разам з функцыйнымі рэзультатамі. Відразлівае прадставленне костаў з’являецца рана, што запобегае неспакойным счыткам, калі працэс пераходзіць з дэмаверсіі ў спяльныя сераўы. Закрепіце інтэрпретара і файл з правіламі залежнасцяў пры навучэнні циклу. Разлікі межы ноутбукам і системай CI ёсць найчастэйшым таямным абрывам дэмаверсій API. Модэль Ollama, настроеныя па методы Cline, працюе найкраща, калі яе расследжваць як меравальную паверхню. Запісайце адны ідеальны прыклад работы, адны прыклад неудачы і прыказку па вярнэнні да пачатковага стану пры розширэнні сферы застосоўвання. Дакументавайце як успішны, так і вярнучыся парадоксы разам. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных запытаў ёсць часткай продукту, а не чымось, што дадаецца пазней.
# Modelfile — cline-tuned qwen3-coder
# Save as: ./Modelfile
FROM qwen3-coder:30b
# Cline's system prompt is ~25-30K tokens before your code is added.
# Ollama's default num_ctx for Qwen 3 is 40K, but Cline ships an
# expanded system prompt in v3+ that comfortably exceeds it on any
# real task. 65536 is the safe floor for serious use; 131072 if
# you have RAM/VRAM to spare.
PARAMETER num_ctx 65536
# Code edits want low-variance output. The default 0.7 is too loose.
PARAMETER temperature 0.2
# Stop after the model's natural turn. Cline parses this.
PARAMETER stop "<|im_end|>"
# Build the Cline-tuned model from the Modelfile in the current dir
ollama create qwen3-coder-cline -f ./Modelfile
# Verify the context window actually took
ollama show qwen3-coder-cline --modelfile | grep num_ctx
# Expected output: PARAMETER num_ctx 65536
# Sanity-check it answers
ollama run qwen3-coder-cline "write a python one-liner to read /etc/hostname"
API Provider: Ollama
Base URL: http://localhost:11434
Model: qwen3-coder-cline
Context Window: 65536
Шлях B: RAG над вашымі сабственнымі дакументамі
Для шляху B: RAG на власных дакументах — праймеры, власнік крока і крэтыры завершэння трэба задаць раней, чым зменяць код. Аператары должны магчымае перайсці на гэты крок з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшоў, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны процес. Калі наступны крок — це код або вызов інструмента, лепш выбіраць структураваныя выходны данні з пераканальванням схемы, чым працэсны тэкст.
llama pull nomic-embed-text
pip install ollama numpy
"""Local RAG over a folder of notes — Ollama embeddings + numpy cosine.
No vector DB. For a few hundred docs, an in-memory list + numpy is
faster to set up and fast enough to run. Swap in sqlite-vec when
the corpus outgrows it (typically past ~5000 chunks).
Setup:
ollama pull qwen3:8b
ollama pull nomic-embed-text
pip install ollama numpy
Run:
python rag.py ./notes "what did the team decide about pricing?"
"""
from __future__ import annotations
import glob
import os
import sys
import numpy as np
import ollama
EMBED_MODEL = "nomic-embed-text"
CHAT_MODEL = "qwen3:8b"
CHUNK_WORDS = 400 # ~500 tokens; fits 4 chunks in an 8K context
TOP_K = 3
def load_chunks(folder: str) -> list[str]:
"""Read every .md / .txt under folder, split into ~CHUNK_WORDS chunks."""
chunks: list[str] = []
for path in glob.glob(os.path.join(folder, "**/*"), recursive=True):
if not path.endswith((".md", ".txt")):
continue
with open(path, encoding="utf-8") as f:
words = f.read().split()
for i in range(0, len(words), CHUNK_WORDS):
chunk = " ".join(words[i : i + CHUNK_WORDS])
if chunk.strip():
chunks.append(chunk)
return chunks
def embed(texts: list[str]) -> np.ndarray:
"""Embed a batch of texts via Ollama. Returns an (N, D) matrix."""
resp = ollama.embed(model=EMBED_MODEL, input=texts)
return np.array(resp["embeddings"], dtype=np.float32)
def top_k_indices(
query_vec: np.ndarray, doc_mat: np.ndarray, k: int
) -> list[int]:
"""Return indices of the k most cosine-similar rows in doc_mat.
The trick: if both vectors are unit-length (norm == 1), their
dot product equals their cosine similarity. So we normalize
once, then one matrix multiply gives a similarity score for
every chunk against the query — no Python loop needed.
"""
# Normalize query and every chunk to unit length.
# The 1e-8 prevents division by zero on a zero vector.
q = query_vec / (np.linalg.norm(query_vec) + 1e-8)
d = doc_mat / (np.linalg.norm(doc_mat, axis=1, keepdims=True) + 1e-8)
# One matmul across all chunks: scores[i] = cos(query, chunk_i).
scores = d @ q
# Sort descending, take the first k indices.
return np.argsort(scores)[::-1][:k].tolist()
def answer(query: str, chunks: list[str], doc_mat: np.ndarray) -> str:
"""Retrieve top-K chunks, stuff them into a prompt, generate."""
q_vec = embed([query])[0]
idx = top_k_indices(q_vec, doc_mat, k=TOP_K)
context = "\n\n---\n\n".join(chunks[i] for i in idx)
prompt = (
"Answer the question using ONLY the context below. "
"If the context does not contain the answer, say so plainly.\n\n"
f"CONTEXT:\n{context}\n\nQUESTION: {query}"
)
resp = ollama.chat(
model=CHAT_MODEL,
messages=[{"role": "user", "content": prompt}],
)
return resp["message"]["content"]
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit('usage: python rag.py <folder> "<question>"')
folder, query = sys.argv[1], sys.argv[2]
chunks = load_chunks(folder)
if not chunks:
sys.exit(f"no .md or .txt files found under {folder}")
print(f"indexing {len(chunks)} chunks...")
doc_mat = embed(chunks)
print(answer(query, chunks, doc_mat))
python rag.py ~/Documents/meeting_notes "what did the team decide about pricing?"
Шлях C: Цікл голасу
Для шляху C: «Voice Loop» — паказвайце вхідныя даны, адміністратара крока і критэрыя завершэння пры перадзеіснаванні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Спрыяйце цім этапу як кантракту межа вхіднымі данымі і паверканымі выходнымі рэзультатамі. Даўце назвы артыфактам, паказвайце критэрыя успеху і адмовіцеся ад тыхняй частковай роботы без паведамлення. Калі наступны крок — це код або вызов інструмента, валідавайце структураваныя выходныя даны за дапамою схемы, а не простае тэкстовае выражэнне.
brew install whisper-cpp ffmpeg
pip install -U sounddevice ollama kokoro-onnx
«Voice Pipeline»
Для The Voice Pipeline неабяжна прадзефінавацыя вхідных дадзеных, адпраўніка крока і крэтарыяў завершэння працы перад зменым коду. Аперацыйныя працавнікі должны магчымае перайсці на выкананне крока з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Запісвайце час выканання і кост токенаў або запытаў разам з функцыйнальнымі рэзултатамі. Відразы костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмавайнтару ў спяльныя среды. Калі наступны крок — гэта код або вызов інструмента, лепш выкарыстоўваць структураваныя выходныя данні з паўтарной перапрацою схемы, чым вольнай формы тэкст. Для The Voice Pipeline неабяжна прадзефінавацыя вхідных дадзеных, адпраўніка крока і крэтарыяў завершэння працы перад зменым коду. Аперацыйныя працавнікі должны магчымае перайсці на выкананне крока з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Аддзеўжце дакументацыю як пра успішны, так і пра патэнцыйныя шляхі роботы. Перапрыбуткі, людзкія контрольны пункты і обработка некоректных дадзеных ўжо є часткай продукту, а не чымсь, што дадаецца пазней.
"""Local voice loop — mic → whisper.cpp → Ollama → Kokoro → speaker.
Everything runs offline. Nothing leaves the machine.
Setup:
brew install whisper-cpp ffmpeg # macOS
# (Linux: apt install ffmpeg; build whisper.cpp from source)
pip install -U sounddevice ollama kokoro-onnx
# Whisper GGML model — pick one:
# ggml-base.en.bin (~150MB, fast, English-only)
# ggml-large-v3.bin (~3GB, accurate, multilingual)
# Download from https://huggingface.co/ggerganov/whisper.cpp
# Kokoro model files auto-download to ~/.cache/kokoro-onnx/
# on first run, or grab them manually from
# https://github.com/thewh1teagle/kokoro-onnx/releases
Run:
python voice.py
"""
from __future__ import annotations
import subprocess
import tempfile
import wave
from pathlib import Path
import sounddevice as sd
import ollama
from kokoro_onnx import Kokoro
WHISPER_MODEL = Path.home() / "models" / "ggml-base.en.bin"
CHAT_MODEL = "qwen3:8b"
KOKORO_VOICE = "af_sarah" # see kokoro voices.json for all options
SAMPLE_RATE = 16000 # whisper.cpp requires 16kHz mono
RECORD_SECONDS = 5
# Kokoro auto-downloads the model files on first instantiation.
kokoro = Kokoro("kokoro-v1.0.onnx", "voices-v1.0.bin")
def record() -> Path:
"""Record from the default mic, write a 16kHz mono WAV, return its path."""
print(f"listening for {RECORD_SECONDS}s...")
audio = sd.rec(
int(RECORD_SECONDS * SAMPLE_RATE),
samplerate=SAMPLE_RATE,
channels=1, # mono
dtype="int16", # 16-bit PCM is what wave.open expects
)
sd.wait() # block until recording finishes
# Fixed path in the system tempdir — overwritten each invocation.
wav_path = Path(tempfile.gettempdir()) / "voice_in.wav"
with wave.open(str(wav_path), "wb") as w:
w.setnchannels(1) # mono
w.setsampwidth(2) # 2 bytes per sample == int16
w.setframerate(SAMPLE_RATE) # 16 kHz — whisper.cpp's required rate
w.writeframes(audio.tobytes())
return wav_path
def transcribe(wav_path: Path) -> str:
"""Run whisper.cpp on a WAV file, return the transcript text."""
result = subprocess.run(
[
"whisper-cli",
"-m", str(WHISPER_MODEL),
"-f", str(wav_path),
"-otxt", # write the transcript to <input>.txt
"-np", # suppress progress prints on stdout
],
capture_output=True,
text=True,
timeout=60,
)
if result.returncode != 0:
raise RuntimeError(f"whisper-cli failed: {result.stderr}")
# -otxt writes the transcript next to the input file, with .txt
# tacked onto the existing name — so /tmp/voice_in.wav becomes
# /tmp/voice_in.wav.txt (same directory, same stem, extra suffix).
txt_path = wav_path.with_suffix(wav_path.suffix + ".txt")
return txt_path.read_text(encoding="utf-8").strip()
def respond(text: str) -> str:
"""Send the transcript to the local LLM, return the reply."""
resp = ollama.chat(
model=CHAT_MODEL,
messages=[
{
"role": "system",
"content": (
"You are a voice assistant. Keep replies to one or "
"two short sentences. No markdown, no lists."
),
},
{"role": "user", "content": text},
],
)
return resp["message"]["content"]
def speak(text: str) -> None:
"""Synthesize the reply with Kokoro and play it back."""
samples, sample_rate = kokoro.create(
text, voice=KOKORO_VOICE, speed=1.0, lang="en-us"
)
sd.play(samples, sample_rate)
sd.wait()
if __name__ == "__main__":
wav = record()
heard = transcribe(wav)
if not heard:
print("nothing heard. exiting.")
raise SystemExit(0)
print(f"you said: {heard}")
reply = respond(heard)
print(f"model: {reply}")
speak(reply)
Рэальнасць апаратнай часткі
Калі працуеце над кнігай «The Hardware Reality», спачатку запісацеце кантракт: неабяжлівыя даны, сигнал успеху і тое, што выходзіць пад частыя неудачы. Такі список контроля дапамагае заліцварыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест вялікіх скрыптав. Калі якісь крок не выйшае, неудача должна вказваць на адну адпаведальнасць, а не на заплутаны ланцюг задач. Зберагаеце кэш стабільных інструкцый системы і схемаў інструментав. Перадзесланне ідэнтычных прамуров ёсць частым выклікам пераспалювання ресурсаў.
Чытайце далей
Калі працуеце над разделам «Продзейсць чытання», спачатку запішыце угоду: неабяжлівыя даннэ, сігнал успеху і тое, што выходзіць пад частыя неудачы. Такі список пераконтроўкі дапамагае заліцьварыць пазнейшыя змены ў кодзе. Спрыймайце гэты этап як угоду межа даннемі і перакананымі выходамі. Дайце назву рэзультатам, задаць критэрыя успеху і не падзеўляйцеся частым, непূরным выкананнем задачы. Зберагаюце у кэшы стабільныя інструкцыі системы і схемы інструментаў. Перадзесланне ідэнтычных прамаўляючых частак — частая прычына працоўных проблем.
Чэрніця эксплуатацыі
Для чэрніцы эксплуатацыі, перш чым зменяць код, задаць неабяжлівыя даннэ, адпаведальную за крок особу і критэрыя завершэння. Аперацыёныя працавнікі павінны магчымае перазапускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан.
Зберагаюце настройкі праза код аплікацыі. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцый павінны знаходзіцца ў адном месцы, якое працавнікі можу аудытаваць, не чытаючы весь код.
Калі наступным крокам являецца напісанне коду або вызов інструмента, лепш выкарыстоўваць структураваныя выходныя данні з паўерыранням за дапамогай схемы, чым просты прозаічны текст.
Перад налаштаваннем запитоў неабходна пераканацца ў правільным адпаведанні на фіксованы набор запитанняў. Частае зміненне запитоў рэдка калі вярна выправляе проблемы з адзысканнем інформаціі.
Зберагаюць версіі залежнасцяў і фіксуюць дзейнік зображэння, якое было выкарыстоўвана для дэманстрацыі. Возможнасць перадарабаткі важлівей, чым традыцыйныя знання.
Спрыймайце гэты этап як кантракт між вхіднымі даннымі і паўерынутымі выходнымі рэзультатамі. Даўайце назвы артыфактам, визначайце критэрыя успеху і адмовляйцеся ад тыхнай частковай рэалізацыі без паведамлення.
Перад пераходам да наступнага кроку заморажуйце версіі, стварайце фіксаваны транскрыпт для критычнага шляху і паказвайце способы вярнення да пачатковага стану. У спільных средах неабходны ліміты частоты запытоў, перакананні ў правільным адналежнасці ресурсаў і чысткі власнік для змены секрэтных даных. Лепш надзейная надзея, чым крэатывныя, але разовыя дэманстраціі.
Запіска параграфу 9f628082e0d0: не трэба кантрацеўваць ключы прадаўцаў у репазітарыі, задаць максімальную кантроль на токены на адну сесію, а таксама зберагчы транскрыпціі празаўседле з фікстурамі для ацэнкі, каб пазнейшыя замены моделей заставалі пораўнанневымі.
Запіска па прыняццю заходаў абароны 0 будзе эфектывнейшая, якщо яе спрыяваць як мерыемую плошчу. Зберагчы адну ідеальную транскрыпцію, адзін прыклад неудачы і запіску пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Канфігурацыю трэба кантрацеўваць за межамі коду прыемленае. Файлы сяродавішча, хранільнікі секрэтных дадзеных і флагі функций должны знаходзіцца ў аднам месцы, куды аператары можаць адбавляць без неабходнасці чытання всей структуры.
Дзялічны пункт прыняцця заходаў абароны 0/770: мерыць час выканання, класу ошибак і витрату токенав для гэтай запіскі, а пасля — вырашыць, чы робіць змяну на адной падставе фіксаванага набора пытанняў, а не толькі на адной анекдотычнай інформацыі.
Для пункта 1 адаптавання неабяжна ўскладнення: перш чым зменіць код, неабяжна визначыць параметры вхідных дадзеных, адпаведальнага за даны крок і критэрыі завершэння. Аперацыям неабяжна маты можлівасць парабіць крок з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшоў, прычына неабяжна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны процес.
Дзялянка адаптавання 1/770: памерыце час выконання, класія ошибак і колькасць викорыстоўваных токенав для гэтага пункта, а потым вынікніце рашэнне пра тое, чы робіць змяны на адной падставе фіксаванага набора пытанняў, а не на адной лячэнні.
Калі працуеце над пунктом 2 адзынківання, спачатку запісайце умовы: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага неяксамоства. Такі список дапамагае заліцварыць пазнейшыя змены ў кодзе. Запісвайце час выканання і вартасьць токена або запытку пад функцыйнальнымі рэзултатамі. Відразы вартасці з самага пачатку запобегае неспакою, калі процес пераходзіць з дэмаверсіі ў спяльныя среды.
Дзеянне адзынківання 2/770: замерьце час выканання, класію каштоўкаў і витраты токена для гэтага пункту, а потым выберце, чы робіць змены на адной пазначанай базе пытанняў, а не на адной толькі прыватнай інформацыі.
Пункт 3 адзынківання будзе эфектываў, калі яго спрыятаць як меравальную плошчу. Запісайце адну ідеальную транскрыпцыю, адзін прыклад неяксамоства і запіс пра адворачэнне змены, перш чым расширваць сферу дзеяння. Дакументавайце як успішны, так і восстанаўліваючы шляхы. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.
Дзеянне паўжасткі 3/770: звярніце увагу на час выканання, класы паказчыкаў і колькасць токенаў, якія былі выкарыстаны для гэтага запісу, а пасля, на аднойчынай базе фіксаванага набору пытанняў, а не на індывідуальных прыкладах, выявіце, чы хачаце застаўіць змяну.
Для запісу паўжасткі 4 перад змянай коду неабходна визначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аперацыяныя працавнікі должны магчыма было перазапускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Оважайце гэты этап як кантракт межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Дайце назвы артыфактам, визначыце критэрыя успеху і адмовіцеся ад беззвучнага частковага завершэння.
Дзеянне паўжасткі 4/770: звярніце увагу на час выканання, класы паказчыкаў і колькасць токенаў, якія былі выкарыстаны для гэтага запісу, а пасля, на аднойчынай базе фіксаванага набору пытанняў, а не на індывідуальных прыкладах, выявіце, чы хачаце застаўіць змяну.
Калі працюеце над пунктом 5 адаптавання, спачатку запісайце шаблон даговору: неабяжныя вхідныя даны, сигнал успеху і тое, што выходзіць у разе частковага абякання. Такій чэк-ліст дапамагае заставаць будучыя змены коду чыстымі. Зберагаеце настройкі праз аплікацыйны код. Файлы сераўнавання, хранільнікі секрэтных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаюць адбавіць аудыт без неабяжнага чытання всіх элементаў.
Дакладнасць адаптавання 5/770: замеры часу выконання, класаі бядаў і выкарыстання токенаў для гэтага пункту, а потым рашэнне пра тое, чы хацецца застаўіць змену, на адной падставе фіксаванага набору пытанняў, а не на адной лічбе прыкладаў.