В этой статье расскажу, как мы в команде аналитиков начали использовать большие языковые модели (LLM) в повседневных рабочих инструментах. Всё построено на R, и покажу на практике, как можно внедрить такие фичи у себя — без платных подписок, без OpenAI и без строчки кода на Python.
Это не просто обзор, а реальный кейс — как мы встроили LLM в наше Shiny-приложение, которое помогает управлять задачами на сервере. Расскажу и покажу:
Как бесплатно получить API-ключ от Gemini;
Как с помощью пакета
ellmer
собрать свой AI-чат;Как научить чат доставать структурированные данные из текста;
Как подключить внешние API к чату;
Как собрать для всего этого интерфейс на
shinychat
;И как загрузить в модель свои данные, чтобы она давала более точные и контекстные ответы.
Всё, что покажу — реально работает и используется у нас в проде.
А если ты не любишь читать — специально для тебя я записал подробный видеоурок. Смотри, повторяй, внедряй.
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Содержание
Видео
Для тех кто не особо люит читать - ниже видео урок с подробным объяснением всего, о чём я далее буду рассказывать в этой статье.
Тайм коды:
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 моделей:
Anthropic’s Claude:
chat_claude()
.AWS Bedrock:
chat_bedrock()
.Azure OpenAI:
chat_azure()
.Databricks:
chat_databricks()
.DeepSeek:
chat_deepseek()
.GitHub model marketplace:
chat_github()
.Google Gemini:
chat_gemini()
.Groq:
chat_groq()
.Ollama:
chat_ollama()
.OpenAI:
chat_openai()
.OpenRouter:
chat_openrouter()
.Snowflake Cortex:
chat_snowflake()
andchat_cortex_analyst()
.VLLM:
chat_vllm()
.
Установим пакет 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. Всё, что нужно — правильный подход и желание улучшать свои процессы.