Галоўная / Артыкулы

AI-агент з сапраўднай памяццю на Mem0, LangChain і Groq

AI-агент з сапраўднай памяццю на Mem0, LangChain і Groq: чаму LLM забываюць паміж сесіямі, эмбедынгі ў ChromaDB і поўная зборка.

Выдавец: REACTAPP.TOP12 хв чытання2680 слоў

Крыніцы: **Build an AI Agent with Real Memory Using Mem0, LangChain & Groq**

Наступныя прыміткі паказваюць практычны шлях для рэалізацыі праекту “Стварэнне агента AI з рэальным запам’ятованнем за дапомогою Mem0, LangChain & Groq”. Акцэнт ставіцца на контракты, пераконтроўкі і месца для коду, а не на мотывацыйныя аспекты.

Чаму LLM-ы забываюць — і чаму гэта мае значэнне

Калі працуеце над раздзелам “Чаму LLM-ы забываюць”, спачатку запісайце контракт: неабяжлівыя даныя, сігнал успеху і тое, што вядзецца пад частым нявыпаннем задачы. Такі спіс пераконтроўкі дапамагае заліцьварыць пазнейшыя змены ў кодзе. Запісуйце адночасна шлях успеху і шлях вярнення ў нормальны стан. Перапрыбуткі, людзкі контроль і обработка некорэктных паведамленняў ёсць часткай продукту, а не пазнейшым дапрацоўкам. Зберагаюце у кэшы стабільныя інструкцыі сістэмы і схемы інструментаў. Перасылка ідэнтычных прамуслов ёсць частым выклікам для рэсурсаў.

Што мы ствараем

Калі працюеце над стадзіяй «Што мы будуем ствараць», спачатку запісайце угоду: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага неяксамоства. Такі спіс контроля дапамагае залічваць змяны ў кодзе чыста і прозрачна. Валіце маленькія, тэставаныя елементы замест вялікіх скрыптав. Калі якісь крок не выйшае, неяксамоства должны вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцужок задач. Зробіце пераконтроль пасля дорогіх крокаў. Програма не должна зноў ставіць плату за той самы вызов LLM, калі аператар праказвае спробу на болей пазнім элеменце.

Тэхнічны стак у карці

Калі працуеце над тэхнічным стаком на адзінам з етапаў, спачатку запісайце кантракт: неабходныя вхідныя даны, сігнал успеху і тое, што выходзіць у случае частковай нявыполненасці. Такі спіс пераканае ў тым, што пазнейшыя змены коду будуць чыстымі. Спрыймайце гэты этап як кантракт межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Дайце назвы артыфактам, задаце правіла пераканання успеху і не падтрымайце тыхчасова часткова завершэння задачы. Зробіце перакананне пасля дорогіх крокаў. Сістэма адновлення не должна зноў ставіць плату за той самы вызов LLM, калі аператар праказвае пазнейшы вузел.

Mem0 — шар памяці

Калі працуеце з стадзіяй мемарыянага шару Mem0, спачатку запісайце умовы вікорыстоўвання: неабходныя даныя, сігнал успеху і тое, што выходзіць у разе частковага нявыпання задачы. Такі спіс дапамагае заліцварваць можлівыя змены ў кодзе.

Запісвайце час выканання і кост токенаў або запытаў праза функцыйнае рэзультат. Відкрытыя даныя пра косцы запобегаюць неспакойным рахункам, калі працэс пераходзіць з дэмавай версіі ў спяльныя сераўеры.

Зробіце перапаказ пасля дорогіх крокаў. Продовжэнне выканання не должна зноў нарахоўваць косты за той самы вызов LLM, калі аператар прабуе зноў выконаць пазнейшы элемент.

Groq — аналіз даных

Калі працуеце з фазай анферэнсу Groq, спачатку запішыце контракт: неабяжлівыя даныя, сігнал успеху і тое, што выканаецца пад частым неудачам. Такі спіс контроля дапамагае залічваць пазнейшыя змены ў кодзе чыстаюча.

LangChain — фарматаванне паведамленняў

