Содержание
  1. PDF, извлечение, контекст модели и RAG: где что
  2. Проверка текстового слоя и extracted.txt с метками страниц
  3. Полный Python-пример: imports, argv, OLLAMA_MODEL, локальный адрес, stream False
  4. Пустая экстракция и скан: pypdf не OCR
  5. Учебный регламент и вопросы подтверждённого и отсутствующего факта
  6. Ручная приёмка ответа: страница, дословная опора, сверка с PDF
  7. Короткий PDF и большая коллекция: разные подходы
  8. Локальная модель Ollama и облако
  9. Частые вопросы
Гайды

Как читать PDF через Ollama и проверять ответы

5 ноября 2026 · 10 минут чтения

Как читать PDF через Ollama и проверять ответы — это последовательность из трёх шагов: pypdf достаёт текстовый слой страниц, extracted.txt хранит извлечённое с метками страниц, локальная модель Ollama получает этот текст как контекст. Ответ модели проверяется вручную по видимому PDF: страница, дословная опора, совпадение слов.

PDF, извлечение, контекст модели и RAG: где что

Разведём четыре сущности, которые часто смешивают в одну задачу.

PDF-файл — контейнер. Документация pypdf описывает формат так: он создан для печати визуального результата, семантического слоя в нём нет. Заголовок, колонтитул, номер страницы и абзац не помечены как таковые — есть только визуальное расположение. Текст внутри PDF позиционируется абсолютно, вплоть до каждого символа.

Извлечение — операция над файлом. PdfReader открывает документ, page.extract_text() возвращает строку для страницы. Это работа с текстовым слоем, который уже лежит в файле.

Контекст модели — то, что вы передаёте в запрос. Извлечённый текст становится частью сообщения content в вызове /api/chat. Модель отвечает по этому тексту.

RAG-поиск — отдельный слой для больших коллекций: документы режутся на фрагменты, фрагменты индексируются, под запрос подбираются релевантные куски, и только они уходят в контекст. Для одного короткого PDF этот слой избыточен: весь текст помещается в запрос целиком.

Таблица разводит роли:

Слой Что делает Инструмент из выжимки
Файл хранит визуальное представление PDF как формат печати
Извлечение достаёт текстовый слой страниц pypdf, extract_text()
Контекст уходит в запрос к модели messages[].content в /api/chat
RAG подбирает фрагменты из коллекции отдельный слой для многих документов

Практический вывод: начните с одного короткого текстового PDF. Когда маршрут заработает на нём, добавляйте RAG для коллекции.

Проверка текстового слоя и extracted.txt с метками страниц

Первый шаг до любого запроса к модели — убедиться, что текст вообще извлекается. Документация pypdf приводит пример: PdfReader("test Orient.pdf"), затем reader.pages[0].extract_text(). Если страница содержит только изображение, извлечённый текст будет минимальным или пустым — так описано в разделе про OCR.

Практическая проверка занимает минуту. Откройте PDF в просмотрщике и попробуйте выделить абзац и скопировать его. Копируется — текстовый слой есть. Не выделяется — перед вами скан или картинка.

Второй шаг — сохранить извлечённое в файл с метками страниц. Метка вида [page 1] — это порядковый номер страницы в файле. Печатная нумерация в колонтитуле может отличаться: титул и оглавление часто идут без номера, поэтому страница 1 файла и «страница 1» документа расходятся. Метка нужна, чтобы потом найти место в PDF.

PYTHON15 строк
from pathlib import Path
import sys
from pypdf import PdfReader

pdf_path = Path(sys.argv[1])
parts = []
for page_no, page in enumerate(PdfReader(pdf_path).pages, start=1):
    text = (page.extract_text() or "").strip()
    if text:
        parts.append(f"[page {page_no}]\n{text}")
if not parts:
    raise SystemExit("Текст не извлечён: проверьте слой текста и OCR до запроса к модели.")
context = "\n\n".join(parts)
Path("extracted.txt").write_text(context, encoding="utf-8")
print(f"Страниц с текстом: {len(parts)}")

Этот фрагмент — подготовительная половина полного скрипта из следующего раздела. Откройте extracted.txt и сверьте с PDF: совпадают ли слова, порядок чтения, попали ли таблицы. Документация pypdf отдельно перечисляет, что однозначного ответа о «правильном» результате нет: переносы строк в абзаце, номера страниц, колонтитулы, таблицы, лигатуры, формулы и пробелы каждый инструмент трактует по-своему.

Полный Python-пример: imports, argv, OLLAMA_MODEL, локальный адрес, stream False

