Задача 1. Разбиение документа на чанки для RAG

Задача: Необходимо разработать сервис-чанкер для RAG-пайплайна. Текст документа перед эмбеддингом нужно разбить на чанки, которые укладываются в лимит токенов эмбеддер-модели. Реализуйте endpoint POST /chunk-document. Нужно написать FastAPI-приложение с одним endpoint, который принимает текст документа и лимит, а возвращает список чанков с количеством токенов в каждом.

Входные данные

{
  "text": "ИПР — индивидуальный план развития.\n\nОн содержит цели, сроки и показатели.\n\nВ конце — короткий итог.",
  "max_chunk_tokens": 12
}

Выходные данные

{
  "chunks": [
    {"index": 0, "text": "ИПР — индивидуальный план развития. Он содержит цели, сроки и показатели.", "tokens": 11},
    {"index": 1, "text": "В конце — короткий итог.", "tokens": 5}
  ],
  "total_chunks": 2
}

Правила алгоритма:

  1. Документ делится на абзацы — разделитель: пустая строка, т.е. \n\n (эквивалентно regex \n\s*\n — строка с одними пробелами/табами между абзацами тоже считается разделителем). Пример: "Первый абзац.\n\nВторой абзац." → абзацы "Первый абзац." и "Второй абзац.".
  2. Абзац, который целиком помещается в лимит, разрезать нельзя и он идёт в чанк целиком.
  3. Абзац длиннее лимита разбивается по словам — разделитель: любой пробельный символ (пробел, таб, перенос строки), т.е. \s+ (в Python — str.split() без аргументов). Пример: абзац "Один два\tтри\nчетыре" при лимите 2 → единицы "Один", "два", "три", "четыре".
  4. Чанки собираются от начала текста: в чанк добавляют следующую единицу (абзац или слово), пока она влезает в лимит (строгое «больше» начинает новый чанк), иначе начинают новый чанк.
  5. Порядок слов сохраняется; 100% текста документа должно попасть в чанки. Нормализация пробельных символов допустима: \t, \n, двойные и множественные пробелы схлопываются в один пробел. Пример: "a\t\tb\n c" → слова a, b, c.
  6. Пустой документ (после trim — только пробелы/переносы) → ошибка 400.
  7. max_chunk_tokens < 1 → ошибка 422 (валидация Pydantic).

Токенизация

Можно использовать tiktoken, либо для простоты len(text.split()). Главное — алгоритм.

Требования