Этап фарматавання паведамленняў у LangChain працюе найэфектывней, калі яго розглядаць як параметр, які можна змерыць. Запісаўце адны ідэальны прыклад, адну справу з бягам і прыметку па поверненню да пачатковага стану пры расшырэнні масштаба. Разглядзайце этап як кантракт межаў вхідных дадзенняў і перакананых выходных рэзультатаў. Даўце назвы артыфактам, задаце крытэрыя успеху і не падпішывайцеся на тыхую частковую завершэння работы. Храніце стан графа ў простам і тыпаваным фармаце. Вкладзеныя блокі дадзенняў маскуюць інфармацыю пра тое, які вузол запісаў які поле, і спакшуюць возз'яданне пасля перарываў.

ChromaDB — хранілішча вектараў

Этап хранення вектараў у ChromaDB працюе наякша, калі яго розглядаць як мерыемую паверхню. Запісаце адна ідэальная транскрыпцыя, адзін прыклад неудачы і прыметку па абратанні змян перш чым расшырваць сферу дзеяння. Запісвайце часы выканання і вартасць токенаў або запытаў разам з функцыйнальнымі рэзультатамі. Візуабілізацыя вартасцей з самага пачатку запобегае неспакою, калі праця пераходзіць з дэмаверсіі ў спакульнаныя сераўы. Храніце стан графа ў простам і тыпаваным фармаце. Вкладзеныя блобы маскуюць інфармацыю пра тое, які вузел запісаў канкрэтны поле, і спаказваюць працу пасля перарываў.

sentence-transformers/all-MiniLM-L6-v2 — embeddings

Этап эмбедынга Sentence-Transformers all-MiniLM-L6-v2 працюе найэфектывней, калі яго расследжваць як меравальную паверхню. Зберагчы адны ідэальны прыклад, адну справу з бягамі і запіс пра вярнэнне да пачатковага стану, перш чым расшырваць сферу дзеяння. Хавайце настройкі парадульна ад коду прыемлі. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцый павінны знаходзіцца ў адном месцы, куды аператары можаць пераглядаць іх без неабяжнага чытання всіх элементаў сістэмы. Раздзеляйце правілы часткавання дадзеных і правілы ўзяць іх. Змена адных не павінна вымагаць перапісву іншых, калі зменяюцца паказатэлі якосці. Этап эмбедынга Sentence-Transformers all-MiniLM-L6-v2 працюе найэфектывней, калі яго расследжваць як меравальную паверхню. Зберагчы адны ідэальны прыклад, адну справу з бягамі і запіс пра вярнэнне да пачатковага стану, перш чым расшырваць сферу дзеяння. Вядомей выбірайце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына бягу павінна вказываць на адную конкрэтную адпаведальнасць, а не на заплутаны ланцоўкі задач.

Код — паследоўнае адгляданне

Для этапы практычнага аналізу коду неабяжна прадзеўкаваць вхідныя даны, адпаведальнага за кожны крок і крэтыяры завершэння пры змены коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Спрыяйце цій этапы як даговору межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даўце назвы артыфактам, прадзеўкаваць перакананні ў успеху і адмовіцеся ад тыхняга частковага завершэння без паведамлення. Забяжцеся пра людскія падтверджэння ў тых віпадках, калі гэта выклікае вітраты грошаў або змены у продакцыйных даных. Компіляцыйныя налашчэння не ўзроўнаўцуюцца з абсягам выканання бізнес-задач.

1. Цэнтральная налашчэння

Для этапы 1 «Цэнтральная наладка» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крытэрыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць час выканання і кост токена або запыту праза функцыйнальныя рэзултаты. Відразлівасць коста з самага пачатку запобегае неспакоўным рахункам, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя асяроддзя. Пры кроках, якія вітрачаюць грошы або зменяюць даны ў працэйнай асяроддзі, неабяжна выкарыстоўваць людскія апраўды. Падключэнне пад час компілявання не ўзначае повнасці бізнес-функцыйоналу.

@dataclass
class AgentConfig:
    groq_model: str = "llama-3.1-8b-instant"
    temperature: float = 0.7
    max_tokens: int = 1024
    chroma_collection: str = "dev_assistant_memories"
    chroma_path: str = "./mem0_db"
    embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
    memory_trigger_phrases: tuple = (
        "my name is", "i am building", "i am working on",
        "my stack is", "i prefer", "my team", ...
    )

