В этой статье я поделюсь с вами очень быстрым руководством, которое покажет, как создать мультиагентного чат-бота с использованием LangGraph, RAG и долговременной памяти для создания мощного чат-бота-агента для вашего бизнеса или личного использования.

При создании ИИ-агента одним из важнейших элементов является память. Правильная система памяти необходима для запоминания прошлых разговоров с пользователем и дальнейшего их использования для понимания контекста.

В последнее время я все больше работаю над созданием внутренних ИИ-агентов, но всегда сталкиваюсь с проблемами в системах памяти. Когда я долго взаимодействую с ИИ-агентом, он часто теряет контекст или забывает предыдущие разговоры, что создает ощущение, будто я разговариваю с кем-то, страдающим амнезией.

Агент начинает каждый разговор с чистого листа и не помнит предыдущий контекст или извлеченные уроки. Это означает, что:

  • Он не помнит ваши личные настройки или предпочтения.

  • Ему не хватает последовательности в долгосрочных проектах.

  • Одно и то же объяснение приходится повторять много раз.

  • Он не может выстраивать глубокие доверительные отношения.

В человеческих отношениях лучшее общение возникает из общего опыта и узнанных друг о друге черт, и ИИ нуждается в такой же способности.

Однако технология, позволяющая наделить ИИ памятью, уже существует, и разрабатывается множество проектов с открытым исходным кодом.

Именно здесь на сцену выходит Mem0, чтобы решить эту проблему, предоставляя постоянную память для ИИ-моделей. Это помогает им сохранять информацию между сессиями, делая взаимодействие с пользователем более естественным и обеспечивая персонализацию для больших языковых моделей, чтобы предоставлять уникальный опыт каждому пользователю.


Делегируйте часть рутинных задач вместе с BotHub! Для доступа к сервису не требуется VPN и можно использовать российскую карту. По ссылке вы можете получить 100 000 бесплатных токенов для первых задач и приступить к работе с нейросетями прямо сейчас!


Итак, позвольте мне быстро продемонстрировать живого чат-бота, чтобы показать, что я имею в виду.

1. Вход в рабочее пространство ИИ

Когда вы вводите запрос, он сначала попадает в функцию chat_with_memories.

  • Это настраивает рабочее пространство ИИ.

  • Состояние инициализируется с пустыми полями для воспоминаний, контекста документов и ответа.

2. Извлечение воспоминаний

ИИ-агент ищет в своем хранилище памяти прошлые разговоры или заметки, связанные с вашим запросом.

  • Он собирает до 50 релевантных записей.

  • Эти записи форматируются в строку для контекста.

3. Извлечение документов

Далее ИИ-агент извлекает релевантные новостные статьи:

  • Собирает данные из RSS-лент Seeking Alpha, Yahoo Finance и Investing.com.

  • Загружает полные статьи с помощью requests и парсит их с помощью BeautifulSoup.

  • Извлекает наиболее релевантное содержимое и добавляет его в контекст.

4. Генерация ответа

Имея под рукой воспоминания и документы, ИИ конструирует системную подсказку, объединяя:

  • Ваш запрос

  • Релевантные воспоминания

  • Извлеченный новостной контент Затем он вызывает языковую модель для получения краткого ответа.

  • Выполняются инструкции, такие как «ответь примерно в 50 словах» и «каждое предложение пиши с новой строки».

5. Сохранение взаимодействия

Наконец:

  • ИИ сохраняет ответ обратно в память.

  • Это позволяет ему помнить это взаимодействие для будущих запросов.

  • Окончательный ответ аккуратно отображается в терминале, готовый к следующему вопросу.

Mem0 и другие слои памяти

До появления Mem0 основным подходом к памяти ИИ была технология Retrieval-Augmented Generation (RAG). RAG, извлекая релевантные данные и генерируя ответы, хорошо себя показывал во многих сценариях применения.

Однако возможности RAG в области памяти ограничены: у него нет встроенного механизма памяти, что делает его неэффективным для обработки долгосрочной информации и требует постоянного обновления базы данных для поддержания точности. В отличие от этого, Mem0 значительно расширяет возможности памяти ИИ.

