← Все гайды AI Release
EN Подписаться на @ai_release1

Как проектировать ИИ-систему: схема, компоненты и типичные ошибки

Обновлено: 04.11.2026 · AI Release · @ai_release1
Как проектировать ИИ-систему: схема, компоненты и типичные ошибки

TL;DR

Требования и подготовка

Иллюстрация к разделу «Требования и подготовка»
Иллюстрация к разделу «Требования и подготовка»
python -m venv .venv && source .venv/bin/activate
pip install fastapi uvicorn "psycopg[binary]" qdrant-client boto3 pyyaml
python -c "import fastapi, qdrant_client, yaml; print('deps ok')"

Ожидаемый вывод: deps ok. Если пакет не ставится — проверьте, что venv активирован (в приглашении появится (.venv)).

Шаг 1. Зафиксировать схему и границы системы

Что сделать: опишите систему как поток данных, а не как «одну модель». Минимальный набор блоков: входной API, обвязка (harness — слой, который знает про пользователя, права, лимиты и правила вызова модели), LLM, RAG-хранилища (S3 — файлы документов, PostgreSQL — метаданные, Qdrant — векторный индекс), интеграции с инструментами, мониторинг.

Зачем: запуск LLM на GPU — только первый шаг; модель сама по себе не знает пользователя, контекст и правила доступа, поэтому рабочий сервис — это модель плюс обвязка. По оценке из разбора про обвязку LLM, из 33 корпоративных ИИ-пилотов до продакшена доходят в среднем только четыре, то есть 88% остаются экспериментом — обычно именно из-за отсутствия спроектированной обвязки.

Зафиксируйте состав в файле system.yaml:

project: support-rag
components:
  api:        {type: fastapi, port: 8000}
  llm:        {type: vllm, endpoint: "http://127.0.0.1:8000/v1"}
  rag_store:  {type: qdrant, port: 6333, collection: docs}
  meta_store: {type: postgresql, port: 5432, db: rag}
  object_store: {type: s3, bucket: docs-raw}
  monitoring: {type: logs}
limits:
  max_input_tokens: 8000
  request_timeout_s: 60

Порты 8000/6333/5432 — стандартные для uvicorn, Qdrant и PostgreSQL в примерах конфигурации; если они заняты, замените значения и сверьтесь с документацией образов.

Пример: таблица компонентов, которую вы переносите в тикет или README.

КомпонентРольЧто потеряется без него
APIвходная точка, права, лимитылюбой пользователь может звать модель напрямую
RAG-хранилищадокументы, метаданные, индексответы «из головы» модели
Мониторинглоги запросов и версийтихие сбои не видны
Конфигсмена поведения без пересборкикаждая правка = релиз

Как проверить: распарсите файл и убедитесь, что все блоки на месте.

python -c "import yaml;d=yaml.safe_load(open('system.yaml'));print(sorted(d['components']))"

Ожидаемый вывод: ['api', 'index'... ] — ровно шесть ключей: api, llm, meta_store, monitoring, object_store, rag_store. Если ключей меньше — блок потерян на схеме, и его не будет в коде.

Структуру папок агента (где лежат промпты, инструменты, конфиги) удобно взять из готового шаблона: ICM на практике: структура папок, которая делает ИИ-агента управляемым.

Шаг 2. Собрать обвязку: API, RAG, лимиты, конфигурируемость

Иллюстрация к разделу «Шаг 2. Собрать обвязку: API, RAG, лимиты, конфигурируемость»
Иллюстрация к разделу «Шаг 2. Собрать обвязку: API, RAG, лимиты, конфигурируемость»

Что сделать: поднимите хранилища, затем напишите сервис на FastAPI, который принимает запрос, проверяет лимит, достаёт документы из Qdrant, подставляет их в промпт и логирует версию индекса.

Зачем: обвязка превращает голую модель в сервис: добавляет RAG с корпоративными данными, интеграции с инструментами, лимиты и мониторинг. Второй принцип — конфигурируемость: в проекте Android-SCADA универсальность достигнута за счёт JSON-конфигурации, для нового объекта не нужна пересборка приложения, достаточно заменить файл. В ИИ-системе работает то же правило: новое поведение — новый конфиг, а не новый релиз кода.

Пример: конфиг хранилищ и запуск.

# docker-compose.yml
services:
  qdrant:
    image: qdrant/qdrant
    ports: ["6333:6333"]
  postgres:
    image: postgres
    environment:
      POSTGRES_DB: rag
      POSTGRES_PASSWORD: <пароль>
    ports: ["5432:5432"]
docker compose up -d
curl -s http://127.0.0.1:6333/collections | jq .

