Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных

в 5:46, , рубрики: AI, chatgpt, llm, ml, python, пет-проект

AI-решения сейчас повсеместно, но всё ещё есть места, где их нет. Например в вашем пет-проекте (возможно).

В статье я поделюсь опытом, как легко интегрировать LLM и сразу получить от этого пользу, встраивая результаты от LLM в дальнейший пайплайн и закладывая фундамент под дальнейшее развитие.

Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 1

Небольшое авторское отступление: на текущий момент, к генеративному LLM отношусь настороженно: чат-боты в службе поддержке, сгенерированные статьи, вездесущие спамеры в комментариях каналов. Это всё вредит интернету и ухудшает отношение шум/сигнал.

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

Изначальная версия скрипта для канала @dogseeker была написана зимой 2019, когда я ещё жил в Москве и периодически посещал приют для собак "Красная сосна" на ВДНХ; там я и узнал, что есть проблема с мониторингом нескольких источников и что канал с репостами в Telegram мог бы с этим помочь.

Спустя пару вечеров, был написан простой скрипт с Crontab и sqlite, который брал данные из VK API выбранных групп и репостил на канал.

В таком состоянии, группа прожила до осени 2024: объявления репостились автоматически, а мы с женой принимали и публиковали объявления вручную через бота обратной связи.

Это было легко, но всё ещё отнимало ручное время. Тогда я решил немного заморочиться и сделать полноценную версию v1 пет-проекта с постингом через бота и небольшим бонусным функционалом в виде геокодинга через OpenStreetMaps-провайдера (Photon + Nominatim) для извлечения координат.

Бизнес-процесс стал выглядеть так:

Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 2

Спустя пару месяцев, у меня был отпуск по работе и за это время получилось:

  • Добавить новые источники из Telegram

Мессенджер отдаёт HTML на простой GET запрос: https://t.me/s/dogseeker, который можно легко распарсить каким-нибудь BeautifulSoup. Но есть минус — в телеграм каналах часто бывает много постов не по теме: реклама, просто флуд, денежные сборы. Мы же, хотим публиковать только те сообщения, которые относятся к потерянным/найденным/замеченным питомцам. Именно поэтому нужен следующий шаг.

  • Интегрировать LLM

Здесь всё прямолинейно: берём текст объявления, подставляем его в наш промпт и на выходе получаем JSON со структурой:

default_features = {
    "is_pet_related": 0.0,
    "raising_money": 0.0,
    "is_ad": 0.0,
    "is_sale": 0.0,
    "is_reward": 0.0,
    "lost_pet": 0.0,
    "found_pet": 0.0,
    "animal_type": None,
    "animal_breed": None,
    "pet_name": None,
    "location": None,
    "phone_number": None,
    "contacts": [],
    "processing_error": None,
}
Текст промпта

Analyze the following post and extract these features:

1. How much is this related to pets? (is_pet_related: float between 0 and 1)

2. How likely is this about raising money for a pet? (raising_money: float between 0 and 1)

3. How likely is this about a pet sale? (is_sale: float between 0 and 1)

4. How likely is this about a reward for finding a pet? (is_reward: float between 0 and 1)

5. How likely is this an advertisement or just a broad post about pets without specifiying one to promote some group? (is_ad: float between 0 and 1)

6. How likely is this about a lost pet? (lost_pet: float between 0 and 1)

7. How likely is this about finding or noticing a pet? (found_pet: float between 0 and 1)

8. What type of animal is mentioned? (animal_type: string) Keep the original language. Use Simple form of noun form in Russian, only one word. If only breed is mentioned, suggest animal type based on breed.

9. What type of dog or cat breed is mentioned? (animal_breed: string or null). Keep the original language. Use Simple form of noun form in Russian, keep it short (up to 2 words).

10. Is there any pet name mentioned? (pet_name: string or null). Keep the original language. Use Simple form.

11. Location mentioned in the post (location: string or null). Keep the original language. Just copy it.

12. Phone number (phone_number: string or null)

13. Other contacts, like vk or telegram links, usernames, names, etc. (contacts: array of strings or empty array)

Use values between 0 (not at all) and 1 (definitely) for the float fields.

Example: 0.8 means very likely, 0.3 means somewhat unlikely, etc.

Post text:

{text}

Return only the JSON object. Don't include any translations. Don't include tripple quotes.

  • Сделать фильтрацию / кластеризацию

Так, итоговая диаграмма стала выглядеть немного интереснее:

Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 3
Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 4

А что по цифрам и стоимости?

Пропорция количества токенов на вход/выход: ~5/1 (итоговый JSON был небольшим, больше токенов тратилось на вход из-за большого промпта).

Публикаций было чуть меньше 400k (телеграм на канале хранит только последние 50 тысяч 🥲).

Миллион токенов расходуется примерно на ~1700 постов, на обработку всех публикаций (400k) потребовалось бы около 235 миллионов токенов.

В процессе протестировал и сравнил разные нейросети:

  • YaGPT Lite / Gigachat Lite — неплохо, можно форсировать ответ в JSON, но миллион токенов стоит 160-200 рублей / ~50k рублей ушло бы за 5 лет за 400к постов.

    Вполне подойдёт для тех компаний, где необходимо производить обработку данных внутри страны в соответствии с законодательством.

  • OpenAI ChatGPT 4o mini — хороший результат, 0.15$/1M input, 0.6$/ output, за все посты вышло бы около 120$

  • Anthropic Claude 3.5 Haiku (аналог mini) — самые дорогие, 0.8$/1M input, 4$/1M output. За все посты вышло бы около 310$ (что всё ещё дешевле яндекса и сбера 🌚)