Mem0 постоянно запоминает предоставленные пользователем данные, включая текст, изображения, голосовые заметки и PDF-файлы, формируя всеобъемлющую базу знаний.

Это позволяет ИИ эффективно работать в течение длительных периодов и, при необходимости, получать доступ к релевантным воспоминаниям, предоставляя более точные ответы.

Давайте начнем кодить!

Теперь давайте шаг за шагом разберемся, как создать чат-бота с LangGraph, Mem0, долговременной памятью, RAG и веб-скрапером. Мы установим библиотеки, которые поддерживают модель. Для этого выполним pip install requirements.

pip install -r requirements.txt

Следующий шаг - обычный: мы импортируем соответствующие библиотеки, значение которых станет очевидным по ходу дела.

  • mem0: добавляет слой памяти к ИИ-приложениям, делая их stateful (с сохранением состояния), что позволяет им хранить и вызывать взаимодействия с пользователем, предпочтения и релевантный контекст.

  • FlashrankRerank: это ультралегкая и сверхбыстрая библиотека Python для добавления переранжирования в ваши существующие конвейеры поиска и извлечения.

  • Contextual Compression Retriever: это система, предназначенная для эффективного извлечения и сжатия релевантной информации из большого набора документов.

import os
import logging
import warnings
from dotenv import load_dotenv
from mem0 import Memory
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import DirectoryLoader, UnstructuredFileLoader
from langchain_community.vectorstores import FAISS
from langchain_community.document_compressors import FlashrankRerank
from langchain.retrievers import ContextualCompressionRetriever
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END
from typing import TypedDict
from scraper import fetch_seekingalpha_trending_news

1. Веб-скрапер

Давайте импортируем os для файловых операций, feedparser для чтения RSS-лент, requests для загрузки веб-страниц и BeautifulSoup для парсинга HTML. Далее, вспомогательная функция get_folder_path(pre_path, folder_name) создает путь вида pre_path/folder_name, создает эту папку, если она не существует, и возвращает путь. Затем fetch_seekingalpha_trending_news() определяет список URL-адресов RSS-лент, обеспечивает наличие папки ./data и пути news.txt, и собирает элементы лент, парся каждую ленту (она берет до 15 записей с каждой ленты и пропускает те, которые не удалось обработать).

После этого она устанавливает User-Agent, похожий на браузерный, открывает news.txt для записи в кодировке UTF-8 и для 30 собранных статей извлекает title и link. Она пытается выполнить GET-запрос по ссылке с 5-секундным тайм-аутом и использует BeautifulSoup с парсером lxml, чтобы взять первые десять параграфов <p> в качестве тела статьи. Если извлечение/парсинг не удается, она возвращается к summary или description из записи ленты. Заголовок и очищенное содержимое каждой статьи записываются в файл с разделителем из дефисов, и по завершении.

Функция выводит, сколько статей было собрано, и возвращает пустой список.

import os
import feedparser
import requests
from bs4 import BeautifulSoup

def get_folder_path(pre_path, folder_name):
    folder_path = f'{pre_path}/{folder_name}'
    os.makedirs(folder_path, exist_ok=True)
    return folder_path

def fetch_seekingalpha_trending_news():
    feeds = [
        'https://seekingalpha.com/feed.xml',
        'https://feeds.finance.yahoo.com/rss/2.0/headline',
        'https://www.investing.com/rss/news.rss',
    ]
    
    data_path = get_folder_path('.', 'data')
    new_path = f'{data_path}/news.txt'
    
    articles = []
    for feed_url in feeds:
        try:
            feed = feedparser.parse(feed_url)
            articles.extend(feed.entries[:15])
        except Exception as e:
            print(f"Error {feed_url}: {e}")
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
    }
    
    with open(new_path, "w", encoding="utf-8") as f:
        for entry in articles[:30]:
            title = entry.get('title', 'No title')
            link = entry.get('link', '')
            
            # Try to fetch full article
            try:
                resp = requests.get(link, headers=headers, timeout=5)
                soup = BeautifulSoup(resp.content, 'lxml')
                # Extract main content - works for most news sites
                paragraphs = soup.find_all('p')
                content = '\n'.join([p.get_text() for p in paragraphs[:10]])
            except:
                # Fallback to summary
                content = entry.get('summary', entry.get('description', ''))
                if content:
                    content = BeautifulSoup(content, 'lxml').get_text()
            
            f.write(f"{title}\n\n{content}\n\n{'-'*50}\n\n")
    
    print(f"✓ Fetched {len(articles)} articles")
    return []

