pip; клиент psql; curl и jq; Docker (опционально, для векторного хранилища); сервисный аккаунт в wiki с правами чтения; роль в базе только с SELECT; API-токен тест-менеджмента; API-ключ провайдера эмбеддингов и LLM; 2 ГБ свободного места
$HOME, sudo для установки пакетовpython3 --versionpsql --versioncurl и jq — curl --version, jq --versionПроверка окружения одной командой:
python3 --version && psql --version && curl --version | head -1 && jq --version && df -h "$HOME" | tail -1
Ожидаемый вывод — четыре строки с версиями и строка вида /dev/sda1 50G 12G 36G 25% /home. Если хоть одна команда даёт command not found, установите пакет до продолжения.
Если LLM и векторное хранилище вы поднимаете у себя, а не берёте облачный API, порядок развёртывания описан в гайде Как подключить ИИ к домашнему серверу.
Пример: для облачного провайдера ключ выглядит как строка sk-... и кладётся в .env, а не в код — так он не попадёт в git.
Что сделать: создать рабочий каталог, зафиксировать в одном файле .env адреса и токены трёх систем, проверить, что каждый токен реально отдаёт данные.
Зачем: 80% времени отладки RAG (RAG — когда нейросеть сначала ищет нужные документы, а потом отвечает на их основе) уходит на «а доступ-то есть?». Проверка на этом шаге отделяет проблему доступа от проблемы качества ответов.
Как проверить: каждый curl возвращает 200, а psql — число таблиц больше нуля.
mkdir -p ~/qa-rag/connectors && cd ~/qa-rag
python3 -m venv .venv && source .venv/bin/activate
pip install requests psycopg2-binary python-dotenv
Файл ~/qa-rag/.env (права chmod 600 .env):
WIKI_BASE_URL=https://wiki.<ваш-домен>
WIKI_SPACE=QA
WIKI_TOKEN=<токен-сервисного-аккаунта>
DB_DSN=postgresql://qa_ro:<пароль>@db.<ваш-домен>:5432/qa
TMS_BASE_URL=https://tms.<ваш-домен>/api
TMS_TOKEN=<токен-тест-менеджмента>
EMBEDDING_BASE_URL=https://api.<провайдер>.com/v1
EMBEDDING_API_KEY=<ключ>
EMBEDDING_MODEL=<имя-модели-эмбеддингов>
LLM_API_KEY=<ключ>
<ваш-домен> заменяйте на реальный хост без протокола и без слэша на конце. Точные пути REST-методов у вашей wiki и тест-менеджмента сверьте с документацией — ниже проверочные вызовы даны в общем виде.
| Источник | Что забираем | Доступ | Обновление |
|---|---|---|---|
| Wiki | страницы пространства QA | токен, чтение | по вебхуку или раз в сутки |
| База | схема с тестовыми данными | роль `qa_ro`, только SELECT | по расписанию |
| Тест-менеджмент | тест-кейсы и результаты прогонов | API-токен | по расписанию |
Проверка wiki (путь /rest/api/content — типовой, сверьте с документацией вашей платформы):
curl -sS -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $WIKI_TOKEN" \
"$WIKI_BASE_URL/rest/api/content?spaceKey=$WIKI_SPACE&limit=1"
Ожидается 200. 401 — токен неверный, 403 — нет прав на пространство.
Проверка базы:
psql "$DB_DSN" -c "select count(*) as tables from information_schema.tables where table_schema='public';"
Ожидается таблица с колонкой tables и числом больше 0. Ошибка password authentication failed означает, что роль ещё не создана — это Шаг 2.
Пример: если тест-менеджмент отдаёт проекты по /api/v1/projects, проверка выглядит так: curl -sS -H "Authorization: Bearer $TMS_TOKEN" "$TMS_BASE_URL/v1/projects" | jq '.items | length' → 7. Любое число, кроме null, подтверждает доступ.

