Локальный ИИ-ассистент прямо в мессенджере HalChat: без серверов, без рисков, только приватность и RAG.
Я занимаюсь разработкой собственного мессенджера HalChat и недавно я запустил систему локальных ИИ, с которыми можно общаться прямо в браузере через обычный чат.
И мне потребовался ИИ помощник, который бы вводил в курс дела внутри экосистемы и помогал пользователям обосноваться. Назвал я его — Halwashka AI.
Почему бы не использовать готовые решения?
Во первых, это приватность, главный принцип HalChat — это сохранность ваших данных, значит ваши сообщения не должны расшифровываться, и передаваться ИИ.
Во вторых, хотелось бы продемонстрировать пользователям сами локальные ИИ, чтобы могли начать пользоваться и другими локальными ИИ, выбрав их на HalNetMarket.
Проблема
Сейчас существует огромное количество разных ИИ моделей, но все они обучены на многосторонних данных. Переобучать их не имеет смысла, из-за огромных погрешностей и возможность повредить модель, нужный результат таким образом очень сложно получить. Один из главных вариантов решения, я выбрал:
RAG (Retrieval-Augmented Generation) — это метод, который объединяет два этапа:
Retrieval (поиск): ИИ ищет релевантные данные из внешней базы (например, документы, файлы, базы знаний).
Generation (генерация): ИИ использует найденные данные для формирования ответа, не обучаясь на них.
Архитектура