2. RAG Rerank (Переранжирование)

Давайте настроим модели и API-ключ. Я выбираю gpt-4o для чат-модели, text-embedding-3-small для эмбеддингов и загружаю OPENAI_API_KEY из переменных окружения. Затем вызывается fetch_seekingalpha_trending_news(), которая сохраняет новостные статьи в файл ./data/news.txt. Далее создается DirectoryLoader, который загружает все .txt файлы из папки ./data/ с помощью UnstructuredFileLoader, и docs = loader.load() парсит эти файлы в объекты документов. После этого создается векторное хранилище FAISS с помощью vs = FAISS.from_documents(...), где каждый документ эмбеддируется с использованием OpenAIEmbeddings, настроенного с выбранной моделью эмбеддингов и API-ключом. Затем настраивается реранкер FlashrankRerank, который пересортировывает результаты с помощью легкой модели BERT (ms-marco-TinyBERT-L-2-v2) и возвращает топ-5 результатов.

Наконец, ретривер FAISS оборачивается в ContextualCompressionRetriever, который сначала извлекает до 5 кандидатов (k=5) из FAISS, а затем сжимает вывод с помощью реранкера, чтобы сохранить только самую релевантную информацию.

chat_model = "gpt-4o"
emb_model = "text-embedding-3-small"
openai_api_key = os.getenv("OPENAI_API_KEY")

fetch_seekingalpha_trending_news()

loader = DirectoryLoader("./data/", glob="*.txt", loader_cls=UnstructuredFileLoader)
docs = loader.load()
vs = FAISS.from_documents(docs, OpenAIEmbeddings(
    model=emb_model,
    openai_api_key=openai_api_key
))

reranker = FlashrankRerank(model="ms-marco-TinyBERT-L-2-v2", top_n=5)
compressed_retriever = ContextualCompressionRetriever(
    base_retriever=vs.as_retriever(search_kwargs={"k": 5}),
    base_compressor=reranker
)

3. Память

Затем я инициализирую объект Memory() для долговременного хранения и определяю llm с помощью ChatOpenAI с моделью chat_model, нулевой температурой и API-ключом.

Далее определяется тип GraphState, который отслеживает сообщение пользователя, его ID, любые извлеченные воспоминания, извлеченные документы и окончательный ответ. Затем retrieve_memories(state) ищет в хранилище памяти до 50 релевантных прошлых записей на основе запроса пользователя, форматирует их в строку и сохраняет в state["memories_str"]. retrieve_docs(state) запрашивает у compressed_retriever релевантные новости/документы и сохраняет их текстовое содержимое в state["docs_ctx"]. generate_response(state) создает структурированную системную подсказку, которая включает инструкции, объединяет ее с сообщением пользователя и отправляет в LLM, сохраняя ответ ИИ в state["response"]. Наконец, save_to_memory(state) упаковывает запрос пользователя и ответ ИИ в запись истории и добавляет ее обратно в долговременную память для будущего извлечения.

memory = Memory()
llm = ChatOpenAI(
    model=chat_model,
    temperature=0,
    api_key=openai_api_key
)

class GraphState(TypedDict):
    message: str
    user_id: str
    memories_str: str
    docs_ctx: str
    response: str

def retrieve_memories(state: GraphState) -> GraphState:
    relevant_memories = memory.search(query=state["message"], user_id=state["user_id"], limit=50)
    state["memories_str"] = "\n".join(f"- {entry['memory']}" for entry in relevant_memories["results"])
    return state