2. Памяцевыя дапаможнікі

Для этапы 2 Memory Helpers неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю без неабяжнага вычыслення захаванога стану. Конфігурацыю трэба залічыць пазначкай ад коду прыемліка. Файлы сераўнавання, храненні секрэтных даных і флагі функцый належыць у аднам месца, якое аператары можаць пераглядаць без чытання всей структуры. Неабяжна прыкласты людзкія падтверджэння да тых рэлей, якія выкарыстоўваюць грошы або зміняюць даны у працэйным рэжыме. Підключэння ў час складання не абавязкова значыць повнайшую рэалізацыю бізнес-функцый. Для этапы 2 Memory Helpers неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю без неабяжнага вычыслення захаванога стану. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выконваецца, прычына неабяжна паказваць на адну конкрэтную відпаведальнасць, а не на заплутаную структуру працы.

def store_memory(memory, text, user_id) -> None:
    try:
        memory.add(text, user_id=user_id, infer=False)
    except Exception as exc:
        logger.warning("Could not store memory: %s", exc)

def retrieve_memories(memory, query, user_id, top_k=5) -> list[str]:
    try:
        results = memory.search(query=query,
                                filters={'user_id': user_id},
                                limit=top_k)
        return [item['memory'] for item in results.get('results', [])
                if 'memory' in item]
    except Exception as exc:
        logger.warning("Memory retrieval failed: %s", exc)
        return []

3. Розумнейшы трыгер памяці

Кал працуеце над стадзіяй «3. Розумнейшы трыгер памяці», спачатку запісайце угоду: неабяжлівыя даныя, сігнал успеху і тое, што выканаецца у разы ў частковай нявыполненасці. Такі спіс пераканае ў тым, што пазнейшыя змены коду буду чыстымі. Спрыятлівае ставленне да гэтай стадзіі як да угоды межаў між вхіднымі данымі і перакананымі выходнымі рэзультатамі. Дайце назвы артыфактам, задаць крытэрыя успеху і не падзеўляйцеся частковым завершэнням без паведамлення.

def should_store(text: str, triggers: tuple) -> bool:
    lowered = text.lower()
    return any(phrase in lowered for phrase in triggers)

4. Чат-запрос на аднозначных падставах

Калі працюеце над 4 стадзямі Grounded Chat Prompt, спачатку запісайце угоду: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі спіс контроля дапамагае заліцьварыцца пад пазнейшыя змены коду. Запісвайце час выканання і кост токеноў або запытаў праза функцыйнальныя рэзултаты. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі працэс пераходзіць з дэмаверыянту ў спакульнаныя сераўысы. Зберагачвайце стабільныя інструкцыі сістэмы і схемы інструментоў. Перадзял у тое ж самае прамэру ёсць частым выклікам вітрачання рэсурсоў.

system = SystemMessage(content=(
    "You are a memory-aware developer assistant. "
    "Answer ONLY from the provided memory context. "
    "If memory is empty or irrelevant, say: "
    "'I don't have that in memory yet — tell me and I'll remember it.' "
    "Never fabricate projects, names, or technical choices."
))
human = HumanMessage(content=(
    f"### Memory context\n{memory_block}\n\n"
    f"### User question\n{user_query}"
))

5. Двойныя рэжымы: REPL & Demo

Калі працуеце з 5 стадзямі REPL у двойнам рэжыме, спачатку запісайце угоду: неабяжлівыя даныя, сігнал успеху і тое, што выходзіць пад частковыя неудачы. Такі спіс контроля дапамагае заліцвачыць пазнейшыя змены коду. Зберагаеце настройкі праз ванярэй коду прыемлена. Файлы сераўіса, храненні секрэтных дадзенаў і флагі функцыйяй должны знаходзіцца ў аднам месцы, куды оператары можаюць адбавіць аўдыт без чытання всей структуры. Ствараеце контрольныя пункты пасля дорогіх крокаў. Функцыя вярнення працы не должна занова ставіць плату за той самы вызов LLM, калі оператар перапрыяўляе працу да пазнейшага элемента. Калі працуеце з 5 стадзямі REPL у двойнам рэжыме, спачатку запісайце угоду: неабяжлівыя даныя, сігнал успеху і тое, што выходзіць пад частковыя неудачы. Такі спіс контроля дапамагае заліцвачыць пазнейшыя змены коду. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшаў, прычына неудачы должна вказываць на адну адпаведальнасць, а не на заплутаную лінію апрацоўкі.

