Chat PDF: как задавать вопросы к PDF через нейросеть по API
Два способа спрашивать PDF у модели: положить документ в контекст и кэшировать или искать нужные куски. Код на Python и расчёт цены двадцати вопросов.
6 мин · · автоматизация · API-ключи
Chat PDF нейросеть делает простую вещь: вы загружаете документ и задаёте вопросы обычными словами. Готовые сервисы такого рода есть, но если документов много, они конфиденциальны или нужна своя логика, проще собрать это самому по API. Есть два принципиально разных подхода: положить весь документ в контекст модели и кэшировать его или искать нужные фрагменты и отправлять только их. Разберём оба на Python и посчитаем, сколько стоит серия вопросов.
Chat PDF нейросеть: шаг 0, достаём текст из PDF
Если в PDF есть текстовый слой, достаньте текст с номерами страниц, чтобы модель могла на них ссылаться:
from pypdf import PdfReader
def pdf_pages(path):
reader = PdfReader(path)
return [(i, (p.extract_text() or "").strip())
for i, p in enumerate(reader.pages, 1)]
pages = pdf_pages("report.pdf")
empty = [n for n, t in pages if len(t) < 20]
print("страниц без текста:", len(empty))Если пустых страниц много, перед вами скан. Тогда нужно либо распознавание, либо отправка страниц как картинок, подробно в статье как распознать PDF нейросетью. Документы Word и таблицы Excel сводятся к той же схеме: текст из .docx достаёт python-docx, из .xlsx листы читает openpyxl или pandas. Дальше всё одинаково: получили текст с метками (страница, лист), передали модели.
Способ A. Весь документ в контексте и кэш
У моделей Zukko контекст 1M токенов (у GLM 5.3 — 128K, у Grok — 256K), поэтому документ в сотню страниц помещается целиком. Измерьте размер заранее через count_tokens (см. справку):
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.zukko.pro",
api_key=os.environ["ZUKKO_API_KEY"])
DOC = "\n\n".join(f"[стр. {n}]\n{t}" for n, t in pages)
MODEL = "claude-sonnet-5-5"
n = client.messages.count_tokens(
model=MODEL, messages=[{"role": "user", "content": DOC}]).input_tokens
print("токенов в документе:", n)Теперь сам чат. Документ идёт в system с пометкой кэша, а вопросы добавляются в историю. Весь повторяющийся префикс (документ плюс прежние реплики) должен оставаться байт в байт одинаковым:
SYSTEM = [
{"type": "text",
"text": "Отвечай только по документу ниже. После каждого утверждения "
"указывай страницу в виде [стр. N]. Если ответа в документе нет, "
"напиши: в документе не найдено."},
{"type": "text", "text": DOC, "cache_control": {"type": "ephemeral"}},
]
history = []
def ask(question):
history.append({"role": "user", "content": question})
msg = client.messages.create(model=MODEL, max_tokens=1000,
system=SYSTEM, messages=history)
answer = msg.content[0].text
history.append({"role": "assistant", "content": answer})
u = msg.usage
print("свежий вход:", u.input_tokens,
"из кэша:", getattr(u, "cache_read_input_tokens", 0),
"запись:", getattr(u, "cache_creation_input_tokens", 0))
return answerНа первом вопросе вы увидите запись в кэш, на втором и дальше чтение. Кэш живёт ограниченное время и продлевается при обращениях, точные условия (в том числе минимальную длину префикса) смотрите в документации Anthropic. Если пауза между вопросами длинная, кэш будет записан заново. Подробнее о тарификации: кэширование промптов: чтение и запись в Zukko. Скидку на кэш Claude даёт именно Anthropic-формат; в OpenAI-формате весь вход Claude идёт по цене входа.
Плюсы подхода: модель видит документ целиком, поэтому ей можно задавать вопросы вроде «в чём противоречия между разделами». Минусы: в каждый запрос уходит весь документ, и чем он длиннее, тем больше токенов оплачивается на каждом вопросе.
Способ B. Поиск по частям
Режем документ на фрагменты, находим релевантные вопросу и отправляем только их. Без векторной БД поиск можно сделать TF-IDF:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from openai import OpenAI
chunks = [(n, t) for n, t in pages if t]
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5))
matrix = vec.fit_transform([t for _, t in chunks])
oa = OpenAI(base_url="https://api.zukko.pro/v1",
api_key=os.environ["ZUKKO_API_KEY"])
def ask_rag(question, k=5):
scores = cosine_similarity(vec.transform([question]), matrix)[0]
top = scores.argsort()[::-1][:k]Символьные n-граммы выбраны потому, что TF-IDF по целым словам плохо справляется с русской морфологией. Это простейший вариант, для сложных документов используют векторный поиск, как в RAG-пайплайне. Слабое место подхода: вопросы, ответ на которые разбросан по всему документу («сделай сводку по всем разделам»), поиск может пропустить.
Что выбрать
| A: документ в контексте | B: поиск по частям | |
|---|---|---|
| Вопросы по всему документу | Хорошо | Слабо |
| Точечные вопросы | Хорошо | Хорошо |
| Размер документа | Ограничен контекстом | Любой |
| Цена серии вопросов | Растёт с размером документа | Почти не зависит от размера |
| Сложность | Минимальная | Нужен поиск |
Практический порядок: документы до сотни страниц начните со способа A, для больших архивов и десятков файлов переходите на B.
Сколько стоит серия вопросов
Формула: (свежий вход × цена входа + чтение кэша × цена чтения + запись кэша × цена записи + выход × цена выхода) / 1 000 000. Допущения (рост истории диалога для простоты не учитываем): документ 60 000 токенов (измерьте свой через count_tokens), 20 вопросов по 300 токенов, ответы по 400 токенов (8 000 токенов выхода). Цены Zukko на 18 октября 2026.
claude-sonnet-5-5, вход 0.4, кэш чтение 0.05, кэш запись 0.5, выход 2 ($ за 1M):
- Без кэша: 20 × 60 300 = 1 206 000 токенов × 0.4 / 1 000 000 = 0.4824, плюс выход 8 000 × 2 / 1 000 000 = 0.016. Итого около $0.50.
- С кэшем (Anthropic-формат): запись 60 000 × 0.5 / 1 000 000 = 0.03; чтение 19 × 60 000 = 1 140 000 × 0.05 / 1 000 000 = 0.057; вопросы 20 × 300 = 6 000 × 0.4 / 1 000 000 = 0.0024; выход 0.016. Итого около $0.105.
- Способ B (по 5 фрагментов, около 2 800 токенов на вопрос): 56 000 × 0.4 / 1 000 000 + 0.016 = около $0.038.
gpt-6.1-sol (вход 0.27, чтение кэша 0.013, выход 1.32): без попадания в кэш 1 206 000 × 0.27 / 1 000 000 + 8 000 × 1.32 / 1 000 000 = 0.3256 + 0.0106, около $0.34. Если кэш сработал со второго вопроса, то первый вопрос 60 300 × 0.27 / 1 000 000 = 0.0163, чтение 1 140 000 × 0.013 / 1 000 000 = 0.0148, остальной вход 5 700 × 0.27 / 1 000 000 = 0.0015, выход 0.0106; итого около $0.043. Для GPT чтение кэша видно в обоих форматах, но запись отдельно не списывается.
deepseek-v4.1-flash (вход 0.013, выход 0.038, чтение кэша 0.013): 1 206 000 × 0.013 / 1 000 000 + 8 000 × 0.038 / 1 000 000 = 0.0157 + 0.0003, около $0.016. Скидки за кэш здесь нет, но цена и так низкая.
Вывод простой: при длинном документе и серии вопросов разница между «без кэша» и «с кэшем» кратная, поэтому не забывайте про него. Но по цифрам и индексу силы (Sonnet 5.5 имеет 48, GPT-6.1 Sol 52, DeepSeek V4.1 Flash 39) нельзя понять, кто лучше отвечает по вашему PDF, проверьте на десяти своих вопросах. Актуальные цены: GET https://api.zukko.pro/v1/models.
Подключение
Создайте ключ в кабинете и используйте нужный формат: Anthropic SDK с base URL https://api.zukko.pro или OpenAI SDK с https://api.zukko.pro/v1. Пополнение от $1: СБП (платёжная страница добавляет 8,5% сверху), криптовалюта или карта MasterCard.
Частые вопросы
Можно ли загружать в такой чат конфиденциальные документы?
Текст уходит провайдеру модели. Решите заранее, что допустимо отправлять, обезличьте данные и прочитайте условия поставщиков, мы по ним ничего не утверждаем.
Почему модель отвечает «не найдено», хотя ответ в PDF есть?
Либо текст не извлёкся (скан, сложная вёрстка, таблицы), либо при поиске по частям нужный фрагмент не попал в выборку. Проверьте извлечённый текст и увеличьте число фрагментов.
Как избежать выдуманных ответов?
Требуйте ссылку на страницу и проверяйте цитаты кодом, ответ без ссылки считайте недействительным.
Какой нейросетью анализировать PDF-файл?
Для проверки возьмите три модели из разных ценовых групп и десять реальных вопросов к вашим документам. Решайте по результату и цене.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- DeepSeek API OCR: распознаём текст с картинок через V4 ProDeepSeek V4 Pro принимает изображения, а значит умеет читать текст со скриншотов и сканов. Показываем код на Python, JSON-режим, проверки точности и расчёт стоимости на 1000 страниц.
- База знаний в Open WebUI: настройка RAG на своих документахВ Open WebUI можно загрузить документы и задавать по ним вопросы. Показываем, как создать базу знаний, какие настройки влияют на точность ответов и во что обходится один вопрос.
- Анализ сайта нейросетью: аудит страниц, текстов и структуры через APIСобираем данные страниц скриптом, отправляем модели компактный JSON и получаем отчёт по заголовкам, мета-тегам и структуре текста. С расчётом цены и честными ограничениями.
- Ключи OpenAI API в открытом доступе: почему ими нельзя пользоватьсяОткуда берутся «бесплатные» ключи на GitHub, почему пользоваться ими опасно и для вас, и для владельца, и как защитить собственный ключ от такой же утечки.