Принцип работы
Этап 0:
Пользователь заходит в чат. Плагин загружается и загружает ИИ модель (скачивает, либо из кэша). Сообщение от бота показывает процент загрузки. Когда модель загрузится он изменит сообщение на модель загружена.
global_msg_id=-1 is_load=false ... #download model def on_load() { is_load=true if(global_msg_id==-1) { return false } HalChat.editMessage(global_msg_id, "Модель успешно загружена"); } def on_progress_load(pr) { if(global_msg_id==-1) { return false } HalChat.editLocalMessage(global_msg_id, "Загрузка: "+pr+"%"); } ... #loadModel def loadModel() { HalChat.sendMessage("Модель загружается, подождите...", [], "", "", -1, -1, '{"LocalBotMsg":{ "nickname":"Halwashka AI", "icon":"f7hXkUfGJGseWFpao4DvLzBPrGH6PU6IXejt6ExPS717assk2oTCtLmfaJfT9py0ZrDQHK5x1rajN5wiIm65gG1EI7Acnhuh1AUz" },"LocalLLM":"ignore"}') } ... LocalLLM.addEvent("load", on_load) LocalLLM.addEvent("progressload",on_progress_load) loadModel()
Также загружаем нейросеть Xenova/all-mpnet-base-v2, для преобразования текста в эмбеддинг (векторное представление), чтобы мы могли сравнивать и находить более схожие данные по тем какое просит пользователь.
this.embedder = await pipeline('feature-extraction', 'Xenova/all-mpnet-base-v2');
Этап 1:
Пользователь отправляет сообщение "Привет! Что такое HalChat?". Оно передаётся к HalSM плагину. После, плагин передаёт запрос с промптом и списком файлов для RAG и др. параметрами в LocalLLMHalSM.js
model="https://haldrive.halwarsing.net/file/WH6H9uXDHpxtnXJQahHpO8J1LrrwByN7EAkLYT8LV8S0wrmmTnP91fRB8dD5v23rcezKj25oGNhxTF81u4cab8AmaL2oF2kOCTi" files=[] files.append("DgqvzZyhJfNNIxt7Wx6XlGdAywbtNAj2D2cdvUDf2MudnLzQjwJqEzrcMZDb06ZR5Itli3Y6TSS65iFe8ekSWvKpYM4Ow9IezFks") ... #on send message user def on_send_message(msgId,type,time,text,fromNickname,fromId,fromIcon,chatUid,attachments, pluginData) { if(!is_load) { return false } HalChat.sendMessage("Генерация...", [], "", "", -1, -1, '{"LocalBotMsg":{ "nickname":"Halwashka AI", "icon":"f7hXkUfGJGseWFpao4DvLzBPrGH6PU6IXejt6ExPS717assk2oTCtLmfaJfT9py0ZrDQHK5x1rajN5wiIm65gG1EI7Acnhuh1AUz" }}') LocalLLM.run(system_prompt, text, 0.5, 40, 0.9, 1000, 1, files) } ... def on_local_sended_message(msgId, pluginData) { global_msg_id=msgId if(!is_load) { LocalLLM.load(model,4096,256) } } HalChat.addEvent("onUserSendMessage", on_send_message) HalChat.addEvent("onLocalBotSendedMessage",on_local_sended_message) LocalLLM.addEvent("generate", on_generate) LocalLLM.addEvent("generate_stream", on_generate_stream) ...
Этап 2:
LocalLLMHalSM.js преобразовывает HalSM переменные в javascript (temp, top_k, system_prompt и т.д.).
Загружает RAG хранилище по предоставленным ссылкам (скачивает с HalDrive) и преобразует все данные в эмбеддинги.
const docs = []; for (const file of files) { const resj = await fetch(`https://haldrive.halwarsing.net/file/`+file+`?isJson=1`,{ method: 'GET', mode:'cors', credentials: 'include', }); if (!resj.ok) { throw new Error(`Ошибка загрузки модели: HTTP ${resj.status}`); } const jsonData=await resj.json(); if(jsonData['errorCode']!==0) { throw new Error(`Ошибка загрузки модели: HD ${jsonData.errorCode}:${jsonData.error}`); } const response=await fetch(jsonData['url'],{ method:'GET', credentials:'omit' }); const jdata = await response.json(); if("data" in jdata) { const l=jdata.data.length; for(var i=0;i<l;i++) { const text=jdata.data[i]; docs.push({ id: file+";"+i, text }); } } } for (const doc of docs) { const embedding = await this.embedText(doc.text); this.embeddings.push({ ...doc, embedding }); }
После также, преобразует запрос пользователя в эмбеддинг, и начинает поиск по схожести среди всех данных и выбирает топ-3 подходящих эмбеддинга. Превращает выбранные данные в пользовательское сообщение, как контекст и добавляет его в список сообщений.
const queryEmbedding = await this.embedText(query); const similarities = this.embeddings.map((doc) => { const dot = queryEmbedding.reduce((sum, val, i) => sum + val * doc.embedding[i], 0); const norm1 = Math.sqrt(queryEmbedding.reduce((sum, val) => sum + val * val, 0)); const norm2 = Math.sqrt(doc.embedding.reduce((sum, val) => sum + val * val, 0)); return { id: doc.id, text: doc.text, similarity: dot / (norm1 * norm2) }; }); return similarities .sort((a, b) => b.similarity - a.similarity) .filter(doc => doc.similarity > 0.6) // порог .slice(0, topK);
Этап 3:
LocalLLMHalSM.js создаёт потоковый чат с LLM моделью с историей сообщений и указанными параметрами. На каждый загруженный токен срабатывает эвент и плагин обновляет локально сообщение.
for await (const ch of LocalLLMHalSM.localLLM.generateChatStream(messages_ai, { maxNewTokens: max_new_tokens, stop: [], temperature: temperature, top_k: top_k, top_p: top_p })) { const t=ch.text.replace("—","-"); LocalLLMHalSM.runEvent("generate_stream", [t]); lastCh=t; }
Этап 4:
ИИ закончил и срабатывает итоговое событие и плагин обновляет сообщения на сервере.
#generation def on_generate(text) { HalChat.editMessage(global_msg_id, text) } def on_generate_stream(text) { HalChat.editLocalMessage(global_msg_id, text) if(text=="[вызов техподдержки]"||text=="[Вызов техподдержки]") { HalChat.inviteUser(47) } } ... LocalLLM.addEvent("generate", on_generate) LocalLLM.addEvent("generate_stream", on_generate_stream)
Практическое применение
Общение с ИИ помощником и проводником по экосистеме HalNet.
Базовая техподдержка и возможность вызвать ручную техподдержку.
Недостатки
Самый главный минус — это производительность, пока что на данный момент генерация одного ответа занимает от 2-х до 4-х минут, и это почти не зависит от мощности вашего устройства, ведь он уходит в один поток.
Мало знаний — это маленькая модель, так что она не может позволить себе поддерживать полноценный информативный диалог и давать советы, вне знаний RAG хранилища.
Перспективы
Улучшение производительности, за счёт многопоточности и перехода на Web-LLM.
Интеграция в экосистему, и автоматизация чатов, и анализ чатов для пользователя.
Полноценный помощник с возможностью живого общения.
Реализация
Я добавил новый файл HalChatRAG.js, где будет находиться система RAG. Сделана она на базе библиотеке transformers.js и модели Xenova/all-mpnet-base-v2.
Также обновил код LocalLLMHalSM.js для взаимодействия с RAG. А именно добавил отображение RAG как сообщение от пользователя, чтобы он его воспринимал как контекст.
И написал ИИ-плагин на HalSM, где уже прописан промпт и взаимодействие с пользователем, и ещё вызов техподдержки, если он не знает ответа, или пользователь сам запросил, в таком с��учае он приглашает в чат меня. Для этого пользователю появляется окошко с предложением пригласить пользователя (для безопасности и согласованности действия).
Модель
Библиотеки:
https://github.com/ngxson/wllama
https://huggingface.co/docs/transformers.js/en/index
Код

Демо
Создаём чат с ИИ, дожидаемся загрузки модели, и пишем наши запросы. Получаем ответы)

Итог
У меня получилось реализовать — локального ИИ помощника, с загрузкой дополнительной информации без переобучения модели. Это создаёт потенциал и для поиска в интернете благодаря RAG системе.
Жду ваших вопросов связанной с этой статьёй, так и про мою экосистему и язык программирования HalSM.
