Локальный AI-ассистент: архитектура Spaceduck и 5 функций MVP | Гайд 2026 | AiManual
AiManual Logo Ai / Manual.
20 Фев 2026 Гайд

Как собрать приватного AI-ассистента: архитектура Spaceduck и 5 функций, которые реально работают

Пошаговый гайд по созданию приватного AI-ассистента с архитектурой Spaceduck. Топ-5 функций для MVP: работа с PDF, планировщик, инструменты, напоминания, интегр

Зачем вам ещё один AI-ассистент, если их уже десятки?

Потому что все они смотрят в облака. Ваши PDF-ки, разговоры о проектах, напоминания о встречах — всё это путешествует по серверам OpenAI, Google или какого-нибудь стартапа из Кремниевой долины. А потом вы читаете новости об утечках данных и думаете: "Ну хоть бы не мои".

В 2026 году строить локального ассистента проще, чем кажется. Модели стали меньше и умнее, железо — доступнее, а инструменты — злее. Я собрал Spaceduck — архитектуру, которая работает на обычном ноутбуке и делает то, что нужно, а не то, что маркетологи придумали.

Правда, о которой не говорят: большинство "уникальных функций" AI-ассистентов — это обёртка вокруг одного и того же API. Ваша задача — выбрать 5-6 функций, которые действительно решают ваши проблемы, и сделать их идеально.

Spaceduck: архитектура, которая не умрёт от первого же апдейта

Назвал так, потому что утка — птица, которая одинаково хорошо чувствует себя на земле, воде и в воздухе. Spaceduck работает с текстом, голосом, документами и внешними сервисами, не превращаясь в монстра с сотней зависимостей.

Слой Что внутри Почему именно это
Интерфейс Web UI (Gradio/Streamlit) + REST API Gradio в 2026-м стал быстрее, а Streamlit — стабильнее. Выбирайте по вкусу, оба работают локально без интернета.
Оркестратор FastAPI + агентский фреймворк (Lance или CrewAI) FastAPI — потому что асинхронность и документация генерируется сама. Lance — новый игрок 2025 года, который не требует танцев с бубном вокруг промптов.
Мозг Ollama + Qwen2.5-32B или DeepSeek-V3-Lite Ollama в 2026-м поддерживает GPU-ускорение даже на интегрированных видеокартах Intel. Qwen2.5-32B — золотая середина между качеством и скоростью. DeepSeek-V3-Lite появился в конце 2025 и жрёт в 2 раза меньше памяти при той же производительности.
Память ChromaDB + RAG-цепочки ChromaDB научилась работать с миллионами векторов без падения производительности. Главное — не хранить там пароли (да, я видел такие кейсы).
Инструменты MCP-серверы + кастомные Python-скрипты Model Context Protocol от Anthropic стал стандартом де-факто. В 2026 году под него написаны десятки серверов для работы с календарём, почтой, файловой системой.

Самая частая ошибка — пытаться запихнуть в архитектуру всё, что увидел на GitHub. Не надо. Spaceduck строится по принципу "один слой — одна ответственность". Если слой начинает делать две вещи — режьте его пополам.

💡
Если вы только начинаете, посмотрите мою статью "Собираем локального Jarvis за вечер". Там простой вариант без сложной архитектуры, чтобы понять базовые принципы.

Топ-5 функций для MVP: что реально работает, а что — маркетинг

Собрать архитектуру — полдела. Наполнить её функциями — вторая половина. Я перепробовал десятки вариантов и отобрал 5, которые дают 80% пользы при 20% усилий.

1 Работа с документами: не просто "прочитай PDF", а "найди в 50 файлах"

Все делают RAG. Большинство делает его криво. Проблема не в том, чтобы загрузить PDF в векторную БД, а в том, чтобы найти ответ в папке с 50 файлами, когда ты помнишь только "там что-то про API-ключи и ограничения".

# Не делайте так (это сломается на первом же сложном документе):
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("doc.pdf")
docs = loader.load()  # Всё в память, даже если файл на 200 МБ

# Делайте так:
from unstructured.partition.pdf import partition_pdf
import chromadb

