
В этой статье я поделюсь с вами очень быстрым руководством, которое покажет, как создать мультиагентного чат-бота с использованием 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 внесет значительный вклад в реализацию более персонализированного опыта ИИ, снижая операционные расходы и упрощая разработку.
Исходный код:
- ссылка