Что сделать: выгрузить тексты из трёх систем, нарезать их на чанки (чанк — кусок текста фиксированного размера, который ищется как единица), векторизовать, сохранить в хранилище и описать нейросети три инструмента: поиск по wiki, SQL-запрос к базе, запрос тест-кейса.
Зачем: нейросеть сама не имеет доступа к вашим системам — она умеет только вызывать описанные вами функции. Индекс нужен, чтобы в контекст попадали 3–5 релевантных кусков, а не вся wiki.
Как проверить: скрипт печатает число страниц и чанков, а тестовый поиск возвращает непустой список.
Сначала — безопасная роль в базе:
CREATE ROLE qa_ro LOGIN PASSWORD '<пароль>';
GRANT CONNECT ON DATABASE qa TO qa_ro;
GRANT USAGE ON SCHEMA public TO qa_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO qa_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO qa_ro;
ALTER ROLE qa_ro SET default_transaction_read_only = on;
Последняя строка запрещает запись на уровне сессии: даже если нейросеть сгенерирует DELETE, база вернёт ошибку. Это и есть причина, по которой роль создаётся до подключения модели.
Файл ~/qa-rag/index.py:
import os, requests
from dotenv import load_dotenv
load_dotenv()
def chunk(text, size=800, overlap=100):
step = size - overlap
return [text[i:i + size] for i in range(0, len(text), step) if text[i:i + size].strip()]
def fetch_wiki(space):
r = requests.get(
f"{os.environ['WIKI_BASE_URL']}/rest/api/content",
params={"spaceKey": space, "limit": 100, "expand": "body.storage"},
headers={"Authorization": f"Bearer {os.environ['WIKI_TOKEN']}"},
timeout=30,
)
r.raise_for_status()
return [{"id": p["id"], "title": p["title"],
"text": p["body"]["storage"]["value"]} for p in r.json()["results"]]
def embed(texts):
r = requests.post(
f"{os.environ['EMBEDDING_BASE_URL']}/embeddings",
headers={"Authorization": f"Bearer {os.environ['EMBEDDING_API_KEY']}"},
json={"model": os.environ["EMBEDDING_MODEL"], "input": texts},
timeout=60,
)
r.raise_for_status()
return [d["embedding"] for d in r.json()["data"]]
Формат тела и ответа /embeddings сверьте с документацией провайдера: у части сервисов поля называются иначе.
Проверка нарезки:
python -c "from index import chunk; print(len(chunk('a'*2000)))"
Ожидается 3: при size=800 и overlap=100 шаг равен 700, и 2000 символов дают три куска.
Описание инструментов для модели — файл tools.json:
[
{"name": "wiki_search",
"description": "Поиск по QA-документации. Возвращает 5 чанков с id страницы.",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}},
{"name": "db_query",
"description": "SELECT-запрос к базе, максимум 200 строк, только схема public.",
"parameters": {"type": "object", "properties": {"sql": {"type": "string"}}, "required": ["sql"]}},
{"name": "tms_get_case",
"description": "Карточка тест-кейса по ID: шаги, ожидаемый результат, последний статус.",
"parameters": {"type": "object", "properties": {"case_id": {"type": "string"}}, "required": ["case_id"]}}
]
Пример: на запрос «почему падает TC-1042» модель должна вызвать tms_get_case с {"case_id": "TC-1042"}, затем db_query с SELECT status, count(*) FROM runs WHERE case_id='TC-1042' GROUP BY status; — два вызова подряд, а не один общий вопрос к базе.