# Interactive session
python memory_agent.py

# Scripted demo (no terminal needed)
python memory_agent.py demo

Настройка за менейш чым 5 хвілін

Этап «Установка за меней чым 5 хвілін» працюе найэфектыўней, калі яго розглядаць як мерыемую паверхню. Зафіксавайце адна ідэальная версія рэзультата, адын прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць масштабы. Разглядайце гэты этап як кантракт межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Дайце назвы артыфактам, задаце крытэрыя успеху і не падзеўляйцеся частым, непрыканальным завершэнням задачы.

Установіце залежнасці

!pip install mem0ai langchain langchain-groq chromadb sentence-transformers

Атрымайце безкоштовны ключ API Groq

Запусціце

export GROQ_API_KEY=gsk_...
python memory_agent.py

Як выглядае дэманстрацыя

User: My name is Naveen and I am building a multimodal RAG pipeline using Groq and LangChain.
AI: Got it, Aditya! I'll remember your multimodal RAG project.

User: I recently switched from OpenAI embeddings to HuggingFace sentence-transformers.
AI: Noted — HuggingFace sentence-transformers for embeddings.

User: What AI project am I working on?
AI: You are building a multimodal RAG pipeline using Groq and LangChain.

User: Which embedding provider did I switch to?
AI: You switched from OpenAI embeddings to HuggingFace sentence-transformers.

Полны код:

import os
import logging
from dataclasses import dataclass, field
from typing import Optional
from getpass import getpass

from mem0 import Memory
from langchain_groq import ChatGroq
from langchain_core.messages import SystemMessage, HumanMessage

# ── Logging ──────────────────────────────────────────────────
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)


# ── Configuration ─────────────────────────────────────────────
@dataclass
class AgentConfig:
    """Central config – tweak here, nowhere else."""
    groq_model: str = "llama-3.1-8b-instant"
    temperature: float = 0.7
    max_tokens: int = 1024
    chroma_collection: str = "dev_assistant_memories"
    chroma_path: str = "./mem0_db"
    embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
    # Phrases that signal the user is sharing personal context
    memory_trigger_phrases: tuple = (
        "my name is", "i am building", "i am researching",
        "i am working on", "my favorite", "i like", "i study",
        "i use", "i prefer", "my stack is", "i switched to",
        "i recently", "my team", "my company",
    )


# ── Mem0 + LLM bootstrap ──────────────────────────────────────
def build_memory(cfg: AgentConfig) -> Memory:
    mem0_config = {
        "vector_store": {
            "provider": "chroma",
            "config": {
                "collection_name": cfg.chroma_collection,
                "path": cfg.chroma_path,
            },
        },
        "embedder": {
            "provider": "huggingface",
            "config": {"model": cfg.embedding_model},
        },
    }
    memory = Memory.from_config(mem0_config)
    logger.info("Mem0 memory store ready  (collection=%s)", cfg.chroma_collection)
    return memory


def build_llm(cfg: AgentConfig) -> ChatGroq:
    llm = ChatGroq(model=cfg.groq_model, temperature=cfg.temperature, max_tokens=cfg.max_tokens)
    logger.info("Groq LLM ready  (model=%s)", cfg.groq_model)
    return llm


# ── Memory helpers ────────────────────────────────────────────
def store_memory(memory: Memory, text: str, user_id: str) -> None:
    """Persist a memory fragment for the given user."""
    try:
        memory.add(text, user_id=user_id, infer=False)
        logger.debug("Stored memory for %s: %s", user_id, text[:80])
    except Exception as exc:
        logger.warning("Could not store memory: %s", exc)


def retrieve_memories(memory: Memory, query: str, user_id: str, top_k: int = 5) -> list[str]:
    """Return the top-k relevant memory strings for a query."""
    try:
        results = memory.search(query=query, filters={"user_id": user_id}, limit=top_k)
        memories = [
            item["memory"]
            for item in results.get("results", [])
            if "memory" in item
        ]
        logger.debug("Retrieved %d memories for query: %s", len(memories), query[:60])
        return memories
    except Exception as exc:
        logger.warning("Memory retrieval failed: %s", exc)
        return []


