Почему ваш следующий AI-ассистент должен жить в вашем компьютере
Представьте себе: вы спрашиваете ассистента "Что я говорил тебе про проект в прошлый вторник?", и он не просто вспоминает - он открывает ваши заметки, находит конкретный файл и предлагает продолжить с того места, где остановились. Все это работает без интернета, без подписок, без отправки ваших данных кому-либо. Звучит как фантастика? Это ATOM.
ATOM - это не просто очередная обертка вокруг API. Это полностью локальная система, которая умеет запоминать все ваши разговоры, управлять устройствами в доме, искать информацию в интернете (через приватный поисковик) и даже работать с файлами. И самое безумное - все это умещается на скромной GTX 1650 с её 4 ГБ видеопамяти.
Почему GTX 1650? Потому что если мы сделаем это на слабой карте, то на любой другой системе оно просто взлетит. Это как собрать гоночный двигатель в Жигулях - если заработает здесь, то на Ferrari будет просто космос.
Архитектура: что внутри этого монстра
ATOM строится по принципу "мозг + инструменты". Мозг - это языковая модель, которая понимает, что вы хотите. Инструменты - это всё, что она может сделать по вашей команде. Вот как это выглядит:
| Компонент | Что делает | Почему именно это | VRAM (ГБ) |
|---|---|---|---|
| Qwen3-VL-4B | Основная LLM | Поддерживает инструменты, малый размер | 2.5-3.0 |
| ChromaDB | Долговременная память | Встраивается в память, быстрая | 0.5 |
| SearXNG | Поиск в интернете | Приватный, агрегирует источники | 0.2 |
| Home Assistant API | Управление устройствами | Универсальный протокол | 0.1 |
| Итого | 3.3-3.8 |
Видите магию? Всё умещается в 4 ГБ с запасом. Теперь давайте разбираться, как собрать эту конструкцию.
1 Подготовка: что нужно установить до начала
Перед тем как начать, убедитесь, что у вас есть:
- NVIDIA драйверы версии 535 или новее (для CUDA 12)
- Python 3.10 или 3.11 (3.12 может вызвать проблемы)
- Git (очевидно, но проверьте)
- Минимум 8 ГБ оперативной памяти
- 20 ГБ свободного места на диске
2 Устанавливаем LM Studio - наш командный центр
LM Studio - это не просто запускалка моделей. Это швейцарский нож для локальных LLM. Скачиваем с официального сайта и устанавливаем.
# Для Linux (если хотите из терминала):
wget https://releases.lmstudio.ai/linux/latest/lmstudio_x86_64.AppImage
chmod +x lmstudio_x86_64.AppImage
./lmstudio_x86_64.AppImage
После запуска идем в раздел "Search" и ищем "Qwen3-VL-4B". Нас интересует GGUF версия с квантованием Q4_K_M - это оптимальный баланс качества и размера.
Не совершайте эту ошибку: Не берите версии с квантованием Q8 или выше для GTX 1650. Они займут всю память и система будет постоянно свапаться на диск. Q4_K_M - ваш максимум.
3 Настраиваем ChromaDB для долговременной памяти
Вот где начинается магия. Обычные LLM забывают всё после перезагрузки. ChromaDB решает эту проблему.
# Создаем виртуальное окружение
python -m venv atom_env
source atom_env/bin/activate # для Linux/Mac
# или atom_env\Scripts\activate для Windows
# Устанавливаем ChromaDB и зависимости
pip install chromadb sentence-transformers
Теперь создаем простой скрипт для работы с памятью:
import chromadb
from sentence_transformers import SentenceTransformer
class ATOMMemory:
def __init__(self):
# Используем легкую модель для эмбеддингов
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
self.client = chromadb.PersistentClient(path="./atom_memory")
try:
self.collection = self.client.get_collection("conversations")
except:
self.collection = self.client.create_collection(
name="conversations",
embedding_function=self._embed_fn
)
def _embed_fn(self, texts):
return self.embedder.encode(texts).tolist()
def remember(self, conversation_id, text, metadata=None):
"""Сохраняем фрагмент разговора"""
self.collection.add(
documents=[text],
metadatas=[metadata or {}],
ids=[f"{conversation_id}_{len(self.collection.get()['ids'])}"]
)
def recall(self, query, n_results=5):
"""Вспоминаем похожие разговоры"""
results = self.collection.query(
query_texts=[query],
n_results=n_results
)
return results['documents'][0]
# Пример использования
memory = ATOMMemory()
memory.remember("project_meeting", "Мы договорились использовать Redis для кэширования")
related = memory.recall("Что мы решили про кэширование?")
print(related)
Теперь ваш ассистент будет помнить всё, что вы ему сказали. Даже через неделю.
4 Интегрируем инструменты: от поиска до управления светом
Вот самый интересный момент. Мы учим LLM пользоваться инструментами. Создаем файл tools.py:
import requests
import json
import subprocess
from typing import Dict, Any
class ATOMTools:
def __init__(self):
self.searxng_url = "http://localhost:8080" # локальный SearXNG
self.ha_url = "http://homeassistant.local:8123"
self.ha_token = "your_long_lived_token"
def search_web(self, query: str) -> str:
"""Поиск в интернете через SearXNG"""
try:
response = requests.get(
f"{self.searxng_url}/search",
params={
"q": query,
"format": "json",
"language": "ru"
}
)
results = response.json()
# Берем первые 3 результата
summary = "\n".join([
f"{r.get('title', '')}: {r.get('content', '')[:200]}"
for r in results.get('results', [])[:3]
])
return summary
except:
return "Не удалось выполнить поиск"
def control_light(self, entity_id: str, action: str) -> str:
"""Управление светом через Home Assistant"""
headers = {
"Authorization": f"Bearer {self.ha_token}",
"Content-Type": "application/json"
}
service = "turn_on" if action == "on" else "turn_off"
response = requests.post(
f"{self.ha_url}/api/services/light/{service}",
headers=headers,
json={"entity_id": entity_id}
)
if response.status_code == 200:
return f"Свет {entity_id} включен" if action == "on" else f"Свет {entity_id} выключен"
else:
return f"Ошибка: {response.text}"
def run_command(self, command: str) -> str:
"""Выполнение команд в системе"""
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=10
)
return result.stdout if result.stdout else result.stderr
except subprocess.TimeoutExpired:
return "Команда выполнена слишком долго"
except Exception as e:
return f"Ошибка: {str(e)}"
def get_tool_description(self) -> str:
"""Описание инструментов для LLM"""
return """
Доступные инструменты:
1. search_web(query): Ищет информацию в интернете
2. control_light(entity_id, action): Управляет светом (on/off)
3. run_command(command): Выполняет команду в терминале
Пример использования:
Пользователь: "Найди последние новости про ИИ"
Ответ: Использую search_web("последние новости искусственный интеллект 2024")
"""
5 Собираем всё вместе: оркестратор
Теперь нужен мозг, который будет решать, какой инструмент использовать. Создаем orchestrator.py:
import requests
import json
from tools import ATOMTools
from memory import ATOMMemory
class ATOMOrchestrator:
def __init__(self, lmstudio_url="http://localhost:1234"):
self.lmstudio_url = lmstudio_url
self.tools = ATOMTools()
self.memory = ATOMMemory()
self.conversation_id = "default"
def detect_tool_use(self, user_input: str) -> tuple:
"""Определяем, нужно ли использовать инструмент"""
tool_keywords = {
"search_web": ["найди", "поищи", "что такое", "кто такой"],
"control_light": ["включи свет", "выключи свет", "свет в комнате"],
"run_command": ["выполни команду", "запусти", "открой файл"]
}
for tool, keywords in tool_keywords.items():
for keyword in keywords:
if keyword in user_input.lower():
return tool, user_input
return None, user_input
def execute_tool(self, tool_name: str, user_input: str) -> str:
"""Выполняем инструмент"""
if tool_name == "search_web":
# Извлекаем запрос из вопроса
query = user_input.replace("найди", "").replace("поищи", "").strip()
return self.tools.search_web(query)
elif tool_name == "control_light":
if "включи" in user_input:
entity = "light.living_room" # пример
return self.tools.control_light(entity, "on")
else:
entity = "light.living_room"
return self.tools.control_light(entity, "off")
elif tool_name == "run_command":
# Безопасное извлечение команды
if "выполни команду" in user_input:
cmd = user_input.split("выполни команду")[1].strip()
return self.tools.run_command(cmd)
return "Не понял команду"
return "Инструмент не найден"
def chat(self, user_input: str) -> str:
"""Основной цикл чата"""
# Сохраняем в память
self.memory.remember(self.conversation_id, user_input)
# Проверяем контекст из памяти
context = self.memory.recall(user_input)
full_context = f"Контекст из памяти: {context}\nВопрос: {user_input}"
# Определяем нужен ли инструмент
tool_name, processed_input = self.detect_tool_use(user_input)
if tool_name:
# Используем инструмент
tool_result = self.execute_tool(tool_name, processed_input)
# Формируем промпт с результатом инструмента
prompt = f"""
Пользователь спросил: {user_input}
Я использовал инструмент {tool_name} и получил результат:
{tool_result}
Сформулируй краткий ответ пользователю на основе этого результата.
"""
else:
# Обычный ответ
prompt = full_context
# Отправляем в LM Studio
response = requests.post(
f"{self.lmstudio_url}/v1/chat/completions",
json={
"model": "Qwen3-VL-4B",
"messages": [
{"role": "system", "content": "Ты полезный ассистент ATOM с доступом к инструментам."},
{"role": "user", "content": prompt}
],
"max_tokens": 500,
"temperature": 0.7
}
)
result = response.json()
answer = result["choices"][0]["message"]["content"]
# Сохраняем ответ в память
self.memory.remember(self.conversation_id, answer, {"role": "assistant"})
return answer
# Запускаем
if __name__ == "__main__":
atom = ATOMOrchestrator()
print("ATOM готов к работе. Введите 'выход' для завершения.")
while True:
user_input = input("Вы: ")
if user_input.lower() == "выход":
break
response = atom.chat(user_input)
print(f"ATOM: {response}")
Оптимизация для GTX 1650: где выжать последние капли производительности
4 ГБ VRAM - это мало. Но мы можем сделать эту малость работать эффективно.
Настройки LM Studio для максимальной производительности:
{
"model": "Qwen3-VL-4B-Q4_K_M.gguf",
"context_length": 2048, // Не больше! Иначе не влезет
"gpu_layers": 20, // Максимум для этой модели
"batch_size": 512, // Уменьшаем если есть проблемы
"threads": 4, // По количеству ядер CPU
"mmap": true, // Используем mmap для экономии RAM
"mlock": false, // Не блокируем всю память
"n_gpu_layers": 20, // Столько слоев на GPU сколько возможно
"main_gpu": 0,
"tensor_split": [1.0], // Вся модель на одну карту
"verbose": false
}
Что делать если не хватает памяти:
- Уменьшаем контекст с 4096 до 2048 или даже 1024 токенов
- Используем более агрессивное квантование - Q3_K_S вместо Q4_K_M
- Выгружаем часть модели в RAM через уменьшение gpu_layers
- Закрываем все лишние приложения особенно браузер с 20 вкладками
--no-sandbox на Linux или от имени администратора на Windows. Иногда это помогает обойти ограничения системы.
3D интерфейс на React Three Fiber: зачем это нужно
Да, можно сделать простой веб-интерфейс. Но мы же не ищем легких путей? 3D визуализация работы инструментов - это не просто красиво. Это наглядно показывает, что происходит внутри системы.
Когда ассистент ищет информацию в интернете, вы видите как "лучи" идут к разным источникам. Когда он управляет светом - видите как меняется освещение в 3D модели вашей комнаты. Это помогает понять, что система действительно работает, а не просто генерирует текст.
// Пример компонента визуализации поиска
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Text, Line } from '@react-three/drei';
function SearchVisualization({ query, results }) {
return (
);
}
Частые проблемы и их решения
1. "Out of memory" при запуске модели
Проблема: LM Studio падает с ошибкой нехватки памяти.
Решение: Запускаем с параметром --ngl 18 вместо 20. Если не помогает - --ngl 15. Каждый слой на GPU экономит ~200 МБ.
2. ChromaDB медленно работает
Проблема: Поиск по памяти занимает секунды.
Решение: Используем более легкую модель для эмбеддингов. Вместо all-MiniLM-L6-v2 (80 МБ) пробуем all-MiniLM-L4-v2 (40 МБ).
3. SearXNG не возвращает результаты
Проблема: Поиск работает, но результаты пустые.
Решение: В конфиге SearXNG (/etc/searxng/settings.yml) увеличиваем timeout: 10.0 до timeout: 30.0. Некоторые поисковые движки медленные.
4. Home Assistant не отвечает
Проблема: Ассистент не может управлять устройствами.
Решение: Проверяем:
- Работает ли Home Assistant (
http://homeassistant.local:8123) - Правильный ли токен (должен быть "Long-Lived Access Token")
- Есть ли у устройства правильный
entity_id
Что дальше? Куда развивать проект
Вы собрали базовую версию. Теперь можно добавить:
- Голосовой интерфейс - как в нашей статье про голосового ассистента на RTX 3090, но с учетом ограничений GTX 1650
- Поддержку файлов - чтение PDF, DOCX, таблиц
- Планировщик задач - чтобы ассистент напоминал о делах
- Мультимодальность - Qwen3-VL умеет работать с изображениями
- Кластер - если одна карта не справляется, можно распределить нагрузку как в статье про масштабирование
Последний совет: Не пытайтесь сделать всё сразу. Добавляйте функции постепенно. Сначала добейтесь стабильной работы базовой системы. Потом добавляйте голос. Потом файлы. Иначе утонете в багах.
Самый интересный момент начнется, когда вы поймете, что эта система - не просто игрушка. Это ваш личный помощник, который знает о вас больше, чем любая облачная служба. Он помнит ваши предпочтения, привычки, проекты. И всё это работает локально, приватно, без подписок.
Попробуйте задать ему вопрос, на который не ответит ChatGPT: "Где я оставил ключи в прошлый раз?". И если вы вели с ним диалог - он вспомнит.
Это будущее. Оно уже здесь. И оно помещается на GTX 1650.