Что сделать: задать системе реальный вопрос QA-инженера и убедиться, что ответ содержит ссылку на страницу wiki, результат SQL и ID тест-кейса.
Зачем: по отдельности все три коннектора могут работать, а вместе — нет: модель «забывает» вызвать инструмент или подставляет выдуманный ID.
Как проверить: в логах есть три вызова инструментов, в ответе — три источника; при отсутствии данных модель отвечает «в документации не найдено».
curl -sS http://localhost:8080/health | jq .
Ожидаемый вывод:
{"status": "ok", "chunks": 1240}
Число 1240 — фактическое количество чанков в вашем индексе, оно зависит от объёма wiki.
Проверка поиска без модели:
curl -sS -X POST http://localhost:8080/search \
-H "Content-Type: application/json" \
-d '{"q":"флаки-тест на стейдже","k":5}' | jq '.results | length'
Ожидается 5. Ноль означает, что индекс пуст или запрос не совпал с лексикой документов.
Чек-лист приёмки:
/health возвращает status: oktms_get_case и db_querydb_query с DROP TABLE завершается ошибкой правПоследний пункт проверяется руками:
psql "$DB_DSN" -c "DROP TABLE runs;"
Ожидается ERROR: cannot execute DROP TABLE in a read-only transaction либо permission denied for table runs.
Пример: успешный ответ на «почему TC-1042 падает на стейдже» выглядит так: «Падение связано с таймаутом внешнего сервиса (wiki: страница 88421, раздел „Известные флаки“). По данным прогонов: 12 failed, 3 passed (SQL по runs). Шаги кейса — TC-1042, последний статус failed». Три источника — три явных указания.
Как наблюдать за сервисом после запуска (порты, аптайм, уведомления в Telegram) — в гайде мониторинг VPS через Telegram-бота с Mini App.
Проблема. Wiki не отдаёт страницы, хотя логин верный
{"statusCode": 401, "message": "Unauthorized"}curl -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $WIKI_TOKEN" "$WIKI_BASE_URL/rest/api/content?limit=1"
Если без Bearer возвращается 403, а с ним 200 — дело было в схеме авторизации.
Проблема. Индекс не собирается: коннектор не видит базу
psycopg2.OperationalError: could not translate host name "db" to address: Name or service not knownDB_DSN содержит имя сервиса из docker-compose, а скрипт запущен на хосте, вне docker-сети.psql "postgresql://qa_ro:<пароль>@127.0.0.1:5432/qa" -c "select 1;"
Ответ ?column? = 1 означает, что дело было только в имени хоста.
Проблема. Модель отвечает «не найдено», хотя страница в wiki есть
{"results": []}WIKI_SPACE, либо индекс собран до её последнего изменения.python index.py --space "$WIKI_SPACE" --rebuild && curl -sS http://localhost:8080/health | jq '.chunks'
Число chunks должно вырасти. Точные флаги CLI задаёт ваш скрипт — если --rebuild не поддерживается, удалите каталог индекса и запустите сборку заново.
Три коннектора проверяются по отдельности до того, как вы дадите модели право их вызывать: сначала 200 от каждого API и роль qa_ro с SELECT, потом индекс, потом сквозной вопрос с тремя источниками в ответе. Дальше добавьте расписание переиндексации и тест на «вопрос вне базы знаний», чтобы ловить регрессии качества при смене модели эмбеддингов.
Чем RAG отличается от дообучения модели на документации?
RAG ищет документы во время ответа и не меняет веса модели: обновили страницу — переиндексировали и всё. Дообучение меняет саму модель, стоит дороже и требует пересборки при каждой правке документа.
Нужно ли давать нейросети доступ к продакшен-базе?
Нет. Создайте отдельную роль только с SELECT и включите default_transaction_read_only, как в Шаге 2: тогда запись отклоняется на уровне транзакции, а не на уровне вашей внимательности.
Какой размер чанка выбрать?
Начните с 800 символов с перекрытием 100 и проверьте выдачу на 10 реальных вопросах. Перекрытие сохраняет смысл на границе кусков; если ответы обрываются на середине мысли — увеличьте перекрытие.
Что делать, если тест-менеджмент не имеет API?
Забирайте данные выгрузкой (CSV/JSON) по расписанию и индексируйте файл как ещё один источник. Конкретный способ выгрузки сверьте с документацией вашей системы — универсального метода нет.