def should_store(text: str, triggers: tuple) -> bool:
    """Return True if the utterance carries information worth storing."""
    lowered = text.lower()
    return any(phrase in lowered for phrase in triggers)


# ── Core chat function ────────────────────────────────────────
def chat(
    llm: ChatGroq,
    memory: Memory,
    user_query: str,
    user_id: str,
    cfg: AgentConfig,
) -> str:
    """
    Full turn:
      1. Retrieve relevant memories
      2. Build a grounded prompt
      3. Call LLM
      4. Conditionally store new memories
      5. Return response text
    """
    # 1. Retrieve
    memories = retrieve_memories(memory, user_query, user_id)
    memory_block = "\n".join(f"- {m}" for m in memories) if memories else "(none yet)"

    # 2. Build prompt
    system = SystemMessage(content=(
        "You are a memory-aware developer assistant. "
        "Answer ONLY from the provided memory context. "
        "If memory is empty or irrelevant, say: "
        "'I don't have that in memory yet — tell me and I'll remember it.' "
        "Never fabricate projects, names, or technical choices."
    ))
    human = HumanMessage(content=(
        f"### Memory context\n{memory_block}\n\n"
        f"### User question\n{user_query}"
    ))

    # 3. Call LLM
    response = llm.invoke([system, human])
    answer = response.content

    # 4. Store if the query carries new personal context
    if should_store(user_query, cfg.memory_trigger_phrases):
        store_memory(memory, user_query, user_id)

    return answer


# ── Interactive REPL ──────────────────────────────────────────
def run_repl(user_id: str = "dev_user") -> None:
    """
    A simple command-line loop – type 'quit' or 'exit' to stop.
    Special commands:
      /memories   – dump all stored memories
      /clear      – (future) clear memories for this user
    """
    cfg = AgentConfig()
    memory = build_memory(cfg)
    llm = build_llm(cfg)

    print("\nDeveloper Assistant  (type 'exit' to quit)\n")

    while True:
        try:
            user_input = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nGoodbye!")
            break

        if not user_input:
            continue

        if user_input.lower() in {"exit", "quit"}:
            print("Goodbye!")
            break

        if user_input == "/memories":
            mems = retrieve_memories(memory, "", user_id, top_k=20)
            if mems:
                print("\nStored memories:")
                for i, m in enumerate(mems, 1):
                    print(f"  {i}. {m}")
            else:
                print("  (no memories stored yet)")
            print()
            continue

        answer = chat(llm, memory, user_input, user_id, cfg)
        print(f"\nAssistant: {answer}\n")


# ── Demo (non-interactive) ────────────────────────────────────
def run_demo() -> None:
    """
    Scripted demo that shows the memory round-trip without
    needing a terminal.  Useful for notebooks or CI checks.
    """
    cfg = AgentConfig()
    memory = build_memory(cfg)
    llm = build_llm(cfg)

    USER = "demo_user"

    turns = [
        "My name is Naveen and I am building a multimodal RAG pipeline using Groq and LangChain.",
        "I recently switched from OpenAI embeddings to HuggingFace sentence-transformers.",
        "My team prefers async FastAPI endpoints for all our inference services.",
        # Recall queries
        "What AI project am I working on?",
        "Which embedding provider did I switch to and why?",
        "What's my team's preferred API framework?",
    ]

    for query in turns:
        print(f"\n{query}")
        response = chat(llm, memory, query, USER, cfg)
        print(f"{response}")


# ── Entry point ───────────────────────────────────────────────
if __name__ == "__main__":
    import sys

    # Set keys
    if not os.getenv("GROQ_API_KEY"):
        os.environ["GROQ_API_KEY"] = getpass("Enter GROQ API Key: ")
    # Mem0 needs OPENAI_API_KEY present even when using local embeddings
    os.environ.setdefault("OPENAI_API_KEY", "sk-dummy")

    mode = sys.argv[1] if len(sys.argv) > 1 else "repl"

    if mode == "demo":
        run_demo()
    else:
        run_repl()

Вывык

Чэрніца кантролю эксплуатацыі