Ваш RAG не умеет думать. А мой умеет. ai.. ai. graphrag.. ai. graphrag. HippoRAG.. ai. graphrag. HippoRAG. rag.. ai. graphrag. HippoRAG. rag. rag ai.. ai. graphrag. HippoRAG. rag. rag ai. rag pipeline.. ai. graphrag. HippoRAG. rag. rag ai. rag pipeline. RAG система.. ai. graphrag. HippoRAG. rag. rag ai. rag pipeline. RAG система. ruvds_статьи.. ai. graphrag. HippoRAG. rag. rag ai. rag pipeline. RAG система. ruvds_статьи. исскуство.
Ваш RAG не умеет думать. А мой умеет - 1

Базовые 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 ООО «МТ ФИНАНС»

Автор: rRenegat

Источник