Строим мощного ИИ-агента с долговременной памятью на базе LangGraph, RAG и веб-скрапера

Строим мощного ИИ-агента с долговременной памятью на базе LangGraph, RAG и веб-скрапера

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

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

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

В результате:

  • агент не учитывает ваши предпочтения;
  • теряется сквозная логика в долгосрочных проектах;
  • приходится повторять одни и те же объяснения;
  • невозможно выстроить доверительный диалог.

В общении людей ценятся общие воспоминания и понимание друг друга, и ИИ-ассистенты заслуживают такой же «памяти».

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

Одно из таких решений — Mem0, обеспечивающее надёжную долговременную память для LLM: система сохраняет информацию между сессиями, делая диалог более естественным и персонализированным.


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


Теперь небольшой демо-пример работы живого чат-бота.

Пример взаимодействия с чат-ботом

1. Инициализация рабочего пространства

При вводе запроса вызывается функция chat_with_memories:

  • создаётся пустая среда с полями для воспоминаний, контекста документов и ответа;
  • устанавливаются стартовые параметры ИИ-агента.

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

Агент ищет в памяти до 50 релевантных записей по запросу, формирует из них текстовый блок и добавляет в контекст.

3. Получение новостного контента

Через RSS-ленты Seeking Alpha, Yahoo Finance и Investing.com собираются актуальные статьи:

  • парсинг через requests и BeautifulSoup;
  • выбор самых релевантных фрагментов для контекста.

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

ИИ объединяет пользовательский запрос, извлечённые воспоминания и новостной контент в системную подсказку и формирует ответ (~50 слов, по одному предложению на строку).

5. Сохранение истории

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

Mem0 и другие решения для памяти

Ранее основной подход к «памяти» в ИИ — RAG (Retrieval-Augmented Generation), который извлекает данные из внешних источников. Однако у RAG нет встроенного хранилища, и для долгосрочного контекста требуется постоянное обновление базы.

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

Готовы кодить?

Установите зависимости:

pip install -r requirements.txt

Импортируем ключевые библиотеки:

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. Веб-скрапер

Функция fetch_seekingalpha_trending_news() парсит RSS-ленты, скачивает страницы с новостями и сохраняет до 30 статей в ./data/news.txt.

import feedparser
import requests
from bs4 import BeautifulSoup

def get_folder_path(pre_path, folder_name): path = f'{pre_path}/{folder_name}' os.makedirs(path, exist_ok=True) return 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_dir = get_folder_path('.', 'data') out_file = f'{data_dir}/news.txt' entries = []

for url in feeds:
    try:
        feed = feedparser.parse(url)
        entries.extend(feed.entries[:15])
    except Exception as e:
        print(f"Error parsing {url}: {e}")

headers = {'User-Agent': 'Mozilla/5.0'}
with open(out_file, "w", encoding="utf-8") as f:
    for item in entries[:30]:
        title = item.get('title', 'No title')
        link = item.get('link', '')
        try:
            resp = requests.get(link, headers=headers, timeout=5)
            soup = BeautifulSoup(resp.content, 'lxml')
            paragraphs = soup.find_all('p')
            content = "\n".join(p.get_text() for p in paragraphs[:10])
        except:
            content = BeautifulSoup(item.get('summary', ''), 'lxml').get_text()
        f.write(f"{title}\n\n{content}\n\n{'-'*50}\n\n")
print(f"✓ Fetched {len(entries)} articles")</code></pre>

2. Настройка RAG и переранжирования

Конфигурируем модели и хранилище FAISS, оборачиваем его в ContextualCompressionRetriever с переранжером Flashrank.

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. Долговременная память

Инициализируем Mem0 и определяем функции для работы с памятью, документами и генерацией ответа:

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: mems = memory.search(query=state["message"], user_id=state["user_id"], limit=50) state["memories_str"] = "\n".join(f"- {e['memory']}" for e in mems["results"]) return state

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

def generate_response(state: GraphState) -> GraphState: prompt = f\"\"\" You are a helpful AI. Answer based on query, memories, and docs. Reply in ~50 English words, each sentence on a new line.

User Memories: {state["memories_str"]}

Docs: {state["docs_ctx"]} \"\"\" messages = [SystemMessage(content=prompt), HumanMessage(content=state["message"])] result = llm.invoke(messages) state["response"] = result.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

Определяем последовательность узлов: извлечение воспоминаний → получение документов → генерация ответа → сохранение в память. Затем компилируем граф в единый pipeline.

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) оборачивает вызов графа, а main() реализует консольный диалог с командой «exit» для завершения.

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.

Исходный код проекта на GitHub: смотреть репозиторий

 

Источник

Читайте также