Глава 1 Введение в разработку AI инструментов на языке R (ellmer, shinychat)
1.1 Описание урока
Добро пожаловать в первую главу курса. Мы начинаем путь в мир прикладного AI с позиции практиков. Сегодня большие языковые модели (LLM) перестали быть игрушкой в браузере и стали полноценным компонентом ИТ-инфраструктуры.
В этом уроке мы разберем, как аналитику на языке R «приручить» эти модели для решения ежедневных задач: от автоматического анализа логов на сервере до написания кода с учетом ваших внутренних библиотек. Мы не будем тратить время на теорию нейросетей — мы сразу перейдем к сборке работающего инструмента на базе пакетов ellmer и shinychat. Главная цель этой главы — показать, что современный AI-стек доступен любому R-разработчику бесплатно и без необходимости переходить на Python.
1.2 Видео
1.2.1 Тайм коды
- 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 — Добавляем в чат инструменты (Tool Calling)
-
28:17 — Создаём графический интерфейс с помощью
shinychat - 31:02 — Как дообучить модель своими данными (System Prompt)
- 33:50 — Заключение
1.4 Конспект
1.4.1 Как мы используем LLM в рабочих процессах
Львиная часть нашей работы заключается в разработке скриптов, которые получают данные из различных источников, обрабатывают их, и далее либо куда-то записывают, либо формируют из них сообщения или письма и рассылают. Все скрипты крутятся на сервере под Windows, и запускаются через планировщик задач. На данный момент в планировщике заданий настроено более 350 задач, запускающих разные скрипты.
Для мониторинга планировщика заданий был написан бот, который запускается раз в 10 минут, и проверяет статус последнего выполнения всех настроенных нами задач. О самом боте я уже рассказывал в первой главе учебника по разработке telegram ботов.
Недавно мы пошли дальше, и развернули на сервере Shiny приложение, которое запрашивает все данные по задачам из планировщика заданий Windows, и позволяет:
- Просматривать и фильтровать список настроенных задач
- Просматривать логи, т.е. Rout файлы запускаемых задачей скриптов
- Отправлять .Rout файл на анализ в LLM, и получать объяснение о возникновении ошибки при выполнении скрипта, и пошаговый план по устранению ошибки
- Просматривать листинг скрипта запускаемого задачей
- Отправить код на анализ в Gemini и получить объяснение того, что этот код делает
- Запускать задачу
- Активировать и деактивировать задачи в планировщике заданий
Так же в приложении есть и много другого функционала, среди которых есть чат основанный на LLM, который помогает генерировать код, и исправлять ошибки.
У нас достаточно много внутренних источников данных:
- Внутренняя самописная ERP/CRM система
- Внутренняя самописная HRM система
- Менеджер задач
- Система финансового учёта
- И ряд других внутренних источников данных.
Под работу с каждым из этих источников данных у нас написаны пакеты, которые по сути являются обёрткми в которые вшиты либо SQL запросы либо вызовы API. Так вот, чат помогающий нам писать код, анализирующий Rout файл и листинг кода, о котором я писал выше, дообучен документацией по работе к нашим пакетам, и он генерирует и анализирует код и логи с использованием подробной документации к нашим внутренним пакетам, чего не может делать ни один внешний LLM чат, ни ChatGPT, ни Claude, ни Gemini.
Всё это работает на базе Shiny, ellmer, shinychat и Gemini, абсолютно бесплатно. Далее я подробно расскажу о том, как всё это было разработано.
1.4.2 Генерация API ключа для работы с LLM
Для работы с API любой LLM модели вам надо сгенерировать API ключ. Практически все провайдеры сейчас убрали из планов бесплатный доступ к API, единственный провайдер, у которого я нашел бесплатный тарифы это Gemini.
Для генерации ключа просто зайдите в Google AI Studio, и нажмите кнопку Get API Key. В бесплатном тарифе вам доступно несколько моделей, среди которых очень неплохо себя показала Gemini 2.5 Flash.
Для удобства дальнейшей работы создайте переменную среды GOOGLE_API_KEY с полученным ключём.
Важное замечание по безопасности: В примерах этого урока мы будем использовать переменные среды для работы с ключами. Это удобно для обучения, но критически опасно, если вы решите опубликовать свой код. Чтобы узнать, как профессионально изолировать ключи от кода с помощью .Renviron или пакета keyring, обязательно посмотрите мой урок “Как правильно хранить секретные данные в R”.
1.4.3 Работа с LLM в R
Для взаимодействия с LLM моделями в R с недавних пор появился пакет ellmer, который предоставляет вам единый интерфейс к огромному количеству провайдеров LLM, моделей. Работа с ellmer начинается с создания объекта чата через один из конструкторов chat_*().
Под каждый провайдер есть свой конструктор, на данный момент ellmer поддерживает работу со следующими провайдерами LLM моделей:
- Anthropic’s Claude:
chat_anthropic(). - AWS Bedrock:
chat_bedrock(). - Azure OpenAI:
chat_azure(). - Databricks:
chat_databricks(). - DeepSeek:
chat_deepseek(). - GitHub model marketplace:
chat_github(). - Google Gemini:
chat_google_gemini(). - Groq:
chat_groq(). - Ollama:
chat_ollama(). - OpenAI:
chat_openai(). - OpenRouter:
chat_openrouter(). - perplexity.ai:
chat_perplexity(). - Snowflake Cortex:
chat_snowflake()иchat_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'
)Все функции-конструкторы чатов имеют общий набор основных аргументов:
-
model- Имя модели, которую вы хотите использовать. Каждый провайдер предлагает различные модели, и вы можете выбрать ту, которая лучше всего подходит для вашего случая использования. Например, у 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, который позволяет обрабатывать асинхронные результаты с течением времени. -
chat_structured()- Используется для извлечения структурированных данных из текста или изображений. Принимает схему, определяющую, как должны быть структурированы данные. Возвращает данные в R-представлении, например в виде списка или таблицы данных. -
register_tool()- Регистрирует внешние функции или “инструменты”, которые чат-бот может вызывать. Позволяет настроить чат для выполнения дополнительных действий в зависимости от контекста, таких как выполнение API-запросов или манипуляции с данными. -
token_usage()- Возвращает информацию об использовании токенов в текущей сессии. Это полезно для оптимизации затрат на использование модели, так как позволяет следить за количеством использованных и оставшихся токенов.
1.4.4 Как с помощью LLM извлекать структурированные данные из текста
Большинство LLM моделей обладают функцией извлечения структурированных данных их текста. С помощью чего вы можете:
- быстро обрабатывать полнотекстовые анкеты извлекая из них нужные данные
- определить настроение комментариев
- классифицировать статьи по тематикам
Да и в целом можно найти огромное количество вариантов применения этой функции. Реализуется она с помощью метода $chat_structured(), в который вам необходимо передать текст, из которого планируете извлечь структурированные данные, и описание структуры, которую ы хотите извлечь из текста.
## описание структуры
personal_data_str <- type_object(
age = type_integer('Возраст в годах, целое число'),
name = type_string('Имя'),
job = type_string('Занимаемая на работе должность')
)
## извлекаем информацию
text <- "
Привет, меня зовут Алексей, мне 40 лет, с 2016 года занимаю должность
руководителя отдела аналитики.
"
personal_data <- chat$chat_structured(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$chat_structured(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.
1.4.5 Добавляем в чат инструменты
Метод $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,
name = "get_current_time",
description = "Получить текущее время в указанном часовом поясе.",
arguments = list(
tz = type_string(
"Часовой пояс. По умолчанию `\"UTC\"`.",
required = FALSE
)
)
))
chat$chat('Какое текущее время сейчас по Киеву?')В этом примере мы написали функцию get_current_time(), которая получает текущее время по указанному часовому поясу. Далее мы добавили эту функцию в чат с помощью метода register_tool(), и функции tool(), которая позволяет вам дать описание того что передаваемая в чат функция делает, и описать каждый её аргумент.
1.4.6 Создаём Shiny интерфейс для чата
С функционалом пакета ellmer разобрались, теперь перейдём к тому, как упаковать чат в графический интерфейс. Для этого проще всего использовать пакет 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 приложения запустит интерфейс для вашего чата. Более подробно настройку и кастомизацию интерфейса чата мы будем разбирать в 5 главе Кастомизация интерфейса AI чата (пакет shinychat).
1.4.7 Как дообучить модель своими данными
Основным аргументом позволяющим вам дообучать модель собственными данными, например документацией к вашим пакетам, или просто описанием каких либо ваших рабочих процессов источников данных, или чего угодно, является 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 = interpolate_file(path = 'system_prompt.md')
)После чего ваш чат будет обучен на основе ваших данных, и будет помогать как в генерации кода, так и в исправлении ошибок при запуске созданных ранее скриптов.
1.4.8 Заключение
Мы заложили фундамент: наш AI-чат понимает документацию, умеет использовать инструменты R и возвращает данные в удобном нам формате. Но интерфейс Shiny требует открытого браузера и активной сессии.
В следующей главе мы сделаем этот интеллект мобильным. Мы возьмем наработки из этой главы и «упакуем» их в Telegram-бота. Это позволит вам получать анализ серверных ошибок или генерировать код прямо со смартфона, находясь в дороге или на совещании. Мы разберем, как подружить асинхронность ellmer с событийной моделью Telegram-бота.
1.5 Вопросы для самопроверки
В чем главное преимущество пакета ellmer перед прямым обращением к API конкретной модели?
Пакетellmerпредоставляет единый интерфейс (унифицированные функции и методы) для работы с десятками разных провайдеров (Gemini, Claude, OpenAI, Ollama). Это позволяет переключаться между моделями разных компаний, практически не меняя основной код вашего приложения.Какой метод объекта
Для этого используется методchatследует использовать, если вам нужно получить результат анализа текста в виде именованного списка R, а не просто строки?$chat_structured(). Он заставляет модель возвращать ответ в строго заданном формате (JSON под капотом), которыйellmerавтоматически преобразует в привычные объекты R (списки или векторы).Зачем нужен метод
Этот метод позволяет расширить возможности модели, предоставив ей доступ к вашим собственным функциям на языке R. С его помощью модель может получать актуальные данные из интернета, обращаться к базам данных или выполнять вычисления, которые ей недоступны «из коробки».$register_tool()?Как “обучить” модель специфике вашей компании, если вы не занимаетесь Fine-tuning (дообучением весов)?
Это делается через System Prompt. Вы можете передать в этот аргумент (в том числе загрузив из.mdфайла) подробную инструкцию, описание ваших бизнес-процессов и документацию к внутренним пакетам. Модель будет использовать этот контекст при каждом ответе.Какие три категории типов данных используются при извлечении структурированной информации методом
$chat_structured()?-
Скаляры (
type_string,type_integer,type_number,type_boolean,type_enum) — одиночные значения. -
Массивы (
type_array) — списки однотипных значений (аналог векторов). -
Объекты (
type_object) — наборы именованных полей, которые могут содержать в себе скаляры, массивы и другие объекты (аналог именованных списков).
-
Скаляры (