Локальный ИИ-ассистент прямо в мессенджере 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.
