Ваш RAG не умеет думать. А мой умеет

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



Базовые RAG-системы уже научились неплохо справляться с прямыми вопросами по тексту. Но только если ответ лежит в одном конкретном абзаце, а вопрос сформулирован почти так же, как сам исходный документ. Попробуйте заставить систему связать факты из трёх разных источников или сделать банальный логический вывод. В большинстве случаев результат будет неутешительным. А уж про поиск скрытых связей я даже спрашивать боюсь.

Сегодня рассмотрим open-source RAG-фреймворк HippoRAG 2. В сфере RAG главным преимуществом данного фреймворка является качество ответов, потому что принципы его работы основаны на реальном человеческом мозге. Давайте разберёмся, откуда он взялся, как устроен изнутри и как его запустить.


Принципы работы

Префикс «Hippo» в названии — это отсылка к гиппокампу, структуре мозга, которая отвечает за формирование и извлечение долговременных воспоминаний. 

Разработчики из Ohio State University взяли за основу теорию гиппокампального индексирования Тейлера и Дисченна. Согласно ей, мозг хранит не сами воспоминания целиком, а лишь некоторые связи между ними, и при запросе восстанавливает полную картину через цепочку ассоциаций. 

В архитектуре HippoRAG каждый компонент соответствует своему нейробиологическому аналогу:

  • LLM играет роль неокортекса. Отвечает за извлечение структурированных представлений из текста. 

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

  • Knowledge граф вместе с алгоритмом PPR (Personalized PageRank) имитирует сам гиппокамп. Он хранит сеть знаний и фактов и умеет строить в ней ассоциативные цепочки при поиске.

В HippoRAG подобный механизм долговременной памяти позволяет выявить скрытые связи между фактами. Речь идёт о многошаговом рассуждении (multi-hop reasoning). Это процесс, при котором система последовательно сопоставляет факты из разных источников по контексту и выстраивает цепочку связей, чтобы ответить на абстрактный вопрос.

Маскот фреймворка :)
Маскот фреймворка :)

Факты внутри системы называются триплетами. Это структурированное представление знаний в виде «субъект — отношение — объект». 

К примеру:

  • (Оливер Бэдмен, является, политик).

    Или:

  •  (Монтебелло, часть округа, Рокленд Каунти).

За извлечение триплетов отвечает OpenIE (Open Information Extraction) с помощью LLM. При индексации фреймворк отправляет каждый чанк документа в языковую модель с инструкцией извлечь все структурированные утверждения в виде троек. Результаты кэшируются в openie_cache/, чтобы при повторном запуске не тратить токены снова.

Зачем это нужно? Потому что для многошагового вопроса вроде: «В каком округе родился политик, который…» — обычный RAG просто найдёт документ про политика и отдельно про округ, а связь не уловит. А HippoRAG 2 склеивает два триплета через общую сущность (политик ? место рождения ? округ). Это и есть так называемая «скрытая связь».

Из всех извлечённых триплетов строится knowledge граф. Не такой огромный, как в GraphRAG, а более компактный(HippoRAG на датасете MuSiQue использует около 9 млн токенов — против 115 млн у GraphRAG).

При загрузке документов помимо создания эмбеддингов для чанков проводится извлечение упомянутых триплетов. Из извлечённых фактов строится knowledge-граф. Соответственно, при ответе на вопрос используется как стандартный проход по эмбеддированным документам с косинусным подобием, так и ранжирование фактов по графу. В этот момент задействуется PPR алгоритм. PPR — это вариация PageRank, где вместо случайного блуждания по всему графу, релевантность узлов измеряется относительно конкретного набора начальных (seed) узлов.

Схема работы
Схема работы

Установка и первый запуск

Установить фреймворк можно через pip или клонировав репозиторий.

conda create -n hipporag python=3.10 conda activate hipporag pip install hipporag 

Далее — пара экспортов (API-ключи и пути к кэшу) из env:

export OPENAI_API_KEY="sk-..." export HF_HOME="/путь/к/кэшу" 

Допустим, у нас есть три документа:

