Python: от данных до своей сводки Урок 48 из 56
Языковая модель у себя на машине: Ollama и первый локальный запрос
Сорок седьмой урок курса по Python. Ставим Ollama, получаем первый ответ без API-ключа и разбираем путь запроса целиком: программа отправляет JSON на localhost, проверяет HTTP-ответ и только потом берёт текст модели. Данные остаются на вашем компьютере — если вы сами не открыли локальный порт наружу.
Зачем это нужно
До сих пор модель в курсе была формулой: числа вошли, число вышло. Языковая модель принимает текст и возвращает текст. Из-за этого её особенно легко принять за собеседника и забыть, что перед нами программа с входом, выходом и ошибками.
В этом уроке модель работает локально через Ollama. API-ключ не нужен. Интернет понадобится один раз, чтобы скачать программу и файл модели; после этого локальная модель может отвечать без сети.
Локально не значит автоматически безопасно. Ollama по умолчанию слушает localhost:11434: это адрес вашего компьютера. Не публикуйте этот порт в интернете и не отправляйте модели секреты, которые не стали бы сохранять в обычный файл. Доступ к компьютеру означает потенциальный доступ к запущенной модели и её данным.
Подготовка
- Установите Ollama с официальной страницы для Windows, macOS или Linux.
- Откройте терминал и выполните
ollama— так рекомендует актуальная инструкция Ollama. - Выберите локальную, а не облачную модель. На момент урока официальный пример использует
gemma4; доступные модели и размеры смотрите в каталоге Ollama. Модель должна помещаться в память и на диск вашего компьютера. - Первый запуск скачает модель. Не закрывайте терминал до появления приглашения ввода.
ollama run gemma4
Напишите короткий вопрос, затем выйдите командой /bye. Если компьютер не тянет выбранную модель, возьмите меньшую из актуального каталога и используйте её точное имя во всех примерах ниже.
Сразу целиком
Файл local_model.py. Поменяйте значение MODEL, если установили другую модель.
"""Урок 47: локальная языковая модель через HTTP API Ollama."""
import json
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
OLLAMA_CHAT = "http://localhost:11434/api/chat"
MODEL = "gemma4"
def ask(prompt, *, model=MODEL, timeout=120):
"""Отправить один вопрос и вернуть только текст проверенного ответа."""
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"stream": False,
}
request = Request(
OLLAMA_CHAT,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=timeout) as response:
data = json.load(response)
except HTTPError as error:
raise RuntimeError(f"Ollama ответила HTTP {error.code}") from error
except URLError as error:
raise RuntimeError(
"Ollama недоступна: запустите её и проверьте localhost:11434"
) from error
message = data.get("message")
if not isinstance(message, dict) or not isinstance(message.get("content"), str):
raise ValueError("в ответе Ollama нет message.content")
content = message["content"].strip()
if not content:
raise ValueError("Ollama вернула пустой message.content")
return content
print(ask("Объясни одним предложением, чем медиана отличается от среднего."))
Ваш текст будет отличаться: языковая модель генерирует ответ, а не достаёт одну заранее записанную строку. Правильный результат первого запуска — непустой осмысленный ответ и отсутствие исключения.
Разбор
Сервер уже работает у вас
ollama run делает две вещи: при необходимости получает файл модели и запускает диалог. Python не запускает модель заново — он обращается к уже работающему HTTP API по адресу http://localhost:11434/api.
localhost означает именно текущий компьютер. Это не облачный адрес. Суффикс :cloud в имени модели, напротив, обозначает облачный запуск и для цели этого урока не подходит.
Запрос — обычный JSON
У /api/chat два обязательных поля: имя model и список messages. Каждое сообщение имеет роль и содержание. Пока история состоит из одного сообщения пользователя.
stream: False просит один JSON-объект. По умолчанию API отдаёт поток фрагментов; это полезно для интерфейса, но добавляет новую тему, поэтому здесь поток явно выключен.
json.dumps(...).encode("utf-8") превращает словарь в байты. Content-Type сообщает серверу, что внутри JSON. Это тот же путь, который вы уже прошли в уроках про JSON и HTTP, только сервер теперь ваш.
Таймаут нужен и локальному вызову
Первый ответ может быть медленным: модель загружается в память. Но бесконечное ожидание всё равно является ошибкой. Поэтому у вызова есть явный timeout=120, а сетевые и HTTP-ошибки превращаются в сообщения, по которым ученик понимает, что проверить.
Ответ сначала проверяют
Текст лежит в message.content, но программа не индексирует ответ вслепую. Чужая версия сервера, ошибка или неожиданная форма JSON не должны превращаться в непонятный KeyError. Проверка границы даёт конкретный ValueError.
Это ещё не проверка истинности текста. Сейчас проверена лишь форма ответа. Факты начнём проверять в уроках 49–51.
Что значит «без интернета»
После загрузки локальной модели сам запрос идёт на localhost; текст не обязан покидать компьютер. Но урок не обещает того, чего не контролирует:
- установщик и файл модели сначала скачиваются из сети;
- облачная модель работает не локально;
- другие программы и журналы на компьютере остаются отдельной зоной доверия;
- локальность не делает ответ правильным.
Карта урока
Закройте текст и восстановите путь по карте: вопрос → JSON → localhost → JSON → проверка → текст.
Скажите своими словами
- Почему для локальной модели не нужен API-ключ?
- Зачем ставить
stream: Falseв первом учебном клиенте? - Что именно проверяет условие про
message.content, а чего оно не проверяет? - Почему нельзя открывать порт 11434 всему интернету?
Разминка
1. Предскажите. Что напечатает разбор готового ответа?
import json
raw = '{"message": {"role": "assistant", "content": " Локально. "}}'
data = json.loads(raw)
print(data["message"]["content"].strip())
2. Заполните пропуск. Вместо ... укажите поле, выключающее поток.
payload = {
"model": "gemma4",
"messages": [{"role": "user", "content": "Один факт"}],
...: False,
}
3. Почините. Этот код молча принимает пустой ответ:
data = {"message": {"content": " "}}
answer = data["message"]["content"].strip()
print(answer)
Задание
Обязательное. Напишите функцию answer_text(data), которая принимает уже разобранный ответ Ollama. Она должна вернуть непустой message.content без пробелов по краям. При отсутствии message, неправильном типе content или пустой строке функция должна поднять ValueError("нет текста модели"). Проверьте её на четырёх готовых ответах и напечатайте результат проверки.
1: ответ: 42
2: ошибка: нет текста модели
3: ошибка: нет текста модели
4: ошибка: нет текста модели
проверено ответов: 4
Готово — это: вывод совпадает построчно, функция не обращается к Ollama и каждую ошибочную форму отклоняет одинаково понятно.
На своих данных. Запустите ask с вопросом по одной строке своей сводки. Не просите объяснить причину; попросите только пересказать переданное число и вручную проверьте, сохранились ли значение, единица и год.
По желанию. Добавьте аргумент url со значением по умолчанию OLLAMA_CHAT, а в тесте передайте адрес своего локального поддельного HTTP-сервера. Так сетевой клиент проверяют без настоящей модели.
Куда это встанет в проекте
Пока — никуда, и это намеренная пауза. Один успешный ответ ещё нельзя помещать в отчёт: он меняется и может содержать выдумку. В уроках 48 и 49 закрепим параметры и потребуем структурированный ответ; только в уроке 50 языковая модель войдёт в сводку. Хорошая архитектура подключает внешний компонент после определения его контракта, а не после первого впечатляющего запуска.
Ответы
Показать ответы
На вопросы
- Запрос идёт к серверу на вашем компьютере; отдельного удалённого аккаунта между программой и моделью нет.
- Чтобы получить один JSON и сначала освоить контракт, не смешивая его с разбором потока фрагментов.
- Только наличие непустой строки в ожидаемом месте. Истинность, полноту и безопасность содержания она не доказывает.
- У локального API нет причины становиться публичной точкой входа: посторонний сможет расходовать ресурсы компьютера и отправлять модели данные.
К разминке
Локально.
- Поле называется
"stream".
payload = {
"model": "gemma4",
"messages": [{"role": "user", "content": "Один факт"}],
"stream": False,
}
print(payload["stream"])
False
- После
strip()нужно проверить, что строка не пуста, и поднять понятное исключение.
data = {"message": {"content": " "}}
answer = data["message"]["content"].strip()
if not answer:
raise ValueError("нет текста модели")
Источники
- Ollama Quickstart — актуальная установка и первый локальный запуск.
- Ollama API introduction — базовый адрес
localhost:11434/apiи политика совместимости API. - Ollama
/api/chat— поля запроса,stream, сообщения и форма ответа.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.