GPT вооруженный плагинами
GPT вооруженный плагинами

Я сделал небольшой клиент для Wolfram Language, который умеет вызывать OpenAI API и другие API, которые на него похожи. Сам активно пользуюсь им и хочу рассказать о том, как легко создать ассистента на основе OpenAI API и добавить в него свои собственные плагины.

Зачем я это делаю?

Во-первых, я не так часто вижу на Хабре утилитарные статьи, где рассказывается о том, как использовать нейросеть с примерами кода. И особенно мало таких статей, где речь идет про конкретные плагины. Чаще всего мы видим здесь рекламу, ссылочки на телеграм и восхваление очередных достижений нейросете-строения.

Во-вторых, у Wolfram Language есть фантастически крутой блокнотный пользовательский интерфейс. Речь конечно же про Mathematica и про наш родной отечественный WLJS Notebook. Формат интерактивного блокнота как нельзя лучше подходит для работы с чат-ботами, LLM и нейросетями.

В-третьих, в пакете AILink есть киллер-фича WL из коробки, которая доступна всем пользователям Wolfram Language - это Cloud Evaluate. С его помощью вам не потребуется VPN для обхода блокировки по региону со стороны OpenAI. То есть AILink в Wolfram Language работает в РФ без использования прокси!

В-четвертых, я как фанат Wolfram Language просто в очередной раз хочу про него рассказать.

Подготовка

Будем считать, что среда выполнения когда уже установлена, но если нет, то всегда можно получить бесплатное ядро Wolfram Engine с регистрацией, но без смс. Там очень простые инструкции, нужно всего-то зарегистрировать учетную запись и нажать кнопку "получить лицензию".

Установить ядро на Windows можно так:

winget install WolframEngine

На MacOS так:

brew cask install wolfram-engine

На Linux можно только скачать файл-установщик по ссылке, но зато можно в одну строчку получить образ для docker:

docker pull wolframresearch/wolframengine

После выполнения шагов выше и получения лицензии создадим где-нибудь директорию LLMBot, а в ней новый файл llmbot.nb:

Структура файлов проекта
Структура файлов проекта

Затем откроем файл при помощи Mathematica и приступим к написанию кода.

Импорт зависимостей

Первое, что мы добавим в наш скрипт - это установка и импорт зависимостей. Нам понадобится несколько пакетов из Paclet Repository:

(*function like apt update*)
Map[PacletSiteUpdate] @ PacletSites[]; 


(*install paclets*)
PacletInstall["KirillBelov/Internal"];
PacletInstall["KirillBelov/Objects"];
PacletInstall["KirillBelov/AILink"];


(*paclets importing*)
Get["KirillBelov`AILink`"];

После чего пользователю станут доступны функции из пакета AILink:

  • AIModels[] - список моделей OpenAI

  • AIImageGenerate["prompt"] - создает картинку в DALL-E

  • AISpeech["text"] - превращает текст в аудио в TTS

  • AITranscription[audio] - преобразует звук в текст в Whisper

  • AIChatObject[<>] - представление объекта чата в языке

  • AIChatComplete[chat, message] - дополнение чата в GPT

  • AIChatCompleteAync[chat, message, callback] - асинхронное дополнение

Для того чтобы ими всеми пользоваться необходимо задать ключ для доступа к API. Сделать это можно выполнив команду:

(*set api key*)
SystemCredential["OPENAI_API_KEY"] = "your openai api key"; 

Готово! Проверить что все работает можно добавим в скрипт вот такую строчку:

(*print models*)
AIModels[]
Доступные модели
Доступные модели

Если все примерно как на скриншоте - то можно продолжать. Следующим шагом проверим, что работают другие функции:

Текст в голос

Можно передать в качестве аргументов скрипта текстовую строку и OpenAI модель TTS создаст для него аудио файл:

hiAudio = AISpeech["Привет!"]
Export["hi.mp3", hiAudio]
Файл со звуком
Файл со звуком

В качестве опциональных аргументов функция AISpeech[] принимает имя голоса. с помощью которого нужно озвучить текст. Доступные голоса можно посмотреть в документации OpenAI. Вот как будут отличаться разные голоса:

hiAlloy = AISpeech["Привет Хабр!", "Voice" -> "alloy"]
hiNova = AISpeech["Привет Хабр!", "Voice" -> "nova"]

AudioPlot[{hiAlloy, hiNova}]
То что звуки отличаются видно "невооруженным глазом"
То что звуки отличаются видно "невооруженным глазом"

Голос в текст

Все тоже самое можно проделать и в обратную сторону:

AITranscription[hiAlloy]
AITranscription[hiNova]
Да, сама OpenAI озвучила слово Хабр так, что не смогла в итоге восстановить. Остается порадоваться что не "хабробер"
Да, сама OpenAI озвучила слово Хабр так, что не смогла в итоге восстановить.
Остается порадоваться что не "хабробер"