def smart_pdf_processing(file_path):
    # Разбиваем на логические блоки с метаданными
    elements = partition_pdf(
        filename=file_path,
        strategy="hi_res",  # Новый метод 2025 года, лучше распознаёт таблицы
        infer_table_structure=True
    )
    
    # Фильтруем только текст и таблицы
    text_elements = [el for el in elements if el.category in ["Title", "NarrativeText", "Table"]]
    
    # Сохраняем с указанием страницы и типа контента
    for idx, el in enumerate(text_elements):
        metadata = {
            "page_number": el.metadata.page_number,
            "category": el.category,
            "source": file_path,
            "element_id": idx
        }
        # Векторизуем и сохраняем в ChromaDB
        # ...

Ключевое — сохранять метаданные. Когда ассистент говорит "на странице 23 в таблице про лимиты", вы должны понимать, откуда он это взял. И да, unstructured в 2026 году стал стандартом для парсинга документов — забудьте про старые библиотеки.

2 Умные напоминания: не просто таймер, а контекстные триггеры

"Напомни позвонить Маше" — скучно. "Напомни про звонок с Машей, когда открою Slack" — вот это уже интересно. Контекстные напоминания срабатывают не по времени, а по событиям: открыл почту, запустил IDE, зашёл в определённую папку.

# MCP-сервер для системных событий (упрощённая версия)
import psutil
import time
from typing import List
from mcp.server import Server
from mcp.server.models import InitializationOptions

class SystemMonitorServer:
    def __init__(self):
        self.reminders = []
        self.active_windows = set()
    
    async def check_triggers(self):
        """Проверяем, не наступили ли условия для напоминаний"""
        current_processes = self._get_active_processes()
        
        for reminder in self.reminders:
            if reminder["trigger"] == "slack_open" and "slack" in current_processes:
                await self._show_notification(reminder["message"])
            elif reminder["trigger"] == "vscode_open" and "code" in current_processes:
                await self._show_notification(reminder["message"])
    
    def _get_active_processes(self) -> List[str]:
        """Получаем список активных процессов (Linux/Mac/Windows)"""
        processes = []
        for proc in psutil.process_iter(["name"]):
            try:
                processes.append(proc.info["name"].lower())
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass
        return processes

Система мониторит процессы через psutil (кроссплатформенно) и срабатывает, когда появляется нужное окно. Бонус — можно делать напоминания типа "когда потратишь 2 часа в TikTok, напомни про дедлайн".

3 Планировщик workflows: "каждый понедельник анализируй мои траты"

Одноразовые команды — для новичков. Профессионалы используют планировщик. Представьте: каждый понедельник утром ассистент сам анализирует ваши траты за неделю, генерирует отчёт и кладёт на рабочий стол. Или каждую пятницу собирает список незакрытых задач из разных сервисов.

Секрет в том, чтобы workflows описывались на естественном языке, а не в JSON-конфигах. Вы говорите "анализируй траты по понедельникам", а система сама создаёт cron-задачу, настраивает RAG для доступа к банковским выпискам (локально, конечно) и готовит шаблон отчёта.

# Пример workflow-описания (не конфиг, а то, что генерирует LLM)
workflow:
  name: "Анализ трат по понедельникам"
  trigger:
    type: "cron"
    schedule: "0 9 * * 1"  # Каждый понедельник в 9:00
  steps:
    - action: "collect_data"
      source: "~/Documents/bank/statements/"
      format: "csv"
    - action: "analyze_with_llm"
      prompt: "Проанализируй траты за неделю, выдели основные категории, сравни с предыдущей неделей"
      model: "qwen2.5:7b"  # Лёгкая модель для аналитики
    - action: "generate_report"
      template: "weekly_expenses.md"
      output: "~/Desktop/траты_{{date}}.md"
  notifications:
    - type: "desktop"
      message: "Отчёт о тратах готов"

4 Инструменты AI-агента: когда ассистент сам запускает код

"Найди все Python-файлы, где используется устаревшая библиотека, и предложи миграцию" — это не запрос в ChatGPT, это команда вашему ассистенту. Он проходит по проекту, анализирует код, находит проблемные места и готовит патч.

В 2026 году для этого не нужны сложные фреймворки. Достаточно дать ассистенту доступ к файловой системе (в песочнице!) и инструментам анализа кода. Главное — ограничить права. Ассистент не должен иметь возможность удалить node_modules или запустить rm -rf /.

# Sandboxed tool execution - безопасный запуск инструментов
import subprocess
import tempfile
import os
from pathlib import Path