Ожидаемый вывод: JSON с полем "result": {"collections": []} — Qdrant жив, коллекций пока нет. Пароль <пароль> замените своим и держите в .env, а не в git.

Минимальный сервис с проверкой лимита:

import os, time
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

MAX_INPUT_CHARS = int(os.getenv("MAX_INPUT_CHARS", "8000"))
app = FastAPI()

class Query(BaseModel):
    user_id: str
    text: str

@app.get("/health")
def health():
    return {"status": "ok"}

@app.post("/v1/answer")
def answer(q: Query):
    if len(q.text) > MAX_INPUT_CHARS:
        raise HTTPException(status_code=413, detail="input too long")
    t0 = time.time()
    # здесь: поиск в Qdrant -> сборка промпта -> вызов LLM
    return {"user_id": q.user_id, "latency_ms": int((time.time()-t0)*1000)}

Почему это работает: лимит отсекает запросы до вызова модели, поэтому стоимость и задержка предсказуемы; /health даёт мониторингу точку, по которой видно, что сервис поднялся.

Про выбор агентского инструмента для обвязки: в разборе Tiel-Coder-35B автор дробит задачи через plan.md и лёгкий агент Pi вместо Codex или Claude Code — сравнение вариантов есть в гайде Claude Code Desktop: установка, лимиты и чем отличается от терминала.

Как проверить:

uvicorn app:app --port 8000 &
curl -s http://127.0.0.1:8000/health
curl -s -X POST http://127.0.0.1:8000/v1/answer \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u1","text":"сколько хранятся документы?"}'

Ожидаемый вывод: {"status":"ok"} и {"user_id":"u1","latency_ms":<число>}. Если второй запрос вернул 413 — сработал лимит, это правильное поведение защиты.

Шаг 3. Проверить работу и согласованность версий

Иллюстрация к разделу «Шаг 3. Проверить работу и согласованность версий»
Иллюстрация к разделу «Шаг 3. Проверить работу и согласованность версий»

Что сделать: проверьте не только «отвечает ли сервис», но и согласованность набора: версии документов, метаданные, индекс, версии кода и моделей должны соответствовать друг другу. Единица восстановления в RAG-помощнике — именно согласованный набор, а не отдельная база.

Зачем: сбой бывает тихим: сервис жив и отвечает, но по старому индексу — «14 дней» вместо «30». Формально всё работает, фактически вы врёте пользователю.

Пример: регрессионный вопрос в чек-лист приёмки.

curl -s http://127.0.0.1:6333/collections/docs | jq '.result.points_count'
curl -s -X POST http://127.0.0.1:8000/v1/answer \
  -H "Content-Type: application/json" \
  -d '{"user_id":"test","text":"Сколько дней хранятся документы по политике v5?"}'

Ожидаемый вывод: число точек больше нуля и ответ, совпадающий с актуальной версией политики. Успех выглядит так: в ответе указан фрагмент с номером версии документа, а points_count меняется после переиндексации.

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

Проблема. Сервис отвечает, но по устаревшим данным

Проблема. Коллекция не создана

Проблема. Модель не влезает в память

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

Сначала фиксируется схема из шести блоков, затем собирается обвязка с лимитами и RAG, и только потом проверяется согласованность версий документов, индекса и модели. Следующий шаг — автоматизировать проверку версий в CI и настроить регулярные снапшоты Qdrant и PostgreSQL в одну точку времени.

FAQ

Чем JEV от TypeSafe AI отличается от обычной LLM?

JEV выбирает действие внутри кода, а не сочиняет текст: она не ведёт диалог и не пишет код, а относится к классу System One Models. Открытый аналог — Laya; в разборе на Хабре Laya и Gemma 4 запущены на своём сервере в сервисе на FastAPI.

Зачем бэкапить векторную базу, если исходные документы лежат в S3?

Потому что единица восстановления — согласованный набор: версии документов, метаданные, индекс, версии кода и моделей. Без индекса сервис поднимется, но будет отвечать по устаревшим данным.

Сколько корпоративных ИИ-пилотов доходит до продакшена?

По приведённой в разборе оценке — в среднем четыре из 33, то есть 88% остаются экспериментом. Причина обычно не в модели, а в отсутствии обвязки.

Можно ли обойтись без RAG?

Да, если нужные данные целиком помещаются в контекст: у Tiel-Coder-35B-A3B-MTP контекст 65 535 токенов, и длинные задачи в разборе дробятся через plan.md. Если корпус больше — нужен поиск по индексу, иначе ответы будут без опоры на документы.

🤖 Кратко для ИИ