# Пошаговое руководство: как подключить нейросеть к wiki, базе данных и тест-менеджменту для QA

> AI Release · @ai_release1 · https://ai-release.org/guides/poshagovoe-rukovodstvo-kak-podklyuchit-neyroset-k-wiki.html

## TL;DR

- Время выполнения: 90–120 минут
- Уровень сложности: Intermediate
- Понадобится: Linux (Ubuntu 22.04+) или Windows 11 + WSL2; Python 3.10+ и `pip`; клиент `psql`; `curl` и `jq`; Docker (опционально, для векторного хранилища); сервисный аккаунт в wiki с правами чтения; роль в базе только с `SELECT`; API-токен тест-менеджмента; API-ключ провайдера эмбеддингов и LLM; 2 ГБ свободного места

## Требования и подготовка
![Иллюстрация к разделу «Требования и подготовка»](https://ai-release.org/guides/img/poshagovoe-rukovodstvo-kak-podklyuchit-neyroset-k-wiki-ru-1.jpg)


- [ ] ОС и права: пользователь с правами на создание каталогов в `$HOME`, `sudo` для установки пакетов
- [ ] Python 3.10+ — `python3 --version`
- [ ] Клиент БД — `psql --version`
- [ ] `curl` и `jq` — `curl --version`, `jq --version`
- [ ] Сервисный аккаунт в wiki: логин/токен, права «чтение» на пространство, где лежит QA-документация
- [ ] Роль в БД только на чтение (создаётся в Шаге 2)
- [ ] API-токен тест-менеджмента (системы, где ведутся тест-кейсы и прогоны)
- [ ] API-ключ LLM-провайдера: отдельно для эмбеддингов (векторизация текста) и для генерации ответов
- [ ] 2 ГБ свободного места под индекс и логи

Проверка окружения одной командой:

```bash
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, порядок развёртывания описан в гайде [Как подключить ИИ к домашнему серверу](https://ai-release.org/guides/kak-podklyuchit-ii-k-domashnemu-serveru-poshagovoe-ruko.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

**Пример:** для облачного провайдера ключ выглядит как строка `sk-...` и кладётся в `.env`, а не в код — так он не попадёт в git.

## Шаг 1. Инвентаризация трёх источников и доступов

**Что сделать:** создать рабочий каталог, зафиксировать в одном файле `.env` адреса и токены трёх систем, проверить, что каждый токен реально отдаёт данные.

**Зачем:** 80% времени отладки RAG (RAG — когда нейросеть сначала ищет нужные документы, а потом отвечает на их основе) уходит на «а доступ-то есть?». Проверка на этом шаге отделяет проблему доступа от проблемы качества ответов.

**Как проверить:** каждый `curl` возвращает `200`, а `psql` — число таблиц больше нуля.

```bash
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`):

```bash
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` — типовой, сверьте с документацией вашей платформы):

```bash
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` — нет прав на пространство.

Проверка базы:

```bash
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`, подтверждает доступ.

## Шаг 2. Сборка индекса и подключение инструментов к нейросети
![Иллюстрация к разделу «Шаг 2. Сборка индекса и подключение инструментов к нейросети»](https://ai-release.org/guides/img/poshagovoe-rukovodstvo-kak-podklyuchit-neyroset-k-wiki-ru-2.jpg)


**Что сделать:** выгрузить тексты из трёх систем, нарезать их на чанки (чанк — кусок текста фиксированного размера, который ищется как единица), векторизовать, сохранить в хранилище и описать нейросети три инструмента: поиск по wiki, SQL-запрос к базе, запрос тест-кейса.

**Зачем:** нейросеть сама не имеет доступа к вашим системам — она умеет только вызывать описанные вами функции. Индекс нужен, чтобы в контекст попадали 3–5 релевантных кусков, а не вся wiki.

**Как проверить:** скрипт печатает число страниц и чанков, а тестовый поиск возвращает непустой список.

Сначала — безопасная роль в базе:

```sql
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`:

```python
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` сверьте с документацией провайдера: у части сервисов поля называются иначе.

Проверка нарезки:

```bash
python -c "from index import chunk; print(len(chunk('a'*2000)))"
```

Ожидается `3`: при `size=800` и `overlap=100` шаг равен 700, и 2000 символов дают три куска.

Описание инструментов для модели — файл `tools.json`:

```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;` — два вызова подряд, а не один общий вопрос к базе.

## Шаг 3. Сквозная проверка и приёмка результата
![Иллюстрация к разделу «Шаг 3. Сквозная проверка и приёмка результата»](https://ai-release.org/guides/img/poshagovoe-rukovodstvo-kak-podklyuchit-neyroset-k-wiki-ru-3.jpg)


**Что сделать:** задать системе реальный вопрос QA-инженера и убедиться, что ответ содержит ссылку на страницу wiki, результат SQL и ID тест-кейса.

**Зачем:** по отдельности все три коннектора могут работать, а вместе — нет: модель «забывает» вызвать инструмент или подставляет выдуманный ID.

**Как проверить:** в логах есть три вызова инструментов, в ответе — три источника; при отсутствии данных модель отвечает «в документации не найдено».

```bash
curl -sS http://localhost:8080/health | jq .
```

Ожидаемый вывод:

```json
{"status": "ok", "chunks": 1240}
```

Число `1240` — фактическое количество чанков в вашем индексе, оно зависит от объёма wiki.

Проверка поиска без модели:

```bash
curl -sS -X POST http://localhost:8080/search \
  -H "Content-Type: application/json" \
  -d '{"q":"флаки-тест на стейдже","k":5}' | jq '.results | length'
```

Ожидается `5`. Ноль означает, что индекс пуст или запрос не совпал с лексикой документов.

Чек-лист приёмки:

- [ ] `/health` возвращает `status: ok`
- [ ] Поиск по известной фразе из wiki даёт не менее 3 чанков
- [ ] На вопрос про тест-кейс модель вызывает `tms_get_case` и `db_query`
- [ ] В ответе есть ID страницы wiki и ID тест-кейса
- [ ] На вопрос вне базы знаний ответ — «данных нет», без домыслов
- [ ] `db_query` с `DROP TABLE` завершается ошибкой прав

Последний пункт проверяется руками:

```bash
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](https://ai-release.org/guides/kak-nastroit-monitoring-vps-cherez-telegram-bota-s-mini.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

## Возможные проблемы и решения

**Проблема. Wiki не отдаёт страницы, хотя логин верный**

- Ошибка: `{"statusCode": 401, "message": "Unauthorized"}`
- Причина: токен передан без префикса схемы либо истёк срок жизни сервисного аккаунта.
- Решение: проверьте заголовок и перевыпустите токен.

```bash
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 known`
- Причина: `DB_DSN` содержит имя сервиса из docker-compose, а скрипт запущен на хосте, вне docker-сети.
- Решение: замените хост на резолвящийся адрес и проверьте связь.

```bash
psql "postgresql://qa_ro:<пароль>@127.0.0.1:5432/qa" -c "select 1;"
```

Ответ `?column? = 1` означает, что дело было только в имени хоста.

**Проблема. Модель отвечает «не найдено», хотя страница в wiki есть**

- Ошибка: лог поиска `{"results": []}`
- Причина: страница лежит в другом пространстве, чем `WIKI_SPACE`, либо индекс собран до её последнего изменения.
- Решение: сверьте пространство, перезапустите индексацию и проверьте число чанков.

```bash
python index.py --space "$WIKI_SPACE" --rebuild && curl -sS http://localhost:8080/health | jq '.chunks'
```

Число `chunks` должно вырасти. Точные флаги CLI задаёт ваш скрипт — если `--rebuild` не поддерживается, удалите каталог индекса и запустите сборку заново.

## Резюме и следующие шаги

Три коннектора проверяются по отдельности до того, как вы дадите модели право их вызывать: сначала `200` от каждого API и роль `qa_ro` с `SELECT`, потом индекс, потом сквозной вопрос с тремя источниками в ответе. Дальше добавьте расписание переиндексации и тест на «вопрос вне базы знаний», чтобы ловить регрессии качества при смене модели эмбеддингов.

## FAQ

**Чем RAG отличается от дообучения модели на документации?**
RAG ищет документы во время ответа и не меняет веса модели: обновили страницу — переиндексировали и всё. Дообучение меняет саму модель, стоит дороже и требует пересборки при каждой правке документа.

**Нужно ли давать нейросети доступ к продакшен-базе?**
Нет. Создайте отдельную роль только с `SELECT` и включите `default_transaction_read_only`, как в Шаге 2: тогда запись отклоняется на уровне транзакции, а не на уровне вашей внимательности.

**Какой размер чанка выбрать?**
Начните с 800 символов с перекрытием 100 и проверьте выдачу на 10 реальных вопросах. Перекрытие сохраняет смысл на границе кусков; если ответы обрываются на середине мысли — увеличьте перекрытие.

**Что делать, если тест-менеджмент не имеет API?**
Забирайте данные выгрузкой (CSV/JSON) по расписанию и индексируйте файл как ещё один источник. Конкретный способ выгрузки сверьте с документацией вашей системы — универсального метода нет.
