From f35ee34bbf761b0c91055e256c7e4847f166f774 Mon Sep 17 00:00:00 2001 From: Travis Vasceannie Date: Wed, 14 Jan 2026 23:45:48 -0500 Subject: [PATCH] Initial MCP server with Docker support --- .dockerignore | 13 + .gitignore | 11 + Dockerfile | 31 ++ README.md | 52 +++ docker-compose.yml | 15 + openapi.json | 1 + pyproject.toml | 33 ++ src/lightrag_mcp/__init__.py | 5 + src/lightrag_mcp/client.py | 105 +++++ src/lightrag_mcp/server.py | 779 +++++++++++++++++++++++++++++++++++ src/lightrag_mcp/settings.py | 25 ++ src/lightrag_mcp/smoke.py | 72 ++++ 12 files changed, 1142 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 openapi.json create mode 100644 pyproject.toml create mode 100644 src/lightrag_mcp/__init__.py create mode 100644 src/lightrag_mcp/client.py create mode 100644 src/lightrag_mcp/server.py create mode 100644 src/lightrag_mcp/settings.py create mode 100644 src/lightrag_mcp/smoke.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5e7d266 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.venv +__pycache__ +*.pyc +*.pyo +*.pyd +.git +.gitignore +.DS_Store +.cache +.mypy_cache +.pytest_cache +.env +.env.* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40a9f0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.venv/ +__pycache__/ +*.pyc +*.pyo +*.pyd +*.egg-info/ +.cache/ +.mypy_cache/ +.pytest_cache/ +.env +.env.* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cdd38ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.12-slim AS builder +WORKDIR /app +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 + +COPY pyproject.toml README.md openapi.json ./ +COPY src ./src + +RUN pip install --upgrade pip \ + && pip wheel . -w /wheels + +FROM python:3.12-slim AS runtime +WORKDIR /app +ENV PYTHONUNBUFFERED=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 + +COPY --from=builder /wheels /wheels +RUN pip install /wheels/*.whl \ + && rm -rf /wheels + +ENV MCP_TRANSPORT=streamable-http \ + MCP_HOST=0.0.0.0 \ + MCP_PORT=8000 \ + LIGHTRAG_BASE_URL=http://host.docker.internal:9621 + +EXPOSE 8000 + +ENTRYPOINT ["lightrag-mcp"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..4056b41 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# LightRAG MCP Server + +An MCP server exposing the LightRAG Server API as tools, resources, and prompts for coding agents. + +## Features + +- Retrieval tools: `query_data`, `query`, `query_stream`, `query_stream_chunks` +- Ingestion tools: `ingest_text`, `ingest_texts`, `ingest_file`, `ingest_files`, `upload_document` +- Freshness tools: `scan_documents`, `scan_and_wait`, `pipeline_status`, `wait_for_idle`, `track_status` +- Memory tool: `ingest_memory` for lessons, preferences, decisions, structures, functions, relationships +- Graph tools: entity/relation CRUD, entity existence check, label search, graph export +- Health tool: `health` +- Macro tool: `refresh_and_query` (scan -> wait idle -> query_data -> query) +- Resources: health, pipeline status, documents, graph, labels (list + popular), status counts +- Prompts: evidence-first answering, refresh-then-query, record project memory + +## Quickstart + +```bash +# Create a venv and install +python -m venv .venv +. .venv/bin/activate +pip install -e . + +# Run the MCP server (streamable HTTP) +export MCP_TRANSPORT=streamable-http +export MCP_HOST=127.0.0.1 +export MCP_PORT=8000 +export LIGHTRAG_BASE_URL=http://127.0.0.1:9621 +lightrag-mcp + +# Smoke test (health + optional retrieval) +lightrag-mcp-smoke --query "What is this project?" --format pretty +``` + +## Configuration + +- `LIGHTRAG_BASE_URL` (default `http://127.0.0.1:9621`) +- `LIGHTRAG_TIMEOUT_S` (default `60`) +- `LIGHTRAG_POLL_INTERVAL_S` (default `1`) +- `LIGHTRAG_POLL_TIMEOUT_S` (default `120`) +- `MCP_TRANSPORT` (default `streamable-http`) +- `MCP_HOST` (default `127.0.0.1`) +- `MCP_PORT` (default `8000`) +- `MCP_SERVER_NAME` (default `LightRAG MCP`) + +## Notes + +- `query_stream` collects the streaming response and returns it as a single string. +- `query_stream_chunks` returns chunked output and reports progress to clients that support progress events. +- `refresh_and_query` is a convenience macro for evidence-first workflows. +- `ingest_file(s)` chunk local files and store them with `file_source` references. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..114c2c4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + lightrag-mcp: + build: + context: . + image: git.baked.rocks/vasceannie/lightrag-mcp:latest + environment: + MCP_TRANSPORT: streamable-http + MCP_HOST: 0.0.0.0 + MCP_PORT: 8000 + LIGHTRAG_BASE_URL: http://host.docker.internal:9621 + ports: + - "8000:8000" + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..d17d822 --- /dev/null +++ b/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"LightRAG Server API","description":"Providing API for LightRAG core, Web UI and Ollama Model Emulation\n\n[View ReDoc documentation](/redoc)","version":"0262"},"paths":{"/documents/scan":{"post":{"tags":["documents"],"summary":"Scan For New Documents","description":"Trigger the scanning process for new documents.\n\nThis endpoint initiates a background task that scans the input directory for new documents\nand processes them. If a scanning process is already running, it returns a status indicating\nthat fact.\n\nReturns:\n ScanResponse: A response object containing the scanning status and track_id","operationId":"scan_for_new_documents_documents_scan_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/upload":{"post":{"tags":["documents"],"summary":"Upload To Input Dir","description":"Upload a file to the input directory and index it.\n\nThis API endpoint accepts a file through an HTTP POST request, checks if the\nuploaded file is of a supported type, saves it in the specified input directory,\nindexes it for retrieval, and returns a success status with relevant details.\n\nArgs:\n background_tasks: FastAPI BackgroundTasks for async processing\n file (UploadFile): The file to be uploaded. It must have an allowed extension.\n\nReturns:\n InsertResponse: A response object containing the upload status and a message.\n status can be \"success\", \"duplicated\", or error is thrown.\n\nRaises:\n HTTPException: If the file type is not supported (400) or other errors occur (500).","operationId":"upload_to_input_dir_documents_upload_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_to_input_dir_documents_upload_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/text":{"post":{"tags":["documents"],"summary":"Insert Text","description":"Insert text into the RAG system.\n\nThis endpoint allows you to insert text data into the RAG system for later retrieval\nand use in generating responses.\n\nArgs:\n request (InsertTextRequest): The request body containing the text to be inserted.\n background_tasks: FastAPI BackgroundTasks for async processing\n\nReturns:\n InsertResponse: A response object containing the status of the operation.\n\nRaises:\n HTTPException: If an error occurs during text processing (500).","operationId":"insert_text_documents_text_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsertTextRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/texts":{"post":{"tags":["documents"],"summary":"Insert Texts","description":"Insert multiple texts into the RAG system.\n\nThis endpoint allows you to insert multiple text entries into the RAG system\nin a single request.\n\nArgs:\n request (InsertTextsRequest): The request body containing the list of texts.\n background_tasks: FastAPI BackgroundTasks for async processing\n\nReturns:\n InsertResponse: A response object containing the status of the operation.\n\nRaises:\n HTTPException: If an error occurs during text processing (500).","operationId":"insert_texts_documents_texts_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsertTextsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents":{"delete":{"tags":["documents"],"summary":"Clear Documents","description":"Clear all documents from the RAG system.\n\nThis endpoint deletes all documents, entities, relationships, and files from the system.\nIt uses the storage drop methods to properly clean up all data and removes all files\nfrom the input directory.\n\nReturns:\n ClearDocumentsResponse: A response object containing the status and message.\n - status=\"success\": All documents and files were successfully cleared.\n - status=\"partial_success\": Document clear job exit with some errors.\n - status=\"busy\": Operation could not be completed because the pipeline is busy.\n - status=\"fail\": All storage drop operations failed, with message\n - message: Detailed information about the operation results, including counts\n of deleted files and any errors encountered.\n\nRaises:\n HTTPException: Raised when a serious error occurs during the clearing process,\n with status code 500 and error details in the detail field.","operationId":"clear_documents_documents_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearDocumentsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["documents"],"summary":"Documents","description":"Get the status of all documents in the system. This endpoint is deprecated; use /documents/paginated instead.\nTo prevent excessive resource consumption, a maximum of 1,000 records is returned.\n\nThis endpoint retrieves the current status of all documents, grouped by their\nprocessing status (PENDING, PROCESSING, PREPROCESSED, PROCESSED, FAILED). The results are\nlimited to 1000 total documents with fair distribution across all statuses.\n\nReturns:\n DocsStatusesResponse: A response object containing a dictionary where keys are\n DocStatus values and values are lists of DocStatusResponse\n objects representing documents in each status category.\n Maximum 1000 documents total will be returned.\n\nRaises:\n HTTPException: If an error occurs while retrieving document statuses (500).","operationId":"documents_documents_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocsStatusesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/pipeline_status":{"get":{"tags":["documents"],"summary":"Get Pipeline Status","description":"Get the current status of the document indexing pipeline.\n\nThis endpoint returns information about the current state of the document processing pipeline,\nincluding the processing status, progress information, and history messages.\n\nReturns:\n PipelineStatusResponse: A response object containing:\n - autoscanned (bool): Whether auto-scan has started\n - busy (bool): Whether the pipeline is currently busy\n - job_name (str): Current job name (e.g., indexing files/indexing texts)\n - job_start (str, optional): Job start time as ISO format string\n - docs (int): Total number of documents to be indexed\n - batchs (int): Number of batches for processing documents\n - cur_batch (int): Current processing batch\n - request_pending (bool): Flag for pending request for processing\n - latest_message (str): Latest message from pipeline processing\n - history_messages (List[str], optional): List of history messages (limited to latest 1000 entries,\n with truncation message if more than 1000 messages exist)\n\nRaises:\n HTTPException: If an error occurs while retrieving pipeline status (500)","operationId":"get_pipeline_status_documents_pipeline_status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/delete_document":{"delete":{"tags":["documents"],"summary":"Delete a document and all its associated data by its ID.","description":"Delete documents and all their associated data by their IDs using background processing.\n\nDeletes specific documents and all their associated data, including their status,\ntext chunks, vector embeddings, and any related graph data. When requested,\ncached LLM extraction responses are removed after graph deletion/rebuild completes.\nThe deletion process runs in the background to avoid blocking the client connection.\n\nThis operation is irreversible and will interact with the pipeline status.\n\nArgs:\n delete_request (DeleteDocRequest): The request containing the document IDs and deletion options.\n background_tasks: FastAPI BackgroundTasks for async processing\n\nReturns:\n DeleteDocByIdResponse: The result of the deletion operation.\n - status=\"deletion_started\": The document deletion has been initiated in the background.\n - status=\"busy\": The pipeline is busy with another operation.\n\nRaises:\n HTTPException:\n - 500: If an unexpected internal error occurs during initialization.","operationId":"delete_document_documents_delete_document_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteDocRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteDocByIdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/clear_cache":{"post":{"tags":["documents"],"summary":"Clear Cache","description":"Clear all cache data from the LLM response cache storage.\n\nThis endpoint clears all cached LLM responses regardless of mode.\nThe request body is accepted for API compatibility but is ignored.\n\nArgs:\n request (ClearCacheRequest): The request body (ignored for compatibility).\n\nReturns:\n ClearCacheResponse: A response object containing the status and message.\n\nRaises:\n HTTPException: If an error occurs during cache clearing (500).","operationId":"clear_cache_documents_clear_cache_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearCacheRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearCacheResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/delete_entity":{"delete":{"tags":["documents"],"summary":"Delete Entity","description":"Delete an entity and all its relationships from the knowledge graph.\n\nArgs:\n request (DeleteEntityRequest): The request body containing the entity name.\n\nReturns:\n DeletionResult: An object containing the outcome of the deletion process.\n\nRaises:\n HTTPException: If the entity is not found (404) or an error occurs (500).","operationId":"delete_entity_documents_delete_entity_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEntityRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/delete_relation":{"delete":{"tags":["documents"],"summary":"Delete Relation","description":"Delete a relationship between two entities from the knowledge graph.\n\nArgs:\n request (DeleteRelationRequest): The request body containing the source and target entity names.\n\nReturns:\n DeletionResult: An object containing the outcome of the deletion process.\n\nRaises:\n HTTPException: If the relation is not found (404) or an error occurs (500).","operationId":"delete_relation_documents_delete_relation_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRelationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/track_status/{track_id}":{"get":{"tags":["documents"],"summary":"Get Track Status","description":"Get the processing status of documents by tracking ID.\n\nThis endpoint retrieves all documents associated with a specific tracking ID,\nallowing users to monitor the processing progress of their uploaded files or inserted texts.\n\nArgs:\n track_id (str): The tracking ID returned from upload, text, or texts endpoints\n\nReturns:\n TrackStatusResponse: A response object containing:\n - track_id: The tracking ID\n - documents: List of documents associated with this track_id\n - total_count: Total number of documents for this track_id\n\nRaises:\n HTTPException: If track_id is invalid (400) or an error occurs (500).","operationId":"get_track_status_documents_track_status__track_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"track_id","in":"path","required":true,"schema":{"type":"string","title":"Track Id"}},{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/paginated":{"post":{"tags":["documents"],"summary":"Get Documents Paginated","description":"Get documents with pagination support.\n\nThis endpoint retrieves documents with pagination, filtering, and sorting capabilities.\nIt provides better performance for large document collections by loading only the\nrequested page of data.\n\nArgs:\n request (DocumentsRequest): The request body containing pagination parameters\n\nReturns:\n PaginatedDocsResponse: A response object containing:\n - documents: List of documents for the current page\n - pagination: Pagination information (page, total_count, etc.)\n - status_counts: Count of documents by status for all documents\n\nRaises:\n HTTPException: If an error occurs while retrieving documents (500).","operationId":"get_documents_paginated_documents_paginated_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedDocsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/status_counts":{"get":{"tags":["documents"],"summary":"Get Document Status Counts","description":"Get counts of documents by status.\n\nThis endpoint retrieves the count of documents in each processing status\n(PENDING, PROCESSING, PROCESSED, FAILED) for all documents in the system.\n\nReturns:\n StatusCountsResponse: A response object containing status counts\n\nRaises:\n HTTPException: If an error occurs while retrieving status counts (500).","operationId":"get_document_status_counts_documents_status_counts_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusCountsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/reprocess_failed":{"post":{"tags":["documents"],"summary":"Reprocess Failed Documents","description":"Reprocess failed and pending documents.\n\nThis endpoint triggers the document processing pipeline which automatically\npicks up and reprocesses documents in the following statuses:\n- FAILED: Documents that failed during previous processing attempts\n- PENDING: Documents waiting to be processed\n- PROCESSING: Documents with abnormally terminated processing (e.g., server crashes)\n\nThis is useful for recovering from server crashes, network errors, LLM service\noutages, or other temporary failures that caused document processing to fail.\n\nThe processing happens in the background and can be monitored by checking the\npipeline status. The reprocessed documents retain their original track_id from\ninitial upload, so use their original track_id to monitor progress.\n\nReturns:\n ReprocessResponse: Response with status and message.\n track_id is always empty string because reprocessed documents retain\n their original track_id from initial upload.\n\nRaises:\n HTTPException: If an error occurs while initiating reprocessing (500).","operationId":"reprocess_failed_documents_documents_reprocess_failed_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReprocessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/cancel_pipeline":{"post":{"tags":["documents"],"summary":"Cancel Pipeline","description":"Request cancellation of the currently running pipeline.\n\nThis endpoint sets a cancellation flag in the pipeline status. The pipeline will:\n1. Check this flag at key processing points\n2. Stop processing new documents\n3. Cancel all running document processing tasks\n4. Mark all PROCESSING documents as FAILED with reason \"User cancelled\"\n\nThe cancellation is graceful and ensures data consistency. Documents that have\ncompleted processing will remain in PROCESSED status.\n\nReturns:\n CancelPipelineResponse: Response with status and message\n - status=\"cancellation_requested\": Cancellation flag has been set\n - status=\"not_busy\": Pipeline is not currently running\n\nRaises:\n HTTPException: If an error occurs while setting cancellation flag (500).","operationId":"cancel_pipeline_documents_cancel_pipeline_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelPipelineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/query":{"post":{"tags":["query"],"summary":"Query Text","description":"Comprehensive RAG query endpoint with non-streaming response. Parameter \"stream\" is ignored.\n\nThis endpoint performs Retrieval-Augmented Generation (RAG) queries using various modes\nto provide intelligent responses based on your knowledge base.\n\n**Query Modes:**\n- **local**: Focuses on specific entities and their direct relationships\n- **global**: Analyzes broader patterns and relationships across the knowledge graph\n- **hybrid**: Combines local and global approaches for comprehensive results\n- **naive**: Simple vector similarity search without knowledge graph\n- **mix**: Integrates knowledge graph retrieval with vector search (recommended)\n- **bypass**: Direct LLM query without knowledge retrieval\n\nconversation_history parameteris sent to LLM only, does not affect retrieval results.\n\n**Usage Examples:**\n\nBasic query:\n```json\n{\n \"query\": \"What is machine learning?\",\n \"mode\": \"mix\"\n}\n```\n\nBypass initial LLM call by providing high-level and low-level keywords:\n```json\n{\n \"query\": \"What is Retrieval-Augmented-Generation?\",\n \"hl_keywords\": [\"machine learning\", \"information retrieval\", \"natural language processing\"],\n \"ll_keywords\": [\"retrieval augmented generation\", \"RAG\", \"knowledge base\"],\n \"mode\": \"mix\"\n}\n```\n\nAdvanced query with references:\n```json\n{\n \"query\": \"Explain neural networks\",\n \"mode\": \"hybrid\",\n \"include_references\": true,\n \"response_type\": \"Multiple Paragraphs\",\n \"top_k\": 10\n}\n```\n\nConversation with history:\n```json\n{\n \"query\": \"Can you give me more details?\",\n \"conversation_history\": [\n {\"role\": \"user\", \"content\": \"What is AI?\"},\n {\"role\": \"assistant\", \"content\": \"AI is artificial intelligence...\"}\n ]\n}\n```\n\nArgs:\n request (QueryRequest): The request object containing query parameters:\n - **query**: The question or prompt to process (min 3 characters)\n - **mode**: Query strategy - \"mix\" recommended for best results\n - **include_references**: Whether to include source citations\n - **response_type**: Format preference (e.g., \"Multiple Paragraphs\")\n - **top_k**: Number of top entities/relations to retrieve\n - **conversation_history**: Previous dialogue context\n - **max_total_tokens**: Token budget for the entire response\n\nReturns:\n QueryResponse: JSON response containing:\n - **response**: The generated answer to your query\n - **references**: Source citations (if include_references=True)\n\nRaises:\n HTTPException:\n - 400: Invalid input parameters (e.g., query too short)\n - 500: Internal processing error (e.g., LLM service unavailable)","operationId":"query_text_query_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Successful RAG query response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponse","type":"object","properties":{"response":{"type":"string","description":"The generated response from the RAG system"},"references":{"type":"array","items":{"type":"object","properties":{"reference_id":{"type":"string"},"file_path":{"type":"string"},"content":{"type":"array","items":{"type":"string"},"description":"List of chunk contents from this file (only included when include_chunk_content=True)"}}},"description":"Reference list (only included when include_references=True)"}},"required":["response"]},"examples":{"with_references":{"summary":"Response with references","description":"Example response when include_references=True","value":{"response":"Artificial Intelligence (AI) is a branch of computer science that aims to create intelligent machines capable of performing tasks that typically require human intelligence, such as learning, reasoning, and problem-solving.","references":[{"reference_id":"1","file_path":"/documents/ai_overview.pdf"},{"reference_id":"2","file_path":"/documents/machine_learning.txt"}]}},"with_chunk_content":{"summary":"Response with chunk content","description":"Example response when include_references=True and include_chunk_content=True. Note: content is an array of chunks from the same file.","value":{"response":"Artificial Intelligence (AI) is a branch of computer science that aims to create intelligent machines capable of performing tasks that typically require human intelligence, such as learning, reasoning, and problem-solving.","references":[{"reference_id":"1","file_path":"/documents/ai_overview.pdf","content":["Artificial Intelligence (AI) represents a transformative field in computer science focused on creating systems that can perform tasks requiring human-like intelligence. These tasks include learning from experience, understanding natural language, recognizing patterns, and making decisions.","AI systems can be categorized into narrow AI, which is designed for specific tasks, and general AI, which aims to match human cognitive abilities across a wide range of domains."]},{"reference_id":"2","file_path":"/documents/machine_learning.txt","content":["Machine learning is a subset of AI that enables computers to learn and improve from experience without being explicitly programmed. It focuses on the development of algorithms that can access data and use it to learn for themselves."]}]}},"without_references":{"summary":"Response without references","description":"Example response when include_references=False","value":{"response":"Artificial Intelligence (AI) is a branch of computer science that aims to create intelligent machines capable of performing tasks that typically require human intelligence, such as learning, reasoning, and problem-solving."}},"different_modes":{"summary":"Different query modes","description":"Examples of responses from different query modes","value":{"local_mode":"Focuses on specific entities and their relationships","global_mode":"Provides broader context from relationship patterns","hybrid_mode":"Combines local and global approaches","naive_mode":"Simple vector similarity search","mix_mode":"Integrates knowledge graph and vector retrieval"}}}}}},"400":{"description":"Bad Request - Invalid input parameters","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Query text must be at least 3 characters long"}}}},"500":{"description":"Internal Server Error - Query processing failed","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Failed to process query: LLM service unavailable"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/query/stream":{"post":{"tags":["query"],"summary":"Query Text Stream","description":"Advanced RAG query endpoint with flexible streaming response.\n\nThis endpoint provides the most flexible querying experience, supporting both real-time streaming\nand complete response delivery based on your integration needs.\n\n**Response Modes:**\n- Real-time response delivery as content is generated\n- NDJSON format: each line is a separate JSON object\n- First line: `{\"references\": [...]}` (if include_references=True)\n- Subsequent lines: `{\"response\": \"content chunk\"}`\n- Error handling: `{\"error\": \"error message\"}`\n\n> If stream parameter is False, or the query hit LLM cache, complete response delivered in a single streaming message.\n\n**Response Format Details**\n- **Content-Type**: `application/x-ndjson` (Newline-Delimited JSON)\n- **Structure**: Each line is an independent, valid JSON object\n- **Parsing**: Process line-by-line, each line is self-contained\n- **Headers**: Includes cache control and connection management\n\n**Query Modes (same as /query endpoint)**\n- **local**: Entity-focused retrieval with direct relationships\n- **global**: Pattern analysis across the knowledge graph\n- **hybrid**: Combined local and global strategies\n- **naive**: Vector similarity search only\n- **mix**: Integrated knowledge graph + vector retrieval (recommended)\n- **bypass**: Direct LLM query without knowledge retrieval\n\nconversation_history parameteris sent to LLM only, does not affect retrieval results.\n\n**Usage Examples**\n\nReal-time streaming query:\n```json\n{\n \"query\": \"Explain machine learning algorithms\",\n \"mode\": \"mix\",\n \"stream\": true,\n \"include_references\": true\n}\n```\n\nBypass initial LLM call by providing high-level and low-level keywords:\n```json\n{\n \"query\": \"What is Retrieval-Augmented-Generation?\",\n \"hl_keywords\": [\"machine learning\", \"information retrieval\", \"natural language processing\"],\n \"ll_keywords\": [\"retrieval augmented generation\", \"RAG\", \"knowledge base\"],\n \"mode\": \"mix\"\n}\n```\n\nComplete response query:\n```json\n{\n \"query\": \"What is deep learning?\",\n \"mode\": \"hybrid\",\n \"stream\": false,\n \"response_type\": \"Multiple Paragraphs\"\n}\n```\n\nConversation with context:\n```json\n{\n \"query\": \"Can you elaborate on that?\",\n \"stream\": true,\n \"conversation_history\": [\n {\"role\": \"user\", \"content\": \"What is neural network?\"},\n {\"role\": \"assistant\", \"content\": \"A neural network is...\"}\n ]\n}\n```\n\n**Response Processing:**\n\n```python\nasync for line in response.iter_lines():\n data = json.loads(line)\n if \"references\" in data:\n # Handle references (first message)\n references = data[\"references\"]\n if \"response\" in data:\n # Handle content chunk\n content_chunk = data[\"response\"]\n if \"error\" in data:\n # Handle error\n error_message = data[\"error\"]\n```\n\n**Error Handling:**\n- Streaming errors are delivered as `{\"error\": \"message\"}` lines\n- Non-streaming errors raise HTTP exceptions\n- Partial responses may be delivered before errors in streaming mode\n- Always check for error objects when processing streaming responses\n\nArgs:\n request (QueryRequest): The request object containing query parameters:\n - **query**: The question or prompt to process (min 3 characters)\n - **mode**: Query strategy - \"mix\" recommended for best results\n - **stream**: Enable streaming (True) or complete response (False)\n - **include_references**: Whether to include source citations\n - **response_type**: Format preference (e.g., \"Multiple Paragraphs\")\n - **top_k**: Number of top entities/relations to retrieve\n - **conversation_history**: Previous dialogue context for multi-turn conversations\n - **max_total_tokens**: Token budget for the entire response\n\nReturns:\n StreamingResponse: NDJSON streaming response containing:\n - **Streaming mode**: Multiple JSON objects, one per line\n - References object (if requested): `{\"references\": [...]}`\n - Content chunks: `{\"response\": \"chunk content\"}`\n - Error objects: `{\"error\": \"error message\"}`\n - **Non-streaming mode**: Single JSON object\n - Complete response: `{\"references\": [...], \"response\": \"complete content\"}`\n\nRaises:\n HTTPException:\n - 400: Invalid input parameters (e.g., query too short, invalid mode)\n - 500: Internal processing error (e.g., LLM service unavailable)\n\nNote:\n This endpoint is ideal for applications requiring flexible response delivery.\n Use streaming mode for real-time interfaces and non-streaming for batch processing.","operationId":"query_text_stream_query_stream_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Flexible RAG query response - format depends on stream parameter","content":{"application/json":{"schema":{}},"application/x-ndjson":{"schema":{"type":"string","format":"ndjson","description":"Newline-delimited JSON (NDJSON) format used for both streaming and non-streaming responses. For streaming: multiple lines with separate JSON objects. For non-streaming: single line with complete JSON object.","example":"{\"references\": [{\"reference_id\": \"1\", \"file_path\": \"/documents/ai.pdf\"}]}\n{\"response\": \"Artificial Intelligence is\"}\n{\"response\": \" a field of computer science\"}\n{\"response\": \" that focuses on creating intelligent machines.\"}"},"examples":{"streaming_with_references":{"summary":"Streaming mode with references (stream=true)","description":"Multiple NDJSON lines when stream=True and include_references=True. First line contains references, subsequent lines contain response chunks.","value":"{\"references\": [{\"reference_id\": \"1\", \"file_path\": \"/documents/ai_overview.pdf\"}, {\"reference_id\": \"2\", \"file_path\": \"/documents/ml_basics.txt\"}]}\n{\"response\": \"Artificial Intelligence (AI) is a branch of computer science\"}\n{\"response\": \" that aims to create intelligent machines capable of performing\"}\n{\"response\": \" tasks that typically require human intelligence, such as learning,\"}\n{\"response\": \" reasoning, and problem-solving.\"}"},"streaming_with_chunk_content":{"summary":"Streaming mode with chunk content (stream=true, include_chunk_content=true)","description":"Multiple NDJSON lines when stream=True, include_references=True, and include_chunk_content=True. First line contains references with content arrays (one file may have multiple chunks), subsequent lines contain response chunks.","value":"{\"references\": [{\"reference_id\": \"1\", \"file_path\": \"/documents/ai_overview.pdf\", \"content\": [\"Artificial Intelligence (AI) represents a transformative field...\", \"AI systems can be categorized into narrow AI and general AI...\"]}, {\"reference_id\": \"2\", \"file_path\": \"/documents/ml_basics.txt\", \"content\": [\"Machine learning is a subset of AI that enables computers to learn...\"]}]}\n{\"response\": \"Artificial Intelligence (AI) is a branch of computer science\"}\n{\"response\": \" that aims to create intelligent machines capable of performing\"}\n{\"response\": \" tasks that typically require human intelligence.\"}"},"streaming_without_references":{"summary":"Streaming mode without references (stream=true)","description":"Multiple NDJSON lines when stream=True and include_references=False. Only response chunks are sent.","value":"{\"response\": \"Machine learning is a subset of artificial intelligence\"}\n{\"response\": \" that enables computers to learn and improve from experience\"}\n{\"response\": \" without being explicitly programmed for every task.\"}"},"non_streaming_with_references":{"summary":"Non-streaming mode with references (stream=false)","description":"Single NDJSON line when stream=False and include_references=True. Complete response with references in one message.","value":"{\"references\": [{\"reference_id\": \"1\", \"file_path\": \"/documents/neural_networks.pdf\"}], \"response\": \"Neural networks are computational models inspired by biological neural networks that consist of interconnected nodes (neurons) organized in layers. They are fundamental to deep learning and can learn complex patterns from data through training processes.\"}"},"non_streaming_without_references":{"summary":"Non-streaming mode without references (stream=false)","description":"Single NDJSON line when stream=False and include_references=False. Complete response only.","value":"{\"response\": \"Deep learning is a subset of machine learning that uses neural networks with multiple layers (hence deep) to model and understand complex patterns in data. It has revolutionized fields like computer vision, natural language processing, and speech recognition.\"}"},"error_response":{"summary":"Error during streaming","description":"Error handling in NDJSON format when an error occurs during processing.","value":"{\"references\": [{\"reference_id\": \"1\", \"file_path\": \"/documents/ai.pdf\"}]}\n{\"response\": \"Artificial Intelligence is\"}\n{\"error\": \"LLM service temporarily unavailable\"}"}}}}},"400":{"description":"Bad Request - Invalid input parameters","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Query text must be at least 3 characters long"}}}},"500":{"description":"Internal Server Error - Query processing failed","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Failed to process streaming query: Knowledge graph unavailable"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/query/data":{"post":{"tags":["query"],"summary":"Query Data","description":"Advanced data retrieval endpoint for structured RAG analysis.\n\nThis endpoint provides raw retrieval results without LLM generation, perfect for:\n- **Data Analysis**: Examine what information would be used for RAG\n- **System Integration**: Get structured data for custom processing\n- **Debugging**: Understand retrieval behavior and quality\n- **Research**: Analyze knowledge graph structure and relationships\n\n**Key Features:**\n- No LLM generation - pure data retrieval\n- Complete structured output with entities, relationships, and chunks\n- Always includes references for citation\n- Detailed metadata about processing and keywords\n- Compatible with all query modes and parameters\n\n**Query Mode Behaviors:**\n- **local**: Returns entities and their direct relationships + related chunks\n- **global**: Returns relationship patterns across the knowledge graph\n- **hybrid**: Combines local and global retrieval strategies\n- **naive**: Returns only vector-retrieved text chunks (no knowledge graph)\n- **mix**: Integrates knowledge graph data with vector-retrieved chunks\n- **bypass**: Returns empty data arrays (used for direct LLM queries)\n\n**Data Structure:**\n- **entities**: Knowledge graph entities with descriptions and metadata\n- **relationships**: Connections between entities with weights and descriptions\n- **chunks**: Text segments from documents with source information\n- **references**: Citation information mapping reference IDs to file paths\n- **metadata**: Processing information, keywords, and query statistics\n\n**Usage Examples:**\n\nAnalyze entity relationships:\n```json\n{\n \"query\": \"machine learning algorithms\",\n \"mode\": \"local\",\n \"top_k\": 10\n}\n```\n\nExplore global patterns:\n```json\n{\n \"query\": \"artificial intelligence trends\",\n \"mode\": \"global\",\n \"max_relation_tokens\": 2000\n}\n```\n\nVector similarity search:\n```json\n{\n \"query\": \"neural network architectures\",\n \"mode\": \"naive\",\n \"chunk_top_k\": 5\n}\n```\n\nBypass initial LLM call by providing high-level and low-level keywords:\n```json\n{\n \"query\": \"What is Retrieval-Augmented-Generation?\",\n \"hl_keywords\": [\"machine learning\", \"information retrieval\", \"natural language processing\"],\n \"ll_keywords\": [\"retrieval augmented generation\", \"RAG\", \"knowledge base\"],\n \"mode\": \"mix\"\n}\n```\n\n**Response Analysis:**\n- **Empty arrays**: Normal for certain modes (e.g., naive mode has no entities/relationships)\n- **Processing info**: Shows retrieval statistics and token usage\n- **Keywords**: High-level and low-level keywords extracted from query\n- **Reference mapping**: Links all data back to source documents\n\nArgs:\n request (QueryRequest): The request object containing query parameters:\n - **query**: The search query to analyze (min 3 characters)\n - **mode**: Retrieval strategy affecting data types returned\n - **top_k**: Number of top entities/relationships to retrieve\n - **chunk_top_k**: Number of text chunks to retrieve\n - **max_entity_tokens**: Token limit for entity context\n - **max_relation_tokens**: Token limit for relationship context\n - **max_total_tokens**: Overall token budget for retrieval\n\nReturns:\n QueryDataResponse: Structured JSON response containing:\n - **status**: \"success\" or \"failure\"\n - **message**: Human-readable status description\n - **data**: Complete retrieval results with entities, relationships, chunks, references\n - **metadata**: Query processing information and statistics\n\nRaises:\n HTTPException:\n - 400: Invalid input parameters (e.g., query too short, invalid mode)\n - 500: Internal processing error (e.g., knowledge graph unavailable)\n\nNote:\n This endpoint always includes references regardless of the include_references parameter,\n as structured data analysis typically requires source attribution.","operationId":"query_data_query_data_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Successful data retrieval response with structured RAG data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryDataResponse","type":"object","properties":{"status":{"type":"string","enum":["success","failure"],"description":"Query execution status"},"message":{"type":"string","description":"Status message describing the result"},"data":{"type":"object","properties":{"entities":{"type":"array","items":{"type":"object","properties":{"entity_name":{"type":"string"},"entity_type":{"type":"string"},"description":{"type":"string"},"source_id":{"type":"string"},"file_path":{"type":"string"},"reference_id":{"type":"string"}}},"description":"Retrieved entities from knowledge graph"},"relationships":{"type":"array","items":{"type":"object","properties":{"src_id":{"type":"string"},"tgt_id":{"type":"string"},"description":{"type":"string"},"keywords":{"type":"string"},"weight":{"type":"number"},"source_id":{"type":"string"},"file_path":{"type":"string"},"reference_id":{"type":"string"}}},"description":"Retrieved relationships from knowledge graph"},"chunks":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"file_path":{"type":"string"},"chunk_id":{"type":"string"},"reference_id":{"type":"string"}}},"description":"Retrieved text chunks from vector database"},"references":{"type":"array","items":{"type":"object","properties":{"reference_id":{"type":"string"},"file_path":{"type":"string"}}},"description":"Reference list for citation purposes"}},"description":"Structured retrieval data containing entities, relationships, chunks, and references"},"metadata":{"type":"object","properties":{"query_mode":{"type":"string"},"keywords":{"type":"object","properties":{"high_level":{"type":"array","items":{"type":"string"}},"low_level":{"type":"array","items":{"type":"string"}}}},"processing_info":{"type":"object","properties":{"total_entities_found":{"type":"integer"},"total_relations_found":{"type":"integer"},"entities_after_truncation":{"type":"integer"},"relations_after_truncation":{"type":"integer"},"final_chunks_count":{"type":"integer"}}}},"description":"Query metadata including mode, keywords, and processing information"}},"required":["status","message","data","metadata"]},"examples":{"successful_local_mode":{"summary":"Local mode data retrieval","description":"Example of structured data from local mode query focusing on specific entities","value":{"status":"success","message":"Query executed successfully","data":{"entities":[{"entity_name":"Neural Networks","entity_type":"CONCEPT","description":"Computational models inspired by biological neural networks","source_id":"chunk-123","file_path":"/documents/ai_basics.pdf","reference_id":"1"}],"relationships":[{"src_id":"Neural Networks","tgt_id":"Machine Learning","description":"Neural networks are a subset of machine learning algorithms","keywords":"subset, algorithm, learning","weight":0.85,"source_id":"chunk-123","file_path":"/documents/ai_basics.pdf","reference_id":"1"}],"chunks":[{"content":"Neural networks are computational models that mimic the way biological neural networks work...","file_path":"/documents/ai_basics.pdf","chunk_id":"chunk-123","reference_id":"1"}],"references":[{"reference_id":"1","file_path":"/documents/ai_basics.pdf"}]},"metadata":{"query_mode":"local","keywords":{"high_level":["neural","networks"],"low_level":["computation","model","algorithm"]},"processing_info":{"total_entities_found":5,"total_relations_found":3,"entities_after_truncation":1,"relations_after_truncation":1,"final_chunks_count":1}}}},"global_mode":{"summary":"Global mode data retrieval","description":"Example of structured data from global mode query analyzing broader patterns","value":{"status":"success","message":"Query executed successfully","data":{"entities":[],"relationships":[{"src_id":"Artificial Intelligence","tgt_id":"Machine Learning","description":"AI encompasses machine learning as a core component","keywords":"encompasses, component, field","weight":0.92,"source_id":"chunk-456","file_path":"/documents/ai_overview.pdf","reference_id":"2"}],"chunks":[],"references":[{"reference_id":"2","file_path":"/documents/ai_overview.pdf"}]},"metadata":{"query_mode":"global","keywords":{"high_level":["artificial","intelligence","overview"],"low_level":[]}}}},"naive_mode":{"summary":"Naive mode data retrieval","description":"Example of structured data from naive mode using only vector search","value":{"status":"success","message":"Query executed successfully","data":{"entities":[],"relationships":[],"chunks":[{"content":"Deep learning is a subset of machine learning that uses neural networks with multiple layers...","file_path":"/documents/deep_learning.pdf","chunk_id":"chunk-789","reference_id":"3"}],"references":[{"reference_id":"3","file_path":"/documents/deep_learning.pdf"}]},"metadata":{"query_mode":"naive","keywords":{"high_level":[],"low_level":[]}}}}}}}},"400":{"description":"Bad Request - Invalid input parameters","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Query text must be at least 3 characters long"}}}},"500":{"description":"Internal Server Error - Data retrieval failed","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"Failed to retrieve data: Knowledge graph unavailable"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/label/list":{"get":{"tags":["graph"],"summary":"Get Graph Labels","description":"Get all graph labels\n\nReturns:\n List[str]: List of graph labels","operationId":"get_graph_labels_graph_label_list_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/label/popular":{"get":{"tags":["graph"],"summary":"Get Popular Labels","description":"Get popular labels by node degree (most connected entities)\n\nArgs:\n limit (int): Maximum number of labels to return (default: 300, max: 1000)\n\nReturns:\n List[str]: List of popular labels sorted by degree (highest first)","operationId":"get_popular_labels_graph_label_popular_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of popular labels to return","default":300,"title":"Limit"},"description":"Maximum number of popular labels to return"},{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/label/search":{"get":{"tags":["graph"],"summary":"Search Labels","description":"Search labels with fuzzy matching\n\nArgs:\n q (str): Search query string\n limit (int): Maximum number of results to return (default: 50, max: 100)\n\nReturns:\n List[str]: List of matching labels sorted by relevance","operationId":"search_labels_graph_label_search_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query string","title":"Q"},"description":"Search query string"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of search results to return","default":50,"title":"Limit"},"description":"Maximum number of search results to return"},{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graphs":{"get":{"tags":["graph"],"summary":"Get Knowledge Graph","description":"Retrieve a connected subgraph of nodes where the label includes the specified label.\nWhen reducing the number of nodes, the prioritization criteria are as follows:\n 1. Hops(path) to the staring node take precedence\n 2. Followed by the degree of the nodes\n\nArgs:\n label (str): Label of the starting node\n max_depth (int, optional): Maximum depth of the subgraph,Defaults to 3\n max_nodes: Maxiumu nodes to return\n\nReturns:\n Dict[str, List[str]]: Knowledge graph for label","operationId":"get_knowledge_graph_graphs_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"label","in":"query","required":true,"schema":{"type":"string","description":"Label to get knowledge graph for","title":"Label"},"description":"Label to get knowledge graph for"},{"name":"max_depth","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Maximum depth of graph","default":3,"title":"Max Depth"},"description":"Maximum depth of graph"},{"name":"max_nodes","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Maximum nodes to return","default":1000,"title":"Max Nodes"},"description":"Maximum nodes to return"},{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/entity/exists":{"get":{"tags":["graph"],"summary":"Check Entity Exists","description":"Check if an entity with the given name exists in the knowledge graph\n\nArgs:\n name (str): Name of the entity to check\n\nReturns:\n Dict[str, bool]: Dictionary with 'exists' key indicating if entity exists","operationId":"check_entity_exists_graph_entity_exists_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string","description":"Entity name to check","title":"Name"},"description":"Entity name to check"},{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/entity/edit":{"post":{"tags":["graph"],"summary":"Update Entity","description":"Update an entity's properties in the knowledge graph\n\nThis endpoint allows updating entity properties, including renaming entities.\nWhen renaming to an existing entity name, the behavior depends on allow_merge:\n\nArgs:\n request (EntityUpdateRequest): Request containing:\n - entity_name (str): Name of the entity to update\n - updated_data (Dict[str, Any]): Dictionary of properties to update\n - allow_rename (bool): Whether to allow entity renaming (default: False)\n - allow_merge (bool): Whether to merge into existing entity when renaming\n causes name conflict (default: False)\n\nReturns:\n Dict with the following structure:\n {\n \"status\": \"success\",\n \"message\": \"Entity updated successfully\" | \"Entity merged successfully into 'target_name'\",\n \"data\": {\n \"entity_name\": str, # Final entity name\n \"description\": str, # Entity description\n \"entity_type\": str, # Entity type\n \"source_id\": str, # Source chunk IDs\n ... # Other entity properties\n },\n \"operation_summary\": {\n \"merged\": bool, # Whether entity was merged into another\n \"merge_status\": str, # \"success\" | \"failed\" | \"not_attempted\"\n \"merge_error\": str | None, # Error message if merge failed\n \"operation_status\": str, # \"success\" | \"partial_success\" | \"failure\"\n \"target_entity\": str | None, # Target entity name if renaming/merging\n \"final_entity\": str, # Final entity name after operation\n \"renamed\": bool # Whether entity was renamed\n }\n }\n\noperation_status values explained:\n - \"success\": All operations completed successfully\n * For simple updates: entity properties updated\n * For renames: entity renamed successfully\n * For merges: non-name updates applied AND merge completed\n\n - \"partial_success\": Update succeeded but merge failed\n * Non-name property updates were applied successfully\n * Merge operation failed (entity not merged)\n * Original entity still exists with updated properties\n * Use merge_error for failure details\n\n - \"failure\": Operation failed completely\n * If merge_status == \"failed\": Merge attempted but both update and merge failed\n * If merge_status == \"not_attempted\": Regular update failed\n * No changes were applied to the entity\n\nmerge_status values explained:\n - \"success\": Entity successfully merged into target entity\n - \"failed\": Merge operation was attempted but failed\n - \"not_attempted\": No merge was attempted (normal update/rename)\n\nBehavior when renaming to an existing entity:\n - If allow_merge=False: Raises ValueError with 400 status (default behavior)\n - If allow_merge=True: Automatically merges the source entity into the existing target entity,\n preserving all relationships and applying non-name updates first\n\nExample Request (simple update):\n POST /graph/entity/edit\n {\n \"entity_name\": \"Tesla\",\n \"updated_data\": {\"description\": \"Updated description\"},\n \"allow_rename\": false,\n \"allow_merge\": false\n }\n\nExample Response (simple update success):\n {\n \"status\": \"success\",\n \"message\": \"Entity updated successfully\",\n \"data\": { ... },\n \"operation_summary\": {\n \"merged\": false,\n \"merge_status\": \"not_attempted\",\n \"merge_error\": null,\n \"operation_status\": \"success\",\n \"target_entity\": null,\n \"final_entity\": \"Tesla\",\n \"renamed\": false\n }\n }\n\nExample Request (rename with auto-merge):\n POST /graph/entity/edit\n {\n \"entity_name\": \"Elon Msk\",\n \"updated_data\": {\n \"entity_name\": \"Elon Musk\",\n \"description\": \"Corrected description\"\n },\n \"allow_rename\": true,\n \"allow_merge\": true\n }\n\nExample Response (merge success):\n {\n \"status\": \"success\",\n \"message\": \"Entity merged successfully into 'Elon Musk'\",\n \"data\": { ... },\n \"operation_summary\": {\n \"merged\": true,\n \"merge_status\": \"success\",\n \"merge_error\": null,\n \"operation_status\": \"success\",\n \"target_entity\": \"Elon Musk\",\n \"final_entity\": \"Elon Musk\",\n \"renamed\": true\n }\n }\n\nExample Response (partial success - update succeeded but merge failed):\n {\n \"status\": \"success\",\n \"message\": \"Entity updated successfully\",\n \"data\": { ... }, # Data reflects updated \"Elon Msk\" entity\n \"operation_summary\": {\n \"merged\": false,\n \"merge_status\": \"failed\",\n \"merge_error\": \"Target entity locked by another operation\",\n \"operation_status\": \"partial_success\",\n \"target_entity\": \"Elon Musk\",\n \"final_entity\": \"Elon Msk\", # Original entity still exists\n \"renamed\": true\n }\n }","operationId":"update_entity_graph_entity_edit_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/relation/edit":{"post":{"tags":["graph"],"summary":"Update Relation","description":"Update a relation's properties in the knowledge graph\n\nArgs:\n request (RelationUpdateRequest): Request containing source ID, target ID and updated data\n\nReturns:\n Dict: Updated relation information","operationId":"update_relation_graph_relation_edit_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelationUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/entity/create":{"post":{"tags":["graph"],"summary":"Create Entity","description":"Create a new entity in the knowledge graph\n\nThis endpoint creates a new entity node in the knowledge graph with the specified\nproperties. The system automatically generates vector embeddings for the entity\nto enable semantic search and retrieval.\n\nRequest Body:\n entity_name (str): Unique name identifier for the entity\n entity_data (dict): Entity properties including:\n - description (str): Textual description of the entity\n - entity_type (str): Category/type of the entity (e.g., PERSON, ORGANIZATION, LOCATION)\n - source_id (str): Related chunk_id from which the description originates\n - Additional custom properties as needed\n\nResponse Schema:\n {\n \"status\": \"success\",\n \"message\": \"Entity 'Tesla' created successfully\",\n \"data\": {\n \"entity_name\": \"Tesla\",\n \"description\": \"Electric vehicle manufacturer\",\n \"entity_type\": \"ORGANIZATION\",\n \"source_id\": \"chunk-123chunk-456\"\n ... (other entity properties)\n }\n }\n\nHTTP Status Codes:\n 200: Entity created successfully\n 400: Invalid request (e.g., missing required fields, duplicate entity)\n 500: Internal server error\n\nExample Request:\n POST /graph/entity/create\n {\n \"entity_name\": \"Tesla\",\n \"entity_data\": {\n \"description\": \"Electric vehicle manufacturer\",\n \"entity_type\": \"ORGANIZATION\"\n }\n }","operationId":"create_entity_graph_entity_create_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/relation/create":{"post":{"tags":["graph"],"summary":"Create Relation","description":"Create a new relationship between two entities in the knowledge graph\n\nThis endpoint establishes an undirected relationship between two existing entities.\nThe provided source/target order is accepted for convenience, but the backend\nstored edge is undirected and may be returned with the entities swapped.\nBoth entities must already exist in the knowledge graph. The system automatically\ngenerates vector embeddings for the relationship to enable semantic search and graph traversal.\n\nPrerequisites:\n - Both source_entity and target_entity must exist in the knowledge graph\n - Use /graph/entity/create to create entities first if they don't exist\n\nRequest Body:\n source_entity (str): Name of the source entity (relationship origin)\n target_entity (str): Name of the target entity (relationship destination)\n relation_data (dict): Relationship properties including:\n - description (str): Textual description of the relationship\n - keywords (str): Comma-separated keywords describing the relationship type\n - source_id (str): Related chunk_id from which the description originates\n - weight (float): Relationship strength/importance (default: 1.0)\n - Additional custom properties as needed\n\nResponse Schema:\n {\n \"status\": \"success\",\n \"message\": \"Relation created successfully between 'Elon Musk' and 'Tesla'\",\n \"data\": {\n \"src_id\": \"Elon Musk\",\n \"tgt_id\": \"Tesla\",\n \"description\": \"Elon Musk is the CEO of Tesla\",\n \"keywords\": \"CEO, founder\",\n \"source_id\": \"chunk-123chunk-456\"\n \"weight\": 1.0,\n ... (other relationship properties)\n }\n }\n\nHTTP Status Codes:\n 200: Relationship created successfully\n 400: Invalid request (e.g., missing entities, invalid data, duplicate relationship)\n 500: Internal server error\n\nExample Request:\n POST /graph/relation/create\n {\n \"source_entity\": \"Elon Musk\",\n \"target_entity\": \"Tesla\",\n \"relation_data\": {\n \"description\": \"Elon Musk is the CEO of Tesla\",\n \"keywords\": \"CEO, founder\",\n \"weight\": 1.0\n }\n }","operationId":"create_relation_graph_relation_create_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelationCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/graph/entities/merge":{"post":{"tags":["graph"],"summary":"Merge Entities","description":"Merge multiple entities into a single entity, preserving all relationships\n\nThis endpoint consolidates duplicate or misspelled entities while preserving the entire\ngraph structure. It's particularly useful for cleaning up knowledge graphs after document\nprocessing or correcting entity name variations.\n\nWhat the Merge Operation Does:\n 1. Deletes the specified source entities from the knowledge graph\n 2. Transfers all relationships from source entities to the target entity\n 3. Intelligently merges duplicate relationships (if multiple sources have the same relationship)\n 4. Updates vector embeddings for accurate retrieval and search\n 5. Preserves the complete graph structure and connectivity\n 6. Maintains relationship properties and metadata\n\nUse Cases:\n - Fixing spelling errors in entity names (e.g., \"Elon Msk\" -> \"Elon Musk\")\n - Consolidating duplicate entities discovered after document processing\n - Merging name variations (e.g., \"NY\", \"New York\", \"New York City\")\n - Cleaning up the knowledge graph for better query performance\n - Standardizing entity names across the knowledge base\n\nRequest Body:\n entities_to_change (list[str]): List of entity names to be merged and deleted\n entity_to_change_into (str): Target entity that will receive all relationships\n\nResponse Schema:\n {\n \"status\": \"success\",\n \"message\": \"Successfully merged 2 entities into 'Elon Musk'\",\n \"data\": {\n \"merged_entity\": \"Elon Musk\",\n \"deleted_entities\": [\"Elon Msk\", \"Ellon Musk\"],\n \"relationships_transferred\": 15,\n ... (merge operation details)\n }\n }\n\nHTTP Status Codes:\n 200: Entities merged successfully\n 400: Invalid request (e.g., empty entity list, target entity doesn't exist)\n 500: Internal server error\n\nExample Request:\n POST /graph/entities/merge\n {\n \"entities_to_change\": [\"Elon Msk\", \"Ellon Musk\"],\n \"entity_to_change_into\": \"Elon Musk\"\n }\n\nNote:\n - The target entity (entity_to_change_into) must exist in the knowledge graph\n - Source entities will be permanently deleted after the merge\n - This operation cannot be undone, so verify entity names before merging","operationId":"merge_entities_graph_entities_merge_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityMergeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/version":{"get":{"tags":["ollama"],"summary":"Get Version","description":"Get Ollama version information","operationId":"get_version_api_version_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tags":{"get":{"tags":["ollama"],"summary":"Get Tags","description":"Return available models acting as an Ollama server","operationId":"get_tags_api_tags_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ps":{"get":{"tags":["ollama"],"summary":"Get Running Models","description":"List Running Models - returns currently running models","operationId":"get_running_models_api_ps_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/generate":{"post":{"tags":["ollama"],"summary":"Generate","description":"Handle generate completion requests acting as an Ollama model\nFor compatibility purpose, the request is not processed by LightRAG,\nand will be handled by underlying LLM model.\nSupports both application/json and application/octet-stream Content-Types.","operationId":"generate_api_generate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chat":{"post":{"tags":["ollama"],"summary":"Chat","description":"Process chat completion requests by acting as an Ollama model.\nRoutes user queries through LightRAG by selecting query mode based on query prefix.\nDetects and forwards OpenWebUI session-related requests (for meta data generation task) directly to LLM.\nSupports both application/json and application/octet-stream Content-Types.","operationId":"chat_api_chat_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Redirect To Webui","description":"Redirect root path based on WebUI availability","operationId":"redirect_to_webui__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth-status":{"get":{"summary":"Get Auth Status","description":"Get authentication status and guest token if auth is not configured","operationId":"get_auth_status_auth_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/login":{"post":{"summary":"Login","operationId":"login_login_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_login_login_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Get system health and configuration status","description":"Returns comprehensive system status including WebUI availability, configuration, and operational metrics","operationId":"get_status_health_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"api_key_header_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Header Value"}}],"responses":{"200":{"description":"Successful response with system status","content":{"application/json":{"schema":{},"example":{"status":"healthy","webui_available":true,"working_directory":"/path/to/working/dir","input_directory":"/path/to/input/dir","configuration":{"llm_binding":"openai","llm_model":"gpt-4","embedding_binding":"openai","embedding_model":"text-embedding-ada-002","workspace":"default"},"auth_mode":"enabled","pipeline_busy":false,"core_version":"0.0.1","api_version":"0.0.1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_login_login_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_login_post"},"Body_upload_to_input_dir_documents_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_to_input_dir_documents_upload_post"},"CancelPipelineResponse":{"properties":{"status":{"type":"string","enum":["cancellation_requested","not_busy"],"title":"Status","description":"Status of the cancellation request"},"message":{"type":"string","title":"Message","description":"Human-readable message describing the operation"}},"type":"object","required":["status","message"],"title":"CancelPipelineResponse","description":"Response model for pipeline cancellation operation\n\nAttributes:\n status: Status of the cancellation request\n message: Message describing the operation result","example":{"message":"Pipeline cancellation has been requested. Documents will be marked as FAILED.","status":"cancellation_requested"}},"ClearCacheRequest":{"properties":{},"type":"object","title":"ClearCacheRequest","description":"Request model for clearing cache\n\nThis model is kept for API compatibility but no longer accepts any parameters.\nAll cache will be cleared regardless of the request content.","example":{}},"ClearCacheResponse":{"properties":{"status":{"type":"string","enum":["success","fail"],"title":"Status","description":"Status of the clear operation"},"message":{"type":"string","title":"Message","description":"Message describing the operation result"}},"type":"object","required":["status","message"],"title":"ClearCacheResponse","description":"Response model for cache clearing operation\n\nAttributes:\n status: Status of the clear operation\n message: Detailed message describing the operation result","example":{"message":"Successfully cleared cache for modes: ['default', 'naive']","status":"success"}},"ClearDocumentsResponse":{"properties":{"status":{"type":"string","enum":["success","partial_success","busy","fail"],"title":"Status","description":"Status of the clear operation"},"message":{"type":"string","title":"Message","description":"Message describing the operation result"}},"type":"object","required":["status","message"],"title":"ClearDocumentsResponse","description":"Response model for document clearing operation\n\nAttributes:\n status: Status of the clear operation\n message: Detailed message describing the operation result","example":{"message":"All documents cleared successfully. Deleted 15 files.","status":"success"}},"DeleteDocByIdResponse":{"properties":{"status":{"type":"string","enum":["deletion_started","busy","not_allowed"],"title":"Status","description":"Status of the deletion operation"},"message":{"type":"string","title":"Message","description":"Message describing the operation result"},"doc_id":{"type":"string","title":"Doc Id","description":"The ID of the document to delete"}},"type":"object","required":["status","message","doc_id"],"title":"DeleteDocByIdResponse","description":"Response model for single document deletion operation."},"DeleteDocRequest":{"properties":{"doc_ids":{"items":{"type":"string"},"type":"array","title":"Doc Ids","description":"The IDs of the documents to delete."},"delete_file":{"type":"boolean","title":"Delete File","description":"Whether to delete the corresponding file in the upload directory.","default":false},"delete_llm_cache":{"type":"boolean","title":"Delete Llm Cache","description":"Whether to delete cached LLM extraction results for the documents.","default":false}},"type":"object","required":["doc_ids"],"title":"DeleteDocRequest"},"DeleteEntityRequest":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"The name of the entity to delete."}},"type":"object","required":["entity_name"],"title":"DeleteEntityRequest"},"DeleteRelationRequest":{"properties":{"source_entity":{"type":"string","title":"Source Entity","description":"The name of the source entity."},"target_entity":{"type":"string","title":"Target Entity","description":"The name of the target entity."}},"type":"object","required":["source_entity","target_entity"],"title":"DeleteRelationRequest"},"DeletionResult":{"properties":{"status":{"type":"string","enum":["success","not_found","fail"],"title":"Status"},"doc_id":{"type":"string","title":"Doc Id"},"message":{"type":"string","title":"Message"},"status_code":{"type":"integer","title":"Status Code","default":200},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"}},"type":"object","required":["status","doc_id","message"],"title":"DeletionResult"},"DocStatus":{"type":"string","enum":["pending","processing","preprocessed","processed","failed"],"title":"DocStatus","description":"Document processing status"},"DocStatusResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Document identifier"},"content_summary":{"type":"string","title":"Content Summary","description":"Summary of document content"},"content_length":{"type":"integer","title":"Content Length","description":"Length of document content in characters"},"status":{"$ref":"#/components/schemas/DocStatus","description":"Current processing status"},"created_at":{"type":"string","title":"Created At","description":"Creation timestamp (ISO format string)"},"updated_at":{"type":"string","title":"Updated At","description":"Last update timestamp (ISO format string)"},"track_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Id","description":"Tracking ID for monitoring progress"},"chunks_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Chunks Count","description":"Number of chunks the document was split into"},"error_msg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Msg","description":"Error message if processing failed"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Additional metadata about the document"},"file_path":{"type":"string","title":"File Path","description":"Path to the document file"}},"type":"object","required":["id","content_summary","content_length","status","created_at","updated_at","file_path"],"title":"DocStatusResponse","example":{"chunks_count":12,"content_length":15240,"content_summary":"Research paper on machine learning","created_at":"2025-03-31T12:34:56","file_path":"research_paper.pdf","id":"doc_123456","metadata":{"author":"John Doe","year":2025},"status":"processed","track_id":"upload_20250729_170612_abc123","updated_at":"2025-03-31T12:35:30"}},"DocsStatusesResponse":{"properties":{"statuses":{"additionalProperties":{"items":{"$ref":"#/components/schemas/DocStatusResponse"},"type":"array"},"propertyNames":{"$ref":"#/components/schemas/DocStatus"},"type":"object","title":"Statuses","description":"Dictionary mapping document status to lists of document status responses"}},"type":"object","title":"DocsStatusesResponse","description":"Response model for document statuses\n\nAttributes:\n statuses: Dictionary mapping document status to lists of document status responses","example":{"statuses":{"PENDING":[{"content_length":5000,"content_summary":"Pending document","created_at":"2025-03-31T10:00:00","file_path":"pending_doc.pdf","id":"doc_123","status":"pending","track_id":"upload_20250331_100000_abc123","updated_at":"2025-03-31T10:00:00"}],"PREPROCESSED":[{"chunks_count":10,"content_length":7200,"content_summary":"Document pending final indexing","created_at":"2025-03-31T09:30:00","file_path":"preprocessed_doc.pdf","id":"doc_789","status":"preprocessed","track_id":"upload_20250331_093000_xyz789","updated_at":"2025-03-31T09:35:00"}],"PROCESSED":[{"chunks_count":8,"content_length":8000,"content_summary":"Processed document","created_at":"2025-03-31T09:00:00","file_path":"processed_doc.pdf","id":"doc_456","metadata":{"author":"John Doe"},"status":"processed","track_id":"insert_20250331_090000_def456","updated_at":"2025-03-31T09:05:00"}]}}},"DocumentsRequest":{"properties":{"status_filter":{"anyOf":[{"$ref":"#/components/schemas/DocStatus"},{"type":"null"}],"description":"Filter by document status, None for all statuses"},"page":{"type":"integer","minimum":1.0,"title":"Page","description":"Page number (1-based)","default":1},"page_size":{"type":"integer","maximum":200.0,"minimum":10.0,"title":"Page Size","description":"Number of documents per page (10-200)","default":50},"sort_field":{"type":"string","enum":["created_at","updated_at","id","file_path"],"title":"Sort Field","description":"Field to sort by","default":"updated_at"},"sort_direction":{"type":"string","enum":["asc","desc"],"title":"Sort Direction","description":"Sort direction","default":"desc"}},"type":"object","title":"DocumentsRequest","description":"Request model for paginated document queries\n\nAttributes:\n status_filter: Filter by document status, None for all statuses\n page: Page number (1-based)\n page_size: Number of documents per page (10-200)\n sort_field: Field to sort by ('created_at', 'updated_at', 'id', 'file_path')\n sort_direction: Sort direction ('asc' or 'desc')","example":{"page":1,"page_size":50,"sort_direction":"desc","sort_field":"updated_at","status_filter":"PROCESSED"}},"EntityCreateRequest":{"properties":{"entity_name":{"type":"string","minLength":1,"title":"Entity Name","description":"Unique name for the new entity","examples":["Tesla"]},"entity_data":{"additionalProperties":true,"type":"object","title":"Entity Data","description":"Dictionary containing entity properties. Common fields include 'description' and 'entity_type'.","examples":[{"description":"Electric vehicle manufacturer","entity_type":"ORGANIZATION"}]}},"type":"object","required":["entity_name","entity_data"],"title":"EntityCreateRequest"},"EntityMergeRequest":{"properties":{"entities_to_change":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Entities To Change","description":"List of entity names to be merged and deleted. These are typically duplicate or misspelled entities.","examples":[["Elon Msk","Ellon Musk"]]},"entity_to_change_into":{"type":"string","minLength":1,"title":"Entity To Change Into","description":"Target entity name that will receive all relationships from the source entities. This entity will be preserved.","examples":["Elon Musk"]}},"type":"object","required":["entities_to_change","entity_to_change_into"],"title":"EntityMergeRequest"},"EntityUpdateRequest":{"properties":{"entity_name":{"type":"string","title":"Entity Name"},"updated_data":{"additionalProperties":true,"type":"object","title":"Updated Data"},"allow_rename":{"type":"boolean","title":"Allow Rename","default":false},"allow_merge":{"type":"boolean","title":"Allow Merge","default":false}},"type":"object","required":["entity_name","updated_data"],"title":"EntityUpdateRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InsertResponse":{"properties":{"status":{"type":"string","enum":["success","duplicated","partial_success","failure"],"title":"Status","description":"Status of the operation"},"message":{"type":"string","title":"Message","description":"Message describing the operation result"},"track_id":{"type":"string","title":"Track Id","description":"Tracking ID for monitoring processing status"}},"type":"object","required":["status","message","track_id"],"title":"InsertResponse","description":"Response model for document insertion operations\n\nAttributes:\n status: Status of the operation (success, duplicated, partial_success, failure)\n message: Detailed message describing the operation result\n track_id: Tracking ID for monitoring processing status","example":{"message":"File 'document.pdf' uploaded successfully. Processing will continue in background.","status":"success","track_id":"upload_20250729_170612_abc123"}},"InsertTextRequest":{"properties":{"text":{"type":"string","minLength":1,"title":"Text","description":"The text to insert"},"file_source":{"type":"string","minLength":0,"title":"File Source","description":"File Source"}},"type":"object","required":["text"],"title":"InsertTextRequest","description":"Request model for inserting a single text document\n\nAttributes:\n text: The text content to be inserted into the RAG system\n file_source: Source of the text (optional)","example":{"file_source":"Source of the text (optional)","text":"This is a sample text to be inserted into the RAG system."}},"InsertTextsRequest":{"properties":{"texts":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Texts","description":"The texts to insert"},"file_sources":{"items":{"type":"string"},"type":"array","minItems":0,"title":"File Sources","description":"Sources of the texts"}},"type":"object","required":["texts"],"title":"InsertTextsRequest","description":"Request model for inserting multiple text documents\n\nAttributes:\n texts: List of text contents to be inserted into the RAG system\n file_sources: Sources of the texts (optional)","example":{"file_sources":["First file source (optional)"],"texts":["This is the first text to be inserted.","This is the second text to be inserted."]}},"PaginatedDocsResponse":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/DocStatusResponse"},"type":"array","title":"Documents","description":"List of documents for the current page"},"pagination":{"$ref":"#/components/schemas/PaginationInfo","description":"Pagination information"},"status_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Counts","description":"Count of documents by status for all documents"}},"type":"object","required":["documents","pagination","status_counts"],"title":"PaginatedDocsResponse","description":"Response model for paginated document queries\n\nAttributes:\n documents: List of documents for the current page\n pagination: Pagination information\n status_counts: Count of documents by status for all documents","example":{"documents":[{"chunks_count":12,"content_length":15240,"content_summary":"Research paper on machine learning","created_at":"2025-03-31T12:34:56","file_path":"research_paper.pdf","id":"doc_123456","metadata":{"author":"John Doe","year":2025},"status":"PROCESSED","track_id":"upload_20250729_170612_abc123","updated_at":"2025-03-31T12:35:30"}],"pagination":{"has_next":true,"has_prev":false,"page":1,"page_size":50,"total_count":150,"total_pages":3},"status_counts":{"FAILED":5,"PENDING":10,"PREPROCESSED":5,"PROCESSED":130,"PROCESSING":5}}},"PaginationInfo":{"properties":{"page":{"type":"integer","title":"Page","description":"Current page number"},"page_size":{"type":"integer","title":"Page Size","description":"Number of items per page"},"total_count":{"type":"integer","title":"Total Count","description":"Total number of items"},"total_pages":{"type":"integer","title":"Total Pages","description":"Total number of pages"},"has_next":{"type":"boolean","title":"Has Next","description":"Whether there is a next page"},"has_prev":{"type":"boolean","title":"Has Prev","description":"Whether there is a previous page"}},"type":"object","required":["page","page_size","total_count","total_pages","has_next","has_prev"],"title":"PaginationInfo","description":"Pagination information\n\nAttributes:\n page: Current page number\n page_size: Number of items per page\n total_count: Total number of items\n total_pages: Total number of pages\n has_next: Whether there is a next page\n has_prev: Whether there is a previous page","example":{"has_next":true,"has_prev":false,"page":1,"page_size":50,"total_count":150,"total_pages":3}},"PipelineStatusResponse":{"properties":{"autoscanned":{"type":"boolean","title":"Autoscanned","default":false},"busy":{"type":"boolean","title":"Busy","default":false},"job_name":{"type":"string","title":"Job Name","default":"Default Job"},"job_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Start"},"docs":{"type":"integer","title":"Docs","default":0},"batchs":{"type":"integer","title":"Batchs","default":0},"cur_batch":{"type":"integer","title":"Cur Batch","default":0},"request_pending":{"type":"boolean","title":"Request Pending","default":false},"latest_message":{"type":"string","title":"Latest Message","default":""},"history_messages":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"History Messages"},"update_status":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Update Status"}},"additionalProperties":true,"type":"object","title":"PipelineStatusResponse","description":"Response model for pipeline status\n\nAttributes:\n autoscanned: Whether auto-scan has started\n busy: Whether the pipeline is currently busy\n job_name: Current job name (e.g., indexing files/indexing texts)\n job_start: Job start time as ISO format string with timezone (optional)\n docs: Total number of documents to be indexed\n batchs: Number of batches for processing documents\n cur_batch: Current processing batch\n request_pending: Flag for pending request for processing\n latest_message: Latest message from pipeline processing\n history_messages: List of history messages\n update_status: Status of update flags for all namespaces"},"QueryDataResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Query execution status"},"message":{"type":"string","title":"Message","description":"Status message"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Query result data containing entities, relationships, chunks, and references"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Query metadata including mode, keywords, and processing information"}},"type":"object","required":["status","message","data","metadata"],"title":"QueryDataResponse"},"QueryRequest":{"properties":{"query":{"type":"string","minLength":3,"title":"Query","description":"The query text"},"mode":{"type":"string","enum":["local","global","hybrid","naive","mix","bypass"],"title":"Mode","description":"Query mode","default":"mix"},"only_need_context":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Only Need Context","description":"If True, only returns the retrieved context without generating a response."},"only_need_prompt":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Only Need Prompt","description":"If True, only returns the generated prompt without producing a response."},"response_type":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Response Type","description":"Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'."},"top_k":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Top K","description":"Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode."},"chunk_top_k":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Chunk Top K","description":"Number of text chunks to retrieve initially from vector search and keep after reranking."},"max_entity_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Entity Tokens","description":"Maximum number of tokens allocated for entity context in unified token control system."},"max_relation_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Relation Tokens","description":"Maximum number of tokens allocated for relationship context in unified token control system."},"max_total_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Total Tokens","description":"Maximum total tokens budget for the entire query context (entities + relations + chunks + system prompt)."},"hl_keywords":{"items":{"type":"string"},"type":"array","title":"Hl Keywords","description":"List of high-level keywords to prioritize in retrieval. Leave empty to use the LLM to generate the keywords."},"ll_keywords":{"items":{"type":"string"},"type":"array","title":"Ll Keywords","description":"List of low-level keywords to refine retrieval focus. Leave empty to use the LLM to generate the keywords."},"conversation_history":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Conversation History","description":"Stores past conversation history to maintain context. Format: [{'role': 'user/assistant', 'content': 'message'}]."},"user_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Prompt","description":"User-provided prompt for the query. If provided, this will be used instead of the default value from prompt template."},"enable_rerank":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enable Rerank","description":"Enable reranking for retrieved text chunks. If True but no rerank model is configured, a warning will be issued. Default is True."},"include_references":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include References","description":"If True, includes reference list in responses. Affects /query and /query/stream endpoints. /query/data always includes references.","default":true},"include_chunk_content":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Chunk Content","description":"If True, includes actual chunk text content in references. Only applies when include_references=True. Useful for evaluation and debugging.","default":false},"stream":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stream","description":"If True, enables streaming output for real-time responses. Only affects /query/stream endpoint.","default":true}},"type":"object","required":["query"],"title":"QueryRequest"},"QueryResponse":{"properties":{"response":{"type":"string","title":"Response","description":"The generated response"},"references":{"anyOf":[{"items":{"$ref":"#/components/schemas/ReferenceItem"},"type":"array"},{"type":"null"}],"title":"References","description":"Reference list (Disabled when include_references=False, /query/data always includes references.)"}},"type":"object","required":["response"],"title":"QueryResponse"},"ReferenceItem":{"properties":{"reference_id":{"type":"string","title":"Reference Id","description":"Unique reference identifier"},"file_path":{"type":"string","title":"File Path","description":"Path to the source file"},"content":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Content","description":"List of chunk contents from this file (only present when include_chunk_content=True)"}},"type":"object","required":["reference_id","file_path"],"title":"ReferenceItem","description":"A single reference item in query responses."},"RelationCreateRequest":{"properties":{"source_entity":{"type":"string","minLength":1,"title":"Source Entity","description":"Name of the source entity. This entity must already exist in the knowledge graph.","examples":["Elon Musk"]},"target_entity":{"type":"string","minLength":1,"title":"Target Entity","description":"Name of the target entity. This entity must already exist in the knowledge graph.","examples":["Tesla"]},"relation_data":{"additionalProperties":true,"type":"object","title":"Relation Data","description":"Dictionary containing relationship properties. Common fields include 'description', 'keywords', and 'weight'.","examples":[{"description":"Elon Musk is the CEO of Tesla","keywords":"CEO, founder","weight":1.0}]}},"type":"object","required":["source_entity","target_entity","relation_data"],"title":"RelationCreateRequest"},"RelationUpdateRequest":{"properties":{"source_id":{"type":"string","title":"Source Id"},"target_id":{"type":"string","title":"Target Id"},"updated_data":{"additionalProperties":true,"type":"object","title":"Updated Data"}},"type":"object","required":["source_id","target_id","updated_data"],"title":"RelationUpdateRequest"},"ReprocessResponse":{"properties":{"status":{"type":"string","const":"reprocessing_started","title":"Status","description":"Status of the reprocessing operation"},"message":{"type":"string","title":"Message","description":"Human-readable message describing the operation"},"track_id":{"type":"string","title":"Track Id","description":"Always empty string. Reprocessed documents retain their original track_id from initial upload.","default":""}},"type":"object","required":["status","message"],"title":"ReprocessResponse","description":"Response model for reprocessing failed documents operation\n\nAttributes:\n status: Status of the reprocessing operation\n message: Message describing the operation result\n track_id: Always empty string. Reprocessed documents retain their original track_id.","example":{"message":"Reprocessing of failed documents has been initiated in background","status":"reprocessing_started","track_id":""}},"ScanResponse":{"properties":{"status":{"type":"string","const":"scanning_started","title":"Status","description":"Status of the scanning operation"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Additional details about the scanning operation"},"track_id":{"type":"string","title":"Track Id","description":"Tracking ID for monitoring scanning progress"}},"type":"object","required":["status","track_id"],"title":"ScanResponse","description":"Response model for document scanning operation\n\nAttributes:\n status: Status of the scanning operation\n message: Optional message with additional details\n track_id: Tracking ID for monitoring scanning progress","example":{"message":"Scanning process has been initiated in the background","status":"scanning_started","track_id":"scan_20250729_170612_abc123"}},"StatusCountsResponse":{"properties":{"status_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Counts","description":"Count of documents by status"}},"type":"object","required":["status_counts"],"title":"StatusCountsResponse","description":"Response model for document status counts\n\nAttributes:\n status_counts: Count of documents by status","example":{"status_counts":{"FAILED":5,"PENDING":10,"PREPROCESSED":5,"PROCESSED":130,"PROCESSING":5}}},"TrackStatusResponse":{"properties":{"track_id":{"type":"string","title":"Track Id","description":"The tracking ID"},"documents":{"items":{"$ref":"#/components/schemas/DocStatusResponse"},"type":"array","title":"Documents","description":"List of documents associated with this track_id"},"total_count":{"type":"integer","title":"Total Count","description":"Total number of documents for this track_id"},"status_summary":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Summary","description":"Count of documents by status"}},"type":"object","required":["track_id","documents","total_count","status_summary"],"title":"TrackStatusResponse","description":"Response model for tracking document processing status by track_id\n\nAttributes:\n track_id: The tracking ID\n documents: List of documents associated with this track_id\n total_count: Total number of documents for this track_id\n status_summary: Count of documents by status","example":{"documents":[{"chunks_count":12,"content_length":15240,"content_summary":"Research paper on machine learning","created_at":"2025-03-31T12:34:56","file_path":"research_paper.pdf","id":"doc_123456","metadata":{"author":"John Doe","year":2025},"status":"PROCESSED","track_id":"upload_20250729_170612_abc123","updated_at":"2025-03-31T12:35:30"}],"status_summary":{"PROCESSED":1},"total_count":1,"track_id":"upload_20250729_170612_abc123"}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","description":"OAuth2 Password Authentication","flows":{"password":{"scopes":{},"tokenUrl":"login"}}}}}} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..da5f0a5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "lightrag-mcp" +version = "0.1.0" +description = "MCP server for LightRAG Server API" +readme = "README.md" +requires-python = ">=3.11" +license = { text = "MIT" } +authors = [ + { name = "LightRAG MCP" } +] +dependencies = [ + "httpx>=0.27.0", + "mcp>=1.2.0", + "pydantic>=2.6.0", + "pydantic-settings>=2.1.0", +] + +[project.scripts] +lightrag-mcp = "lightrag_mcp.server:main" +lightrag-mcp-smoke = "lightrag_mcp.smoke:main" + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "I", "B"] + +[tool.ruff.lint.isort] +known-first-party = ["lightrag_mcp"] + +[build-system] +requires = ["hatchling>=1.20.0"] +build-backend = "hatchling.build" diff --git a/src/lightrag_mcp/__init__.py b/src/lightrag_mcp/__init__.py new file mode 100644 index 0000000..6d19291 --- /dev/null +++ b/src/lightrag_mcp/__init__.py @@ -0,0 +1,5 @@ +"""LightRAG MCP server package.""" + +__all__ = ["__version__"] + +__version__ = "0.1.0" diff --git a/src/lightrag_mcp/client.py b/src/lightrag_mcp/client.py new file mode 100644 index 0000000..db67159 --- /dev/null +++ b/src/lightrag_mcp/client.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from pathlib import Path +from typing import Any + +import httpx + + +class LightRAGClient: + def __init__(self, base_url: str, timeout_s: float = 60.0) -> None: + self._base_url = base_url.rstrip("/") + self._timeout = timeout_s + + @asynccontextmanager + async def _client(self) -> AsyncIterator[httpx.AsyncClient]: + async with httpx.AsyncClient(base_url=self._base_url, timeout=self._timeout) as client: + yield client + + async def request_json( + self, + method: str, + path: str, + *, + json: dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + files: dict[str, Any] | None = None, + ) -> dict[str, Any]: + async with self._client() as client: + response = await client.request(method, path, json=json, params=params, files=files) + return _json_or_raise(response, method, path) + + async def request_text( + self, + method: str, + path: str, + *, + json: dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + ) -> str: + async with self._client() as client: + response = await client.request(method, path, json=json, params=params) + return _text_or_raise(response, method, path) + + async def stream_text( + self, + path: str, + *, + json: dict[str, Any] | None = None, + ) -> str: + chunks = [chunk async for chunk in self.stream_chunks(path, json=json)] + return "\n".join(chunks) + + async def stream_chunks( + self, + path: str, + *, + json: dict[str, Any] | None = None, + ) -> AsyncIterator[str]: + async with self._client() as client: + async with client.stream( + "POST", + path, + json=json, + headers={"accept": "text/event-stream"}, + ) as response: + _raise_for_status(response, "POST", path) + async for line in response.aiter_lines(): + if not line: + continue + if line.startswith("data:"): + line = line[5:].lstrip() + yield line + + async def upload_file(self, path: str | Path) -> dict[str, Any]: + file_path = Path(path) + if not file_path.exists(): + raise FileNotFoundError(f"File not found: {file_path}") + async with self._client() as client: + with file_path.open("rb") as handle: + files = {"file": (file_path.name, handle)} + response = await client.request("POST", "/documents/upload", files=files) + return _json_or_raise(response, "POST", "/documents/upload") + + +def _raise_for_status(response: httpx.Response, method: str, path: str) -> None: + try: + response.raise_for_status() + except httpx.HTTPStatusError as exc: + text = exc.response.text.strip() + message = f"{method} {path} failed with {exc.response.status_code}" + if text: + message = f"{message}: {text}" + raise RuntimeError(message) from exc + + +def _json_or_raise(response: httpx.Response, method: str, path: str) -> dict[str, Any]: + _raise_for_status(response, method, path) + return response.json() + + +def _text_or_raise(response: httpx.Response, method: str, path: str) -> str: + _raise_for_status(response, method, path) + return response.text diff --git a/src/lightrag_mcp/server.py b/src/lightrag_mcp/server.py new file mode 100644 index 0000000..f6b5b14 --- /dev/null +++ b/src/lightrag_mcp/server.py @@ -0,0 +1,779 @@ +from __future__ import annotations + +import asyncio +import json +import time +from pathlib import Path +from typing import Any, Iterable + +from mcp.server.fastmcp import Context, FastMCP + +from lightrag_mcp.client import LightRAGClient +from lightrag_mcp.settings import load_settings + +settings = load_settings() +client = LightRAGClient(settings.base_url, timeout_s=settings.timeout_s) + +mcp = FastMCP(settings.mcp_server_name, json_response=True) + + +def _compact_payload(payload: dict[str, Any]) -> dict[str, Any]: + return {key: value for key, value in payload.items() if value is not None} + + +def _build_query_payload( + query: str, + *, + mode: str | None = None, + top_k: int | None = None, + chunk_top_k: int | None = None, + include_references: bool | None = None, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + only_need_context: bool | None = None, + only_need_prompt: bool | None = None, + response_type: str | None = None, + user_prompt: str | None = None, + conversation_history: list[dict[str, Any]] | None = None, + hl_keywords: list[str] | None = None, + ll_keywords: list[str] | None = None, + max_total_tokens: int | None = None, + max_entity_tokens: int | None = None, + max_relation_tokens: int | None = None, + stream: bool | None = None, + options: dict[str, Any] | None = None, +) -> dict[str, Any]: + payload: dict[str, Any] = { + "query": query, + "mode": mode, + "top_k": top_k, + "chunk_top_k": chunk_top_k, + "include_references": include_references, + "include_chunk_content": include_chunk_content, + "enable_rerank": enable_rerank, + "only_need_context": only_need_context, + "only_need_prompt": only_need_prompt, + "response_type": response_type, + "user_prompt": user_prompt, + "conversation_history": conversation_history, + "hl_keywords": hl_keywords, + "ll_keywords": ll_keywords, + "max_total_tokens": max_total_tokens, + "max_entity_tokens": max_entity_tokens, + "max_relation_tokens": max_relation_tokens, + "stream": stream, + } + payload = _compact_payload(payload) + if options: + payload.update(options) + return payload + + +async def _wait_for_idle(timeout_s: float, interval_s: float) -> dict[str, Any]: + started = time.monotonic() + while True: + status = await client.request_json("GET", "/documents/pipeline_status") + if not status.get("busy"): + return status + if time.monotonic() - started > timeout_s: + raise TimeoutError("Pipeline did not become idle within timeout") + await asyncio.sleep(interval_s) + + +def _chunk_text(text: str, max_chars: int, overlap: int) -> list[str]: + if max_chars <= 0: + raise ValueError("max_chars must be > 0") + if overlap < 0 or overlap >= max_chars: + raise ValueError("overlap must be >= 0 and < max_chars") + + chunks: list[str] = [] + start = 0 + length = len(text) + while start < length: + end = min(start + max_chars, length) + cut = text.rfind("\n", start, end) + if cut == -1 or cut <= start: + cut = end + chunk = text[start:cut].strip() + if chunk: + chunks.append(chunk) + if cut >= length: + break + next_start = max(cut - overlap, 0) + if next_start <= start: + next_start = cut + start = next_start + return chunks + + +def _format_list(items: Iterable[str]) -> str: + return ", ".join(item.strip() for item in items if item.strip()) + + +def _build_memory_note( + *, + title: str, + content: str, + memory_type: str | None = None, + tags: list[str] | None = None, + related_files: list[str] | None = None, + related_symbols: list[str] | None = None, + source: str | None = None, + timestamp: str | None = None, +) -> str: + lines = [f"Title: {title}"] + if memory_type: + lines.append(f"Type: {memory_type}") + if tags: + lines.append(f"Tags: {_format_list(tags)}") + if related_files: + lines.append(f"Related files: {_format_list(related_files)}") + if related_symbols: + lines.append(f"Related symbols: {_format_list(related_symbols)}") + if source: + lines.append(f"Source: {source}") + if timestamp: + lines.append(f"Timestamp: {timestamp}") + lines.append("") + lines.append(content.strip()) + return "\n".join(lines).strip() + + +@mcp.tool() +async def query_data( + query: str, + mode: str | None = "mix", + top_k: int | None = None, + chunk_top_k: int | None = None, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + only_need_context: bool | None = None, + only_need_prompt: bool | None = None, + response_type: str | None = None, + user_prompt: str | None = None, + conversation_history: list[dict[str, Any]] | None = None, + hl_keywords: list[str] | None = None, + ll_keywords: list[str] | None = None, + max_total_tokens: int | None = None, + max_entity_tokens: int | None = None, + max_relation_tokens: int | None = None, + options: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Run retrieval-only query and return raw entities, relations, chunks, and references.""" + payload = _build_query_payload( + query, + mode=mode, + top_k=top_k, + chunk_top_k=chunk_top_k, + include_chunk_content=include_chunk_content, + enable_rerank=enable_rerank, + only_need_context=only_need_context, + only_need_prompt=only_need_prompt, + response_type=response_type, + user_prompt=user_prompt, + conversation_history=conversation_history, + hl_keywords=hl_keywords, + ll_keywords=ll_keywords, + max_total_tokens=max_total_tokens, + max_entity_tokens=max_entity_tokens, + max_relation_tokens=max_relation_tokens, + options=options, + ) + return await client.request_json("POST", "/query/data", json=payload) + + +@mcp.tool() +async def query( + query: str, + mode: str | None = "mix", + top_k: int | None = None, + chunk_top_k: int | None = None, + include_references: bool | None = True, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + only_need_context: bool | None = None, + only_need_prompt: bool | None = None, + response_type: str | None = None, + user_prompt: str | None = None, + conversation_history: list[dict[str, Any]] | None = None, + hl_keywords: list[str] | None = None, + ll_keywords: list[str] | None = None, + max_total_tokens: int | None = None, + max_entity_tokens: int | None = None, + max_relation_tokens: int | None = None, + options: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Run generation query and return response with references when available.""" + payload = _build_query_payload( + query, + mode=mode, + top_k=top_k, + chunk_top_k=chunk_top_k, + include_references=include_references, + include_chunk_content=include_chunk_content, + enable_rerank=enable_rerank, + only_need_context=only_need_context, + only_need_prompt=only_need_prompt, + response_type=response_type, + user_prompt=user_prompt, + conversation_history=conversation_history, + hl_keywords=hl_keywords, + ll_keywords=ll_keywords, + max_total_tokens=max_total_tokens, + max_entity_tokens=max_entity_tokens, + max_relation_tokens=max_relation_tokens, + options=options, + ) + return await client.request_json("POST", "/query", json=payload) + + +@mcp.tool() +async def query_stream( + query: str, + mode: str | None = "mix", + top_k: int | None = None, + chunk_top_k: int | None = None, + include_references: bool | None = True, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + only_need_context: bool | None = None, + only_need_prompt: bool | None = None, + response_type: str | None = None, + user_prompt: str | None = None, + conversation_history: list[dict[str, Any]] | None = None, + hl_keywords: list[str] | None = None, + ll_keywords: list[str] | None = None, + max_total_tokens: int | None = None, + max_entity_tokens: int | None = None, + max_relation_tokens: int | None = None, + options: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Run streaming query and return the collected stream output.""" + payload = _build_query_payload( + query, + mode=mode, + top_k=top_k, + chunk_top_k=chunk_top_k, + include_references=include_references, + include_chunk_content=include_chunk_content, + enable_rerank=enable_rerank, + only_need_context=only_need_context, + only_need_prompt=only_need_prompt, + response_type=response_type, + user_prompt=user_prompt, + conversation_history=conversation_history, + hl_keywords=hl_keywords, + ll_keywords=ll_keywords, + max_total_tokens=max_total_tokens, + max_entity_tokens=max_entity_tokens, + max_relation_tokens=max_relation_tokens, + stream=True, + options=options, + ) + text = await client.stream_text("/query/stream", json=payload) + return {"response": text} + + +@mcp.tool() +async def query_stream_chunks( + query: str, + mode: str | None = "mix", + top_k: int | None = None, + chunk_top_k: int | None = None, + include_references: bool | None = True, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + only_need_context: bool | None = None, + only_need_prompt: bool | None = None, + response_type: str | None = None, + user_prompt: str | None = None, + conversation_history: list[dict[str, Any]] | None = None, + hl_keywords: list[str] | None = None, + ll_keywords: list[str] | None = None, + max_total_tokens: int | None = None, + max_entity_tokens: int | None = None, + max_relation_tokens: int | None = None, + max_chunks: int | None = None, + options: dict[str, Any] | None = None, + ctx: Context | None = None, +) -> dict[str, Any]: + """Stream query output as chunks, reporting progress when supported.""" + payload = _build_query_payload( + query, + mode=mode, + top_k=top_k, + chunk_top_k=chunk_top_k, + include_references=include_references, + include_chunk_content=include_chunk_content, + enable_rerank=enable_rerank, + only_need_context=only_need_context, + only_need_prompt=only_need_prompt, + response_type=response_type, + user_prompt=user_prompt, + conversation_history=conversation_history, + hl_keywords=hl_keywords, + ll_keywords=ll_keywords, + max_total_tokens=max_total_tokens, + max_entity_tokens=max_entity_tokens, + max_relation_tokens=max_relation_tokens, + stream=True, + options=options, + ) + chunks: list[str] = [] + count = 0 + async for chunk in client.stream_chunks("/query/stream", json=payload): + chunks.append(chunk) + count += 1 + if ctx is not None: + await ctx.report_progress(count, None, message=f"Received chunk {count}") + if max_chunks is not None and count >= max_chunks: + break + return {"chunks": chunks, "response": "\n".join(chunks), "count": count} + + +@mcp.tool() +async def ingest_text(text: str, file_source: str | None = None) -> dict[str, Any]: + """Ingest a single text chunk into the knowledge base.""" + payload = _compact_payload({"text": text, "file_source": file_source}) + return await client.request_json("POST", "/documents/text", json=payload) + + +@mcp.tool() +async def ingest_texts( + texts: list[str], + file_sources: list[str] | None = None, +) -> dict[str, Any]: + """Ingest multiple text chunks into the knowledge base.""" + payload: dict[str, Any] = {"texts": texts} + if file_sources is not None: + payload["file_sources"] = file_sources + return await client.request_json("POST", "/documents/texts", json=payload) + + +@mcp.tool() +async def ingest_file( + path: str, + max_chars: int = 4000, + overlap: int = 200, + encoding: str = "utf-8", +) -> dict[str, Any]: + """Read a local file, chunk it, and ingest as texts with file_sources set per chunk.""" + file_path = Path(path) + if not file_path.exists(): + raise FileNotFoundError(f"File not found: {file_path}") + text = file_path.read_text(encoding=encoding, errors="replace") + chunks = _chunk_text(text, max_chars=max_chars, overlap=overlap) + if not chunks: + raise ValueError(f"No content to ingest from {file_path}") + file_sources = [f"{file_path}#chunk:{idx + 1}/{len(chunks)}" for idx in range(len(chunks))] + payload: dict[str, Any] = {"texts": chunks, "file_sources": file_sources} + return await client.request_json("POST", "/documents/texts", json=payload) + + +@mcp.tool() +async def ingest_files( + paths: list[str], + max_chars: int = 4000, + overlap: int = 200, + encoding: str = "utf-8", +) -> dict[str, Any]: + """Ingest multiple local files by chunking each file into texts.""" + results: dict[str, Any] = {} + for path in paths: + results[path] = await ingest_file( + path=path, + max_chars=max_chars, + overlap=overlap, + encoding=encoding, + ) + return {"results": results} + + +@mcp.tool() +async def upload_document(path: str) -> dict[str, Any]: + """Upload a local file to the LightRAG input directory.""" + return await client.upload_file(path) + + +@mcp.tool() +async def scan_documents() -> dict[str, Any]: + """Scan the input directory for new documents.""" + return await client.request_json("POST", "/documents/scan") + + +@mcp.tool() +async def scan_and_wait(timeout_s: float | None = None) -> dict[str, Any]: + """Scan for new documents and wait until the pipeline is idle.""" + await client.request_json("POST", "/documents/scan") + wait_timeout = timeout_s if timeout_s is not None else settings.poll_timeout_s + status = await _wait_for_idle(wait_timeout, settings.poll_interval_s) + return {"status": status} + + +@mcp.tool() +async def pipeline_status() -> dict[str, Any]: + """Get the ingestion pipeline status.""" + return await client.request_json("GET", "/documents/pipeline_status") + + +@mcp.tool() +async def wait_for_idle(timeout_s: float | None = None) -> dict[str, Any]: + """Wait for pipeline idle and return the final status.""" + wait_timeout = timeout_s if timeout_s is not None else settings.poll_timeout_s + return await _wait_for_idle(wait_timeout, settings.poll_interval_s) + + +@mcp.tool() +async def track_status(track_id: str) -> dict[str, Any]: + """Get document processing status for a scan track id.""" + return await client.request_json("GET", f"/documents/track_status/{track_id}") + + +@mcp.tool() +async def list_documents() -> dict[str, Any]: + """List documents in the knowledge base.""" + return await client.request_json("GET", "/documents") + + +@mcp.tool() +async def list_documents_paginated( + status_filter: list[str] | None = None, + page: int | None = None, + page_size: int | None = None, + sort_field: str | None = None, + sort_direction: str | None = None, +) -> dict[str, Any]: + """List documents with pagination and status filters.""" + payload = _compact_payload( + { + "status_filter": status_filter, + "page": page, + "page_size": page_size, + "sort_field": sort_field, + "sort_direction": sort_direction, + } + ) + return await client.request_json("POST", "/documents/paginated", json=payload) + + +@mcp.tool() +async def status_counts() -> dict[str, Any]: + """Get document status counts.""" + return await client.request_json("GET", "/documents/status_counts") + + +@mcp.tool() +async def delete_documents( + doc_ids: list[str], + delete_file: bool | None = None, + delete_llm_cache: bool | None = None, +) -> dict[str, Any]: + """Delete documents by ID.""" + payload = _compact_payload( + {"doc_ids": doc_ids, "delete_file": delete_file, "delete_llm_cache": delete_llm_cache} + ) + return await client.request_json("DELETE", "/documents/delete_document", json=payload) + + +@mcp.tool() +async def clear_documents() -> dict[str, Any]: + """Clear all documents and associated data.""" + return await client.request_json("DELETE", "/documents") + + +@mcp.tool() +async def clear_cache() -> dict[str, Any]: + """Clear cached LLM data in LightRAG.""" + return await client.request_json("POST", "/documents/clear_cache", json={}) + + +@mcp.tool() +async def reprocess_failed() -> dict[str, Any]: + """Reprocess failed documents in the pipeline.""" + return await client.request_json("POST", "/documents/reprocess_failed") + + +@mcp.tool() +async def cancel_pipeline() -> dict[str, Any]: + """Cancel the active ingestion pipeline.""" + return await client.request_json("POST", "/documents/cancel_pipeline") + + +@mcp.tool() +async def create_entity(entity_name: str, entity_data: dict[str, Any]) -> dict[str, Any]: + """Create a new entity in the knowledge graph.""" + payload = {"entity_name": entity_name, "entity_data": entity_data} + return await client.request_json("POST", "/graph/entity/create", json=payload) + + +@mcp.tool() +async def edit_entity( + entity_name: str, + updated_data: dict[str, Any], + allow_rename: bool | None = None, + allow_merge: bool | None = None, +) -> dict[str, Any]: + """Update an entity in the knowledge graph.""" + payload = _compact_payload( + { + "entity_name": entity_name, + "updated_data": updated_data, + "allow_rename": allow_rename, + "allow_merge": allow_merge, + } + ) + return await client.request_json("POST", "/graph/entity/edit", json=payload) + + +@mcp.tool() +async def merge_entities(entities_to_change: list[str], entity_to_change_into: str) -> dict[str, Any]: + """Merge multiple entities into one target entity.""" + payload = { + "entities_to_change": entities_to_change, + "entity_to_change_into": entity_to_change_into, + } + return await client.request_json("POST", "/graph/entities/merge", json=payload) + + +@mcp.tool() +async def create_relation( + source_entity: str, + target_entity: str, + relation_data: dict[str, Any], +) -> dict[str, Any]: + """Create a relation in the knowledge graph.""" + payload = { + "source_entity": source_entity, + "target_entity": target_entity, + "relation_data": relation_data, + } + return await client.request_json("POST", "/graph/relation/create", json=payload) + + +@mcp.tool() +async def edit_relation( + source_id: str, + target_id: str, + updated_data: dict[str, Any], +) -> dict[str, Any]: + """Update a relation in the knowledge graph.""" + payload = {"source_id": source_id, "target_id": target_id, "updated_data": updated_data} + return await client.request_json("POST", "/graph/relation/edit", json=payload) + + +@mcp.tool() +async def delete_entity(entity_name: str) -> dict[str, Any]: + """Delete an entity from the knowledge graph.""" + payload = {"entity_name": entity_name} + return await client.request_json("DELETE", "/documents/delete_entity", json=payload) + + +@mcp.tool() +async def delete_relation(source_id: str, target_id: str) -> dict[str, Any]: + """Delete a relation from the knowledge graph.""" + payload = {"source_id": source_id, "target_id": target_id} + return await client.request_json("DELETE", "/documents/delete_relation", json=payload) + + +@mcp.tool() +async def graph_labels() -> dict[str, Any]: + """List graph labels.""" + return await client.request_json("GET", "/graph/label/list") + + +@mcp.tool() +async def popular_labels() -> dict[str, Any]: + """List popular graph labels.""" + return await client.request_json("GET", "/graph/label/popular") + + +@mcp.tool() +async def search_labels(query: str, limit: int | None = None) -> dict[str, Any]: + """Search graph labels by query string.""" + params: dict[str, Any] = {"q": query} + if limit is not None: + params["limit"] = limit + return await client.request_json("GET", "/graph/label/search", params=params) + + +@mcp.tool() +async def get_graph() -> dict[str, Any]: + """Fetch the full knowledge graph.""" + return await client.request_json("GET", "/graphs") + + +@mcp.tool() +async def entity_exists(name: str) -> dict[str, Any]: + """Check if an entity exists in the knowledge graph.""" + return await client.request_json("GET", "/graph/entity/exists", params={"name": name}) + + +@mcp.tool() +async def health() -> dict[str, Any]: + """Fetch health and configuration status.""" + return await client.request_json("GET", "/health") + + +@mcp.tool() +async def ingest_memory( + title: str, + content: str, + memory_type: str | None = None, + tags: list[str] | None = None, + related_files: list[str] | None = None, + related_symbols: list[str] | None = None, + source: str | None = None, + timestamp: str | None = None, + file_source: str | None = None, +) -> dict[str, Any]: + """Store a structured project memory (lessons, preferences, decisions, structures, etc.).""" + note = _build_memory_note( + title=title, + content=content, + memory_type=memory_type, + tags=tags, + related_files=related_files, + related_symbols=related_symbols, + source=source, + timestamp=timestamp, + ) + if file_source is None: + normalized_type = (memory_type or "memory").replace(" ", "_").lower() + file_source = f"memory/{normalized_type}/{title}" + return await client.request_json( + "POST", + "/documents/text", + json={"text": note, "file_source": file_source}, + ) + + +@mcp.tool() +async def refresh_and_query( + query: str, + mode: str | None = "mix", + top_k: int | None = None, + chunk_top_k: int | None = None, + include_references: bool | None = True, + include_chunk_content: bool | None = None, + enable_rerank: bool | None = None, + wait_timeout_s: float | None = None, +) -> dict[str, Any]: + """Scan for new documents, wait for idle, then run query_data and query.""" + await client.request_json("POST", "/documents/scan") + timeout_s = wait_timeout_s if wait_timeout_s is not None else settings.poll_timeout_s + status = await _wait_for_idle(timeout_s, settings.poll_interval_s) + payload = _build_query_payload( + query, + mode=mode, + top_k=top_k, + chunk_top_k=chunk_top_k, + include_references=include_references, + include_chunk_content=include_chunk_content, + enable_rerank=enable_rerank, + ) + data = await client.request_json("POST", "/query/data", json=payload) + response = await client.request_json("POST", "/query", json=payload) + return {"pipeline_status": status, "query_data": data, "query": response} + + +@mcp.resource("lightrag://health") +async def health_resource() -> str: + """Health check and config status.""" + data = await client.request_json("GET", "/health") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://pipeline/status") +async def pipeline_status_resource() -> str: + """Current pipeline status.""" + data = await client.request_json("GET", "/documents/pipeline_status") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://documents") +async def documents_resource() -> str: + """Documents list.""" + data = await client.request_json("GET", "/documents") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://graph") +async def graph_resource() -> str: + """Full knowledge graph.""" + data = await client.request_json("GET", "/graphs") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://labels/list") +async def labels_resource() -> str: + """List of graph labels.""" + data = await client.request_json("GET", "/graph/label/list") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://labels/popular") +async def popular_labels_resource() -> str: + """Popular graph labels.""" + data = await client.request_json("GET", "/graph/label/popular") + return json.dumps(data, indent=2) + + +@mcp.resource("lightrag://documents/status_counts") +async def status_counts_resource() -> str: + """Document status counts.""" + data = await client.request_json("GET", "/documents/status_counts") + return json.dumps(data, indent=2) + + +@mcp.prompt() +async def evidence_first_answer(question: str, mode: str = "mix", top_k: int | None = None) -> str: + """Prefer evidence-first retrieval before answering.""" + return ( + "Use query_data first to gather evidence, then decide if you need more retrieval. " + "After that, call query with include_references=true to produce the final response.\n\n" + f"Question: {question}\nMode: {mode}\nTop_k: {top_k}" + ) + + +@mcp.prompt() +async def refresh_kb_and_query(question: str, mode: str = "mix", top_k: int | None = None) -> str: + """Refresh the knowledge base, wait idle, then retrieve and answer.""" + return ( + "Run refresh_and_query to scan, wait for idle, and then retrieve evidence and answer. " + "Prefer include_references=true for the final answer.\n\n" + f"Question: {question}\nMode: {mode}\nTop_k: {top_k}" + ) + + +@mcp.prompt() +async def record_project_memory( + title: str, + content: str, + source: str | None = None, +) -> str: + """Record a durable project memory into LightRAG.""" + source_line = f"Source: {source}\n" if source else "" + return ( + "Summarize the memory as a concise note with bullet points and then ingest it using ingest_memory. " + "Prefer memory types like lesson, preference, decision, structure, function, relationship.\n\n" + f"Title: {title}\n{source_line}Content: {content}" + ) + + +def main() -> None: + run_kwargs: dict[str, Any] = {"transport": settings.mcp_transport} + try: + import inspect + + params = inspect.signature(mcp.run).parameters + if "host" in params: + run_kwargs["host"] = settings.mcp_host + if "port" in params: + run_kwargs["port"] = settings.mcp_port + except (TypeError, ValueError): + pass + mcp.run(**run_kwargs) + + +if __name__ == "__main__": + main() diff --git a/src/lightrag_mcp/settings.py b/src/lightrag_mcp/settings.py new file mode 100644 index 0000000..1092e8d --- /dev/null +++ b/src/lightrag_mcp/settings.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Runtime settings for the LightRAG MCP server.""" + + model_config = SettingsConfigDict(extra="ignore", populate_by_name=True) + + base_url: str = Field(default="http://127.0.0.1:9621", alias="LIGHTRAG_BASE_URL") + timeout_s: float = Field(default=60.0, alias="LIGHTRAG_TIMEOUT_S") + + mcp_transport: str = Field(default="streamable-http", alias="MCP_TRANSPORT") + mcp_host: str = Field(default="127.0.0.1", alias="MCP_HOST") + mcp_port: int = Field(default=8000, alias="MCP_PORT") + mcp_server_name: str = Field(default="LightRAG MCP", alias="MCP_SERVER_NAME") + + poll_interval_s: float = Field(default=1.0, alias="LIGHTRAG_POLL_INTERVAL_S") + poll_timeout_s: float = Field(default=120.0, alias="LIGHTRAG_POLL_TIMEOUT_S") + + +def load_settings() -> Settings: + return Settings() diff --git a/src/lightrag_mcp/smoke.py b/src/lightrag_mcp/smoke.py new file mode 100644 index 0000000..48c6cc4 --- /dev/null +++ b/src/lightrag_mcp/smoke.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import argparse +import asyncio +import json + +from lightrag_mcp.client import LightRAGClient +from lightrag_mcp.settings import load_settings + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="LightRAG MCP smoke test") + parser.add_argument("--base-url", help="LightRAG base URL") + parser.add_argument("--timeout", type=float, help="HTTP timeout in seconds") + parser.add_argument("--query", help="Optional query to run via /query/data") + parser.add_argument("--mode", default="mix", help="Query mode") + parser.add_argument("--top-k", type=int, help="Top-k entities/relations") + parser.add_argument("--chunk-top-k", type=int, help="Top-k chunks") + parser.add_argument( + "--include-chunk-content", + action="store_true", + help="Include chunk content in query_data results", + ) + parser.add_argument( + "--format", + choices=["pretty", "json"], + default="pretty", + help="Output format", + ) + return parser + + +async def _run_smoke(args: argparse.Namespace) -> int: + settings = load_settings() + base_url = args.base_url or settings.base_url + timeout = args.timeout or settings.timeout_s + client = LightRAGClient(base_url, timeout_s=timeout) + + health = await client.request_json("GET", "/health") + _print_payload("health", health, args.format) + + if args.query: + payload = { + "query": args.query, + "mode": args.mode, + "top_k": args.top_k, + "chunk_top_k": args.chunk_top_k, + "include_chunk_content": True if args.include_chunk_content else None, + } + payload = {k: v for k, v in payload.items() if v is not None} + data = await client.request_json("POST", "/query/data", json=payload) + _print_payload("query_data", data, args.format) + + return 0 + + +def _print_payload(label: str, payload: dict[str, object], fmt: str) -> None: + if fmt == "json": + print(json.dumps({label: payload})) + else: + print(f"[{label}]") + print(json.dumps(payload, indent=2)) + + +def main() -> None: + parser = _build_parser() + args = parser.parse_args() + raise SystemExit(asyncio.run(_run_smoke(args))) + + +if __name__ == "__main__": + main()