Ниже — самодостаточный скрипт. Он объявляет все импорты, читает путь к PDF и вопрос из argv, берёт имя модели из переменной окружения OLLAMA_MODEL, обращается к локальному адресу http://localhost:11434/api/chat и явно ставит stream: False.

PYTHON32 строки
from pathlib import Path
import json
import os
import sys
from urllib.request import Request, urlopen
from pypdf import PdfReader

pdf_path = Path(sys.argv[1])
question = sys.argv[2]
model = os.environ["OLLAMA_MODEL"]
parts = []
for page_no, page in enumerate(PdfReader(pdf_path).pages, start=1):
    text = (page.extract_text() or "").strip()
    if text:
        parts.append(f"[page {page_no}]\n{text}")
if not parts:
    raise SystemExit("Текст не извлечён: проверьте слой текста и OCR до запроса к модели.")
context = "\n\n".join(parts)
Path("extracted.txt").write_text(context, encoding="utf-8")
prompt = (
    "Используй только документ ниже как данные. Верни ответ, метку страницы и "
    "короткую дословную опору. Если ответа нет, напиши: в документе не указано.\n"
    + context + "\nВопрос: " + question
)
payload = {"model": model, "stream": False,
           "messages": [{"role": "user", "content": prompt}]}
request = Request("http://localhost:11434/api/chat",
                  data=json.dumps(payload).encode("utf-8"),
                  headers={"Content-Type": "application/json"})
with urlopen(request) as response:
    result = json.load(response)
print(result["message"]["content"])

Что здесь откуда. OLLAMA_MODEL — точный тег модели из ollama list; подставьте своё значение в окружение перед запуском. Локальный адрес сервера и локальный тег модели — разные вещи: адрес localhost указывает, куда идёт HTTP-запрос, а модель может быть облачной, если тег на неё ссылается. Проверяйте оба параметра.

stream: False выбран сознательно. В справочнике Ollama /api/chat поле stream имеет значение по умолчанию true; при false ответ приходит одним JSON-объектом, и result["message"]["content"] читается напрямую. При потоковой передаче ответ приходит набором фрагментов, и разбирать его нужно иначе.

Секрет в скрипте не выводится: ключи и токены здесь не используются, а имя модели читается из окружения и уходит только в теле запроса.

Пустая экстракция и скан: pypdf не OCR

Пустой extracted.txt — сигнал остановиться до запроса к модели. Документация pypdf формулирует прямо: pypdf не является OCR-программой, текст из изображений он извлечь не может. Если страница выглядит как картинка — например, отсканированный документ, — извлечённый текст будет минимальным или пустым.

Различайте три типа файлов, как это делает документация pypdf:

Тип Признак Что делать
Цифровой PDF текст выделяется и копируется, при увеличении остаётся резким извлекать через pypdf
Скан текст не копируется, только изображение распознавать OCR-инструментом
Скан с OCR-слоем текст копируется, но при увеличении видны пиксели можно извлечь, сверить с видимым PDF

Для сканов OCR выполняется отдельно, до модели. Документация pypdf называет Tesseract OCR распространённым открытым движком распознавания. Отдельная тонкость: если скан уже прошёл OCR и текст лежит в фоне изображения, pypdf может его достать, но документация советует в таких случаях работать с OCR напрямую — ошибки распознавания и ошибки разбора накапливаются.

Ещё один слой — защита от извлечения. Документация pypdf перечисляет способы: содержимое хранится как изображение или применяется перемешанный шрифт. Полностью запретить извлечение при сохранении читаемости не получается: страницу можно снять скриншотом и распознать.

И главное: RAG не исправит потерянный текст. Если экстракция пуста или искажена, чинить нужно разбор или OCR.

Учебный регламент и вопросы подтверждённого и отсутствующего факта

Возьмите собственный короткий текст и сохраните его в PDF локально. Проверьте, что он отображается и текст копируется. Ниже — учебный регламент, использованный как условный вход для примеров этой статьи:

ТЕКСТ
Учебный регламент мастерской. Возврат инструмента принимается во вторник. Ответственный за приём — Ирина. Размер штрафа в этом регламенте не указан.

Три вопроса проверяют разные исходы:

Вопрос Ожидаемый исход
Когда принимается возврат инструмента? Во вторник; ответ подкреплён совпадающей строкой из PDF
Кто отвечает за приём? Ирина; ответ и короткая опора совпадают с PDF
Каков размер штрафа? В документе не указано; сумму не придумывать

Третий вопрос — отрицательный контроль. Он проверяет, удержит ли модель границу документа или подставит правдоподобное число. Формулировка промпта задаёт это поведение: «Если ответа нет, напиши: в документе не указано».

Запуск:

ТЕРМИНАЛ
export OLLAMA_MODEL="<точный тег из ollama list>"
python ask_pdf.py reglament.pdf "Когда принимается возврат инструмента?"