Опен-сорс модели:

  • LLama 3.1 70b Turbo — качество неплохое, 0.35$ за 1М токенов, за всё ушло бы ~9k рублей

  • LLama 3.1 8b — качество начинает дико проседать: сломанные JSON'ы, вкрапление латиницы с кириллицей (например вид животного мог записаться как ник из 2005-го: коtiк). Цена низкая — 0.04$/1M, что в сумме стоило бы чуть дешевле 1000 рублей за все посты. Вполне возможно, что дообученная модификация с русским языком была бы более надежной, но в нашем случае – результат был слишком грязным.

Примеры некачественных выводов
Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 5
Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 6

На самом деле, можно было бы ещё протестировать Deepseek V3 или прогнать локально отечественные языковые модели (например от Тинькофф), но я решил остановиться на следующем варианте.

Возвращение Google?

Два года назад, Google объявил внутри "красный код" — ChatGPT и прочие, угрожали их основному продукту: поиску. Я и сам начал чаще ходить в ChatGPT/Perplexity вместо Google, чтобы тратить меньше времени для получения нужной инфорации.

Потом Google анонсировали Gemini с невиданным ранее размером контекстного окна — 1M (!). Но качество оставляло желать лучшего: я тестил суммаризацию текста на старой версии и со своей задачей она не справилась, на бенчмарках они занимали второстепенные позиции. Да и в обществе шпыняли общий UX: создание приложений для интеграции с Google — задача далеко не на пару кликов.

Однако ребята с Google AI Studio подсуетились и сделали выпуск API токена максимально легким, буквально за минуту можно получить access_token и пример curl-реквеста

Цены на грани демпинга:

0.075$/1M input, 0.15$/1M output, (~21$ за 235 миллионов токенов с пропорцией 5/1).

Для проектов, где используются публичные данные и нет требования к обработке большого потока данных, доступна бесплатная версия API с Gemini 1.5 Pro и Flash (скоро должны появится и 2.0 версии).

Для Pro версии доступно только 50 запросов в день, 2 в минуту, а вот Flash — позволяет выполнить до 1500 запросов в день с ограничением 1М токенов и 15 запросов в минуту.

Flash-8B и Flash в бесплатной версии не отличаются по условиям, но по качеству разница видна на глаз

Кстати, в API можно включить ссылки на источники, но цена достаточно высокая: 35$ за 1к запросов

сравнение Google Flash 1.5: обычная версия против 8B

сравнение Google Flash 1.5: обычная версия против 8B
пример выдачи с ссылкой на источник

пример выдачи с ссылкой на источник

Конечным выбором стал бесплатный Google Flash 1.5: модель оценивает текст на ряд критериев + вытаскивает контакты, локацию, породу, вид животного; на выходе получаем JSON с результатом.

В постах (особенно после добавления новых источников), есть много спама, оффтопика и постов с финансовыми просьбами — эти посты мы хотим отсеивать, поскольку у нас направленная тематика: пропавшие/найденные животные. Посты без конкретной информации в тексте — тоже отсеиваем, пример в спойлере

Скрытый текст
Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 9

Для этого, мы берём JSON, раскладываем его по колонкам и обучаем классический ML алгоритм RandomForrest: достаточно проставить значение to_filter на нежелательных постах и прогнать fit/predict.

Точность получилась в районе 98% (1.5% false positive и 0.5% false negative). Инференс ML-модели на 1000 постах на VPS-сервере — выполняется за ~1 секунду, что совершенно нас устраивает. Ложные результаты иногда бывают из-за ошибочной оценки от LLM

Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 10

Далее нужно собирать большой объединённый датасет из разных источников и кластеризировать его, чтобы не выкладывать дубликаты одного объявления из вк/тг несколько раз.

За основу был взят код для канала-агрегатора новостей (nyan news), правда в моей имплементации нет анализа изображений (пока что), но учитываются контакты. Точность и производительность тоже приятно порадовали: скор тоже оказался ближе к 100%, VPS легко переварил эту задачу.

Опыт интеграции LLM и классического ML в пет-проект про поиск домашних животных - 11

В итоге: спам не публикуется, дубликаты не постятся повторно, а за счёт определения локации и вида/породы животного из текста, можно будет в дальнейшем сделать интерактивную карту с агрегированными объявлениями и обучить vision-модель для поиска по картинке животного (кстати, похожую задачу решала команда Selectel).

Но оставим это на потом :)

Сборник рандомных фактов, которые были получены в процесе:
  • JSON'ы приходится лишний раз верифицировать, с LLama 3.1 8b это супер-актуально. Strict-JSON output поддерживают не все провайдеры

  • Инференс классических ML-моделей — быстро даже на арендованном VPS (4 vcpu, 8 gb ram).

  • Но что касается LLM, не вижу смысла запускать их если там нет GPU

  • К условной PostgreSQL на вашей VPS проще подключаться через SSH-туннел (-L 5432:localhost:5432), не сразу вспомнил про это и поначалу шаманил с pg_hba.conf :)

  • Символ экранирования в тексте объявления может сломать весь пайплайн :) (сломалось декодирование JSON в PostgreSQL). Нужно это учитывать.

  • Ruff не дружит с SQLAlchemy, он требовал поменять условие (field == None) на (field is None). Не ведитесь и смело пишите # noqa, иначе запрос будет сломан.

  • Для деплоя был использован Coolify: когда перестали выкладываться новые посты, я просто посмотрел логи докер-контейнера в web-ui и сразу увидел причину проблемы. Достаточно удобно.

Автор: ivantgam

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js