from hipporag import HippoRAG  docs = [     "Oliver Badman is a politician.",     "Montebello is a part of Rockland County.",     "Erik Hort's birthplace is Montebello." ]  hipporag = HippoRAG(     save_dir="my_rag_memory",    # сюда упадёт всё: эмбеддинги, граф, кэш     llm_model_name="gpt-4o-mini",         llm_api_key = OPENAI_API_KEY,     llm_base_url = OPENAI_BASE_URL,     embedding_model_name="nvidia/NV-Embed-v2",     embedding_api_key = EMBEDDING_API_KEY,     embedding_base_url = EMBEDDING_BASE_URL      ) 

Кстати говоря, фреймворк позволяет переопределять base_url для работы с локальными серверами. То есть ничто не мешает поднять собственные FastAPI-серверы с локальными LLM и моделями эмбеддингов или в Google Colab, используя библиотеку transformers, и затем просто указать эндпоинты с их выводом в openai-совместимом формате.

Пример эмбеддинг-модели

Ниже — самописный FastAPI-сервер для эмбеддингов.

import torch from fastapi import FastAPI, HTTPException from pydantic import BaseModel from transformers import AutoTokenizer, AutoModel from typing import List import uvicorn  class EmbeddingRequest(BaseModel):     input: List[str] | str               model: str                          encoding_format: str = "float"    class EmbeddingResponse(BaseModel):     object: str = "list"     data: List[dict]     model: str     usage: dict  MODEL_NAME = "название вашей модели"   device = "cuda" if torch.cuda.is_available() else "cpu"  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) model = AutoModel.from_pretrained(MODEL_NAME).to(device) model.eval()  def embed_texts(texts: List[str]) -> List[List[float]]:     inputs = tokenizer(         texts,         padding=True,         truncation=True,         return_tensors="pt",         max_length=512     ).to(device)      with torch.no_grad():         outputs = model(**inputs)         embeddings = outputs.last_hidden_state.mean(dim=1)      return embeddings.cpu().numpy().tolist()  app = FastAPI(title="Local Embedding Server (OpenAI-compatible)")  @app.get("/v1/models") async def list_models():     return {         "object": "list",         "data": [             {                 "id": MODEL_NAME,                 "object": "model",                 "owned_by": "local",                 "permission": []             }         ]     }  @app.post("/v1/embeddings") async def create_embedding(request: EmbeddingRequest):     texts = [request.input] if isinstance(request.input, str) else request.input      if not texts:         raise HTTPException(status_code=400, detail="Input text list is empty")      try:         embeddings = embed_texts(texts)     except Exception as e:         raise HTTPException(status_code=500, detail=f"Embedding failed: {str(e)}")      response_data = []     for idx, emb in enumerate(embeddings):         response_data.append({             "object": "embedding",             "index": idx,             "embedding": emb         })      return EmbeddingResponse(         data=response_data,         model=MODEL_NAME,         usage={             "prompt_tokens": sum(len(t) for t in texts),             "total_tokens": sum(len(t) for t in texts)         }     )  if __name__ == "__main__":     uvicorn.run(app, host="0.0.0.0", port=8001) 

Теперь этот сервер можно передать в embedding_base_url="http://localhost:8001/v1".

Более простой и производительный вариант — использовать SentenceTransformer. Сервер остаётся полностью совместимым с OpenAI API.

import asyncio from contextlib import asynccontextmanager from typing import List, Union  from fastapi import FastAPI, HTTPException from pydantic import BaseModel from sentence_transformers import SentenceTransformer import uvicorn  class EmbeddingRequest(BaseModel):     input: Union[str, List[str]]       model: str       encoding_format: str = "float"  class EmbeddingResponse(BaseModel):     object: str = "list"     data: List[dict]     model: str     usage: dict  @asynccontextmanager async def lifespan(app: FastAPI):     app.state.model = SentenceTransformer("intfloat/e5-large-v2", device="cuda")     yield     app.state.model = None  app = FastAPI(title="Local Embedding Server (OpenAI-compatible)", lifespan=lifespan)  @app.get("/v1/models") async def list_models():     return {         "object": "list",         "data": [             {                 "id": "local-embedding-model",                 "object": "model",                 "owned_by": "local",                 "permission": []             }         ]     }  @app.post("/v1/embeddings") async def create_embedding(request: EmbeddingRequest):     texts = [request.input] if isinstance(request.input, str) else request.input      if not texts:         raise HTTPException(status_code=400, detail="Input text list is empty")      model: SentenceTransformer = app.state.model     try:         embeddings = model.encode(texts, normalize_embeddings=True)     except Exception as e:         raise HTTPException(status_code=500, detail=f"Embedding failed: {str(e)}")      response_data = [         {             "object": "embedding",             "index": idx,             "embedding": emb.tolist()         }         for idx, emb in enumerate(embeddings)     ]      return EmbeddingResponse(         data=response_data,         model=request.model,         usage={             "prompt_tokens": sum(len(text.split()) for text in texts),             "total_tokens": sum(len(text.split()) for text in texts)         }     )  if __name__ == "__main__":     uvicorn.run(app, host="0.0.0.0", port=8001) 