Генерация изображений

Последней доп-функцией в обзоре будет генерация изображений. Все очень просто:

AIImageGenerate["wolf and ram in the village"]
Для наглядности в ответе "свернуты" полное описание а адрес
Для наглядности в ответе "свернуты" полное описание а адрес

Чат с LLM

Теперь перейдем к главной части статьи! Чат бот! Чтобы создать пустой объект чата используем функцию:

chat = AIChatObject[]
Представление изменяемого объекта чата в Mathematica
Представление изменяемого объекта чата в Mathematica

Отправим первое сообщение в OpenAI. После выполнения запроса оно сохранится в чат и чат же вернется как результат выполнения функции:

AIChatComplete[chat, "Привет!"]; 

chat["Messages"]
В чате пока два сообщения
В чате пока два сообщения

Ну и дальше вызывая функцию AIChatComplete можно продолжать общаться с LLM. Но это слишком просто!

Плагины-функции

У функции AIChatComplete есть несколько важных опций:

  • "Model" - позволяет указать конкретную модель. По умолчанию "gpt-4o"

  • "Temperature" - позволяет указать температуру - не все модели ее поддерживают

  • "Tools" - позволяет использовать функции-плагины, опять же не все модели их поддерживают

Можно передать как в функцию дополнения так и в качестве параметров конструктора чата:

chat = AIChatObject["Tools" -> {tool1, tool2}]; 
AIChatComplete[chat, "Model" -> "gpt-4o"]; 

С выбором модели все еще более менее понятно.

  • "gpt-3.5-turbo" - быстрая и не самая умная

  • "gpt-4o" - умнее и медленнее

  • "o1-preview" - умеет рассуждать и тулы не поддерживает

Но что передавать в качестве tool1, tool2, ...? Это должны быть функции! Функции, которые создаются с некоторыми ограничениями, но очень простыми:

  1. Они возвращают в качестве ответа строку

  2. Они имеют описание в usage

  3. Типы аргументов явно указаны и могут быть String, Real или Integer

Создадим очень простую такую функцию:

time::usage = "time[] returns current time in string format."; 

time[] := DateString[]
Теперь LLM всегда знает точное время
Теперь LLM всегда знает точное время

Создадим еще функцию с параметрами. Например получение текущей температуры в указанном населенном пункте. Для того чтобы узнать температуру я буду использовать WeatherAPI.

В процессе написания статьи я зарегистрировался там и посмотрел как выполняется запрос в документации. На Wolfram Language это будет вот так:

SystemCredential["WEATHERAPI_KEY"] = "<api key>";

wheather::usage = "wheather[lat, lon] returns info about current wheathe for specific geo coordinates. Result is JSON object with coordinates, text description, temperature, wind speed and etc.";

wheather[lat_Real, lon_Real] := 
 Module[{
   endpoint = "http://api.weatherapi.com/v1/current.json",
   apiKey = SystemCredential["WEATHERAPI_KEY"],  
   request, response
   }, 
  request = URLBuild[endpoint, {
     "q" -> StringTemplate["``,``"][lat, lon], 
     "key" -> apiKey
     }]; 
  
  response = URLRead[request]; 
  response["Body"]
]

Плюс еще одна функция мне потребуется для определения координат населенного пункта по названию. Ее я сделаю с использованием Wolfram Alpha:

geoPosition::usage = "geoPosition[city] geo position of the specific city. city parameter - name of the city only in English."; 

geoPosition[city_String] := 
 StringTemplate["lat = ``, lon = ``"] @@ 
  First @ WolframAlpha[city <> " GeoPosition", "WolframResult"]
Две новых функции-плагина
Две новых функции-плагина

Теперь я просто добавлю эти функции в список функций LLM и посмотрю что получится:

Теперь LLM может узнать погоду в почти любом городе!
Теперь LLM может узнать погоду в почти любом городе!

Чтобы информация поместилась в окне блокнота я многое удалил, но если коротко. то произошло следующее:

  1. Пользователь спросил у ГПТ текущую погоду в Саратове

  2. ГПТ вернул сообщение, где попросил вызвать функцию geoPosition["Saratov"]

  3. Функция отправила в ГПТ координаты

  4. ГПТ снова вернул вызов функции и параметры - теперь weather[lat, lon]

  5. Функция отправила в ГПТ информацию о погоде

  6. ГПТ вернул пользователю отформатированный читаемый текст.

  7. Да, пока я пишу эту статью в Саратове и правда около 6-7 градусов и моросит дождь - вот он на фото ниже

    На самом деле это Энгельс, но облака на горизонте как раз на Саратовом
    На самом деле это Энгельс, но облака на горизонте как раз на Саратовом

Чат-бот знает теперь три функции, с помощью которых он может получить доступ к внешнему миру. Но они довольно узконаправленные: время. координаты и погода. Но что если дать боту более общий инструмент для общения с миром? Например, поиск в интернете? На самом деле сделать это довольно легко! Пусть с первого раза реализация будет не лучшей и не оптимальной, но я потратил на это буквально несколько минут. Сначала я вспомнил, что DuckDuckGo невозбранно дает использовать свой поиск, а потом посмотрел какие там есть варианты и нашел что там есть поиск lite. И вот как выглядит инструмент поиска в сети:

duckDuckGoSearch::usage = "duckDuckGoSearch[query] call DuckDuckGo search engine. .."; 

duckDuckGoSearch[query_String] := 
  Module[{url = "https://lite.duckduckgo.com/lite/", request, 
    response, responseBody}, 
   request = HTTPRequest[url, 
     <|
      Method -> "POST", 
      "Body" -> {
        "q" -> query
        }, 
      "ContentType" -> "application/x-www-form-urlencoded"
      |>
     ]; 
   response = URLRead[request]; 	
   responseBody = response["Body"];
   ImportString[ExportString[responseBody, "String"], "HTML"] <> 
    "\nhttps://duckduckgo.com?" <> URLQueryEncode[{"q" -> query}]
]; 
Код поиска в сети. Возвращается текст, пусть не самый релевантный
Код поиска в сети. Возвращается текст, пусть не самый релевантный

Ну и опять попробуем что получится:

AIChatComplete[
   "Try to search in the web what the last Donald Trump speech", 
   "Tools" -> {time, geoPosition, wheather, duckDuckGoSearch}, 
   "Model" -> "gpt-4o"
]["Messages"][[-1, "content"]]
Примерно эти заголовки поисковой выдачи и прочитал LLM
Примерно эти заголовки поисковой выдачи и прочитал LLM

Хм... там есть ссылки. А что есть просто взять и научить читать LLM страницы по ссылкам! Это очень просто и лежит на поверхности. Просто добавим urlRead!

urlRead::usage = "urlRead[url] read text from html page with address == url. Use this function when you need to get data from the specific address or site in the internet.  result is a imple text."; 

urlRead[url_String] := ImportString[URLRead[url]["Body"], "HTML"]; 
Повторяем вопрос про Дональда Трампа
Повторяем вопрос про Дональда Трампа

А теперь попросим LLM прочитать подробнее что написано в статье по первой ссылке!

Да, это вполне реальная статья: https://time.com/7026339/donald-trump-speech-app-comment-public-reaction-kamala-harris-campaign/, а не сгенерированный URL, который просто похож на настоящий.

В принципе, продолжать добавлять плагины можно еще очень долго и можно реализовать самые смелые и интересные идеи. Кроме показанных примеров я так же делал для себя:

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

  • Составление кратного описание большого файла чтобы иметь "каталог"

  • Сделать свой CoPilot

  • Дать доступ к ядру и возможность создавать и редактировать ячейки блокнота

  • Выполнять код

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

  • Добавить в групповой чат и дать доступ ко всем сообщениям и поиску по ним

  • и т.д.

Вывод

То, что сделано на коленке в рамках статьи - я очень часто вижу в виде товара, но нечасто в виде реализации. Самые разные инфлюэнсеры, криптовалютные эксперты и тарологи рассказывают о новой невероятной нейросети с самыми крутыми возможностями - главное купить подписку именно на их телеграм. Может быть я не там смотрю и нужно идти не в магазин, а на завод. Но в любом случае я хотел поделиться своими лучшими практиками по программному использованию OpenAI API и созданию плагинов для него. Всем спасибо за внимание!

Комментарии (3)


  1. 7313
    18.10.2024 06:04

    Интересно и решил попробовать, но обломился на этапе загрузки математики... Она сейчас платная или как ее вообще ставить-то? Все попытки добавить математику в аккаунт заканчиваются предложением ввести ключ активации, а в доступных приложениях Wolfram Engine Basic и все.


    1. KirillBelovTest Автор
      18.10.2024 06:04

      Wolfram Engine - бесплатный с особой лицензией. Для Wolfram Engine наша команда (из двух человек) делает альтернативный бесплатный блокнотный интерфейс - можете посмотреть последнюю статью с обзором: https://habr.com/ru/articles/839140/

      Mathematica - всегда была платная с самой первой версии 36 лет назад. Чтобы попробовать можно взять триал на две недели, а если не распробовали - сделать новый аккаунт и повторить. В крайнем случае спиратить, если вам совесть позволяет, так как, если у вас, например, карта банка РФ вы наверное не сможете ее купить ;-)

      Если у вас еще есть вопросы и желание попробовать напишите мне в личку - я могу подробно описать шаги решения проблем!


    1. KirillBelovTest Автор
      18.10.2024 06:04

      Вот кстати как в WLJS все тоже самое выглядит