From 709461b87531d7f0024d90b7cd85262168f7d998 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:07:06 +0100 Subject: [PATCH 01/11] added insert texts --- lightrag/api/lightrag_server.py | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index a392e67a..8b35e34b 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -1493,6 +1493,37 @@ def create_app(args): logging.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=str(e)) + @app.post( + "/documents/texts", + response_model=InsertResponse, + dependencies=[Depends(optional_api_key)], + ) + async def insert_texts( + request: InsertTextsRequest, background_tasks: BackgroundTasks + ): + """ + Insert texts into the Retrieval-Augmented Generation (RAG) system. + + This endpoint allows you to insert text data into the RAG system for later retrieval and use in generating responses. + + Args: + request (InsertTextsRequest): The request body containing the text to be inserted. + background_tasks: FastAPI BackgroundTasks for async processing + + Returns: + InsertResponse: A response object containing the status of the operation, a message, and the number of documents inserted. + """ + try: + background_tasks.add_task(pipeline_index_texts, request.texts) + return InsertResponse( + status="success", + message="Text successfully received. Processing will continue in background.", + ) + except Exception as e: + logging.error(f"Error /documents/text: {str(e)}") + logging.error(traceback.format_exc()) + raise HTTPException(status_code=500, detail=str(e)) + @app.post( "/documents/file", response_model=InsertResponse, @@ -1537,7 +1568,7 @@ def create_app(args): raise HTTPException(status_code=500, detail=str(e)) @app.post( - "/documents/batch", + "/documents/file_batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)], ) @@ -1622,7 +1653,9 @@ def create_app(args): raise HTTPException(status_code=500, detail=str(e)) @app.post( - "/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)] + "/query", + response_model=QueryResponse, + dependencies=[Depends(optional_api_key)] ) async def query_text(request: QueryRequest): """ @@ -1648,13 +1681,7 @@ def create_app(args): if isinstance(response, str): return QueryResponse(response=response) - # If it's an async generator, decide whether to stream based on stream parameter - if request.stream or hasattr(response, "__aiter__"): - result = "" - async for chunk in response: - result += chunk - return QueryResponse(response=result) - elif isinstance(response, dict): + if isinstance(response, dict): result = json.dumps(response, indent=2) return QueryResponse(response=result) else: @@ -1706,7 +1733,7 @@ def create_app(args): "Cache-Control": "no-cache", "Connection": "keep-alive", "Content-Type": "application/x-ndjson", - "X-Accel-Buffering": "no", # 确保在Nginx代理时正确处理流式响应 + "X-Accel-Buffering": "no", # Ensure proper handling of streaming response when proxied by Nginx }, ) except Exception as e: From b09589cfd9e46cff068ac4cb3c285d91d69a2589 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:13:29 +0100 Subject: [PATCH 02/11] cleaned code --- lightrag/api/lightrag_server.py | 37 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 8b35e34b..e2160b12 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -13,13 +13,12 @@ import re from fastapi.staticfiles import StaticFiles import logging import argparse -from typing import List, Any, Optional, Dict +from typing import List, Any, Literal, Optional, Dict from pydantic import BaseModel from lightrag import LightRAG, QueryParam from lightrag.types import GPTKeywordExtractionFormat from lightrag.api import __api_version__ from lightrag.utils import EmbeddingFunc -from enum import Enum from pathlib import Path import shutil import aiofiles @@ -626,23 +625,11 @@ class DocumentManager: return any(filename.lower().endswith(ext) for ext in self.supported_extensions) -# LightRAG query mode -class SearchMode(str, Enum): - naive = "naive" - local = "local" - global_ = "global" - hybrid = "hybrid" - mix = "mix" - - class QueryRequest(BaseModel): query: str """Specifies the retrieval mode""" - mode: SearchMode = SearchMode.hybrid - - """If True, enables streaming output for real-time responses.""" - stream: Optional[bool] = None + mode: Literal["local", "global", "hybrid", "naive", "mix"] = "hybrid" """If True, only returns the retrieved context without generating a response.""" only_need_context: Optional[bool] = None @@ -688,13 +675,18 @@ class InsertTextRequest(BaseModel): text: str +class InsertTextsRequest(BaseModel): + texts: list[str] + + class InsertResponse(BaseModel): status: str message: str -def QueryRequestToQueryParams(request: QueryRequest): - param = QueryParam(mode=request.mode, stream=request.stream) +def QueryRequestToQueryParams(request: QueryRequest, is_stream: bool): + param = QueryParam(mode=request.mode, stream=is_stream) + if request.only_need_context is not None: param.only_need_context = request.only_need_context if request.only_need_prompt is not None: @@ -1523,7 +1515,7 @@ def create_app(args): logging.error(f"Error /documents/text: {str(e)}") logging.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=str(e)) - + @app.post( "/documents/file", response_model=InsertResponse, @@ -1653,9 +1645,7 @@ def create_app(args): raise HTTPException(status_code=500, detail=str(e)) @app.post( - "/query", - response_model=QueryResponse, - dependencies=[Depends(optional_api_key)] + "/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)] ) async def query_text(request: QueryRequest): """ @@ -1674,7 +1664,7 @@ def create_app(args): """ try: response = await rag.aquery( - request.query, param=QueryRequestToQueryParams(request) + request.query, param=QueryRequestToQueryParams(request, False) ) # If response is a string (e.g. cache hit), return directly @@ -1703,9 +1693,8 @@ def create_app(args): StreamingResponse: A streaming response containing the RAG query results. """ try: - params = QueryRequestToQueryParams(request) + params = QueryRequestToQueryParams(request, True) - params.stream = True response = await rag.aquery( # Use aquery instead of query, and add await request.query, param=params ) From 7f5f44a646b524a894729004e75d16ffa1a68b7f Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:24:12 +0100 Subject: [PATCH 03/11] added type validation --- lightrag/api/lightrag_server.py | 41 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index e2160b12..07ba6505 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -14,7 +14,7 @@ from fastapi.staticfiles import StaticFiles import logging import argparse from typing import List, Any, Literal, Optional, Dict -from pydantic import BaseModel +from pydantic import BaseModel, Field, field_validator from lightrag import LightRAG, QueryParam from lightrag.types import GPTKeywordExtractionFormat from lightrag.api import __api_version__ @@ -629,42 +629,42 @@ class QueryRequest(BaseModel): query: str """Specifies the retrieval mode""" - mode: Literal["local", "global", "hybrid", "naive", "mix"] = "hybrid" + mode: Literal["local", "global", "hybrid", "naive", "mix"] = Field(default="hybrid") """If True, only returns the retrieved context without generating a response.""" - only_need_context: Optional[bool] = None + only_need_context: Optional[bool] = Field(default=None) """If True, only returns the generated prompt without producing a response.""" - only_need_prompt: Optional[bool] = None + only_need_prompt: Optional[bool] = Field(default=None) """Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'.""" - response_type: Optional[str] = None + response_type: Optional[str] = Field(default=None) """Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode.""" - top_k: Optional[int] = None + top_k: Optional[int] = Field(default=None) """Maximum number of tokens allowed for each retrieved text chunk.""" - max_token_for_text_unit: Optional[int] = None + max_token_for_text_unit: Optional[int] = Field(default=None) """Maximum number of tokens allocated for relationship descriptions in global retrieval.""" - max_token_for_global_context: Optional[int] = None + max_token_for_global_context: Optional[int] = Field(default=None) """Maximum number of tokens allocated for entity descriptions in local retrieval.""" - max_token_for_local_context: Optional[int] = None + max_token_for_local_context: Optional[int] = Field(default=None) """List of high-level keywords to prioritize in retrieval.""" - hl_keywords: Optional[List[str]] = None + hl_keywords: Optional[List[str]] = Field(default=None) """List of low-level keywords to refine retrieval focus.""" - ll_keywords: Optional[List[str]] = None + ll_keywords: Optional[List[str]] = Field(default=None) """Stores past conversation history to maintain context. Format: [{"role": "user/assistant", "content": "message"}]. """ - conversation_history: Optional[List[dict[str, Any]]] = None + conversation_history: Optional[List[dict[str, Any]]] = Field(default=None) """Number of complete conversation turns (user-assistant pairs) to consider in the response context.""" - history_turns: Optional[int] = None + history_turns: Optional[int] = Field(default=None) class QueryResponse(BaseModel): @@ -674,9 +674,22 @@ class QueryResponse(BaseModel): class InsertTextRequest(BaseModel): text: str + @field_validator('text', mode='after') + @classmethod + def check_not_empty(cls, text: str) -> str: + if not text: + raise ValueError("Text cannot be empty") + return text class InsertTextsRequest(BaseModel): - texts: list[str] + texts: list[str] = Field(default_factory=list) + + @field_validator('texts', mode='after') + @classmethod + def check_not_empty(cls, texts: list[str]) -> list[str]: + if not texts: + raise ValueError("Texts cannot be empty") + return texts class InsertResponse(BaseModel): From 730e2bc8ce1e340f9f79f4f2725e9e064c976c81 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:34:27 +0100 Subject: [PATCH 04/11] added conditions --- lightrag/api/lightrag_server.py | 43 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 07ba6505..29146f59 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -638,33 +638,35 @@ class QueryRequest(BaseModel): only_need_prompt: Optional[bool] = Field(default=None) """Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'.""" - response_type: Optional[str] = Field(default=None) + response_type: Optional[str] = Field(min_length=1, default=None) """Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode.""" - top_k: Optional[int] = Field(default=None) + top_k: Optional[int] = Field(gt=1, default=None) """Maximum number of tokens allowed for each retrieved text chunk.""" - max_token_for_text_unit: Optional[int] = Field(default=None) + max_token_for_text_unit: Optional[int] = Field(gt=1, default=None) """Maximum number of tokens allocated for relationship descriptions in global retrieval.""" - max_token_for_global_context: Optional[int] = Field(default=None) + max_token_for_global_context: Optional[int] = Field(gt=1, default=None) """Maximum number of tokens allocated for entity descriptions in local retrieval.""" - max_token_for_local_context: Optional[int] = Field(default=None) + max_token_for_local_context: Optional[int] = Field(gt=1, default=None) """List of high-level keywords to prioritize in retrieval.""" - hl_keywords: Optional[List[str]] = Field(default=None) + hl_keywords: Optional[List[str]] = Field(min_length=1, default=None) """List of low-level keywords to refine retrieval focus.""" - ll_keywords: Optional[List[str]] = Field(default=None) + ll_keywords: Optional[List[str]] = Field(min_length=1, default=None) """Stores past conversation history to maintain context. Format: [{"role": "user/assistant", "content": "message"}]. """ - conversation_history: Optional[List[dict[str, Any]]] = Field(default=None) + conversation_history: Optional[List[dict[str, Any]]] = Field( + min_length=1, default=None + ) """Number of complete conversation turns (user-assistant pairs) to consider in the response context.""" - history_turns: Optional[int] = Field(default=None) + history_turns: Optional[int] = Field(gt=1, default=None) class QueryResponse(BaseModel): @@ -672,24 +674,21 @@ class QueryResponse(BaseModel): class InsertTextRequest(BaseModel): - text: str + text: str = Field(min_length=1) - @field_validator('text', mode='after') + @field_validator("text", mode="after") @classmethod - def check_not_empty(cls, text: str) -> str: - if not text: - raise ValueError("Text cannot be empty") - return text + def strip_after(cls, text: str) -> str: + return text.strip() + class InsertTextsRequest(BaseModel): - texts: list[str] = Field(default_factory=list) - - @field_validator('texts', mode='after') + texts: list[str] = Field(min_length=1) + + @field_validator("texts", mode="after") @classmethod - def check_not_empty(cls, texts: list[str]) -> list[str]: - if not texts: - raise ValueError("Texts cannot be empty") - return texts + def strip_after(cls, texts: list[str]) -> list[str]: + return [text.strip() for text in texts] class InsertResponse(BaseModel): From 3119f76e26197c169a0fc42fb1ed33438c24d604 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:42:09 +0100 Subject: [PATCH 05/11] added field check --- lightrag/api/lightrag_server.py | 157 +++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 42 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 29146f59..4ce8ddb3 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -626,55 +626,125 @@ class DocumentManager: class QueryRequest(BaseModel): - query: str - - """Specifies the retrieval mode""" - mode: Literal["local", "global", "hybrid", "naive", "mix"] = Field(default="hybrid") - - """If True, only returns the retrieved context without generating a response.""" - only_need_context: Optional[bool] = Field(default=None) - - """If True, only returns the generated prompt without producing a response.""" - only_need_prompt: Optional[bool] = Field(default=None) - - """Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'.""" - response_type: Optional[str] = Field(min_length=1, default=None) - - """Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode.""" - top_k: Optional[int] = Field(gt=1, default=None) - - """Maximum number of tokens allowed for each retrieved text chunk.""" - max_token_for_text_unit: Optional[int] = Field(gt=1, default=None) - - """Maximum number of tokens allocated for relationship descriptions in global retrieval.""" - max_token_for_global_context: Optional[int] = Field(gt=1, default=None) - - """Maximum number of tokens allocated for entity descriptions in local retrieval.""" - max_token_for_local_context: Optional[int] = Field(gt=1, default=None) - - """List of high-level keywords to prioritize in retrieval.""" - hl_keywords: Optional[List[str]] = Field(min_length=1, default=None) - - """List of low-level keywords to refine retrieval focus.""" - ll_keywords: Optional[List[str]] = Field(min_length=1, default=None) - - """Stores past conversation history to maintain context. - Format: [{"role": "user/assistant", "content": "message"}]. - """ - conversation_history: Optional[List[dict[str, Any]]] = Field( - min_length=1, default=None + query: str = Field( + min_length=1, + description="The query text", ) - """Number of complete conversation turns (user-assistant pairs) to consider in the response context.""" - history_turns: Optional[int] = Field(gt=1, default=None) + mode: Literal["local", "global", "hybrid", "naive", "mix"] = Field( + default="hybrid", + description="Query mode", + ) + + only_need_context: Optional[bool] = Field( + default=None, + description="If True, only returns the retrieved context without generating a response.", + ) + + only_need_prompt: Optional[bool] = Field( + default=None, + description="If True, only returns the generated prompt without producing a response.", + ) + + response_type: Optional[str] = Field( + min_length=1, + default=None, + description="Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'.", + ) + + top_k: Optional[int] = Field( + gt=1, + default=None, + description="Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode.", + ) + + max_token_for_text_unit: Optional[int] = Field( + gt=1, + default=None, + description="Maximum number of tokens allowed for each retrieved text chunk.", + ) + + max_token_for_global_context: Optional[int] = Field( + gt=1, + default=None, + description="Maximum number of tokens allocated for relationship descriptions in global retrieval.", + ) + + max_token_for_local_context: Optional[int] = Field( + gt=1, + default=None, + description="Maximum number of tokens allocated for entity descriptions in local retrieval.", + ) + + hl_keywords: Optional[List[str]] = Field( + min_length=1, + default=None, + description="List of high-level keywords to prioritize in retrieval.", + ) + + ll_keywords: Optional[List[str]] = Field( + min_length=1, + default=None, + description="List of low-level keywords to refine retrieval focus.", + ) + + conversation_history: Optional[List[dict[str, Any]]] = Field( + min_length=1, + default=None, + description="Stores past conversation history to maintain context. Format: [{'role': 'user/assistant', 'content': 'message'}].", + ) + + history_turns: Optional[int] = Field( + gt=1, + default=None, + description="Number of complete conversation turns (user-assistant pairs) to consider in the response context.", + ) + + @field_validator("query", mode="after") + @classmethod + def query_strip_after(cls, query: str) -> str: + return query.strip() + + @field_validator("hl_keywords", mode="after") + @classmethod + def hl_keywords_strip_after(cls, hl_keywords: List[str] | None) -> List[str] | None: + if hl_keywords is None: + return None + return [keyword.strip() for keyword in hl_keywords] + + @field_validator("ll_keywords", mode="after") + @classmethod + def ll_keywords_strip_after(cls, ll_keywords: List[str] | None) -> List[str] | None: + if ll_keywords is None: + return None + return [keyword.strip() for keyword in ll_keywords] + + @field_validator("conversation_history", mode="after") + @classmethod + def conversation_history_role_check( + cls, conversation_history: List[dict[str, Any]] | None + ) -> List[dict[str, Any]] | None: + if conversation_history is None: + return None + for msg in conversation_history: + if "role" not in msg or msg["role"] not in {"user", "assistant"}: + raise ValueError( + "Each message must have a 'role' key with value 'user' or 'assistant'." + ) + return conversation_history class QueryResponse(BaseModel): - response: str + response: str = Field( + description="The generated response", + ) class InsertTextRequest(BaseModel): - text: str = Field(min_length=1) + text: str = Field( + min_length=1, + description="The text to insert", + ) @field_validator("text", mode="after") @classmethod @@ -683,7 +753,10 @@ class InsertTextRequest(BaseModel): class InsertTextsRequest(BaseModel): - texts: list[str] = Field(min_length=1) + texts: list[str] = Field( + min_length=1, + description="The texts to insert", + ) @field_validator("texts", mode="after") @classmethod From e8c80ce68375d10fa901b1ba0afd63758fee8396 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:44:25 +0100 Subject: [PATCH 06/11] cleaned code --- lightrag/api/lightrag_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 4ce8ddb3..84ce16cc 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -765,8 +765,8 @@ class InsertTextsRequest(BaseModel): class InsertResponse(BaseModel): - status: str - message: str + status: str = Field(description="Status of the operation") + message: str = Field(description="Message describing the operation result") def QueryRequestToQueryParams(request: QueryRequest, is_stream: bool): From 1fdcd93e845884ebe4f400820d225f3256e012b3 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 19:52:28 +0100 Subject: [PATCH 07/11] cleaned code --- lightrag/api/lightrag_server.py | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 84ce16cc..59bffa47 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -769,32 +769,13 @@ class InsertResponse(BaseModel): message: str = Field(description="Message describing the operation result") -def QueryRequestToQueryParams(request: QueryRequest, is_stream: bool): - param = QueryParam(mode=request.mode, stream=is_stream) +def QueryRequestToQueryParams(request: QueryRequest, is_stream: bool) -> QueryParam: + """Converts a QueryRequest instance into a QueryParam instance.""" + # Use Pydantic's `.model_dump(exclude_none=True)` to remove None values automatically + request_data = request.model_dump(exclude_none=True, exclude={"query"}) - if request.only_need_context is not None: - param.only_need_context = request.only_need_context - if request.only_need_prompt is not None: - param.only_need_prompt = request.only_need_prompt - if request.response_type is not None: - param.response_type = request.response_type - if request.top_k is not None: - param.top_k = request.top_k - if request.max_token_for_text_unit is not None: - param.max_token_for_text_unit = request.max_token_for_text_unit - if request.max_token_for_global_context is not None: - param.max_token_for_global_context = request.max_token_for_global_context - if request.max_token_for_local_context is not None: - param.max_token_for_local_context = request.max_token_for_local_context - if request.hl_keywords is not None: - param.hl_keywords = request.hl_keywords - if request.ll_keywords is not None: - param.ll_keywords = request.ll_keywords - if request.conversation_history is not None: - param.conversation_history = request.conversation_history - if request.history_turns is not None: - param.history_turns = request.history_turns - return param + # Ensure `mode` and `stream` are set explicitly + return QueryParam(**request_data, stream=is_stream) def get_api_key_dependency(api_key: Optional[str]): From 0b78787b259bc75e5067e22bbe5910f0057fc8df Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 20:03:52 +0100 Subject: [PATCH 08/11] cleaned code --- lightrag/api/lightrag_server.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 59bffa47..756148fe 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -733,6 +733,14 @@ class QueryRequest(BaseModel): ) return conversation_history + def to_query_params(self, is_stream: bool) -> QueryParam: + """Converts a QueryRequest instance into a QueryParam instance.""" + # Use Pydantic's `.model_dump(exclude_none=True)` to remove None values automatically + request_data = self.model_dump(exclude_none=True, exclude={"query"}) + + # Ensure `mode` and `stream` are set explicitly + return QueryParam(**request_data, stream=is_stream) + class QueryResponse(BaseModel): response: str = Field( @@ -769,15 +777,6 @@ class InsertResponse(BaseModel): message: str = Field(description="Message describing the operation result") -def QueryRequestToQueryParams(request: QueryRequest, is_stream: bool) -> QueryParam: - """Converts a QueryRequest instance into a QueryParam instance.""" - # Use Pydantic's `.model_dump(exclude_none=True)` to remove None values automatically - request_data = request.model_dump(exclude_none=True, exclude={"query"}) - - # Ensure `mode` and `stream` are set explicitly - return QueryParam(**request_data, stream=is_stream) - - def get_api_key_dependency(api_key: Optional[str]): if not api_key: # If no API key is configured, return a dummy dependency that always succeeds @@ -1730,7 +1729,7 @@ def create_app(args): """ try: response = await rag.aquery( - request.query, param=QueryRequestToQueryParams(request, False) + request.query, param=request.to_query_params(False) ) # If response is a string (e.g. cache hit), return directly @@ -1759,10 +1758,8 @@ def create_app(args): StreamingResponse: A streaming response containing the RAG query results. """ try: - params = QueryRequestToQueryParams(request, True) - - response = await rag.aquery( # Use aquery instead of query, and add await - request.query, param=params + response = await rag.aquery( + request.query, param=request.to_query_params(True) ) from fastapi.responses import StreamingResponse From efec27c0b8df84cd13492d95e5b110eca2430845 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Sun, 16 Feb 2025 20:10:28 +0100 Subject: [PATCH 09/11] fixed stream --- lightrag/api/lightrag_server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 756148fe..29330fdb 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -739,7 +739,9 @@ class QueryRequest(BaseModel): request_data = self.model_dump(exclude_none=True, exclude={"query"}) # Ensure `mode` and `stream` are set explicitly - return QueryParam(**request_data, stream=is_stream) + param = QueryParam(**request_data) + param.stream = is_stream + return param class QueryResponse(BaseModel): From e9e786b75cbdb47f1c6fe2f72f566319e20312a7 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Mon, 17 Feb 2025 08:44:37 +0100 Subject: [PATCH 10/11] update pr --- lightrag/api/lightrag_server.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 29330fdb..a9838a46 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -653,7 +653,7 @@ class QueryRequest(BaseModel): ) top_k: Optional[int] = Field( - gt=1, + ge=1, default=None, description="Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode.", ) @@ -677,25 +677,22 @@ class QueryRequest(BaseModel): ) hl_keywords: Optional[List[str]] = Field( - min_length=1, default=None, description="List of high-level keywords to prioritize in retrieval.", ) ll_keywords: Optional[List[str]] = Field( - min_length=1, default=None, description="List of low-level keywords to refine retrieval focus.", ) conversation_history: Optional[List[dict[str, Any]]] = Field( - min_length=1, default=None, description="Stores past conversation history to maintain context. Format: [{'role': 'user/assistant', 'content': 'message'}].", ) history_turns: Optional[int] = Field( - gt=1, + ge=0 default=None, description="Number of complete conversation turns (user-assistant pairs) to consider in the response context.", ) From ad5c5bba313fc8031521cc3b07237f2c7e202750 Mon Sep 17 00:00:00 2001 From: Yannick Stephan Date: Mon, 17 Feb 2025 08:48:53 +0100 Subject: [PATCH 11/11] fix merge --- lightrag/api/lightrag_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index 7c5fc581..7a50a512 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -16,6 +16,7 @@ import argparse from typing import List, Any, Literal, Optional, Dict from pydantic import BaseModel, Field, field_validator from lightrag import LightRAG, QueryParam +from lightrag.base import DocProcessingStatus, DocStatus from lightrag.types import GPTKeywordExtractionFormat from lightrag.api import __api_version__ from lightrag.utils import EmbeddingFunc @@ -702,7 +703,7 @@ class QueryRequest(BaseModel): ) history_turns: Optional[int] = Field( - ge=0 + ge=0, default=None, description="Number of complete conversation turns (user-assistant pairs) to consider in the response context.", ) @@ -802,7 +803,6 @@ class DocsStatusesResponse(BaseModel): statuses: Dict[DocStatus, List[DocStatusResponse]] = {} - def get_api_key_dependency(api_key: Optional[str]): if not api_key: # If no API key is configured, return a dummy dependency that always succeeds