class CodeAnalysisTool:
    def __init__(self, workspace_path: str):
        self.workspace = Path(workspace_path)
        self.sandbox_dir = tempfile.mkdtemp(prefix="ai_sandbox_")
        
    async def find_deprecated_imports(self, library_name: str):
        """Ищем устаревшие импорты в безопасной среде"""
        # Копируем только нужные файлы в песочницу
        py_files = list(self.workspace.rglob("*.py"))
        for py_file in py_files[:10]:  # Ограничиваем для безопасности
            sandbox_file = Path(self.sandbox_dir) / py_file.relative_to(self.workspace)
            sandbox_file.parent.mkdir(parents=True, exist_ok=True)
            sandbox_file.write_text(py_file.read_text())
        
        # Запускаем анализ в изолированной среде
        cmd = [
            "python", "-m", "ast",
            "--find-imports", library_name,
            self.sandbox_dir
        ]
        
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=30,  # Таймаут на случай бесконечного цикла
            cwd=self.sandbox_dir  # Важно: запускаем в песочнице
        )
        
        # Очищаем песочницу
        self._cleanup_sandbox()
        return result.stdout

5 Интеграция с внешним миром: календарь, почта, Home Assistant

Локальный — не значит отрезанный от мира. Ваш ассистент должен уметь читать письма (через IMAP, без доступа к облаку Google), смотреть календарь (через CalDAV) и управлять умным домом (через локальный Home Assistant).

Тут работает правило: если есть локальный протокол — используем его. Если нет — ставим прокси, который кэширует данные у себя. Например, для работы с почтой поднимаем локальный IMAP-сервер, который раз в час синхронизируется с Gmail и отдаёт данные ассистенту.

💡
Для сложных интеграций посмотрите статью про AI-монстра. Там подробно разобраны интеграции с Home Assistant и другими системами через n8n.

Что пойдёт не так: 3 ошибки, которые совершат все

Я видел десятки попыток собрать локального ассистента. Все наступают на одни и те же грабли. Сэкономлю вам время.

Ошибка 1: Хранить всё в одной БД

Векторные эмбеддинги документов, настройки пользователя, история диалогов — это разные данные с разными паттернами доступа. Не пихайте всё в ChromaDB или PostgreSQL. Используйте:

  • ChromaDB — для векторного поиска по документам
  • SQLite — для настроек и метаданных
  • Файловую систему (с структурированными JSON) — для истории диалогов
  • Redis (опционально) — для кэширования промптов

Ошибка 2: Доверять LLM доступ к системе

"Ассистент, обнови систему" — и ваша Ubuntu переустанавливается. LLM не понимает последствий команд. Все инструменты должны работать через whitelist разрешённых операций с чёткими границами.

Реальный кейс: ассистент получил команду "освободи место на диске" и начал удалять node_modules во всех проектах. Потом — логи. Потом — системные временные файлы. Закончилось переустановкой системы.

Ошибка 3: Забыть про апдейты моделей

Вы поставили Qwen2.5-7B, всё работает. Через месяц выходит Qwen2.5-14B с лучшей поддержкой русского. А ваша система завязана на конкретную версию модели. Решение — абстракция поверх Ollama/LocalAI, которая позволяет менять модели без переписывания кода.

Собираем всё вместе: 4 шага к работающему прототипу

1 Ставим Ollama и базовую модель

# Устанавливаем Ollama (актуальная версия на февраль 2026)
curl -fsSL https://ollama.ai/install.sh | sh

# Качаем модель (DeepSeek-V3-Lite появился в конце 2025)
ollama pull deepseek-v3-lite:7b  # 7 млрд параметров, работает на 8 ГБ RAM

# Или Qwen2.5, если нужна лучшая поддержка русского
ollama pull qwen2.5:7b

# Проверяем
ollama run deepseek-v3-lite:7b "Привет!"

2 Поднимаем FastAPI-сервер с инструментами

# main.py - упрощённая версия сервера
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import subprocess
from typing import List
import json

app = FastAPI(title="Spaceduck Assistant")

class ChatRequest(BaseModel):
    message: str
    tools: List[str] = ["file_search", "calendar", "reminders"]

class ToolResponse(BaseModel):
    tool_name: str
    result: str
    success: bool

@app.post("/chat")
async def chat_endpoint(request: ChatRequest):
    """Основной endpoint для общения с ассистентом"""
    # 1. Определяем, нужны ли инструменты
    needs_tools = await _analyze_for_tools(request.message)
    
    # 2. Если нужны — вызываем соответствующие инструменты
    if needs_tools:
        tool_results = []
        for tool in needs_tools:
            result = await _execute_tool(tool, request.message)
            tool_results.append(result)
        
        # 3. Формируем промпт с результатами инструментов
        context = "\n".join([f"{r.tool_name}: {r.result}" for r in tool_results])
        prompt = f"Контекст:\n{context}\n\nВопрос: {request.message}"
    else:
        prompt = request.message
    
    # 4. Отправляем в LLM через Ollama API
    response = await _call_ollama(prompt)
    
    return {"response": response, "tools_used": needs_tools}

