Лакальны AI-агент: мадэлі, памяць, сховішча і інтэрфейс
Пяць узроўняў стэка лакальнага AI-агента з параўнаннем Ollama, OpenClaw, Mem0 і Qdrant ці Chroma, а таксама выпраўленні для JSON у think-рэжыме і ціхіх неадпаведнасцей памернасці.
Крыніцы: Building Local AI Agents: A Practical Guide to Models, Memory, and Orchestration
Местны ўраджэнны агент AI захоўвае модэль на вашай сабе апаратыразыёвай базе, дзеяе за вас і перадае кантэкст з адной сесіі ў іншую, не выканаючы перадачу дадзэнняў да зовнішніх API. Базавы чатбот адпавядае і забывае. Агент можа выконваць калькуляціі па кількох кроках, выкарыстоўваць інструменты і накапліваць знанні з часам. Работа ў локальным рэжыме дае вам асістэнта без компромісаў у паводзе з прыватнасцю чы ўзнаковых платаў за кожны вызов, якія ёсць у хоставаных службах.
Для стварэння аднаго такога рашэння павінны разам працаваць пяць слоёў: слой LLM, які выканае інферэнс на вашай машыне, фрэймворк агента, які керуе дзеяннямі, слой памяці, які паўрабляе агента з часам, слой зберагчыка, які захоўвае зібраны контэнт, і слой інтэрфейсу, які падходзіць вашаму спосабу работы. Ніжэй для кожнага слою паказаны ўзнаковыя моменты, можлівыя компромісы і рэкамендацыі, ўсё гэта дапамагае скласці комплект, які падходзіць вашай сітуацыі.
Слоёў 1: модэль
Гэта ядро. Мадэль выклікае намерэння, генеруе код і выдзяляе структураваныя даны, ад калі якіх паводлі працюе рэштка сістэмы, а ўсё іншэ — цэлая сістэма, побудаваная на тым, што ядро можа робіць надзеяйна. Тры інструмента домінуюць у локальных вычысленнях.
Ollama — гэта простэйшы спосаб развязкі. Адной командай яго встановляюць, адной — завантажаюць мадэль, і васямкнуто ўтвараецца API, сумесны з OpenAI, на локальнай машыне. Ён караце модэлі, выбірае спосаб квантавання і настаўляе параметры кантэкстнага вікна без дадатковай настройкі. Ён не створаны для велікага трафіку і больш падходзіць як інструмент для персональнага асістэнта чым для распрацоўшчыка.
ollama pull qwen3:8b
ollama pull nomic-embed-text
llama.cpp знаходзіцца на аднам рэвэне ніжэй. Гэта двайчы-плюс-двойковы моцар, які безпасебна запускае квантаваныя мадэлі 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, аддзельныя корыстувачы все ж можаць знова увімкнуць рэжым “thinking”, калі гэта для іх неабходна. Для выкарыстоўвання JSON і генеравання коду ў паўтараючыхся працэсах інструменту, рэжым “thinking” прыносіць затрымкі без падышча ў якасці.
Слой 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
-
Калі большая частка операцыяў — гэта праца з файламі (I/O), як у дапаможніку для програмавання, такая мадэль падходзіць ідэальна. Лініі 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 — гэта найпашчырэйшы і найяксьпрыятнейшы варыянт для пачатківаўцаў. Дакументы або історыю размовы дзельнуюць на часткі, гэтыя часткі вбудовуюцца, а калі прыходзіць запіт, выкарыстоўваюцца найболей схожыя з ім варыянты. Для ўстановкі трэба мінімум зусіль. Яго слабасць заключаецца у тым, што памяць ператвараецца на купу дакументаў, без разліку на тое, якія з іх варта зберагчы на довгачас, а якія — толькі для тымчасовага выкарыстоўвання.
Памяць, прызначаная для пэўнай сесіі, напрыклад 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
},
}
Калі вы хочаце заменіць Docker, Chroma ўсё тое ж сама аплявацыя: проста зменіце прадастальніка vector_store у настройкach, а рэшта стака застанецца такім жа.
Слой 5: інтэрфейс
Найзовнішы слой вялічынае спосаб, якім вы вясьледзіце з агентам, і правы выбор залежыць ад таго, дзе ён знаходзіцца у вашай рабочай практыцы.
- Веб-Інтэрфейс. Найбольш гнучкі варыянт. Ён працуе ў браузеры, адрасавае маркдаун, выдэляе код і секцыі, якія можна складзіць, і працуе на будзь-якай операцыйной сістэме без неабыходнасці інсталявання. Памагчык для програмавання можа паказаць тэкушчы файл, створэны код і стан памяці агента на аднай экране.
Як варыянты взаімаюцца межу сабою
Кожны слой абсалютвае сваіх суседзей. Мадэль выбірае фармат 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 не выклікае неабходнасці перзначання чаго-небудзь вышэй або ніжэй. Калі якійсь слой заслуговае на ранню інвестыцыю, то цэй будзе памяць. Скромны локальны модэль з добра спраектаваным слоем стойкай памяці надзеянна перамагае большы модэль, які кожную сесію пачынае з нуля.