diff --git a/deeptutor/api/routers/knowledge.py b/deeptutor/api/routers/knowledge.py index bd60ffee23..137a4c3c4f 100644 --- a/deeptutor/api/routers/knowledge.py +++ b/deeptutor/api/routers/knowledge.py @@ -1208,10 +1208,13 @@ async def update_graphrag_pipeline_config(payload: GraphRagConfigUpdate): class LightRagConfigUpdate(BaseModel): - """Partial update for LightRAG query knobs (omitted fields kept).""" + """Partial update for LightRAG query + indexing knobs (omitted fields kept).""" top_k: int | None = None response_type: str | None = None + max_concurrent_files: int | None = None + llm_model_max_async: int | None = None + entity_extract_max_gleaning: int | None = None @router.get("/rag-pipelines/lightrag/config") diff --git a/deeptutor/services/config/runtime_settings.py b/deeptutor/services/config/runtime_settings.py index 7940f4cd88..247d580f12 100644 --- a/deeptutor/services/config/runtime_settings.py +++ b/deeptutor/services/config/runtime_settings.py @@ -257,15 +257,20 @@ "dynamic_community_selection": False, } -# LightRAG retrieval knobs (HKUDS/LightRAG via RAG-Anything). ``top_k`` is the -# number of entities/relations the query pulls; ``response_type`` mirrors +# LightRAG retrieval + indexing knobs (HKUDS/LightRAG via RAG-Anything). ``top_k`` +# is the number of entities/relations the query pulls; ``response_type`` mirrors # GraphRAG's. These ride into ``QueryParam`` via the engine's aquery() call; # wiring is defensive (an older RAG-Anything that rejects a kwarg degrades to a -# mode-only query). +# mode-only query). ``max_concurrent_files`` maps to RAGAnythingConfig's batch +# knob; ``llm_model_max_async`` / ``entity_extract_max_gleaning`` ride into +# LightRAG's own constructor via RAGAnything's ``lightrag_kwargs`` passthrough. DEFAULT_LIGHTRAG_SETTINGS: dict[str, Any] = { "version": 1, "top_k": 60, "response_type": "Multiple Paragraphs", + "max_concurrent_files": 1, + "llm_model_max_async": 4, + "entity_extract_max_gleaning": 1, } IGNORE_PROCESS_OVERRIDES_ENV = "DEEPTUTOR_IGNORE_PROCESS_ENV_OVERRIDES" @@ -782,6 +787,15 @@ def _normalize_lightrag(self, settings: dict[str, Any]) -> dict[str, Any]: "version": 1, "top_k": _coerce_clamped_int(settings.get("top_k"), 60, 1, 200), "response_type": self._normalize_response_type(settings.get("response_type")), + "max_concurrent_files": _coerce_clamped_int( + settings.get("max_concurrent_files"), 1, 1, 16 + ), + "llm_model_max_async": _coerce_clamped_int( + settings.get("llm_model_max_async"), 4, 1, 32 + ), + "entity_extract_max_gleaning": _coerce_clamped_int( + settings.get("entity_extract_max_gleaning"), 1, 0, 5 + ), } def _normalize_document_parsing(self, settings: dict[str, Any]) -> dict[str, Any]: diff --git a/deeptutor/services/rag/pipelines/lightrag/config.py b/deeptutor/services/rag/pipelines/lightrag/config.py index 70e77f2aea..1b7e15faf0 100644 --- a/deeptutor/services/rag/pipelines/lightrag/config.py +++ b/deeptutor/services/rag/pipelines/lightrag/config.py @@ -86,6 +86,47 @@ def query_kwargs_from_settings() -> dict: return {} +def indexing_kwargs_from_settings() -> dict: + """``RAGAnythingConfig`` batch-processing knobs from runtime settings. + + Only ``max_concurrent_files`` is exposed for now (issue #640); the config + object accepts several other batch/context knobs we deliberately leave on + RAG-Anything's own defaults. Empty on any read error, so a bad settings + file falls back to RAG-Anything's built-in default of 1. + """ + try: + from deeptutor.services.config import load_lightrag_settings + + settings = load_lightrag_settings() + return {"max_concurrent_files": int(settings.get("max_concurrent_files", 1))} + except Exception: + return {} + + +def lightrag_kwargs_from_settings() -> dict: + """Extra kwargs forwarded to LightRAG's own constructor via RAG-Anything's + ``lightrag_kwargs`` passthrough. + + ``llm_model_max_async`` bounds how many concurrent LLM calls LightRAG's + internal priority queue issues (covers both query and entity-extraction + traffic, since both ride the same wrapped ``llm_model_func``). + ``entity_extract_max_gleaning`` controls how many extra extraction passes + LightRAG runs per chunk to recover entities/relations the first pass + missed. Empty on any read error, so a bad settings file falls back to + LightRAG's own built-in defaults. + """ + try: + from deeptutor.services.config import load_lightrag_settings + + settings = load_lightrag_settings() + return { + "llm_model_max_async": int(settings.get("llm_model_max_async", 4)), + "entity_extract_max_gleaning": int(settings.get("entity_extract_max_gleaning", 1)), + } + except Exception: + return {} + + def build_llm_model_func(*, io_bridge: OwnerLoopBridge | None = None): """Wrap DeepTutor's unified LLM callable for LightRAG. @@ -190,6 +231,8 @@ async def request(): "is_lightrag_available", "normalize_mode", "query_kwargs_from_settings", + "indexing_kwargs_from_settings", + "lightrag_kwargs_from_settings", "build_llm_model_func", "build_vision_model_func", "build_embedding_func", diff --git a/deeptutor/services/rag/pipelines/lightrag/engine.py b/deeptutor/services/rag/pipelines/lightrag/engine.py index 8bf237bda5..f2abcd4ee2 100644 --- a/deeptutor/services/rag/pipelines/lightrag/engine.py +++ b/deeptutor/services/rag/pipelines/lightrag/engine.py @@ -23,6 +23,8 @@ build_embedding_func, build_llm_model_func, build_vision_model_func, + indexing_kwargs_from_settings, + lightrag_kwargs_from_settings, normalize_mode, query_kwargs_from_settings, ) @@ -39,13 +41,14 @@ def build_rag(working_dir: Path, *, io_bridge: OwnerLoopBridge | None = None) -> """ from raganything import RAGAnything, RAGAnythingConfig - config = RAGAnythingConfig(working_dir=str(working_dir)) + config = RAGAnythingConfig(working_dir=str(working_dir), **indexing_kwargs_from_settings()) adapter_kwargs = {"io_bridge": io_bridge} if io_bridge is not None else {} rag = RAGAnything( config=config, llm_model_func=build_llm_model_func(**adapter_kwargs), vision_model_func=build_vision_model_func(**adapter_kwargs), embedding_func=build_embedding_func(**adapter_kwargs), + lightrag_kwargs=lightrag_kwargs_from_settings(), ) # DeepTutor always feeds RAG-Anything a pre-parsed ``content_list`` (the # parse layer runs upstream via DeepTutor's own ParseService), so