Budowa chatbota CLI
2. Przygotowanie środowiska
2.1. Ustawienia i połączenie z Ollama
Lokalne chatboty, agenci i systemy RAG z własną bazą wiedzy
Instrukcja krok po kroku · materiał uzupełniający
Budujemy chatbota w konsoli
config.json i lokalny.py
Oddziel ustawienia i komunikację z modelem od reszty programu. Dzięki temu kolejne pliki będą krótkimi etapami tego samego przepływu.
Ustawienia wspólne
Otwórz config.json. Na początku pozostaw te wartości. JSON używa null, a Python None; nie zamieniaj tych zapisów między plikami.
{
"chat_model": "qwen3:4b-instruct-2507-q4_K_M",
"embedding_model": "bge-m3:latest",
"top_k": 3,
"similarity_threshold": null,
"context_chars": 4500,
"question_chars": 1000,
"temperature": 0.0,
"seed": 42,
"num_ctx": 4096,
"num_predict": 384,
"timeout": 180
}| Pole | Znaczenie |
|---|---|
| chat_model / embedding_model | Dwa różne zadania: generowanie tekstu oraz wektory do wyszukiwania. |
| top_k | Maksymalna liczba fragmentów z rankingu. |
| similarity_threshold | null oznacza brak filtrowania po wyniku podobieństwa. |
| context_chars | Budżet znaków na pełne fragmenty. To przybliżenie, a nie liczba tokenów. |
| num_ctx / num_predict | Okno modelu i limit generowanej odpowiedzi; wspólne dla porównywanych prób. |
| timeout | Czas oczekiwania klienta na odpowiedź Ollama, w sekundach. Nie ma związku z parametrem fikcyjnej aplikacji DocAssist. |
Utwórz plik lokalny.py
Skopiuj cały kod do pliku lokalny.py i zapisz jako UTF-8. ROOT wskazuje folder skryptu. api() wysyła JSON do Ollama, chat() obsługuje generowanie, a embed() zwraca wektor o długości jeden.
"""Wspólne ustawienia i klient lokalnej Ollama. Python 3.10+."""
import json
import math
from pathlib import Path
from urllib.request import Request, build_opener, ProxyHandler
from urllib.error import HTTPError, URLError
ROOT = Path(__file__).resolve().parent
CONFIG = json.loads((ROOT / "config.json").read_text(encoding="utf-8-sig"))
BASE_URL = "http://127.0.0.1:11434"
def api(endpoint, payload=None):
body = None if payload is None else json.dumps(payload).encode("utf-8")
request = Request(BASE_URL + endpoint, data=body,
headers={"Content-Type": "application/json"})
try:
# Połączenie z localhost nie przechodzi przez firmowy serwer proxy.
with build_opener(ProxyHandler({})).open(
request, timeout=CONFIG["timeout"]) as response:
return json.load(response)
except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")[:500]
raise RuntimeError(f"Ollama HTTP {exc.code}: {detail}") from exc
except (URLError, TimeoutError, OSError) as exc:
raise RuntimeError("Brak odpowiedzi lokalnej Ollama. Sprawdź usługę i timeout.") from exc
def models():
return {m["name"]: m["digest"] for m in api("/api/tags")["models"]}
def require_models(names):
installed = models()
for name in names:
if name not in installed:
raise ValueError(f"Brak modelu {name}. Sprawdź ollama list i config.json.")
return installed
def chat(messages):
result = api("/api/chat", {
"model": CONFIG["chat_model"], "messages": messages, "stream": False,
"options": {k: CONFIG[k] for k in
("temperature", "seed", "num_ctx", "num_predict")},
})
if not result.get("message", {}).get("content", "").strip():
raise RuntimeError("Model zwrócił pustą odpowiedź.")
return result
def unit(vector):
if not vector or any(type(x) not in (int, float) or not math.isfinite(x) for x in vector):
raise ValueError("Niepoprawny wektor.")
norm = math.sqrt(sum(x * x for x in vector))
if norm == 0:
raise ValueError("Wektor zerowy.")
return [x / norm for x in vector]
def embed(text):
result = api("/api/embed", {
"model": CONFIG["embedding_model"], "input": text, "truncate": False,
})
return unit(result["embeddings"][0])
if __name__ == "__main__":
installed = require_models([CONFIG["chat_model"], CONFIG["embedding_model"]])
print("Ollama:", api("/api/version")["version"])
for name in (CONFIG["chat_model"], CONFIG["embedding_model"]):
print(name, installed[name])
print("Połączenie i obecność modeli: OK. Generowanie sprawdzisz w kolejnym kroku.")Wysyłamy stream=False, aby otrzymać jedną odpowiedź JSON. Przy embeddingach truncate=False powoduje zgłoszenie błędu dla zbyt długiego wejścia. Nie chcemy po cichu indeksować uciętego tekstu. Szczegóły pól opisują API chat i API embed.
Uruchom kontrolę
.\.venv\Scripts\python.exe lokalny.pyOczekiwany wynik: wersja Ollama, nazwy obu modeli, ich identyfikatory digest i komunikat „Połączenie i obecność modeli: OK”. To potwierdza dostępność usługi i modeli. Pierwsze generowanie sprawdzisz dopiero w następnym rozdziale.
Punkt kontrolny
- Polecenie kończy się komunikatem OK.
- Wiesz, dlaczego adres to 127.0.0.1 i dlaczego używamy dwóch modeli.
Instrukcja uczestnika · Python i Ollama · wydanie 29.09.2026