ATOM: локальный AI-ассистент на GTX 1650 с памятью и управлением | AiManual
AiManual Logo Ai / Manual.
03 Янв 2026 Гайд

ATOM: как собрать полностью локального AI-ассистента с долговременной памятью и управлением устройствами на GTX 1650

Пошаговое руководство по сборке локального AI-ассистента ATOM с долговременной памятью ChromaDB и управлением устройствами на слабой видеокарте GTX 1650

Почему ваш следующий 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 ГБ свободного места на диске
💡
Важный момент про VRAM: Если у вас меньше 4 ГБ видеопамяти, всё равно можно попробовать - просто часть модели будет загружаться в RAM. Будет медленнее, но работать будет.

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
}

Что делать если не хватает памяти:

  1. Уменьшаем контекст с 4096 до 2048 или даже 1024 токенов
  2. Используем более агрессивное квантование - Q3_K_S вместо Q4_K_M
  3. Выгружаем часть модели в RAM через уменьшение gpu_layers
  4. Закрываем все лишние приложения особенно браузер с 20 вкладками
💡
Секретный трюк: Если LM Studio всё равно жалуется на память, запустите его с флагом --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 (
    
      
      
      
      {/* Центральный узел - запрос */}
      
        
        
        
          {query}
        
      
      
      {/* Результаты поиска как узлы */}
      {results.map((result, index) => {
        const angle = (index / results.length) * Math.PI * 2;
        const radius = 3;
        const x = Math.cos(angle) * radius;
        const z = Math.sin(angle) * radius;
        
        return (
          
            
              
              
              
                {result.source}
              
            
            
            {/* Линия от запроса к результату */}
            
          
        );
      })}
      
      
    
  );
}

Частые проблемы и их решения

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

Что дальше? Куда развивать проект

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

  1. Голосовой интерфейс - как в нашей статье про голосового ассистента на RTX 3090, но с учетом ограничений GTX 1650
  2. Поддержку файлов - чтение PDF, DOCX, таблиц
  3. Планировщик задач - чтобы ассистент напоминал о делах
  4. Мультимодальность - Qwen3-VL умеет работать с изображениями
  5. Кластер - если одна карта не справляется, можно распределить нагрузку как в статье про масштабирование

Последний совет: Не пытайтесь сделать всё сразу. Добавляйте функции постепенно. Сначала добейтесь стабильной работы базовой системы. Потом добавляйте голос. Потом файлы. Иначе утонете в багах.

Самый интересный момент начнется, когда вы поймете, что эта система - не просто игрушка. Это ваш личный помощник, который знает о вас больше, чем любая облачная служба. Он помнит ваши предпочтения, привычки, проекты. И всё это работает локально, приватно, без подписок.

Попробуйте задать ему вопрос, на который не ответит ChatGPT: "Где я оставил ключи в прошлый раз?". И если вы вели с ним диалог - он вспомнит.

Это будущее. Оно уже здесь. И оно помещается на GTX 1650.