Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

156 lines
5.9 KiB
Python

"""Thin adapter over the GraphRAG (microsoft/graphrag) Python API.
This is the ONLY module that imports ``graphrag``. Everything GraphRAG-version
sensitive lives here, so a schema/API shift between releases is a one-file fix.
Pinned to the 3.x line (``graphrag>=3,<4``); the indexing/query surface mirrors
``graphrag.cli.{index,query}`` for that line.
All imports are lazy so the package only loads when a GraphRAG KB is actually
used — DeepTutor runs fine without the optional dependency installed.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Any
from .config import DEFAULT_MODE, normalize_mode, query_config_from_settings
logger = logging.getLogger(__name__)
# Fallback response style + community granularity. The live values come from the
# persisted graphrag.json slice (query_config_from_settings); these constants are
# kept for tests / call sites that reference the defaults directly.
RESPONSE_TYPE = "Multiple Paragraphs"
DEFAULT_COMMUNITY_LEVEL = 2
# Per-mode output tables the query API needs (mirrors graphrag.cli.query).
_OUTPUTS_BY_MODE: dict[str, tuple[list[str], list[str]]] = {
"global": (["entities", "communities", "community_reports"], []),
"local": (
["communities", "community_reports", "text_units", "relationships", "entities"],
["covariates"],
),
"drift": (
["communities", "community_reports", "text_units", "relationships", "entities"],
[],
),
"basic": (["text_units"], []),
}
def _load_config(root_dir: Path):
from graphrag.config.load_config import load_config
return load_config(root_dir=Path(root_dir))
async def build(root_dir: Path, *, is_update: bool = False) -> None:
"""Run the GraphRAG indexing pipeline rooted at ``root_dir``.
Raises on any failed workflow so the caller can surface an error and clean
up the (incomplete) version directory.
"""
from graphrag.api import build_index
from graphrag.config.enums import IndexingMethod
config = _load_config(root_dir)
logger.info("GraphRAG: building index at %s (update=%s)", root_dir, is_update)
results = await build_index(
config=config,
method=IndexingMethod.Standard,
is_update_run=is_update,
)
errors = [r for r in results if getattr(r, "error", None) is not None]
if errors:
detail = "; ".join(f"{r.workflow}: {r.error}" for r in errors[:3])
raise RuntimeError(f"GraphRAG indexing failed: {detail}")
async def _resolve_outputs(config, names: list[str], optional: list[str]) -> dict[str, Any]:
"""Load the requested output parquet tables as DataFrames (mirrors the CLI)."""
from graphrag.data_model.data_reader import DataReader
from graphrag_storage import create_storage
from graphrag_storage.tables.table_provider_factory import create_table_provider
storage_obj = create_storage(config.output_storage)
table_provider = create_table_provider(config.table_provider, storage=storage_obj)
reader = DataReader(table_provider)
frames: dict[str, Any] = {}
for name in names:
frames[name] = await getattr(reader, name)()
for name in optional:
frames[name] = await getattr(reader, name)() if await table_provider.has(name) else None
return frames
async def search(root_dir: Path, query: str, mode: str | None = None) -> tuple[str, dict]:
"""Run a GraphRAG query and return ``(response_text, context_data)``.
``context_data`` is normalised to a dict of record lists
(reports/entities/relationships/claims/sources) via GraphRAG's own helper.
"""
import graphrag.api as api
from graphrag.utils.api import reformat_context_data
resolved_mode = normalize_mode(mode)
cfg = query_config_from_settings()
config = _load_config(root_dir)
names, optional = _OUTPUTS_BY_MODE.get(resolved_mode, _OUTPUTS_BY_MODE[DEFAULT_MODE])
frames = await _resolve_outputs(config, names, optional)
if resolved_mode == "global":
response, context = await api.global_search(
config=config,
entities=frames["entities"],
communities=frames["communities"],
community_reports=frames["community_reports"],
community_level=None,
dynamic_community_selection=cfg.dynamic_community_selection,
response_type=cfg.response_type,
query=query,
)
elif resolved_mode == "drift":
response, context = await api.drift_search(
config=config,
entities=frames["entities"],
communities=frames["communities"],
community_reports=frames["community_reports"],
text_units=frames["text_units"],
relationships=frames["relationships"],
community_level=cfg.community_level,
response_type=cfg.response_type,
query=query,
)
elif resolved_mode == "basic":
response, context = await api.basic_search(
config=config,
text_units=frames["text_units"],
response_type=cfg.response_type,
query=query,
)
else: # local (default)
response, context = await api.local_search(
config=config,
entities=frames["entities"],
communities=frames["communities"],
community_reports=frames["community_reports"],
text_units=frames["text_units"],
relationships=frames["relationships"],
covariates=frames.get("covariates"),
community_level=cfg.community_level,
response_type=cfg.response_type,
query=query,
)
try:
context_data = reformat_context_data(context) if isinstance(context, dict) else {}
except Exception: # pragma: no cover - context shape is best-effort
context_data = {}
return str(response), context_data
__all__ = ["build", "search", "RESPONSE_TYPE", "DEFAULT_COMMUNITY_LEVEL"]