# AI Email Assistant [![CI](https://img.shields.io/badge/CI-passing-brightgreen)](https://git.itqop.pw/itqop/sl-test/actions) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) Сервис для генерации персонализированных холодных писем с использованием RAG и LangGraph. ## О программе - Персонализация писем под роль, индустрию и компанию лида - RAG-подход с базой знаний реальных кейсов - LangGraph пайплайн обработки запросов - Векторный поиск в ChromaDB - Поддержка OpenAI и Google Gemini - RESTful API для интеграции с CRM ### Логика работы Система обрабатывает запросы через 10-этапный LangGraph пайплайн: 1. **Валидация входа** - проверка обязательных полей лида 2. **Извлечение признаков** - нормализация роли, индустрии из входных данных 3. **Построение запроса** - формирование поискового запроса на основе профиля лида 4. **Векторный поиск** - поиск релевантных кейсов в ChromaDB (top-30) 5. **Ранжирование контекста** - отбор лучших кейсов (top-6) и создание bullets 6. **Построение промпта** - формирование системного и пользовательского промптов 7. **LLM генерация** - создание письма через OpenAI/Gemini 8. **Парсинг ответа** - извлечение JSON с темой и телом письма 9. **Проверка качества** - валидация длины, структуры, наличия CTA 10. **Формирование результата** - финальная структура ответа с метаданными ## Промпт-инжиниринг: обоснование подхода ### Почему именно такая структура письма? Структура **Приветствие -> Хук -> Ценность -> Кейс -> CTA -> Подпись** выбрана на основе исследований конверсии холодных писем: **1. Приветствие по имени** - Персонализация создает впечатление индивидуального подхода - Снижает восприятие письма как спама **2. Хук-вопрос в первых строках** - Привлекает внимание и активирует любопытство - Касается специфики индустрии лида -> создает ощущение релевантности **3. Конкретная ценность с цифрами** - Мозг лучше воспринимает конкретные числа vs абстракции - "15 минут" vs "быстро", "95%" vs "значительно" - Снижает скептицизм -> повышает доверие **4. Социальное доказательство с кейсом** - Название реальной компании + численный результат - Психология: "если помогли похожей компании, помогут и нам" - Снижает воспринимаемый риск принятия решения **5. Мягкий CTA без давления** - "Если интересно" vs "Давайте встретимся завтра" - Оставляет ощущение выбора -> снижает сопротивление - 15-минутный формат -> низкий барьер входа **6. Эмоциональные триггеры** - "Спокойствие", "контроль" -> позитивные эмоции от решения проблем - "Избежите", "снизьте риски" -> страх потерь (loss aversion) - Комбинация мотивирует к действию ### Ограничения для максимальной эффективности - **≤ 1000 символов**: оптимальная длина письма для цифрового потребления. Согласно Nielsen Norman Group, пользователи читают в среднем 20–28% текста на веб-странице и быстро теряют внимание после первых 200–250 слов (~1000–1200 символов). Источник: [How Users Read on the Web](https://www.nngroup.com/articles/how-users-read-on-the-web/) - **Микро-абзацы 1-2 предложения**: улучшают восприятие текста на экранах смартфонов. Люди сканируют информацию взглядом, и короткие абзацы помогают быстрее вычленять ключевые мысли. Источник: [F-Shaped Pattern of Reading Web Content](https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content/) - **1 конкретный CTA**: письма с одним призывом к действию получают на **371 % больше кликов** и могут увеличить доход на **1617 %** по сравнению с письмами с несколькими CTA. Источник: [Unlayer - Call to Action in Emails](https://unlayer.com/blog/call-to-action-in-emails) ### Настройка промптов Системные промпты: `src/services/prompt_templates.py` - Тон: деловой, лаконичный, дружелюбный - Длина: до 1000 символов - Структура: Приветствие -> Хук -> Ценность -> Кейс -> CTA -> Подпись ## Быстрый старт ### 1. Подготовка окружения ```bash git clone https://git.itqop.pw/itqop/ai-email-assistant.git cd ai-email-assistant # Создание конфигурации cp .env.example .env # Или создайте .env файл сами ``` ### 2. Базовая конфигурация .env ```bash LLM_PROVIDER=openai OPENAI_API_KEY=your_openai_api_key_here API_SECRET_KEY=your_admin_token ``` ### 3. Запуск через Docker ```bash docker compose up --build ``` ### 4. Загрузка базы знаний Полоджите файлы/папку с md в data/, а после выполните запрос ```bash curl -X POST "http://localhost:8000/api/v1/admin/ingest" \ -H "Authorization: Bearer your_admin_token" ``` ### 5. Использование ```bash curl -X POST "http://localhost:8000/api/v1/generate_email" \ -H "Content-Type: application/json" \ -d '{ "contact": "Помящий Никита", "position": "Технический директор", "company_name": "FIVE", "segment": "маркетинговое агентство" }' ``` ## Альтернативный запуск (без Docker) ```bash # Создание виртуального окружения python -m venv .venv .venv\Scripts\activate # Windows source .venv/bin/activate # Linux/Mac # Установка зависимостей pip install -r requirements.txt # Загрузка данных python -m src.ingest.ingest_cli --data-dir articles_konsol_pro --recreate # Запуск сервиса python -m src.app.main ``` ## API документация ### Основные эндпоинты - `GET /healthz` - Проверка здоровья - `GET /readiness` - Готовность к работе - `GET /docs` - Swagger документация - `POST /api/v1/generate_email` - Генерация письма ### Формат запроса **Обязательные поля:** ```json { "contact": "string", "position": "string", "company_name": "string", "segment": "string" } ``` **Полный формат:** ```json { "contact": "Помящий Никита", "position": "Технический директор", "company_name": "FIVE", "segment": "маркетинговое агентство", "email": "nikita@five.agency", "locale": "ru", "notes": "Дополнительная информация" } ``` ### Формат ответа ```json { "subject": "Как упростить работу с самозанятыми в FIVE?", "body": "Здравствуйте, Никита!....", "meta": { "locale": "ru", "lead_normalized": { "contact_name": "Помящий Никита", "contact_first_name": "Никита", "contact_last_name": "Помящий", "role_title": "Технический директор", "role_category": "tech", "company_name": "FIVE", "industry_segment": "маркетинговое агентство", "industry_tag": "marketing_agency", "email": null, "locale": "ru", "notes": null }, "used_chunks": [ "sds_podkluchenie_k_konsoli#sds_podkluchenie_k_konsoli#c7", "vysotnik_rental#vysotnik_rental#c2" ], "model": "gpt-4o", "tokens_prompt": 1132, "tokens_completion": 289, "guardrails_violations": 0, "context_chunks_used": 5, "context_quality_score": 0.87 } } ``` **Структура meta:** - `locale` - Язык, на котором сгенерировано письмо (например, ru) - `lead_normalized` - Объект с нормализованной и обогащенной информацией о лиде: - `contact_first_name/contact_last_name` - Распознанные имя и фамилия - `role_category/industry_tag` - Машинно-читаемые теги для должности и сегмента компании (например, tech, marketing_agency) - `used_chunks` - Массив с ID фрагментов из базы знаний, которые были использованы для генерации ответа - `model` - Использованная LLM модель - `tokens_prompt/tokens_completion` - Расход токенов на запрос и ответ для мониторинга затрат - `guardrails_violations` - Количество нарушений правил безопасности (guardrails). 0 означает, что все проверки пройдены - `context_chunks_used` - Общее количество фрагментов контекста, которые были использованы для генерации - `context_quality_score` - Оценка релевантности найденного контекста (0-1): - 1.0 - отличное качество контекста - 0.8+ - хорошее качество (высокая релевантность) - 0.6+ - приемлемое качество (достаточная релевантность) - <0.6 - низкое качество (слабая релевантность) ## Конфигурация ### Переменные окружения | Параметр | Описание | По умолчанию | |----------|----------|--------------| | `LLM_PROVIDER` | openai или gemini | openai | | `LLM_MODEL` | Модель генерации | gpt-4o | | `EMBEDDING_MODEL` | Модель векторизации | text-embedding-3-large | | `TOP_K` | Чанков для поиска | 30 | | `TOP_N_CONTEXT` | Чанков в контексте | 6 | | `CHUNK_SIZE` | Размер чанка (токены) | 500 | | `CHUNK_OVERLAP` | Перекрытие чанков | 100 | ## Мониторинг ### Качество контекста Метрика `context_quality_score` вычисляется на основе: - **Средний similarity score** (40%) - релевантность найденных кейсов - **Доля высококачественных chunks** (25%) - процент кейсов с similarity > 0.7 - **Консистентность результатов** (20%) - равномерность качества chunks - **Фактор количества** (15%) - достаточность объема контекста **Интерпретация для мониторинга:** - `context_quality_score < 0.5` - критически низкое качество, требует обновления базы знаний - `context_quality_score < 0.7` - предупреждение о качестве контекста - `context_quality_score >= 0.8` - хорошее качество генерации ### Логи Все запросы логируются в JSON: ```json { "request_id": "1641234567890", "method": "POST", "url": "http://localhost:8000/api/v1/generate_email", "status_code": 200, "process_time": 2.341 } ``` ### Проверка состояния ```bash # Статус сервиса curl http://localhost:8000/healthz # Статистика базы знаний curl -H "Authorization: Bearer secret" \ http://localhost:8000/api/v1/admin/knowledge-base/stats # Docker логи docker-compose logs -f ai-email-assistant ``` ## Устранение ошибок ### Частые проблемы **404 No relevant knowledge found** ```bash # Загрузите базу знаний curl -X POST -H "Authorization: Bearer secret" \ "http://localhost:8000/api/v1/admin/ingest" ``` **502 External service error** - Проверьте API ключи LLM провайдера в .env **400 Validation failed** - Убедитесь что все обязательные поля заполнены ## Обновление базы знаний ### Добавление кейсов 1. Поместите `.md` файлы в `data` 2. Файлы должны содержать: - Заголовки для структуры - Числовые метрики - Ключевые слова индустрии - Упоминания ролей 3. Дополните базу (инкрементально): ```bash curl -X POST -H "Authorization: Bearer secret" \ "http://localhost:8000/api/v1/admin/ingest" ``` 4. (альтернативно) Пересоздайте базу: ```bash curl -X POST -H "Authorization: Bearer secret" \ "http://localhost:8000/api/v1/admin/ingest?recreate=true" ``` ### Формат кейсов ```markdown # Кейс компании FIVE Маркетинговое агентство автоматизировало работу с подрядчиками. ## Результаты - Онбординг: с 2 дней до 15 минут - Снижение ошибок: на 95% - Выплаты: мгновенные Технический директор Никита отмечает эффективность. ``` ## Тестирование ```bash # Все тесты pytest src/tests/ # Только API pytest src/tests/test_api.py ``` ### TODO: - Улучшить валидацию входных данных (почта, защита хака llm) - Увеличить покрытие кода тестами до 80%, доавить unit тесты для бизнес-логики - Сделать качественную докуентацию