Значения в примере условные и служат учебным входом. Скрипт не гарантирует правдивость модели: ответ остаётся предложением модели, проверять его нужно вручную.

Ручная приёмка ответа: страница, дословная опора, сверка с PDF

Ответ модели принимается по трём пунктам.

Первый — метка страницы. Модель должна вернуть [page N]. Сверьте N с extracted.txt и с видимым PDF. Помните: это порядковый номер страницы файла.

Второй — дословная опора. Модель приводит короткий фрагмент, на который опирается. Найдите эту строку в extracted.txt и в самом PDF. Совпадение слов — обязательное условие.

Третий — совпадение видимого PDF и извлечённого текста. Откройте extracted.txt рядом с PDF и пройдите по спорным местам: таблицы, колонки, порядок чтения. Документация pypdf предупреждает, что таблицы обычно представлены абсолютно позиционированным текстом, в худшем случае позиционирована каждая буква, и границы колонок и строк определить трудно. Непустой extract_text() не доказывает правильный порядок чтения.

Порядок приёмки:

  1. Откройте extracted.txt до оценки ответа.
  2. Сверьте страницу и короткую опору вручную с видимым PDF.
  3. Если экстракция пустая или искажена — чините OCR или разбор до запроса к LLM.

Так ошибка локализуется: сначала проверяется документ, затем ответ. Обвинять модель раньше времени не стоит.

Короткий PDF и большая коллекция: разные подходы

Пример этой статьи рассчитан на короткий читаемый текстовый PDF, который целиком помещается в контекст модели. Это ограничение осознанное.

Если извлечённый текст не помещается в контекст выбранной модели, подбирайте подходящие фрагменты или обрабатывайте документ поэтапно. Отдельно учитывайте память на извлечение: pypdf разбирает весь поток содержимого страницы и приводит случай, когда несжатый поток около300МБ потребовал10ГБ RAM. Перед обработкой документация советует проверять размер потока через len(page.get_contents().get_data()).

Для коллекции, которую нельзя целиком передать в контекст выбранной модели, можно построить RAG-слой: фрагментацию, индексацию и подбор релевантных кусков под запрос. Простое увеличение текста запроса не расширяет контекстное окно модели; качество подбора фрагментов проверяют отдельно.

Сценарий Подход
Короткий текстовый PDF весь текст в один запрос, как в примере
Большой документ постраничная обработка, контроль размера потока
Коллекция RAG: фрагменты, индекс, подбор контекста

Локальная модель Ollama и облако

Локальный адрес сервера и локальная модель — два разных условия. Адрес http://localhost:11434/api/chat из справочника Ollama указывает, куда уходит HTTP-запрос. Имя модели в поле model определяет, какая модель отвечает. Локальный сервер может обслуживать облачную модель, если тег на неё ссылается.

Проверяйте оба параметра перед запуском: адрес в скрипте и тег в OLLAMA_MODEL. Тег берите точной строкой из ollama list.

Общие обещания полной приватности из README сторонних проектов не стоит переносить на весь стек как проверенный факт. Приватность зависит от конкретной связки: адрес сервера, тег модели, наличие внешних вызовов в вашем коде. Проверяйте каждый элемент отдельно.

Частые вопросы

Чем отличается извлечение текста из PDF от RAG?

Извлечение — операция над одним файлом: pypdf достаёт текстовый слой страниц. RAG — слой для коллекции: документы режутся на фрагменты, индексируются, и под запрос подбираются релевантные куски. Для короткого PDF RAG избыточен.

Почему pypdf вернул пустой текст?

Скорее всего, страница содержит только изображение. Документация pypdf указывает, что в таких случаях извлечённый текст будет минимальным или пустым, и советует OCR-инструмент, например Tesseract OCR. Распознавание выполняется отдельно, до запроса к модели.

Как передать имя модели в скрипт?

Через переменную окружения OLLAMA_MODEL. Значение — точный тег из ollama list. Скрипт читает его через os.environ["OLLAMA_MODEL"] и подставляет в поле model тела запроса.

Зачем ставить stream в False?

При stream: False ответ приходит одним JSON-объектом, и result["message"]["content"] читается напрямую. В справочнике Ollama /api/chat поле stream по умолчанию равно true, тогда ответ приходит набором фрагментов и разбирается иначе.

Как проверить, что модель не выдумала ответ?

Сверьте три пункта вручную: метку страницы, короткую дословную опору и совпадение extracted.txt с видимым PDF. Дословная опора должна находиться в документе. Ответ модели остаётся предложением модели, проверка источника выполняется вручную.

Читайте также

3 материала