def retrieve_docs(state: GraphState) -> GraphState:
    docs_res = compressed_retriever.invoke(state["message"])
    state["docs_ctx"] = "\n".join(f"- {d.page_content}" for d in docs_res)
    return state

def generate_response(state: GraphState) -> GraphState:
    system_prompt = f"""
You are a helpful AI. Answer the question based on query and memories.
based on retrieved info. Reply in approximately 50 English words.
A deviation of a few words is acceptable.

User Memories:
{state["memories_str"]}

Docs:
{state["docs_ctx"]}

Instructions:
- When generating the answer, put a line break after each sentence ending (e.g., after ".", "!", or "?").
- Make sure each sentence is on its own line.
"""
    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=state["message"])
    ]
    response = llm.invoke(messages)
    state["response"] = response.content
    return state

def save_to_memory(state: GraphState) -> GraphState:
    history = [
        {"role": "user", "content": state["message"]},
        {"role": "assistant", "content": state["response"]}
    ]
    memory.add(history, user_id=state["user_id"])
    return state

4. Граф

После этого я создал StateGraph с GraphState в качестве общего состояния: сначала он добавляет четыре узла, соответствующие функциям retrieve_memories, retrieve_docs, generate_response и save_to_memory.

Затем он устанавливает retrieve_memories в качестве точки входа, определяет ребра так, чтобы рабочий процесс шел от извлечения воспоминаний → извлечения документов → генерации ответа → сохранения разговора в память, и, наконец, помечает конец графа с помощью END.

Как только все узлы и ребра определены, workflow.compile() преобразует это определение графа в исполняемый конвейер, позволяя запросу пользователя автоматически проходить через каждый шаг по порядку, используя память, извлечение документов, генерацию ответа LLM и обновление памяти в едином, stateful процессе.

workflow = StateGraph(GraphState)
workflow.add_node("retrieve_memories", retrieve_memories)
workflow.add_node("retrieve_docs", retrieve_docs)
workflow.add_node("generate", generate_response)
workflow.add_node("save_memory", save_to_memory)

workflow.set_entry_point("retrieve_memories")
workflow.add_edge("retrieve_memories", "retrieve_docs")
workflow.add_edge("retrieve_docs", "generate")
workflow.add_edge("generate", "save_memory")
workflow.add_edge("save_memory", END)

graph = workflow.compile()

5. Чат

Мы обернули весь рабочий процесс в интерактивный чат-интерфейс.

Функция chat_with_memories(message, user_id) действует как мозг ИИ: она вызывает скомпилированный граф, используя сообщение и ID пользователя, инициализирует поля GraphState (memories_str, docs_ctx, response) как пустые и возвращает ответ, сгенерированный ИИ.

Функция main() открывает разговор, печатая дружелюбный заголовок для ИИ-агента, и входит в цикл, где ожидает ввода пользователя. Если пользователь вводит «exit», чат вежливо завершается.

В противном случае, она вызывает chat_with_memories с фиксированным ID пользователя (news_importer) и генерирует ответ.

def chat_with_memories(message: str, user_id: str = "default_user") -> str:
    result = graph.invoke({
        "message": message,
        "user_id": user_id,
        "memories_str": "",
        "docs_ctx": "",
        "response": ""
    })
    return result["response"]

def main():
    print("Deep Research AI Agent with Seeking Alpha Trending News")
    print("Type 'exit' to quit.\n")
    while True:
        print('\n' + '-'*6 + " Start of Response " + '-'*6)
        user_input = input("You: ").strip()
        if user_input.lower() == "exit":
            print("Goodbye!")
            break
        print("AI: ", end="", flush=True)
        response = chat_with_memories(message=user_input, user_id="news_importer")
        print(response)
        print('\n' + '-'*6 + " End of Response " + '-'*6 + '\n')

if __name__ == "__main__":
    main()

Заключение:

По мере того как технология ИИ продолжает развиваться, «память» станет ключевым фактором в переходе ИИ на следующий этап. mem0 - это интригующее решение, которое напрямую решает проблему «дефицита памяти» у LLM, наделяя ИИ-ассистентов постоянной и интеллектуальной памятью.

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

Исходный код:

- ссылка

Комментарии (0)