Индексация документов

Индексация запускается одной командой:

hipporag.index(docs=docs)

Что происходит внутри? 

  • Документы режутся на чанки (стандартно — по предложениям).

  • Каждый чанк эмбеддится.

  • Одновременно LLM обрабатывает каждый чанк через OpenIE и извлекает из него все смысловые триплеты.

  • Триплеты превращаются в узлы и рёбра knowledge-графа.

  • Эмбеддинги узлов графа тоже вычисляются (чтобы потом искать похожие факты). 

Где хранится?

В папке my_rag_memory (которую мы передали в save_dir) создаётся структура: 

  • embeddings/ — плоские файлы с эмбеддингами чанков и узлов графа (обычно .npy или через faiss индекс). 

  • graph/ — сериализованный граф (узлы, рёбра, веса). 

  • openie_cache/ — результаты извлечения триплетов, чтобы при повторном запуске не жечь токены заново.

На данном этапе фреймворк хранит эмбеддинги в формате .parquet. Но ничего не мешает дописать совместимость с векторными БД, особенно в наше время.

Важный нюанс: если вы перезапускаете индекс с теми же документами, HippoRAG 2 не будет заново дёргать OpenIE и LLM, а проверит кэш по хешу текста. Так что за токены, хотя бы тут, можете не переживать.

Что возвращает каждая функция?

retrieve — поиск без генерации ответа:

results = hipporag.retrieve(     queries=["What county is Erik Hort's birthplace a part of?"],     num_to_retrieve=2 ) print(results) 

На выходе — список списков. Для каждого запроса: 

[     [          {"text": "Montebello is a part of Rockland County.", "score": 0.92, "type": "chunk"},         {"text": "Erik Hort's birthplace is Montebello.", "score": 0.87, "type": "chunk"}     ] ] 

Важный момент: type может быть "chunk" (найденный чанк) или "fact" (найденный триплет из графа) — зависит от того, что победило в ранжировании.

rag_qa — выполняет полный цикл: поиск ? передача найденного контекста в LLM ? генерация ответа

answers = hipporag.rag_qa(     queries=["What county is Erik Hort's birthplace a part of?"] ) print(answers) 

Вернёт список строк с ответами, например: ["Rockland County"]. В ответе также возвращается список использованных чанков.

Оценка с gold-данными

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

gold_answers = [["Rockland County"]] gold_docs = [     ["Montebello is a part of Rockland County.",      "Erik Hort's birthplace is Montebello."] ]  eval_results = hipporag.rag_qa(     queries=queries,     gold_docs=gold_docs,     gold_answers=gold_answers ) 

Тогда в eval_results упадёт словарь с метриками: 

  • "retrieval_hit_rate" (попали ли нужные чанки в топ-N), 

  • "answer_accuracy" (точность ответа, часто через F1 или EM), 

  • "latency_seconds" — чтобы потом бенчмаркать.

Удаление и добавление документов

Если понадобится удалить документ, есть hipporag.delete_docs(doc_indices=[0,2]).

Граф перестраивается инкрементально, не с нуля. Для добавления новых данных вызывается hipporag.index(docs=new_docs). Система сама определит, что уже проиндексировано, а что нет.


Заключение

Признаюсь, я обожаю HippoRAG 2. Для меня это максимально удобный инструмент, который ещё и справляется лучше своих аналогов. Естественно, он не универсальная затычка для любой проблемы, но в задачах контекста, рассыпанного по множеству документов, ему нет равных.

Я советую обратить внимание на фреймворк уже за то, что авторам удалось решить задачу ассоциативного рассуждения без многократного роста стоимости запросов. Это ли не чудо?

© 2026 ООО «МТ ФИНАНС»


Телеграм: t.me/ainewsline

Источник: habr.com

Комментарии: