Как стать автором
Обновить

Как мы внедрили LLM в рабочие процессы аналитиков на R — и сделали это бесплатно

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров463

В этой статье расскажу, как мы в команде аналитиков начали использовать большие языковые модели (LLM) в повседневных рабочих инструментах. Всё построено на R, и покажу на практике, как можно внедрить такие фичи у себя — без платных подписок, без OpenAI и без строчки кода на Python.

Это не просто обзор, а реальный кейс — как мы встроили LLM в наше Shiny-приложение, которое помогает управлять задачами на сервере. Расскажу и покажу:

  • Как бесплатно получить API-ключ от Gemini;

  • Как с помощью пакета ellmer собрать свой AI-чат;

  • Как научить чат доставать структурированные данные из текста;

  • Как подключить внешние API к чату;

  • Как собрать для всего этого интерфейс на shinychat;

  • И как загрузить в модель свои данные, чтобы она давала более точные и контекстные ответы.

Всё, что покажу — реально работает и используется у нас в проде.

А если ты не любишь читать — специально для тебя я записал подробный видеоурок. Смотри, повторяй, внедряй.

Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.

Содержание

  1. Видео

  2. Как мы используем LLM в рабочих процессах

  3. Генерация API ключа для работы с LLM

  4. Работа с LLM в R

  5. Как с помощью LLM извлекать структурированные данные из текста

  6. Добавляем в чат инструменты

  7. Создаём Shiny интерфейс для чата

  8. Как дообучить модель своими данными

  9. Заключение

Видео

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

Тайм коды:

  • 00:00 О чём это видео

  • 01:05 Кейс практического внедрения AI в рабочие процессы

  • 06:49 Введение в пакет ellmer

  • 08:20 Как бесплатно сгенерировать API ключ для работы с Gemini API

  • 09:05 Аргументы конструктора LLM чатов

  • 11:07 Создаём объект chat

  • 13:01 Методы объекта chat

  • 14:30 Отправляем запрос в LLM

  • 17:03 Извлечение структурированных данных из текста

  • 24:15 Добавляем в чат инструменты, работа со сторонними API

  • 28:17 Создаём графический интерфейс для нашего чата с помощью shinychat

  • 31:02 Как дообучить модель своими данными

  • 33:50 Заключение

Как мы используем LLM в рабочих процессах

Львиная часть нашей работы заключается в разработке скриптов, которые получают данные из различных источников, обрабатывают их, и далее либо куда-то записывают, либо формируют из них сообщения или письма и рассылают. Все скрипты крутятся на сервере под Windows, и запускаются через планировщик задач. На данный момент в планировщике заданий настроено более 350 задач, запускающих разные скрипты.

Для мониторинга планировщика заданий был написан бот, который запускается раз в 10 минут, и проверяет статус последнего выполнения всех настроенных нами задач. О самом боте я уже рассказывал в публикации Пишем telegram бота на языке R (часть 1): Создаём бота, и отправляем с его помощью сообщения в telegram.

Недавно мы пошли дальше, и развернули на сервере Shiny приложение, которое запрашивает все данные по задачам из планировщика заданий Windows, и позволяет:

  • Просматривать и фильтровать список настроенных задач

  • Просматривать логи, т.е. Rout файлы запускаемых задачей скриптов

  • Отправлять .Rout файл на анализ в LLM, и получать объяснение о возникновении ошибки при выполнении скрипта, и пошаговый план по устранению ошибки

  • Просматривать листинг скрипта запускаемого задачей

  • Отправить код на анализ в Gemini и получить объяснение того, что этот код делает

  • Запускать задачу

Так же в приложении есть и много другого функционала, среди которых есть чат основанный на LLM, который помогает генерировать код, и исправлять ошибки.

У нас достаточно много внутренних источников данных:

  • Внутренняя самописная ERP/CRM система

  • Внутренняя самописная HRM система

  • Менеджер задач

  • Система финансового учёта

  • И ряд других внутренних источников данных.

Под работу с каждым из этих источников данных у нас написаны пакеты, которые по сути являются обёрткми в которые вшиты либо SQL запросы либо вызовы API. Так вот, чат помогающий нам писать код, анализирующий Rout файл и листинг кода, о котором я писал выше, дообучен документацией по работе к нашим пакетам, и он генерирует и анализирует код и логи с использованием подробной документации к нашим внутренним пакетам, чего не может делать ни один внешний LLM чат, ни ChatGPT, ни Claude, ни Gemini.

Всё это работает на базе Shiny, ellmer, shinychat и Gemini, абсолютно бесплатно. Далее я подробно расскажу о том, как всё это было разработано.

Генерация API ключа для работы с LLM

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

Для генерации ключа просто зайдите в Google AI Studio, и нажмите кнопку Get API Key. В бесплатном тарифе вам доступно несколько моделей, среди которых очень неплохо себя показала Gemini 2.5 Flash.

Для удобства дальнейшей работы создайте переменную среды GOOGLE_API_KEY с полученным ключём.

Работа с LLM в R

Для взаимодействия с LLM моделями в R с недавних пор появился пакет ellmer, который предоставляет вам единый интерфейс к огромному количеству провайдеров LLM, моделей. Работа с ellmer начинается с создания объекта чата через один из конструкторов chat_*().

Под каждый провайдер есть свой конструктор, на данный момент ellmer поддерживает работу со следующими провайдерами LLM моделей:

Установим пакет ellmer, и создадим объект chat и отправим свой первый запрос:

pak::pak('ellmer')
library(ellmer)

# API ключ
Sys.setenv(GOOGLE_API_KEY = 'ВАШ API ТОКЕН')

chat <- chat_gemini(
  system_prompt = 
    'Ты специалист по анализу данных, и разработчик на языке R. 
     В этом чате ты помогаешь генерировать код на языке R.'
  )

out <- chat$chat(
  'Напиши мне функцию, которая по заданному 
   городу запрашивает текущу погоду из бесплатного API', 
  echo = 'none'
  )

Все функции-конструкторы чатов имеют общий набор основных аргументов:

  • mode -l Имя модели, которую вы хотите использовать. Каждый провайдер предлагает различные модели, и вы можете выбрать ту, которая лучше всего подходит для вашего случая использования. Например, у OpenAI есть модели GPT-4o, gpt-4o-mini и др.

  • system_prompt - Строка, описывающая роль или поведение чата. Это начальная подсказка, которая задает тон и стиль взаимодействия, например: «You are a friendly assistant.»

  • api_args - Список дополнительных аргументов, которые могут быть переданы API. Это может включать настройки специфичные для конкретного провайдера, например, температуру для генерации текста.

  • echo - Управление выводом результата, принимает одно из значений: none, text, all

Объект chat построен на базе R6 классов, которые являются реализацией классического ООП в R, chat имеет следующие методы:

  • chat() - Этот метод используется для отправки запроса к LLM и получения ответа в виде строки.

  • stream() - Позволяет обрабатывать потоковые данные в реальном времени. Этот метод возвращает генератор coro, который позволяет обрабатывать ответ по мере его поступления. Это удобно для различных случаев использования, таких как запись данных в файл или отправка ответа в интерфейс Shiny.

  • chat_async() - Асинхронная версия метода chat(). Возвращает promise, который разрешает результаты, когда ответ получен. Полезен для одновременного запуска нескольких сеансов общения или в контексте Shiny, чтобы не блокировать интерфейс.

  • stream_async() - Асинхронная версия метода stream(). Она возвращает async generator, который позволяет обрабатывать асинхронные результаты с течением времени.

  • extract_data() - Используется для извлечения структурированных данных из текста или изображений. Принимает схему, определяющую, как должны быть структурированы данные. Возвращает данные в R-представлении, например в виде списка или таблицы данных.

  • register_tool() - Регистрирует внешние функции или "инструменты", которые чат-бот может вызывать. Позволяет настроить чат для выполнения дополнительных действий в зависимости от контекста, таких как выполнение API-запросов или манипуляции с данными.

  • token_usage() - Возвращает информацию об использовании токенов в текущей сессии. Это полезно для оптимизации затрат на использование модели, так как позволяет следить за количеством использованных и оставшихся токенов.

Как с помощью LLM извлекать структурированные данные из текста

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

  • быстро обрабатывать полнотекстовые анкеты извлекая из них нужные данные

  • определить настроение комментариев

  • классифицировать статьи по тематикам

Да и в целом можно найти огромное количество вариантов применения этой функции. Реализуется она с помощью метода extract_data(), в который вам необходимо передать текст, из которого планируете извлечь структурированные данные, и описание структуры, которую ы хотите извлечь из текста.

## описание структуры
personal_data_str <- type_object(
  age  = type_integer('Возраст в годах, целое число'),
  name = type_string('Имя'),
  job  = type_string('Занимаемая на работе должность')
)

## извлекаем информацию
text <- "
Привет, меня зовут Алексей, мне 40 лет, с 2016 года занимаю должность 
руководителя отдела аналитики.
"
personal_data <- chat$extract_data(text, type = personal_data_str)

# классификация настроения комментария
text <- "
Купленный товар работает отлично, к нему никаких притензий нет, 
но обслуживание клиентов было ужасным. 
Я, вероятно, больше не буду у них покупать.
"
type_sentiment <- type_object(
  "Извлеки оценки настроений заданного текста. Сумма оценок настроений должна быть равна 1.",
  positive_score = type_number("Положительная оценка, число от 0.0 до 1.0."),
  negative_score = type_number("Отрицаетльная оценка, число от 0.0 до 1.0."),
  neutral_score = type_number("Нейтральная оценка, число от 0.0 до 1.0.")
)

str(chat$extract_data(text, type = type_sentiment))

