Создание локального ИИ-агента: модели, память, хранение и интерфейс
Пять уровней стека локального ИИ-агента, сравнение Ollama, OpenClaw, Mem0 и Qdrant или Chroma, а также исправления проблем с JSON в режиме размышлений и незаметных несоответствий размерностей.
Источники: Building Local AI Agents: A Practical Guide to Models, Memory, and Orchestration
Локальный ИИ-агент хранит модель на вашем собственном оборудовании, действует от вашего имени и сохраняет контекст между сессиями, не отправляя данные во внешние API. Простой чат-бот отвечает и забывает. Агент может осуществлять логические рассуждения на нескольких этапах, использовать инструменты и постепенно накапливать знания. Работа с ним локально позволяет получить помощника без компромиссов в вопросах конфиденциальности или платы за каждый вызов, характерной для облачных сервисов.
Для создания такого агента необходимо совместная работа пяти слоев: слоя LLM, выполняющего инференс на вашем устройстве, фреймворка агента, отвечающего за маршрутизацию и выполнение действий, слоя памяти, улучшающего способности агента со временем, слоя хранения, сохраняющего собранные данные, и интерфейсного слоя, адаптированного под ваш стиль работы. Ниже приведены опции, компромиссы и рекомендации для каждого слоя, чтобы вы могли собрать комплекс, подходящий именно вам.
Слой 1: модель
Это двигатель. Модель определяет намерение, генерирует код и извлекает структурированные данные, от которых зависит весь остальной комплекс инструментов; всё остальное представляет собой вспомогательные компоненты, созданные на основе её надежных возможностей. Три инструмента доминируют в области локальной интерпретации моделей.
Ollama — самый простой способ начать работу. Одним командом его устанавливают, другой — загружают модель, и сразу же появляется API, совместимый с OpenAI, на локальном хосте. Он сам управляет моделями, выбирает способ квантизации и настраивает окна контекста без необходимости в ручной конфигурации. Он не предназначен для работы с большими объемами данных в производственных условиях и более подходит в качестве инструмента для личного ассистента или разработчика.
ollama pull qwen3:8b
ollama pull nomic-embed-text
llama.cpp находится на нижнем уровне. Это движок на C++, который запускает квантизированные модели GGUF напрямую, без демона и без слоя HTTP. Его минимальное потребление памяти делает его подходящим для периферийных устройств, таких как Raspberry Pi или встраиваемые системы на Linux, где Ollama будет слишком тяжелым. Недостаток заключается в отсутствии встроенного управления моделями и необходимости более трудоемкой настройки.
vLLM — это вариант для производственного использования. Он использует техники PagedAttention и непрерывной группировки запросов, характерные для масштабных облачных вычислений. Если вы обслуживаете множество пользователей или у вас есть высокие требования к пропускной способности, это подходящий вариант, хотя он требует соответствующей инфраструктуры GPU и обеспечивает лучшую масштабируемость по сравнению с первыми двумя решениями.
Для локального помощника одного разработчика Ollama является разумным стандартным выбором, поскольку простота важнее производительности. Стоит упомянуть модель qwen3:8b: она обеспечивает надежный JSON для обнаружения намерений, пишет код чисто и помещается в 8 ГБ VRAM. Следует знать одну особенность: она оборачивает свой ответ блоками <think>…</think> перед фактическим содержимым, что мешает любому последующему парсеру JSON обрабатывать сырой ответ. Небольшой оберточный код, устанавливающий think: False во всех местах, решает эту проблему:
_orig_ollama_chat = ollama.chat
def _no_think_chat(*args, **kwargs):
opts = kwargs.get("options") or {}
if isinstance(opts, dict):
opts.setdefault("think", False)
kwargs["options"] = opts
return _orig_ollama_chat(*args, **kwargs)
ollama.chat = _no_think_chat
Поскольку используется setdefault, отдельные пользователи всё ещё могут включить режим размышлений, когда этого захотят. Для извлечения данных в формате JSON и генерации кода в рамках инструментальной цепочки режим размышлений увеличивает задержку без улучшения качества.
Уровень 2: фреймворк агентов
Модель генерирует текст. Фреймворк определяет, что делать с этим текстом и какие действия выполнять дальше, включая классификацию намерений, маршрутизацию и выполнение задач.
OpenClaw ориентирован на инструменты и работает на уровне файловой системы. Каждое действие представляет собой команду, отправляемую определённой навыку, такой как создание файла, написание кода, открытие папки или выполнение команды в оболочке. Вы пишете файл SKILL.md, в котором указываетесь название навыка и способ его вызова, а фреймворк занимается маршрутизацией:
- -
name: local-ai-assistant
description: Local AI coding assistant with persistent memory
command-dispatch: tool
command-tool: exec
command-arg-mode: raw
-
Когда большинство операций связаны с вводом-выводом файлов, как в кодинг-ассистенте, такая модель подходит идеально. Строки command-dispatch: tool и command-tool: exec указывают OpenClaw на необходимость запуска Python-скриптов при вызове определенной функции, тем самым обеспечивая обработку файлов через эту платформу. Однако её недостаток заключается в том, что она не предназначена для длинных цепочек многоэтапных рассуждений или для координации нескольких агентов.
LangGraph использует противоположный подход. Здесь вы определяете явную графическую структуру состояний, в которой узлами являются вызовы моделей или действия, а ребрами — переходы между ними; платформа сохраняет состояние на протяжении всех этапов. Цена такого подхода — необходимость заранее спроектировать структуру состояний и графа, прежде чем начинать реализацию бизнес-логики. Для простого использования инструментов это избыточно, но для сложных многоэтапных процессов такой подход позволяет иметь полную запись всего, что происходило на каждом этапе.
CrewAI находится ещё на одном уровень выше. Вы описываете агентов с их ролями и задачами, включая подробные описания, а фреймворк определяет порядок выполнения и способы взаимодействия между агентами. Это самый быстрый способ создания рабочего прототипа с несколькими агентами, хотя такая абстракция затрудняет отладку сбоев.
Какой бы вариант вы ни выбрали, сначала проводится классификация намерения для каждого входящего сообщения. Сочетание классификации на основе моделей с резервным методом на основе регулярных выражений позволяет лучше справляться со сбоями, чем любой из методов в отдельности:
def detect_intent(message: str) -> dict:
try:
resp = ollama.chat(
model=OLLAMA_CHAT_MODEL,
messages=[{"role": "user", "content": INTENT_PROMPT.format(message=message)}],
options={"temperature": 0, "num_predict": 1024},
)
return extract_json(resp["message"]["content"])
except Exception:
return keyword_intent_fallback(message)
Механизм резервного варианта обрабатывает распространённые ситуации, когда модель возвращает некорректный результат. При отключённом режиме размышлений и температуре 0 большинство запросов обрабатывается через стандартный путь модели, но именно резервный вариант обеспечивает работоспособность агента.
Слой 3: память
Память позволяет отличить агента, который приспосабливается к вам, от того, который рассматривает каждый диалог как первый. Он учится на ваших привычках, предпочтениях и контексте проекта и применяет полученные знания без дополнительных указаний.
Обычные решения находятся внутри окна контекста: файл PREFERENCES.md, который читается при запуске, длинное системное сообщение или файл CONTEXT.md. У них есть один общий недостаток — внутри окна они сталкиваются с проблемами из-за компактности, ограничений по количеству токенов и перезапусков сессии. На самом деле агенту нужна память вне окна, которая хранилась бы долгосрочно, позволяла бы находить релевантную информацию по значимости и обновляться при изменении фактов. Существуют три распространённых подхода, каждый из которых решает свои задачи.
Базовый RAG — самый распространённый и простой в использовании для начинающих. Документы или история разговора делятся на фрагменты, эти фрагменты встраиваются, и при поступлении запроса извлекаются k наиболее похожих фрагментов; для его работы требуется минимальная настройка. Его слабость заключается в том, что память превращается в набор документов без различия между теми, которые стоит хранить долгосрочно, и временными запросами.
Память сессионного уровня, такая как ConversationBufferMemory и ConversationSummaryMemory от LangChain, обеспечивает целостность информации в рамках одной сессии. Агент не забудет то, что вы сказали пять сообщений назад, а резюме предотвращают переполнение окна обмена сообщениями. Однако эта память не сохраняется после перезапуска, что является основным недостатком для кодинг-ассистента.
Структурированная постоянная память, как в Mem0, рассматривает память как данные первого класса, а не просто текст. Вместо дословных сообщений модель извлекает отдельные факты, которые затем сохраняются в векторной базе данных. При поиске запрос также преобразуется в вектор, и возвращаются семантически похожие факты, даже если новое сообщение не использует исходные формулировки. Сохраненные факты остаются между сессиями, могут объединяться при дублировании и переписываться при изменении реальности. Mem0 поддерживает локальные модели, поэтому его легко интегрировать в стек на основе Ollama.
Возьмем разработчика, который упоминает, что ему нравится pytest. Обычная система RAG сохраняет само исходное предложение. Структурированная система извлекает и сохраняет саму предпочтение. В следующий раз, когда вы попросите функцию с тестами, память вернет информацию "пользователь предпочитает pytest", даже если в сообщении никогда не упоминалась среда тестирования.
Подключение Mem0 к локальной установке Ollama требует небольшой настройки, указывающей на ваш локальный модель, локальную модель эмбеддингов и хранилище векторов:
from mem0 import Memory
config = {
"llm": {
"provider": "ollama",
"config": {"model": "qwen3:8b", "ollama_base_url": "http://localhost:11434"},
},
"embedder": {
"provider": "ollama",
"config": {"model": "nomic-embed-text", "ollama_base_url": "http://localhost:11434"},
},
"vector_store": {
"provider": "qdrant",
"config": {"host": "localhost", "port": 6333, "embedding_model_dims": 768},
},
}
memory = Memory.from_config(config)
memory.add("I always use type hints and pytest", user_id="dev")
results = memory.search("write a utility function", user_id="dev")
Блок llm направляет Mem0 к модели qwen3:8b для извлечения фактов, преобразуя необработанное сообщение в отдельные сохраненные предпочтения вместо дословного текста. Блок embedder использует модель nomic-embed-text для векторизации этих фактов при записи и для включения запросов при поиске, после чего memory.search() выполняет поиск по смыслу, а не по ключевым словам.
Недооцененным улучшением для любого подхода к использованию памяти является фильтрация информации перед её сохранением. Легкий классификатор, работающий перед каждой записью, помогает сохранять память чистой и обеспечивать высокое качество поиска:
def _is_worth_storing(self, user_message: str) -> bool:
response = ollama.chat(
model=OLLAMA_CHAT_MODEL,
messages=[{"role": "user", "content": SMART_MEMORY_PROMPT.format(
user_message=user_message
)}],
options={"temperature": 0, "num_predict": 512},
)
data = self._extract_json_robust(response["message"]["content"])
return bool(data.get("worth_storing", False))
В его инструкции приводятся четкие примеры, иллюстрирующие это различие:
HIGH-VALUE (worth storing):
- "I prefer TypeScript over JavaScript"
- "I use pytest, never unittest"
- "Always use Google-style docstrings"
LOW-VALUE (discard):
- "What does enumerate() do?"
- "Write a retry decorator"
- "Thanks"
Если игнорировать фильтр, векторная база данных постепенно заполняется одноразовыми запросами, что снижает качество поиска.
Слой 4: хранение
Слой памяти определяет, что следует сохранить, а хранение — это место, где данные сохраняются на диске.
FAISS — это библиотека, работающая внутри процесса: она быстрая, хорошо протестирована и не требует инфраструктурных зависимостей. Её можно встроить в процесс на Python и запускать в памяти или сериализовать на диск. Её ограничения связаны с функционированием: для сохранения данных необходимы явные вызовы сериализации, отсутствуют HTTP-API и встроенная репликация. Это подходит для исследовательских работ и пакетных задач, но не подходит для долгосрочно работающего ассистента, который часто перезагружается.
Chroma — это самая простая векторная база данных, которую может выбрать разработчик. Она работает как локальный процесс на Python или встраивается в собственный код, автоматически сохраняя данные в локальную папку. Установите её с помощью:
pip install chromadb
Тысячи сохранённых фактов в личном ассистенте полностью находятся в зоне комфорта Chroma. Он полностью устраняет зависимость от инфраструктуры, но при этом фильтрация данных ограниченнее, чем у Qdrant.
Qdrant работает как полноценный сервис с полными API REST и gRPC, возможностью фильтрации данных и использованием векторов с именами. Он предназначен для сценариев с миллионами векторов и одновременными запросами. Данные сохраняются нативно на диске и остаются доступными после перезапуска Docker без дополнительных мер:
# Qdrant via Docker
docker run -d - name qdrant-local -p 6333:6333 \
-v $(pwd)/qdrant_storage:/qdrant/storage qdrant/qdrant
Одна деталь конфигурации может тихо нарушить работу: размерность векторного хранилища в конфигурации должна полностью совпадать с размерностью выходных векторов вашей модели эмбеддингов. Например, модель nomic-embed-text генерирует векторы размерностью 768. Если изменить модель эмбеддингов, не обновив этот показатель, операции вставки завершатся с ошибкой, которая не будет видна.
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "coding_assistant",
"host": "localhost",
"port": 6333,
"embedding_model_dims": 768, # must match your embedding model exactly
},
}
Chroma — это простая замена, если вы хотите отказаться от Docker: достаточно изменить поставщика vector_store в конфигурации, а остальная часть стека останется прежней.
Уровень 5: интерфейс
Самый внешний уровень определяет способ взаимодействия с агентом, и правильный выбор зависит от его местоположения в вашем рабочем процессе.
- Веб-интерфейс. Самый гибкий вариант. Он работает в браузере, отображает markdown, выделенный код и складываемые разделы, а также функционирует на любой операционной системе без установки. Помощник по программированию может отображать текущий файл, сгенерированный код и состояние памяти агента на одном экране.
Как взаимосвязаны эти варианты
Каждый слой ограничивает свои соседние элементы. Модель выбирает формат API, который использует фреймворк, механизмы памяти определяют тип хранилища, необходимого для работы, а интерфейс задаёт степень важности форматирования результата.
Для локального помощника разработчика это решение минимизирует сложности при сохранении возможности замены каждого слоя.
Interface: Web UI on localhost
│
Framework: OpenClaw - tool-dispatch, SKILL.md routing
│
LLM: Ollama for qwen3:8b
(think mode disabled for JSON reliability)
│
Memory: Structured persistent memory with value filtering
Embeddings via nomic-embed-text
│
Storage: Chroma (zero infrastructure) or Qdrant (more robust)
Поскольку каждый слой можно заменять независимо, начните с самого простого варианта на каждом уровне и обновляйте его по мере возникновения потребностей. Переход с Chroma на Qdrant или с Ollama на vLLM не требует пересоздания ничего выше или ниже этого слоя. Если какой-то слой заслуживает ранней инвестиции, это должна быть память. Небольшая локальная модель с хорошо спроектированным слоем постоянной памяти надежно превосходит более крупную модель, которая каждый раз начинает работу с нуля.