async def _call_ollama(prompt: str) -> str:
    """Вызов локальной модели через Ollama"""
    import aiohttp
    
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "http://localhost:11434/api/generate",
            json={
                "model": "deepseek-v3-lite:7b",
                "prompt": prompt,
                "stream": False
            }
        ) as resp:
            result = await resp.json()
            return result.get("response", "")

3 Добавляем интерфейс (Gradio за 15 минут)

# ui.py
import gradio as gr
import requests
import json

def chat_with_assistant(message, history):
    """Функция для Gradio интерфейса"""
    # Отправляем запрос на наш FastAPI сервер
    try:
        response = requests.post(
            "http://localhost:8000/chat",
            json={"message": message}
        )
        
        if response.status_code == 200:
            data = response.json()
            return data["response"]
        else:
            return f"Ошибка сервера: {response.status_code}"
    except Exception as e:
        return f"Ошибка подключения: {str(e)}"

# Создаём интерфейс
interface = gr.ChatInterface(
    fn=chat_with_assistant,
    title="Spaceduck Assistant",
    description="Приватный AI-ассистент. Всё работает локально.",
    theme="soft",
    examples=[
        "Найди в моих документах информацию про API-ключи",
        "Какие встречи у меня сегодня?",
        "Создай напоминание про звонок, когда открою Slack"
    ]
)

if __name__ == "__main__":
    # Запускаем на localhost:7860
    interface.launch(server_name="0.0.0.0", share=False)

4 Настраиваем первую функцию — работу с документами

# Создаём папку для документов и индексируем их
mkdir -p ~/spaceduck/documents

# Копируем туда PDF, DOCX, TXT файлы
cp ~/Downloads/*.pdf ~/spaceduck/documents/

# Запускаем индексатор
python -c "
from chromadb import PersistentClient
from sentence_transformers import SentenceTransformer
import os

# Инициализируем ChromaDB
client = PersistentClient(path='./chroma_db')
collection = client.get_or_create_collection('documents')

# Загружаем модель для эмбеддингов (легкая, для локального использования)
model = SentenceTransformer('all-MiniLM-L6-v2')

# Проходим по документам и индексируем
for root, dirs, files in os.walk('./documents'):
    for file in files:
        if file.endswith('.pdf'):
            # Здесь используем unstructured для парсинга
            # ... (код из примера выше)
            print(f'Индексируем {file}')
"

Через час у вас будет работающий прототип. Через день — ассистент с 2-3 функциями. Через неделю — система, которой вы действительно пользуетесь.

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

Когда базовые функции работают, возникает вопрос: а что ещё? Вот три направления, которые имеют смысл:

  1. Голосовой интерфейс — но не как у всех. Вместо "Привет, Алиса" сделайте push-to-talk кнопку в углу экрана. Или голосовую активацию по ключевому слову, которое распознаётся локально (смотрите MichiAI для low-latency решений).
  2. Мультимодальность — загружаете скриншот интерфейса, ассистент предлагает, как его улучшить. Или анализирует графики из отчётов. В 2026 году для этого есть локальные vision-модели типа LLaVA-Next, которые работают на потребительских GPU.
  3. Автономные агенты — не те, что "думают" 10 минут перед каждым действием, а реально полезные. Например, агент, который мониторит цены на определённые товары и сообщает, когда они падают ниже порога. Или агент, который следит за обновлениями в ваших зависимостях и предлагает апдейты.

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

Главное преимущество локального ассистента — он evolves вместе с вами. Вы не зависите от roadmap какой-то компании. Хотите функцию для анализа инвестиционного портфеля? Добавляете инструмент для парсинга CSV с биржевыми данными. Нужен контроль за детским экранным временем? Интегрируетесь с родительским контролем ОС.

В 2026 году приватность — не роскошь, а базовое требование. И AI-ассистенты, которые уважают это требование, перестают быть игрушками для гиков. Они становятся такими же необходимыми инструментами, как текстовый редактор или файловый менеджер.

Только без облаков. Только локально. Только ваш контроль.