Т.е. изначально вам необходимо создать объект описания структуры с помощью функции type_object(), внутри которого вы описываете каждый отдельный извлекаемый элемент структуры с помощью других функций семейства type_*(), все типы данных можно условно поделить на 3 категории:

  • Скаляры представляют собой отдельные значения, которые бывают пяти типов: type_boolean(), type_integer(), type_number(), type_string()и type_enum(), представляющие собой отдельные логические, целочисленные, двойные, строковые и факторные значения соответственно.

  • Массивы представляют любое количество значений одного типа и создаются с помощью type_array(). Вы всегда должны указывать itemаргумент, который определяет тип каждого отдельного элемента. Массивы скаляров очень похожи на атомарные векторы R.

  • Объекты представляют собой набор именованных значений и создаются с помощью type_object(). Объекты могут содержать любое количество скаляров, массивов и других объектов. Они похожи на именованные списки в R.

Добавляем в чат инструменты

Метод register_tool() позволяет вам встраивать в ваш чат дополнительные инструменты, например интеграцию с любыми другими API, или в целом описать любой другой инструментарий посредствам создания обычных R функций, которые вы добавите в чат. Например, ниодна LLM модель не может получить данные в реальном времени, она не знает даже текущего времени, не может дать вам информацию о текущей погоде в каком либо городе. Но вы можете обучить этому свой чат, добавив в него функции:

chat <- chat_gemini()

chat$chat('Какое текущее время сейчас по Киеву?')

#' Gets the current time in the given time zone.
#'
#' @param tz The time zone to get the current time in.
#' @return The current time in the given time zone.
get_current_time <- function(tz = "UTC") {
  format(Sys.time(), tz = tz, usetz = TRUE)
}

chat$register_tool(tool(
  get_current_time,
  "Получить текущее время в указанном часовом поясе.",
  tz = type_string(
    "Часовой пояс. По умолчанию `\"UTC\"`.",
    required = FALSE
  )
))

chat$chat('Какое текущее время сейчас по Киеву?')

В этом примере мы написали функцию get_current_time(), которая получает текущее время по указанному часовому поясу. Далее мы добавили эту функцию в чат с помощью метода register_tool(), и функции tool(), которая позволяет вам дать описание того что передаваемая в чат функция делает, и описать каждый её аргумент.

Создаём Shiny интерфейс для чата

С функционалом пакета ellmer разобрались, теперь перейдём к тому, как упаковать чат в графический интерфейс. Для этого проoе всего использовать пакет shinychat.

library(shiny)
library(shinychat)

ui <- bslib::page_fluid(
  chat_ui("chat")
)

server <- function(input, output, session) {
  chat <- ellmer::chat_gemini(
    system_prompt = "Ты специалист по разработке кода и анализу данных на языке R"
    )
  
  observeEvent(input$chat_user_input, {
    stream <- chat$stream_async(input$chat_user_input)
    chat_append("chat", stream)
  })
}

shinyApp(ui, server)

Представленный выше код Shiny приложения запустит интерфейс для вашего чата.

Как дообучить модель своими данными

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

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

  • какие внутренние источники данных у нас есть

  • определение того какой пакет предназначен для работы с каким источником

  • описание всех функций, всех аргументов, и всех возвращаемых каждой функцией пакета полей

Т.е. мой md файл выглядит примерно так:

В этом чате ты выполняешь несколько фукнций:

1. Анализируешь логи выполнения скриптом читая Rout файлы, даёшь объяснения ошибки и пошаговый план её исправления
2. Помогаешь генерировать код на основе внутренних корпоративных пакетов

# Внутренние источники данных
Тут базово описаны внутренние истоники данных

# Соответвие пакета и источника данных
Прописываем какой пакет предназначен для работы с каждым из источников данных

# Документация к пакетам
Ниже приведена подробная документация к корпоративным пакетам, используй эту документацию для генерации кода и анализа Rout файлов.

## Название пакета
Работает с источником 1

## Функции пакета

* название фукнции 1 - описание
  * аргументы
    * аргумент 1 - описание
    * аргумент 2 - описание
  * поля которые возвращает функция
    * поле 1 - описание
    * поле 2 - описание
* название фукнции 2 - описание
  * аргументы
    * аргумент 1 - описание
    * аргумент 2 - описание
  * поля которые возвращает функция
    * поле 1 - описание
    * поле 2 - описание

Далее вы передаёте этот .md файл в аргумент system_prompt:

chat <- ellmer::chat_gemini(
    system_prompt = paste(readLines(here::here('system_prompt.md')), collapse = "\n")
)

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

Заключение

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

Главный вывод: сегодня любой аналитик или разработчик на R может собрать собственное LLM-решение под свои задачи — быстро, бесплатно и без необходимости учить Python. Всё, что нужно — правильный подход и желание улучшать свои процессы.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+1
Комментарии2

Публикации

Работа

Data Scientist
41 вакансия

Ближайшие события