Зачем вам ещё один 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 строится по принципу "один слой — одна ответственность". Если слой начинает делать две вещи — режьте его пополам.
Топ-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 и отдаёт данные ассистенту.
Что пойдёт не так: 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
Когда базовые функции работают, возникает вопрос: а что ещё? Вот три направления, которые имеют смысл:
- Голосовой интерфейс — но не как у всех. Вместо "Привет, Алиса" сделайте push-to-talk кнопку в углу экрана. Или голосовую активацию по ключевому слову, которое распознаётся локально (смотрите MichiAI для low-latency решений).
- Мультимодальность — загружаете скриншот интерфейса, ассистент предлагает, как его улучшить. Или анализирует графики из отчётов. В 2026 году для этого есть локальные vision-модели типа LLaVA-Next, которые работают на потребительских GPU.
- Автономные агенты — не те, что "думают" 10 минут перед каждым действием, а реально полезные. Например, агент, который мониторит цены на определённые товары и сообщает, когда они падают ниже порога. Или агент, который следит за обновлениями в ваших зависимостях и предлагает апдейты.
Самый неочевидный совет: не добавляйте функции, пока текущие не стали привычкой. Если вы пользуетесь поиском по документам раз в неделю — улучшайте его, пока не станете использовать ежедневно. Только потом добавляйте следующую функцию.
Главное преимущество локального ассистента — он evolves вместе с вами. Вы не зависите от roadmap какой-то компании. Хотите функцию для анализа инвестиционного портфеля? Добавляете инструмент для парсинга CSV с биржевыми данными. Нужен контроль за детским экранным временем? Интегрируетесь с родительским контролем ОС.
В 2026 году приватность — не роскошь, а базовое требование. И AI-ассистенты, которые уважают это требование, перестают быть игрушками для гиков. Они становятся такими же необходимыми инструментами, как текстовый редактор или файловый менеджер.
Только без облаков. Только локально. Только ваш контроль.