From b492801d48a8914400fbf3610a42db2e33d83531 Mon Sep 17 00:00:00 2001
From: Ceceliachenen <162673161+Ceceliachenen@users.noreply.github.com>
Date: Wed, 12 Jun 2024 20:14:10 +0800
Subject: [PATCH 01/69] Bugfix: a case that files' encodings can not be
detected by chardet (#61)
---
src/pai_rag/integrations/readers/pai_csv_reader.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/pai_rag/integrations/readers/pai_csv_reader.py b/src/pai_rag/integrations/readers/pai_csv_reader.py
index 0b653395..6653f501 100644
--- a/src/pai_rag/integrations/readers/pai_csv_reader.py
+++ b/src/pai_rag/integrations/readers/pai_csv_reader.py
@@ -141,7 +141,7 @@ def load_data(
with fs.open(file) as f:
encoding = chardet.detect(f.read(100000))["encoding"]
f.seek(0)
- if "GB" in encoding.upper():
+ if encoding is not None and "GB" in encoding.upper():
self._pandas_config["encoding"] = "GB18030"
try:
df = pd.read_csv(f, **self._pandas_config)
@@ -155,7 +155,7 @@ def load_data(
with open(file, "rb") as f:
encoding = chardet.detect(f.read(100000))["encoding"]
f.seek(0)
- if "GB" in encoding.upper():
+ if encoding is not None and "GB" in encoding.upper():
self._pandas_config["encoding"] = "GB18030"
try:
df = pd.read_csv(file, **self._pandas_config)
From ef7090b63fd1facca6a9cca2cb6b8b6c04404c56 Mon Sep 17 00:00:00 2001
From: wwxxzz
Date: Thu, 13 Jun 2024 16:44:49 +0800
Subject: [PATCH 02/69] Bugfix: connection error for longtime upload tasks
(#62)
* Fix connection error for longtime job
* fix testcase bugs
* support num workers for embedding model
* Refactor query api and add dataframe UI
* Refactor query api
* Remove embedding workers
---
src/pai_rag/app/api/query.py | 23 +++++++---
src/pai_rag/app/web/rag_client.py | 15 ++++++-
src/pai_rag/app/web/tabs/upload_tab.py | 51 +++++++++++++++++-----
src/pai_rag/core/rag_application.py | 4 +-
src/pai_rag/core/rag_service.py | 17 ++++++--
src/pai_rag/data/rag_dataloader.py | 11 ++++-
src/pai_rag/modules/embedding/embedding.py | 3 +-
tests/core/test_rag_application.py | 4 +-
8 files changed, 101 insertions(+), 27 deletions(-)
diff --git a/src/pai_rag/app/api/query.py b/src/pai_rag/app/api/query.py
index 60bd0d54..dc0ec7fc 100644
--- a/src/pai_rag/app/api/query.py
+++ b/src/pai_rag/app/api/query.py
@@ -1,5 +1,6 @@
from typing import Any
-from fastapi import APIRouter, Body
+from fastapi import APIRouter, Body, BackgroundTasks
+import uuid
from pai_rag.core.rag_service import rag_service
from pai_rag.app.api.models import (
RagQuery,
@@ -39,12 +40,22 @@ async def aupdate(new_config: Any = Body(None)):
return {"msg": "Update RAG configuration successfully."}
-@router.post("/data")
-async def load_data(input: DataInput):
- await rag_service.add_knowledge(
- file_dir=input.file_path, enable_qa_extraction=input.enable_qa_extraction
+@router.post("/upload_data")
+async def load_data(input: DataInput, background_tasks: BackgroundTasks):
+ task_id = uuid.uuid4().hex
+ background_tasks.add_task(
+ rag_service.add_knowledge_async,
+ task_id=task_id,
+ file_dir=input.file_path,
+ enable_qa_extraction=input.enable_qa_extraction,
)
- return {"msg": "Update RAG configuration successfully."}
+ return {"task_id": task_id}
+
+
+@router.get("/get_upload_state")
+def task_status(task_id: str):
+ status = rag_service.get_task_status(task_id)
+ return {"task_id": task_id, "status": status}
@router.post("/evaluate/response")
diff --git a/src/pai_rag/app/web/rag_client.py b/src/pai_rag/app/web/rag_client.py
index 35e88508..118b923d 100644
--- a/src/pai_rag/app/web/rag_client.py
+++ b/src/pai_rag/app/web/rag_client.py
@@ -41,7 +41,11 @@ def config_url(self):
@property
def load_data_url(self):
- return f"{self.endpoint}service/data"
+ return f"{self.endpoint}service/upload_data"
+
+ @property
+ def get_load_state_url(self):
+ return f"{self.endpoint}service/get_upload_state"
def query(self, text: str, session_id: str = None):
q = dict(question=text, session_id=session_id)
@@ -88,7 +92,14 @@ def add_knowledge(self, file_dir: str, enable_qa_extraction: bool):
q = dict(file_path=file_dir, enable_qa_extraction=enable_qa_extraction)
r = requests.post(self.load_data_url, json=q)
r.raise_for_status()
- return
+ response = dotdict(json.loads(r.text))
+ return response
+
+ def get_knowledge_state(self, task_id: str):
+ r = requests.get(self.get_load_state_url, params={"task_id": task_id})
+ r.raise_for_status()
+ response = dotdict(json.loads(r.text))
+ return response
def reload_config(self, config: Any):
global cache_config
diff --git a/src/pai_rag/app/web/tabs/upload_tab.py b/src/pai_rag/app/web/tabs/upload_tab.py
index 8dba2c6b..96e4d7a2 100644
--- a/src/pai_rag/app/web/tabs/upload_tab.py
+++ b/src/pai_rag/app/web/tabs/upload_tab.py
@@ -1,8 +1,11 @@
import os
from typing import Dict, Any
import gradio as gr
+import time
from pai_rag.app.web.rag_client import rag_client
from pai_rag.app.web.view_model import view_model
+from pai_rag.utils.file_utils import MyUploadFile
+import pandas as pd
def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extraction):
@@ -14,14 +17,36 @@ def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extracti
if not upload_files:
return "No file selected. Please choose at least one file."
+ my_upload_files = []
for file in upload_files:
file_dir = os.path.dirname(file.name)
- rag_client.add_knowledge(file_dir, enable_qa_extraction)
- return (
- "Upload "
- + str(len(upload_files))
- + " files Success! \n \n Relevant content has been added to the vector store, you can now start chatting and asking questions."
- )
+ response = rag_client.add_knowledge(file_dir, enable_qa_extraction)
+ my_upload_files.append(
+ MyUploadFile(os.path.basename(file.name), response["task_id"])
+ )
+
+ result = {"Info": ["StartTime", "EndTime", "Duration(s)", "Status"]}
+ while not all(file.finished is True for file in my_upload_files):
+ for file in my_upload_files:
+ response = rag_client.get_knowledge_state(str(file.task_id))
+ file.update_state(response["status"])
+ file.update_process_duration()
+ result[file.file_name] = file.__info__()
+ if response["status"] in ["completed", "failed"]:
+ file.is_finished()
+ yield [
+ gr.update(visible=True, value=pd.DataFrame(result)),
+ gr.update(visible=False),
+ ]
+ time.sleep(2)
+
+ yield [
+ gr.update(visible=True, value=pd.DataFrame(result)),
+ gr.update(
+ visible=True,
+ value="Uploaded all files successfully! \n Relevant content has been added to the vector store, you can now start chatting and asking questions.",
+ ),
+ ]
def create_upload_tab() -> Dict[str, Any]:
@@ -47,14 +72,20 @@ def create_upload_tab() -> Dict[str, Any]:
label="Upload a knowledge file.", file_count="multiple"
)
upload_file_btn = gr.Button("Upload", variant="primary")
- upload_file_state = gr.Textbox(label="Upload State")
+ upload_file_state_df = gr.DataFrame(
+ label="Upload Status Info", visible=False
+ )
+ upload_file_state = gr.Textbox(label="Upload Status", visible=False)
with gr.Tab("Directory"):
upload_file_dir = gr.File(
label="Upload a knowledge directory.",
file_count="directory",
)
upload_dir_btn = gr.Button("Upload", variant="primary")
- upload_dir_state = gr.Textbox(label="Upload State")
+ upload_dir_state_df = gr.DataFrame(
+ label="Upload Status Info", visible=False
+ )
+ upload_dir_state = gr.Textbox(label="Upload Status", visible=False)
upload_file_btn.click(
fn=upload_knowledge,
inputs=[
@@ -63,7 +94,7 @@ def create_upload_tab() -> Dict[str, Any]:
chunk_overlap,
enable_qa_extraction,
],
- outputs=upload_file_state,
+ outputs=[upload_file_state_df, upload_file_state],
api_name="upload_knowledge",
)
upload_dir_btn.click(
@@ -74,7 +105,7 @@ def create_upload_tab() -> Dict[str, Any]:
chunk_overlap,
enable_qa_extraction,
],
- outputs=upload_dir_state,
+ outputs=[upload_dir_state_df, upload_dir_state],
api_name="upload_knowledge_dir",
)
return {
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index 8a842ca6..20955fab 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -58,8 +58,8 @@ def reload(self, config):
self.logger.info("RagApplication reloaded successfully.")
# TODO: 大量文件上传实现异步添加
- async def load_knowledge(self, file_dir, enable_qa_extraction=False):
- await self.data_loader.load(file_dir, enable_qa_extraction)
+ def load_knowledge(self, file_dir, enable_qa_extraction=False):
+ self.data_loader.load(file_dir, enable_qa_extraction)
async def aquery_retrieval(self, query: RetrievalQuery) -> RetrievalResponse:
if not query.question:
diff --git a/src/pai_rag/core/rag_service.py b/src/pai_rag/core/rag_service.py
index a7f0fc7d..43e7819c 100644
--- a/src/pai_rag/core/rag_service.py
+++ b/src/pai_rag/core/rag_service.py
@@ -11,7 +11,7 @@
)
from pai_rag.app.web.view_model import view_model
from openinference.instrumentation import using_attributes
-from typing import Any
+from typing import Any, Dict
def trace_correlation_id(function):
@@ -41,14 +41,25 @@ def initialize(self, config_file: str):
view_model.sync_app_config(self.rag_configuration.get_value())
self.rag = RagApplication()
self.rag.initialize(self.rag_configuration.get_value())
+ self.tasks_status: Dict[str, str] = {}
def reload(self, new_config: Any):
self.rag_configuration.update(new_config)
self.rag.reload(self.rag_configuration.get_value())
self.rag_configuration.persist()
- async def add_knowledge(self, file_dir: str, enable_qa_extraction: bool = False):
- await self.rag.load_knowledge(file_dir, enable_qa_extraction)
+ def add_knowledge_async(
+ self, task_id: str, file_dir: str, enable_qa_extraction: bool = False
+ ):
+ self.tasks_status[task_id] = "processing"
+ try:
+ self.rag.load_knowledge(file_dir, enable_qa_extraction)
+ self.tasks_status[task_id] = "completed"
+ except Exception:
+ self.tasks_status[task_id] = "failed"
+
+ def get_task_status(self, task_id: str) -> str:
+ return self.tasks_status.get(task_id, "unknown")
async def aquery(self, query: RagQuery) -> RagResponse:
return await self.rag.aquery(query)
diff --git a/src/pai_rag/data/rag_dataloader.py b/src/pai_rag/data/rag_dataloader.py
index a4d3c5a1..2f2f96f5 100644
--- a/src/pai_rag/data/rag_dataloader.py
+++ b/src/pai_rag/data/rag_dataloader.py
@@ -1,5 +1,7 @@
import os
from typing import Any, Dict
+import asyncio
+import nest_asyncio
from llama_index.core import Settings
from llama_index.core.schema import TextNode
from llama_index.llms.huggingface import HuggingFaceLLM
@@ -57,7 +59,7 @@ def _extract_file_type(self, metadata: Dict[str, Any]):
file_name = metadata.get("file_name", "dummy.txt")
return os.path.splitext(file_name)[1]
- async def load(self, file_directory: str, enable_qa_extraction: bool):
+ async def aload(self, file_directory: str, enable_qa_extraction: bool):
data_reader = self.datareader_factory.get_reader(file_directory)
docs = data_reader.load_data()
logger.info(f"[DataReader] Loaded {len(docs)} docs.")
@@ -113,3 +115,10 @@ async def load(self, file_directory: str, enable_qa_extraction: bool):
self.index.storage_context.persist(persist_dir=store_path.persist_path)
logger.info(f"Inserted {len(nodes)} nodes successfully.")
return
+
+ nest_asyncio.apply() # 应用嵌套补丁到事件循环
+
+ def load(self, file_directory: str, enable_qa_extraction: bool):
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self.aload(file_directory, enable_qa_extraction))
+ return
diff --git a/src/pai_rag/modules/embedding/embedding.py b/src/pai_rag/modules/embedding/embedding.py
index 8b8350aa..75efbb93 100644
--- a/src/pai_rag/modules/embedding/embedding.py
+++ b/src/pai_rag/modules/embedding/embedding.py
@@ -53,7 +53,8 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
model_path = os.path.join(model_dir, model_name)
embed_model = HuggingFaceEmbedding(
- model_name=model_path, embed_batch_size=embed_batch_size
+ model_name=model_path,
+ embed_batch_size=embed_batch_size,
)
logger.info(
diff --git a/tests/core/test_rag_application.py b/tests/core/test_rag_application.py
index 0e42a291..457d599c 100644
--- a/tests/core/test_rag_application.py
+++ b/tests/core/test_rag_application.py
@@ -28,10 +28,10 @@ def rag_app():
# Test load knowledge file
-async def test_add_knowledge_file(rag_app: RagApplication):
+def test_add_knowledge_file(rag_app: RagApplication):
data_dir = os.path.join(BASE_DIR, "tests/testdata/paul_graham")
print(len(rag_app.index.docstore.docs))
- await rag_app.load_knowledge(data_dir)
+ rag_app.load_knowledge(data_dir)
print(len(rag_app.index.docstore.docs))
assert len(rag_app.index.docstore.docs) > 0
From ba1132a65350fcd0f17aeb416b18f53c7b5f4256 Mon Sep 17 00:00:00 2001
From: wwxxzz
Date: Thu, 13 Jun 2024 20:26:00 +0800
Subject: [PATCH 03/69] Add file: file_utils.py (#63)
* Fix connection error for longtime job
* fix testcase bugs
* support num workers for embedding model
* Refactor query api and add dataframe UI
* Refactor query api
* Remove embedding workers
* Add file_utils
---------
Co-authored-by: Yue Fei <59813791+moria97@users.noreply.github.com>
---
src/pai_rag/utils/file_utils.py | 36 +++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 src/pai_rag/utils/file_utils.py
diff --git a/src/pai_rag/utils/file_utils.py b/src/pai_rag/utils/file_utils.py
new file mode 100644
index 00000000..f8cb827b
--- /dev/null
+++ b/src/pai_rag/utils/file_utils.py
@@ -0,0 +1,36 @@
+from datetime import datetime
+import pytz
+
+# 创建北京时区的变量
+beijing_tz = pytz.timezone("Asia/Shanghai")
+
+
+class MyUploadFile:
+ def __init__(self, file_name, task_id):
+ self.file_name = file_name
+ self.task_id = task_id
+ self.start_time = datetime.now(beijing_tz)
+ self.end_time = None
+ self.duration = None
+ self.state = None
+ self.finished = False
+
+ def update_process_duration(self):
+ if not self.finished:
+ self.end_time = datetime.now(beijing_tz)
+ self.duration = (self.end_time - self.start_time).total_seconds()
+ return self.duration
+
+ def update_state(self, state):
+ self.state = state
+
+ def is_finished(self):
+ self.finished = True
+
+ def __info__(self):
+ return [
+ self.start_time.strftime("%Y-%m-%d %H:%M:%S"),
+ self.end_time.strftime("%Y-%m-%d %H:%M:%S"),
+ self.duration,
+ self.state,
+ ]
From daba1f5e8f4b0788f94d2a6ec1cd59f82e2c0775 Mon Sep 17 00:00:00 2001
From: Yue Fei <59813791+moria97@users.noreply.github.com>
Date: Thu, 13 Jun 2024 20:40:53 +0800
Subject: [PATCH 04/69] Remove local storage and enable Elasticsearch hybrid
query mode (#60)
* Add gpu dockerfile
* Fix bug
* Fix gb2312
* Update embedding batch size
* Set default embedding and llm model
* Update docker tag
* Fix hologres check
* Update registry
* Fix bug
* Fix tests
* Add queue
* Update batch size
* Add async interface
* Fix index conflict
* Add change index parameter for FAISS
* Fix batch size
* Update
---
src/pai_rag/app/api/models.py | 10 +-
src/pai_rag/core/rag_application.py | 76 +--
src/pai_rag/core/rag_service.py | 10 +-
src/pai_rag/data/rag_dataloader.py | 4 +-
src/pai_rag/data/rag_datapipeline.py | 15 +-
src/pai_rag/evaluations/batch_evaluator.py | 4 +-
.../dataset_generation/generate_dataset.py | 23 +-
src/pai_rag/modules/__init__.py | 5 +
.../modules/base/configurable_module.py | 26 +-
src/pai_rag/modules/cache/oss_cache.py | 20 +
.../modules/chat/chat_engine_factory.py | 34 +-
.../modules/chat/llm_chat_engine_factory.py | 31 +-
src/pai_rag/modules/datareader/data_loader.py | 25 +
src/pai_rag/modules/embedding/embedding.py | 4 +-
.../embedding/my_huggingface_embedding.py | 143 +++++
src/pai_rag/modules/index/index.py | 58 +-
src/pai_rag/modules/index/index_utils.py | 109 ++++
.../modules/index/my_vector_store_index.py | 119 ++++
src/pai_rag/modules/index/store.py | 22 +-
src/pai_rag/modules/module_registry.py | 54 +-
.../retriever/my_elasticsearch_store.py | 538 ++++++++++++++++++
.../retriever/my_vector_index_retriever.py | 2 +-
src/pai_rag/modules/retriever/retriever.py | 38 +-
src/pai_rag/utils/store_utils.py | 4 +-
src/pai_rag/utils/tokenizer.py | 16 +
tests/core/test_rag_application.py | 7 +-
26 files changed, 1217 insertions(+), 180 deletions(-)
create mode 100644 src/pai_rag/modules/cache/oss_cache.py
create mode 100644 src/pai_rag/modules/datareader/data_loader.py
create mode 100644 src/pai_rag/modules/embedding/my_huggingface_embedding.py
create mode 100644 src/pai_rag/modules/index/index_utils.py
create mode 100644 src/pai_rag/modules/index/my_vector_store_index.py
create mode 100644 src/pai_rag/modules/retriever/my_elasticsearch_store.py
create mode 100644 src/pai_rag/utils/tokenizer.py
diff --git a/src/pai_rag/app/api/models.py b/src/pai_rag/app/api/models.py
index 8cfede1e..62faf36e 100644
--- a/src/pai_rag/app/api/models.py
+++ b/src/pai_rag/app/api/models.py
@@ -2,13 +2,16 @@
from typing import List, Dict
+class VectorDbConfig(BaseModel):
+ faiss_path: str | None = None
+
+
class RagQuery(BaseModel):
question: str
temperature: float | None = 0.1
- vector_topk: int | None = 3
- score_threshold: float | None = 0.5
chat_history: List[Dict[str, str]] | None = None
session_id: str | None = None
+ vector_db: VectorDbConfig | None = None
class LlmQuery(BaseModel):
@@ -20,8 +23,7 @@ class LlmQuery(BaseModel):
class RetrievalQuery(BaseModel):
question: str
- topk: int | None = 3
- score_threshold: float | None = 0.5
+ vector_db: VectorDbConfig | None = None
class RagResponse(BaseModel):
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index 20955fab..cc314882 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -1,5 +1,3 @@
-from pai_rag.data.rag_dataloader import RagDataLoader
-from pai_rag.utils.oss_cache import OssCache
from pai_rag.modules.module_registry import module_registry
from pai_rag.evaluations.batch_evaluator import BatchEvaluator
from pai_rag.app.api.models import (
@@ -24,33 +22,11 @@ def uuid_generator() -> str:
class RagApplication:
def __init__(self):
self.name = "RagApplication"
- logging.basicConfig(level=logging.INFO) # 将日志级别设置为INFO
self.logger = logging.getLogger(__name__)
def initialize(self, config):
self.config = config
-
module_registry.init_modules(self.config)
- self.index = module_registry.get_module("IndexModule")
- self.llm = module_registry.get_module("LlmModule")
- self.retriever = module_registry.get_module("RetrieverModule")
- self.chat_store = module_registry.get_module("ChatStoreModule")
- self.query_engine = module_registry.get_module("QueryEngineModule")
- self.chat_engine_factory = module_registry.get_module("ChatEngineFactoryModule")
- self.llm_chat_engine_factory = module_registry.get_module(
- "LlmChatEngineFactoryModule"
- )
- self.data_reader_factory = module_registry.get_module("DataReaderFactoryModule")
- self.agent = module_registry.get_module("AgentModule")
-
- oss_cache = None
- if config.get("oss_cache", None):
- oss_cache = OssCache(config.oss_cache)
- node_parser = module_registry.get_module("NodeParserModule")
-
- self.data_loader = RagDataLoader(
- self.data_reader_factory, node_parser, self.index, oss_cache
- )
self.logger.info("RagApplication initialized successfully.")
def reload(self, config):
@@ -58,15 +34,22 @@ def reload(self, config):
self.logger.info("RagApplication reloaded successfully.")
# TODO: 大量文件上传实现异步添加
- def load_knowledge(self, file_dir, enable_qa_extraction=False):
- self.data_loader.load(file_dir, enable_qa_extraction)
+ async def load_knowledge(self, file_dir, enable_qa_extraction=False):
+ data_loader = module_registry.get_module_with_config(
+ "DataLoaderModule", self.config
+ )
+ await data_loader.aload(file_dir, enable_qa_extraction)
async def aquery_retrieval(self, query: RetrievalQuery) -> RetrievalResponse:
if not query.question:
return RetrievalResponse(docs=[])
query_bundle = QueryBundle(query.question)
- node_results = await self.query_engine.aretrieve(query_bundle)
+
+ query_engine = module_registry.get_module_with_config(
+ "QueryEngineModule", self.config
+ )
+ node_results = await query_engine.aretrieve(query_bundle)
docs = [
ContextDoc(
@@ -96,11 +79,24 @@ async def aquery(self, query: RagQuery) -> RagResponse:
answer="Empty query. Please input your question.", session_id=session_id
)
- query_chat_engine = self.chat_engine_factory.get_chat_engine(
+ sessioned_config = self.config
+ if query.vector_db and query.vector_db.faiss_path:
+ sessioned_config = self.config.copy()
+ sessioned_config.index.update({"persist_path": query.vector_db.faiss_path})
+ print(sessioned_config)
+
+ chat_engine_factory = module_registry.get_module_with_config(
+ "ChatEngineFactoryModule", sessioned_config
+ )
+ query_chat_engine = chat_engine_factory.get_chat_engine(
session_id, query.chat_history
)
response = await query_chat_engine.achat(query.question)
- self.chat_store.persist()
+
+ chat_store = module_registry.get_module_with_config(
+ "ChatStoreModule", sessioned_config
+ )
+ chat_store.persist()
return RagResponse(answer=response.response, session_id=session_id)
async def aquery_llm(self, query: LlmQuery) -> LlmResponse:
@@ -122,11 +118,18 @@ async def aquery_llm(self, query: LlmQuery) -> LlmResponse:
answer="Empty query. Please input your question.", session_id=session_id
)
- llm_chat_engine = self.llm_chat_engine_factory.get_chat_engine(
+ llm_chat_engine_factory = module_registry.get_module_with_config(
+ "LlmChatEngineFactoryModule", self.config
+ )
+ llm_chat_engine = llm_chat_engine_factory.get_chat_engine(
session_id, query.chat_history
)
response = await llm_chat_engine.achat(query.question)
- self.chat_store.persist()
+
+ chat_store = module_registry.get_module_with_config(
+ "ChatStoreModule", self.config
+ )
+ chat_store.persist()
return LlmResponse(answer=response.response, session_id=session_id)
async def aquery_agent(self, query: LlmQuery) -> LlmResponse:
@@ -143,11 +146,18 @@ async def aquery_agent(self, query: LlmQuery) -> LlmResponse:
if not query.question:
return LlmResponse(answer="Empty query. Please input your question.")
- response = await self.agent.achat(query.question)
+ agent = module_registry.get_module_with_config("AgentModule", self.config)
+ response = await agent.achat(query.question)
return LlmResponse(answer=response.response)
async def batch_evaluate_retrieval_and_response(self, type):
- batch_eval = BatchEvaluator(self.config, self.retriever, self.query_engine)
+ retriever = module_registry.get_module_with_config(
+ "RetrieverModule", self.config
+ )
+ query_engine = module_registry.get_module_with_config(
+ "QueryEngineModule", self.config
+ )
+ batch_eval = BatchEvaluator(self.config, retriever, query_engine)
df, eval_res_avg = await batch_eval.batch_retrieval_response_aevaluation(
type=type, workers=2, save_to_file=True
)
diff --git a/src/pai_rag/core/rag_service.py b/src/pai_rag/core/rag_service.py
index 43e7819c..4e768752 100644
--- a/src/pai_rag/core/rag_service.py
+++ b/src/pai_rag/core/rag_service.py
@@ -12,6 +12,9 @@
from pai_rag.app.web.view_model import view_model
from openinference.instrumentation import using_attributes
from typing import Any, Dict
+import logging
+
+logger = logging.getLogger(__name__)
def trace_correlation_id(function):
@@ -48,14 +51,15 @@ def reload(self, new_config: Any):
self.rag.reload(self.rag_configuration.get_value())
self.rag_configuration.persist()
- def add_knowledge_async(
+ async def add_knowledge_async(
self, task_id: str, file_dir: str, enable_qa_extraction: bool = False
):
self.tasks_status[task_id] = "processing"
try:
- self.rag.load_knowledge(file_dir, enable_qa_extraction)
+ await self.rag.load_knowledge(file_dir, enable_qa_extraction)
self.tasks_status[task_id] = "completed"
- except Exception:
+ except Exception as ex:
+ logger.error(f"Upload failed: {ex}")
self.tasks_status[task_id] = "failed"
def get_task_status(self, task_id: str) -> str:
diff --git a/src/pai_rag/data/rag_dataloader.py b/src/pai_rag/data/rag_dataloader.py
index 2f2f96f5..01246ce1 100644
--- a/src/pai_rag/data/rag_dataloader.py
+++ b/src/pai_rag/data/rag_dataloader.py
@@ -37,8 +37,8 @@ def __init__(
):
self.datareader_factory = datareader_factory
self.node_parser = node_parser
- self.index = index
self.oss_cache = oss_cache
+ self.index = index
if use_local_qa_model:
# API暂不支持此选项
@@ -111,7 +111,7 @@ async def aload(self, file_directory: str, enable_qa_extraction: bool):
logger.info("[DataReader] Start inserting to index.")
- self.index.insert_nodes(nodes)
+ await self.index.insert_nodes_async(nodes)
self.index.storage_context.persist(persist_dir=store_path.persist_path)
logger.info(f"Inserted {len(nodes)} nodes successfully.")
return
diff --git a/src/pai_rag/data/rag_datapipeline.py b/src/pai_rag/data/rag_datapipeline.py
index feb0f750..23787e3b 100644
--- a/src/pai_rag/data/rag_datapipeline.py
+++ b/src/pai_rag/data/rag_datapipeline.py
@@ -2,14 +2,12 @@
import click
import os
from pathlib import Path
-from pai_rag.data.rag_dataloader import RagDataLoader
from pai_rag.core.rag_configuration import RagConfiguration
-from pai_rag.utils.oss_cache import OssCache
from pai_rag.modules.module_registry import module_registry
class RagDataPipeline:
- def __init__(self, data_loader: RagDataLoader):
+ def __init__(self, data_loader):
self.data_loader = data_loader
async def ingest_from_folder(self, folder_path: str, enable_qa_extraction: bool):
@@ -23,16 +21,7 @@ def __init_data_pipeline(use_local_qa_model):
config = RagConfiguration.from_file(config_file).get_value()
module_registry.init_modules(config)
- oss_cache = None
- if config.get("oss_cache", None):
- oss_cache = OssCache(config.oss_cache)
- node_parser = module_registry.get_module("NodeParserModule")
- index = module_registry.get_module("IndexModule")
- data_reader_factory = module_registry.get_module("DataReaderFactoryModule")
-
- data_loader = RagDataLoader(
- data_reader_factory, node_parser, index, oss_cache, use_local_qa_model
- )
+ data_loader = module_registry.get_module_with_config("DataLoaderModule", config)
return RagDataPipeline(data_loader)
diff --git a/src/pai_rag/evaluations/batch_evaluator.py b/src/pai_rag/evaluations/batch_evaluator.py
index 47684e25..abd5f142 100644
--- a/src/pai_rag/evaluations/batch_evaluator.py
+++ b/src/pai_rag/evaluations/batch_evaluator.py
@@ -189,8 +189,8 @@ def __init_evaluator_pipeline():
config = RagConfiguration.from_file(config_file).get_value()
module_registry.init_modules(config)
- retriever = module_registry.get_module("RetrieverModule")
- query_engine = module_registry.get_module("QueryEngineModule")
+ retriever = module_registry.get_module_with_config("RetrieverModule", config)
+ query_engine = module_registry.get_module_with_config("QueryEngineModule", config)
return BatchEvaluator(config, retriever, query_engine)
diff --git a/src/pai_rag/evaluations/dataset_generation/generate_dataset.py b/src/pai_rag/evaluations/dataset_generation/generate_dataset.py
index 3bfcddc0..02b43e60 100644
--- a/src/pai_rag/evaluations/dataset_generation/generate_dataset.py
+++ b/src/pai_rag/evaluations/dataset_generation/generate_dataset.py
@@ -1,5 +1,6 @@
import logging
import os
+from pathlib import Path
from pai_rag.core.rag_configuration import RagConfiguration
from pai_rag.modules.module_registry import module_registry
from llama_index.core.prompts.prompt_type import PromptType
@@ -16,8 +17,13 @@
DEFAULT_TEXT_QA_PROMPT_TMPL,
DEFAULT_QUESTION_GENERATION_QUERY,
)
+
import json
+_BASE_DIR = Path(__file__).parent.parent.parent
+DEFAULT_EVAL_CONFIG_FILE = os.path.join(_BASE_DIR, "config/settings.toml")
+DEFAULT_EVAL_DATA_FOLDER = "tests/testdata/paul_graham"
+
class GenerateDatasetPipeline(ModifiedRagDatasetGenerator):
def __init__(
@@ -29,11 +35,22 @@ def __init__(
show_progress: Optional[bool] = True,
) -> None:
self.name = "GenerateDatasetPipeline"
- self.nodes = list(
- module_registry.get_module("IndexModule").docstore.docs.values()
+ self.config = RagConfiguration.from_file(DEFAULT_EVAL_CONFIG_FILE).get_value()
+
+ # load nodes
+ module_registry.init_modules(self.config)
+ datareader_factory = module_registry.get_module_with_config(
+ "DataReaderFactoryModule", self.config
)
+ self.node_parser = module_registry.get_module_with_config(
+ "NodeParserModule", self.config
+ )
+ reader = datareader_factory.get_reader(DEFAULT_EVAL_DATA_FOLDER)
+ docs = reader.load_data()
+ self.nodes = self.node_parser.get_nodes_from_documents(docs)
+
self.num_questions_per_chunk = num_questions_per_chunk
- self.llm = module_registry.get_module("LlmModule")
+ self.llm = module_registry.get_module_with_config("LlmModule", self.config)
self.text_question_template = PromptTemplate(text_question_template_str)
self.text_qa_template = PromptTemplate(
text_qa_template_str, prompt_type=PromptType.QUESTION_ANSWER
diff --git a/src/pai_rag/modules/__init__.py b/src/pai_rag/modules/__init__.py
index a23c466b..3491bb86 100644
--- a/src/pai_rag/modules/__init__.py
+++ b/src/pai_rag/modules/__init__.py
@@ -1,5 +1,6 @@
from pai_rag.modules.embedding.embedding import EmbeddingModule
from pai_rag.modules.llm.llm_module import LlmModule
+from pai_rag.modules.datareader.data_loader import DataLoaderModule
from pai_rag.modules.datareader.datareader_factory import DataReaderFactoryModule
from pai_rag.modules.index.index import IndexModule
from pai_rag.modules.nodeparser.node_parser import NodeParserModule
@@ -12,10 +13,13 @@
from pai_rag.modules.chat.chat_store import ChatStoreModule
from pai_rag.modules.agent.agent import AgentModule
from pai_rag.modules.tool.tool import ToolModule
+from pai_rag.modules.cache.oss_cache import OssCacheModule
+
ALL_MODULES = [
"EmbeddingModule",
"LlmModule",
+ "DataLoaderModule",
"DataReaderFactoryModule",
"IndexModule",
"NodeParserModule",
@@ -28,6 +32,7 @@
"LlmChatEngineFactoryModule",
"AgentModule",
"ToolModule",
+ "OssCacheModule",
]
__all__ = ALL_MODULES + ["ALL_MODULES"]
diff --git a/src/pai_rag/modules/base/configurable_module.py b/src/pai_rag/modules/base/configurable_module.py
index 78e9b089..de9e3834 100644
--- a/src/pai_rag/modules/base/configurable_module.py
+++ b/src/pai_rag/modules/base/configurable_module.py
@@ -1,19 +1,19 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Any
+import logging
DEFAULT_INSTANCE_KEY = "__DEFAULT_INSTANCE__"
+logger = logging.getLogger(__name__)
+
+
class ConfigurableModule(ABC):
"""Configurable Module
Helps to create instances according to configuration.
"""
- def __init__(self):
- self.__params_map = {}
- self.__instance_map = {}
-
@abstractmethod
def _create_new_instance(self, new_params: Dict[str, Any]):
raise NotImplementedError
@@ -24,20 +24,4 @@ def get_dependencies() -> List[str]:
raise NotImplementedError
def get_or_create(self, new_params: Dict[str, Any]):
- return self.get_or_create_by_name(new_params=new_params)
-
- def get_or_create_by_name(
- self, new_params: Dict[str, Any], name: str = DEFAULT_INSTANCE_KEY
- ):
- # Create new instance when initializing or config changed.
- if (
- self.__params_map.get(name, None) is None
- or self.__params_map[name] != new_params
- ):
- print(f"{self.__class__.__name__} param changed, updating")
- self.__instance_map[name] = self._create_new_instance(new_params)
- self.__params_map[name] = new_params
- else:
- print(f"{self.__class__.__name__} param unchanged, skipping")
-
- return self.__instance_map[name]
+ return self._create_new_instance(new_params)
diff --git a/src/pai_rag/modules/cache/oss_cache.py b/src/pai_rag/modules/cache/oss_cache.py
new file mode 100644
index 00000000..d4a543b9
--- /dev/null
+++ b/src/pai_rag/modules/cache/oss_cache.py
@@ -0,0 +1,20 @@
+from typing import Any, Dict, List
+from pai_rag.utils.oss_cache import OssCache
+from pai_rag.modules.base.configurable_module import ConfigurableModule
+from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class OssCacheModule(ConfigurableModule):
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return []
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ cache_config = new_params[MODULE_PARAM_CONFIG]
+ if cache_config:
+ return OssCache(cache_config)
+ else:
+ return None
diff --git a/src/pai_rag/modules/chat/chat_engine_factory.py b/src/pai_rag/modules/chat/chat_engine_factory.py
index ea8eea29..69282019 100644
--- a/src/pai_rag/modules/chat/chat_engine_factory.py
+++ b/src/pai_rag/modules/chat/chat_engine_factory.py
@@ -18,17 +18,11 @@
logger = logging.getLogger(__name__)
-class ChatEngineFactoryModule(ConfigurableModule):
- @staticmethod
- def get_dependencies() -> List[str]:
- return ["QueryEngineModule", "ChatStoreModule"]
-
- def _create_new_instance(self, new_params: Dict[str, Any]):
- self.config = new_params[MODULE_PARAM_CONFIG]
- self.query_engine = new_params["QueryEngineModule"]
- self.chat_store = new_params["ChatStoreModule"]
-
- return self
+class ChatEngineFactory:
+ def __init__(self, chat_type, query_engine, chat_store):
+ self.chat_type = chat_type
+ self.query_engine = query_engine
+ self.chat_store = chat_store
def get_chat_engine(self, session_id, chat_history):
chat_memory = self.chat_store.get_chat_memory_buffer(session_id)
@@ -36,7 +30,8 @@ def get_chat_engine(self, session_id, chat_history):
history_messages = parse_chat_messages(chat_history)
for hist_mes in history_messages:
chat_memory.put(hist_mes)
- if self.config.type == "CondenseQuestionChatEngine":
+
+ if self.chat_type == "CondenseQuestionChatEngine":
my_chat_engine = CondenseQuestionChatEngine.from_defaults(
query_engine=self.query_engine,
condense_question_prompt=CONDENSE_QUESTION_CHAT_ENGINE_PROMPT_ZH,
@@ -47,3 +42,18 @@ def get_chat_engine(self, session_id, chat_history):
return my_chat_engine
else:
raise ValueError(f"Unknown chat_engine_type: {self.config.type}")
+
+
+class ChatEngineFactoryModule(ConfigurableModule):
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return ["QueryEngineModule", "ChatStoreModule"]
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ config = new_params[MODULE_PARAM_CONFIG]
+ query_engine = new_params["QueryEngineModule"]
+ chat_store = new_params["ChatStoreModule"]
+
+ return ChatEngineFactory(
+ config.type, query_engine=query_engine, chat_store=chat_store
+ )
diff --git a/src/pai_rag/modules/chat/llm_chat_engine_factory.py b/src/pai_rag/modules/chat/llm_chat_engine_factory.py
index 815d0d3e..0b90cb67 100644
--- a/src/pai_rag/modules/chat/llm_chat_engine_factory.py
+++ b/src/pai_rag/modules/chat/llm_chat_engine_factory.py
@@ -12,16 +12,11 @@
logger = logging.getLogger(__name__)
-class LlmChatEngineFactoryModule(ConfigurableModule):
- @staticmethod
- def get_dependencies() -> List[str]:
- return ["LlmModule", "ChatStoreModule"]
-
- def _create_new_instance(self, new_params: Dict[str, Any]):
- self.config = new_params[MODULE_PARAM_CONFIG]
- self.llm = new_params["LlmModule"]
- self.chat_store = new_params["ChatStoreModule"]
- return self
+class LlmChatEngineFactory:
+ def __init__(self, chat_type, llm, chat_store):
+ self.chat_type = chat_type
+ self.llm = llm
+ self.chat_store = chat_store
def get_chat_engine(self, session_id, chat_history):
chat_memory = self.chat_store.get_chat_memory_buffer(session_id)
@@ -30,14 +25,26 @@ def get_chat_engine(self, session_id, chat_history):
for hist_mes in history_messages:
chat_memory.put(hist_mes)
- if self.config.type == "SimpleChatEngine":
+ if self.chat_type == "SimpleChatEngine":
my_chat_engine = SimpleChatEngine.from_defaults(
llm=self.llm,
memory=chat_memory,
verbose=True,
)
logger.info("simple chat_engine instance created")
+
+ return my_chat_engine
else:
raise ValueError(f"Unknown chat_engine_type: {self.config.type}")
- return my_chat_engine
+
+class LlmChatEngineFactoryModule(ConfigurableModule):
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return ["LlmModule", "ChatStoreModule"]
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ config = new_params[MODULE_PARAM_CONFIG]
+ llm = new_params["LlmModule"]
+ chat_store = new_params["ChatStoreModule"]
+ return LlmChatEngineFactory(config.type, llm, chat_store)
diff --git a/src/pai_rag/modules/datareader/data_loader.py b/src/pai_rag/modules/datareader/data_loader.py
new file mode 100644
index 00000000..5e5e0383
--- /dev/null
+++ b/src/pai_rag/modules/datareader/data_loader.py
@@ -0,0 +1,25 @@
+from typing import Any, Dict, List
+from pai_rag.modules.base.configurable_module import ConfigurableModule
+from pai_rag.data.rag_dataloader import RagDataLoader
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class DataLoaderModule(ConfigurableModule):
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return [
+ "OssCacheModule",
+ "DataReaderFactoryModule",
+ "NodeParserModule",
+ "IndexModule",
+ ]
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ oss_cache = new_params["OssCacheModule"]
+ data_reader_factory = new_params["DataReaderFactoryModule"]
+ node_parser = new_params["NodeParserModule"]
+ index = new_params["IndexModule"]
+
+ return RagDataLoader(data_reader_factory, node_parser, index, oss_cache)
diff --git a/src/pai_rag/modules/embedding/embedding.py b/src/pai_rag/modules/embedding/embedding.py
index 75efbb93..ed7eb24c 100644
--- a/src/pai_rag/modules/embedding/embedding.py
+++ b/src/pai_rag/modules/embedding/embedding.py
@@ -3,9 +3,9 @@
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding
from llama_index.embeddings.dashscope import DashScopeEmbedding
-from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from pai_rag.modules.base.configurable_module import ConfigurableModule
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
+from pai_rag.modules.embedding.my_huggingface_embedding import MyHuggingFaceEmbedding
from pai_rag.utils.constants import DEFAULT_MODEL_DIR
import os
import logging
@@ -52,7 +52,7 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
model_name = config.get("model_name", DEFAULT_HUGGINGFACE_EMBEDDING_MODEL)
model_path = os.path.join(model_dir, model_name)
- embed_model = HuggingFaceEmbedding(
+ embed_model = MyHuggingFaceEmbedding(
model_name=model_path,
embed_batch_size=embed_batch_size,
)
diff --git a/src/pai_rag/modules/embedding/my_huggingface_embedding.py b/src/pai_rag/modules/embedding/my_huggingface_embedding.py
new file mode 100644
index 00000000..f21f8cbb
--- /dev/null
+++ b/src/pai_rag/modules/embedding/my_huggingface_embedding.py
@@ -0,0 +1,143 @@
+import logging
+from typing import Any, List, Optional
+
+from llama_index.core.base.embeddings.base import (
+ DEFAULT_EMBED_BATCH_SIZE,
+ BaseEmbedding,
+)
+from llama_index.core.bridge.pydantic import Field, PrivateAttr
+from llama_index.core.callbacks import CallbackManager
+from llama_index.core.utils import get_cache_dir, infer_torch_device
+from llama_index.embeddings.huggingface.utils import (
+ DEFAULT_HUGGINGFACE_EMBEDDING_MODEL,
+ get_query_instruct_for_model_name,
+ get_text_instruct_for_model_name,
+)
+from sentence_transformers import SentenceTransformer
+
+DEFAULT_HUGGINGFACE_LENGTH = 512
+logger = logging.getLogger(__name__)
+
+
+# Add async interfaces
+class MyHuggingFaceEmbedding(BaseEmbedding):
+ max_length: int = Field(
+ default=DEFAULT_HUGGINGFACE_LENGTH, description="Maximum length of input.", gt=0
+ )
+ normalize: bool = Field(default=True, description="Normalize embeddings or not.")
+ query_instruction: Optional[str] = Field(
+ description="Instruction to prepend to query text."
+ )
+ text_instruction: Optional[str] = Field(
+ description="Instruction to prepend to text."
+ )
+ cache_folder: Optional[str] = Field(
+ description="Cache folder for Hugging Face files."
+ )
+
+ _model: Any = PrivateAttr()
+ _device: str = PrivateAttr()
+
+ def __init__(
+ self,
+ model_name: str = DEFAULT_HUGGINGFACE_EMBEDDING_MODEL,
+ tokenizer_name: Optional[str] = "deprecated",
+ pooling: str = "deprecated",
+ max_length: Optional[int] = None,
+ query_instruction: Optional[str] = None,
+ text_instruction: Optional[str] = None,
+ normalize: bool = True,
+ model: Optional[Any] = "deprecated",
+ tokenizer: Optional[Any] = "deprecated",
+ embed_batch_size: int = DEFAULT_EMBED_BATCH_SIZE,
+ cache_folder: Optional[str] = None,
+ trust_remote_code: bool = False,
+ device: Optional[str] = None,
+ callback_manager: Optional[CallbackManager] = None,
+ **model_kwargs,
+ ):
+ self._device = device or infer_torch_device()
+
+ cache_folder = cache_folder or get_cache_dir()
+
+ for variable, value in [
+ ("model", model),
+ ("tokenizer", tokenizer),
+ ("pooling", pooling),
+ ("tokenizer_name", tokenizer_name),
+ ]:
+ if value != "deprecated":
+ raise ValueError(
+ f"{variable} is deprecated. Please remove it from the arguments."
+ )
+ if model_name is None:
+ raise ValueError("The `model_name` argument must be provided.")
+
+ self._model = SentenceTransformer(
+ model_name,
+ device=self._device,
+ cache_folder=cache_folder,
+ trust_remote_code=trust_remote_code,
+ prompts={
+ "query": query_instruction
+ or get_query_instruct_for_model_name(model_name),
+ "text": text_instruction
+ or get_text_instruct_for_model_name(model_name),
+ },
+ **model_kwargs,
+ )
+ if max_length:
+ self._model.max_seq_length = max_length
+ else:
+ max_length = self._model.max_seq_length
+
+ super().__init__(
+ embed_batch_size=embed_batch_size,
+ callback_manager=callback_manager,
+ model_name=model_name,
+ max_length=max_length,
+ normalize=normalize,
+ query_instruction=query_instruction,
+ text_instruction=text_instruction,
+ )
+
+ @classmethod
+ def class_name(cls) -> str:
+ return "HuggingFaceEmbedding"
+
+ def _embed(
+ self,
+ sentences: List[str],
+ prompt_name: Optional[str] = None,
+ ) -> List[List[float]]:
+ """Embed sentences."""
+ return self._model.encode(
+ sentences,
+ batch_size=self.embed_batch_size,
+ prompt_name=prompt_name,
+ normalize_embeddings=self.normalize,
+ ).tolist()
+
+ def _get_query_embedding(self, query: str) -> List[float]:
+ """Get query embedding."""
+ return self._embed(query, prompt_name="query")
+
+ async def _aget_query_embedding(self, query: str) -> List[float]:
+ """Get query embedding async."""
+ return self._get_query_embedding(query)
+
+ async def _aget_text_embedding(self, text: str) -> List[float]:
+ """Get text embedding async."""
+ return self._get_text_embedding(text)
+
+ def _get_text_embedding(self, text: str) -> List[float]:
+ """Get text embedding."""
+ return self._embed(text, prompt_name="text")
+
+ def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
+ """Get text embeddings."""
+ return self._embed(texts, prompt_name="text")
+
+ async def _aget_text_embeddings(self, texts: List[str]) -> List[List[float]]:
+ """Get text embeddings."""
+ return self._embed(texts, prompt_name="text")
diff --git a/src/pai_rag/modules/index/index.py b/src/pai_rag/modules/index/index.py
index 248fbc96..84e22697 100644
--- a/src/pai_rag/modules/index/index.py
+++ b/src/pai_rag/modules/index/index.py
@@ -3,12 +3,12 @@
import sys
from typing import Dict, List, Any
-from llama_index.core import VectorStoreIndex
-
-from llama_index.core import load_index_from_storage
+from pai_rag.modules.index.my_vector_store_index import MyVectorStoreIndex
+from pai_rag.modules.index.index_utils import load_index_from_storage
from pai_rag.modules.base.configurable_module import ConfigurableModule
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
from pai_rag.modules.index.store import RagStore
+from llama_index.vector_stores.faiss import FaissVectorStore
from pai_rag.utils.store_utils import get_store_persist_directory_name, store_path
logging.basicConfig(
@@ -18,7 +18,6 @@
)
DEFAULT_PERSIST_DIR = "./storage"
-INDEX_STATE_FILE = "index.state.json"
class IndexModule(ConfigurableModule):
@@ -33,43 +32,44 @@ class IndexModule(ConfigurableModule):
def get_dependencies() -> List[str]:
return ["EmbeddingModule"]
- def _get_embed_vec_dim(self):
+ def _get_embed_vec_dim(self, embed_model):
# Get dimension size of embedding vector
- return len(self.embed_model._get_text_embedding("test"))
+ return len(embed_model._get_text_embedding("test"))
def _create_new_instance(self, new_params: Dict[str, Any]):
- self.config = new_params[MODULE_PARAM_CONFIG]
- self.embed_model = new_params["EmbeddingModule"]
- self.embed_dims = self._get_embed_vec_dim()
- persist_path = self.config.get("persist_path", DEFAULT_PERSIST_DIR)
- folder_name = get_store_persist_directory_name(self.config, self.embed_dims)
+ config = new_params[MODULE_PARAM_CONFIG]
+ embed_model = new_params["EmbeddingModule"]
+ embed_dims = self._get_embed_vec_dim(embed_model)
+ persist_path = config.get("persist_path", DEFAULT_PERSIST_DIR)
+ folder_name = get_store_persist_directory_name(config, embed_dims)
store_path.persist_path = os.path.join(persist_path, folder_name)
+ is_empty = not os.path.exists(store_path.persist_path)
+ rag_store = RagStore(config, store_path.persist_path, is_empty, embed_dims)
+ storage_context = rag_store.get_storage_context()
- self.is_empty = not os.path.exists(store_path.persist_path)
- rag_store = RagStore(
- self.config, store_path.persist_path, self.is_empty, self.embed_dims
- )
- self.storage_context = rag_store.get_storage_context()
-
- if self.is_empty:
- return self.create_indices()
+ if is_empty:
+ return self.create_indices(storage_context, embed_model)
else:
- return self.load_indices()
+ return self.load_indices(storage_context, embed_model)
- def create_indices(self):
+ def create_indices(self, storage_context, embed_model):
logging.info("Empty index, need to create indices.")
- vector_index = VectorStoreIndex(
- nodes=[],
- storage_context=self.storage_context,
- embed_model=self.embed_model,
- store_nodes_override=True,
+ vector_index = MyVectorStoreIndex(
+ nodes=[], storage_context=storage_context, embed_model=embed_model
)
logging.info("Created vector_index.")
return vector_index
- def load_indices(self):
- vector_index = load_index_from_storage(storage_context=self.storage_context)
-
+ def load_indices(self, storage_context, embed_model):
+ if isinstance(storage_context.vector_store, FaissVectorStore):
+ vector_index = load_index_from_storage(storage_context=storage_context)
+ return vector_index
+ else:
+ vector_index = MyVectorStoreIndex(
+ nodes=[],
+ storage_context=storage_context,
+ embed_model=embed_model,
+ )
return vector_index
diff --git a/src/pai_rag/modules/index/index_utils.py b/src/pai_rag/modules/index/index_utils.py
new file mode 100644
index 00000000..8f1ff4e5
--- /dev/null
+++ b/src/pai_rag/modules/index/index_utils.py
@@ -0,0 +1,109 @@
+import logging
+from typing import Any, List, Optional, Sequence
+
+from llama_index.core.indices.base import BaseIndex
+from llama_index.core.indices.composability.graph import ComposableGraph
+from llama_index.core.indices.registry import INDEX_STRUCT_TYPE_TO_INDEX_CLASS
+from llama_index.core.storage.storage_context import StorageContext
+from llama_index.core.data_structs.struct_type import IndexStructType
+
+from pai_rag.modules.index.my_vector_store_index import MyVectorStoreIndex
+
+
+MODIFIED_INDEX_STRUCT_TYPE_TO_INDEX_CLASS = INDEX_STRUCT_TYPE_TO_INDEX_CLASS
+MODIFIED_INDEX_STRUCT_TYPE_TO_INDEX_CLASS[
+ IndexStructType.VECTOR_STORE
+] = MyVectorStoreIndex
+
+logger = logging.getLogger(__name__)
+
+
+def load_index_from_storage(
+ storage_context: StorageContext,
+ index_id: Optional[str] = None,
+ **kwargs: Any,
+) -> BaseIndex:
+ """Load index from storage context.
+
+ Args:
+ storage_context (StorageContext): storage context containing
+ docstore, index store and vector store.
+ index_id (Optional[str]): ID of the index to load.
+ Defaults to None, which assumes there's only a single index
+ in the index store and load it.
+ **kwargs: Additional keyword args to pass to the index constructors.
+ """
+ index_ids: Optional[Sequence[str]]
+ if index_id is None:
+ index_ids = None
+ else:
+ index_ids = [index_id]
+
+ indices = load_indices_from_storage(storage_context, index_ids=index_ids, **kwargs)
+
+ if len(indices) == 0:
+ raise ValueError(
+ "No index in storage context, check if you specified the right persist_dir."
+ )
+ elif len(indices) > 1:
+ raise ValueError(
+ f"Expected to load a single index, but got {len(indices)} instead. "
+ "Please specify index_id."
+ )
+
+ return indices[0]
+
+
+def load_indices_from_storage(
+ storage_context: StorageContext,
+ index_ids: Optional[Sequence[str]] = None,
+ **kwargs: Any,
+) -> List[BaseIndex]:
+ """Load multiple indices from storage context.
+
+ Args:
+ storage_context (StorageContext): storage context containing
+ docstore, index store and vector store.
+ index_id (Optional[Sequence[str]]): IDs of the indices to load.
+ Defaults to None, which loads all indices in the index store.
+ **kwargs: Additional keyword args to pass to the index constructors.
+ """
+ if index_ids is None:
+ logger.info("Loading all indices.")
+ index_structs = storage_context.index_store.index_structs()
+ else:
+ logger.info(f"Loading indices with ids: {index_ids}")
+ index_structs = []
+ for index_id in index_ids:
+ index_struct = storage_context.index_store.get_index_struct(index_id)
+ if index_struct is None:
+ raise ValueError(f"Failed to load index with ID {index_id}")
+ index_structs.append(index_struct)
+
+ indices = []
+ for index_struct in index_structs:
+ type_ = index_struct.get_type()
+ index_cls = MODIFIED_INDEX_STRUCT_TYPE_TO_INDEX_CLASS[type_]
+ index = index_cls(
+ index_struct=index_struct, storage_context=storage_context, **kwargs
+ )
+ indices.append(index)
+ return indices
+
+
+def load_graph_from_storage(
+ storage_context: StorageContext,
+ root_id: str,
+ **kwargs: Any,
+) -> ComposableGraph:
+ """Load composable graph from storage context.
+
+ Args:
+ storage_context (StorageContext): storage context containing
+ docstore, index store and vector store.
+ root_id (str): ID of the root index of the graph.
+ **kwargs: Additional keyword args to pass to the index constructors.
+ """
+ indices = load_indices_from_storage(storage_context, index_ids=None, **kwargs)
+ all_indices = {index.index_id: index for index in indices}
+ return ComposableGraph(all_indices=all_indices, root_id=root_id)
diff --git a/src/pai_rag/modules/index/my_vector_store_index.py b/src/pai_rag/modules/index/my_vector_store_index.py
new file mode 100644
index 00000000..b4fd3f5a
--- /dev/null
+++ b/src/pai_rag/modules/index/my_vector_store_index.py
@@ -0,0 +1,119 @@
+"""Base vector store index.
+
+An index that is built on top of an existing vector store.
+
+"""
+
+import asyncio
+import logging
+from typing import Any, Sequence
+from llama_index.core import VectorStoreIndex
+from llama_index.core.data_structs.data_structs import IndexDict
+from llama_index.core.schema import (
+ BaseNode,
+ IndexNode,
+)
+from llama_index.core.utils import iter_batch
+
+logger = logging.getLogger(__name__)
+
+
+def call_async(coro):
+ try:
+ loop = asyncio.get_running_loop()
+ except RuntimeError:
+ return asyncio.run(coro)
+ else:
+ return loop.run_until_complete(coro)
+
+
+class MyVectorStoreIndex(VectorStoreIndex):
+ async def _process_one_batch(
+ self,
+ nodes_batch: Sequence[Sequence[BaseNode]],
+ index_struct: IndexDict,
+ semaphore: asyncio.Semaphore,
+ **insert_kwargs: Any,
+ ):
+ async with semaphore:
+ new_ids = await self._vector_store.async_add(nodes_batch, **insert_kwargs)
+
+ # if the vector store doesn't store text, we need to add the nodes to the
+ # index struct and document store
+ if not self._vector_store.stores_text or self._store_nodes_override:
+ for node, new_id in zip(nodes_batch, new_ids):
+ # NOTE: remove embedding from node to avoid duplication
+ node_without_embedding = node.copy()
+ node_without_embedding.embedding = None
+
+ index_struct.add_node(node_without_embedding, text_id=new_id)
+ self._docstore.add_documents(
+ [node_without_embedding], allow_update=True
+ )
+
+ async def _postprocess_all_batch(
+ self,
+ nodes_batch_list: Sequence[Sequence[BaseNode]],
+ index_struct: IndexDict,
+ **insert_kwargs: Any,
+ ):
+ asyncio_semaphore = asyncio.Semaphore(10)
+ batch_process_coroutines = []
+ for nodes_batch in nodes_batch_list:
+ batch_process_coroutines.append(
+ self._process_one_batch(
+ nodes_batch, index_struct, asyncio_semaphore, **insert_kwargs
+ )
+ )
+ await asyncio.gather(*batch_process_coroutines)
+
+ async def _async_add_nodes_to_index(
+ self,
+ index_struct: IndexDict,
+ nodes: Sequence[BaseNode],
+ show_progress: bool = False,
+ **insert_kwargs: Any,
+ ) -> None:
+ """Asynchronously add nodes to index."""
+ if not nodes:
+ return
+
+ node_batch_list = []
+ for nodes_batch in iter_batch(nodes, 100):
+ nodes_batch = await self._aget_node_with_embedding(
+ nodes_batch, show_progress
+ )
+ node_batch_list.append(nodes_batch)
+
+ await self._postprocess_all_batch(
+ node_batch_list, index_struct, **insert_kwargs
+ )
+
+ async def _insert_async(
+ self, nodes: Sequence[BaseNode], **insert_kwargs: Any
+ ) -> None:
+ """Insert a document."""
+ await self._async_add_nodes_to_index(
+ self._index_struct, nodes, show_progress=True, **insert_kwargs
+ )
+
+ async def insert_nodes_async(
+ self, nodes: Sequence[BaseNode], **insert_kwargs: Any
+ ) -> None:
+ """Insert nodes.
+
+ NOTE: overrides BaseIndex.insert_nodes.
+ VectorStoreIndex only stores nodes in document store
+ if vector store does not store text
+ """
+ for node in nodes:
+ if isinstance(node, IndexNode):
+ try:
+ node.dict()
+ except ValueError:
+ self._object_map[node.index_id] = node.obj
+ node.obj = None
+
+ with self._callback_manager.as_trace("insert_nodes"):
+ await self._insert_async(nodes, **insert_kwargs)
+ self._storage_context.index_store.add_index_struct(self._index_struct)
diff --git a/src/pai_rag/modules/index/store.py b/src/pai_rag/modules/index/store.py
index 1a290cdd..4d423f5b 100644
--- a/src/pai_rag/modules/index/store.py
+++ b/src/pai_rag/modules/index/store.py
@@ -6,7 +6,6 @@
from llama_index.vector_stores.analyticdb import AnalyticDBVectorStore
from llama_index.vector_stores.faiss import FaissVectorStore
from llama_index.vector_stores.chroma import ChromaVectorStore
-from llama_index.vector_stores.elasticsearch import ElasticsearchStore
from llama_index.vector_stores.milvus import MilvusVectorStore
from elasticsearch.helpers.vectorstore import AsyncDenseVectorStrategy
@@ -16,6 +15,8 @@
from llama_index.core import StorageContext
import logging
+from pai_rag.modules.retriever.my_elasticsearch_store import MyElasticsearchStore
+
DEFAULT_CHROMA_COLLECTION_NAME = "pairag"
logger = logging.getLogger(__name__)
@@ -36,14 +37,19 @@ def _get_or_create_storage_context(self):
self.vector_store = None
self.doc_store = None
self.index_store = None
+ persist_dir = None
vector_store_type = (
- self.store_config["vector_store"].get("type", "chroma").lower()
+ self.store_config["vector_store"].get("type", "faiss").lower()
)
+
if vector_store_type == "chroma":
self.vector_store = self._get_or_create_chroma()
logger.info("initialized Chroma vector store.")
elif vector_store_type == "faiss":
+ self.doc_store = self._get_or_create_simple_doc_store()
+ self.index_store = self._get_or_create_simple_index_store()
+ persist_dir = self.persist_dir
self.vector_store = self._get_or_create_faiss()
logger.info("initialized FAISS vector store.")
elif vector_store_type == "hologres":
@@ -60,14 +66,11 @@ def _get_or_create_storage_context(self):
else:
raise ValueError(f"Unknown vector_store type '{vector_store_type}'.")
- self.doc_store = self._get_or_create_simple_doc_store()
- self.index_store = self._get_or_create_simple_index_store()
-
storage_context = StorageContext.from_defaults(
docstore=self.doc_store,
index_store=self.index_store,
vector_store=self.vector_store,
- persist_dir=self.persist_dir,
+ persist_dir=persist_dir,
)
return storage_context
@@ -124,12 +127,15 @@ def _get_or_create_adb(self):
def _get_or_create_es(self):
es_config = self.store_config["vector_store"]
- return ElasticsearchStore(
+ return MyElasticsearchStore(
index_name=es_config["es_index"],
es_url=es_config["es_url"],
es_user=es_config["es_user"],
es_password=es_config["es_password"],
- retrieval_strategy=AsyncDenseVectorStrategy(hybrid=True),
+ embedding_dimension=self.embed_dims,
+ retrieval_strategy=AsyncDenseVectorStrategy(
+ hybrid=True, rrf={"window_size": 50}
+ ),
)
def _get_or_create_milvus(self):
diff --git a/src/pai_rag/modules/module_registry.py b/src/pai_rag/modules/module_registry.py
index 12c65ce3..cc2dca46 100644
--- a/src/pai_rag/modules/module_registry.py
+++ b/src/pai_rag/modules/module_registry.py
@@ -1,5 +1,8 @@
+import hashlib
+from typing import Dict, Any
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
import pai_rag.modules as modules
+import logging
MODULE_CONFIG_KEY_MAP = {
"IndexModule": "index",
@@ -16,17 +19,25 @@
"DataReaderFactoryModule": "data_reader",
"AgentModule": "agent",
"ToolModule": "tool",
+ "DataLoaderModule": "data_loader",
+ "OssCacheModule": "cache",
}
+logger = logging.getLogger(__name__)
+
+
class ModuleRegistry:
def __init__(self):
- self._mod_instance_map = {}
self._mod_cls_map = {}
self._mod_deps_map = {}
self._mod_deps_map_inverted = {}
+ self._mod_instance_map = {}
+
for m_name in modules.ALL_MODULES:
+ self._mod_instance_map[m_name] = {}
+
m_cls = getattr(modules, m_name)
self._mod_cls_map[m_name] = m_cls()
@@ -38,12 +49,16 @@ def __init__(self):
self._mod_deps_map_inverted[dep] = []
self._mod_deps_map_inverted[dep].append(m_name)
- def get_module(self, module_key: str):
- return self._mod_instance_map[module_key]
+ def _get_param_hash(self, params: Dict[str, Any]):
+ repr_str = repr(sorted(params.items())).encode("utf-8")
+ return hashlib.sha256(repr_str).hexdigest()
+
+ def get_module_with_config(self, module_key, config):
+ return self._create_mod_lazily(module_key, config)
def init_modules(self, config):
+ mod_cache = {}
mod_stack = []
- mods_inited = []
mod_ref_count = {}
for mod, deps in self._mod_deps_map.items():
ref_count = len(deps)
@@ -53,9 +68,8 @@ def init_modules(self, config):
while mod_stack:
mod = mod_stack.pop()
- mod_obj = self._init_mod(mod, config)
- mods_inited.append(mod)
- self._mod_instance_map[mod] = mod_obj
+ mod_obj = self._create_mod_lazily(mod, config, mod_cache)
+ mod_cache[mod] = mod_obj
# update module ref count that depends on on
ref_mods = self._mod_deps_map_inverted.get(mod, [])
@@ -64,23 +78,35 @@ def init_modules(self, config):
if mod_ref_count[ref_mod] == 0:
mod_stack.append(ref_mod)
- if len(mods_inited) != len(modules.ALL_MODULES):
+ if len(mod_cache) != len(modules.ALL_MODULES):
# dependency circular error!
raise ValueError(
- f"Circular dependency detected. Please check module dependency configuration. Module initialized: {mods_inited}. Module ref count: {mod_ref_count}"
+ f"Circular dependency detected. Please check module dependency configuration. Module initialized: {mod_cache}. Module ref count: {mod_ref_count}"
)
- print(f"RAG modules init successfully. {mods_inited}")
+ logger.info(f"RAG modules init successfully. {mod_cache.keys()}")
return
- def _init_mod(self, mod_name, config):
+ def _create_mod_lazily(self, mod_name, config, mod_cache=None):
+ if mod_cache and mod_name in mod_cache:
+ return mod_cache[mod_name]
+
+ logger.info(f"Get module {mod_name}.")
+
mod_config_key = MODULE_CONFIG_KEY_MAP[mod_name]
mod_deps = self._mod_deps_map[mod_name]
mod_cls = self._mod_cls_map[mod_name]
- params = {MODULE_PARAM_CONFIG: config[mod_config_key]}
+ params = {MODULE_PARAM_CONFIG: config.get(mod_config_key, None)}
for dep in mod_deps:
- params[dep] = self.get_module(dep)
- return mod_cls.get_or_create(params)
+ params[dep] = self._create_mod_lazily(dep, config, mod_cache)
+
+ instance_key = self._get_param_hash(params)
+ if instance_key not in self._mod_instance_map[mod_name]:
+ logger.info(f"Creating new instance for module {mod_name} {instance_key}.")
+ self._mod_instance_map[mod_name][instance_key] = mod_cls.get_or_create(
+ params
+ )
+ return self._mod_instance_map[mod_name][instance_key]
module_registry = ModuleRegistry()
diff --git a/src/pai_rag/modules/retriever/my_elasticsearch_store.py b/src/pai_rag/modules/retriever/my_elasticsearch_store.py
new file mode 100644
index 00000000..63205ccc
--- /dev/null
+++ b/src/pai_rag/modules/retriever/my_elasticsearch_store.py
@@ -0,0 +1,538 @@
+"""Elasticsearch vector store."""
+
+import asyncio
+from logging import getLogger
+from typing import Any, Callable, Dict, List, Literal, Optional, Union
+
+import nest_asyncio
+import numpy as np
+from llama_index.core.bridge.pydantic import PrivateAttr
+from llama_index.core.schema import BaseNode, MetadataMode, TextNode
+from llama_index.core.vector_stores.types import (
+ BasePydanticVectorStore,
+ MetadataFilters,
+ VectorStoreQuery,
+ VectorStoreQueryMode,
+ VectorStoreQueryResult,
+)
+from llama_index.core.vector_stores.utils import (
+ metadata_dict_to_node,
+ node_to_metadata_dict,
+)
+from elasticsearch.helpers.vectorstore import AsyncVectorStore
+from elasticsearch.helpers.vectorstore import (
+ AsyncBM25Strategy,
+ AsyncSparseVectorStrategy,
+ AsyncDenseVectorStrategy,
+ AsyncRetrievalStrategy,
+ DistanceMetric,
+)
+
+from llama_index.vector_stores.elasticsearch.utils import (
+ get_elasticsearch_client,
+ get_user_agent,
+)
+
+logger = getLogger(__name__)
+
+DISTANCE_STRATEGIES = Literal[
+ "COSINE",
+ "DOT_PRODUCT",
+ "EUCLIDEAN_DISTANCE",
+]
+
+
+def _to_elasticsearch_filter(standard_filters: MetadataFilters) -> Dict[str, Any]:
+ """
+ Convert standard filters to Elasticsearch filter.
+
+ Args:
+ standard_filters: Standard Llama-index filters.
+
+ Returns:
+ Elasticsearch filter.
+ """
+ if len(standard_filters.legacy_filters()) == 1:
+ filter = standard_filters.legacy_filters()[0]
+ return {
+ "term": {
+ f"metadata.{filter.key}.keyword": {
+ "value": filter.value,
+ }
+ }
+ }
+ else:
+ operands = []
+ for filter in standard_filters.legacy_filters():
+ operands.append(
+ {
+ "term": {
+ f"metadata.{filter.key}.keyword": {
+ "value": filter.value,
+ }
+ }
+ }
+ )
+ return {"bool": {"must": operands}}
+
+
+def _to_llama_similarities(scores: List[float]) -> List[float]:
+ if scores is None or len(scores) == 0:
+ return []
+
+ scores_to_norm: np.ndarray = np.array(scores)
+ return np.exp(scores_to_norm - np.max(scores_to_norm)).tolist()
+
+
+def _mode_must_match_retrieval_strategy(
+ mode: VectorStoreQueryMode, retrieval_strategy: AsyncRetrievalStrategy
+) -> None:
+ """
+ Different retrieval strategies require different ways of indexing that must be known at the
+ time of adding data. The query mode is known at query time. This function checks if the
+ retrieval strategy (and way of indexing) is compatible with the query mode and raises and
+ exception in the case of a mismatch.
+ """
+ if mode == VectorStoreQueryMode.DEFAULT:
+ # it's fine to not specify an explicit other mode
+ return
+
+ mode_retrieval_dict = {
+ VectorStoreQueryMode.SPARSE: AsyncSparseVectorStrategy,
+ VectorStoreQueryMode.TEXT_SEARCH: AsyncBM25Strategy,
+ VectorStoreQueryMode.HYBRID: AsyncDenseVectorStrategy,
+ }
+
+ required_strategy = mode_retrieval_dict.get(mode)
+ if not required_strategy:
+ raise NotImplementedError(f"query mode {mode} currently not supported")
+
+ if not isinstance(retrieval_strategy, required_strategy):
+ raise ValueError(
+ f"query mode {mode} incompatible with retrieval strategy {type(retrieval_strategy)}, "
+ f"expected {required_strategy}"
+ )
+
+ if mode == VectorStoreQueryMode.HYBRID and not retrieval_strategy.hybrid:
+ raise ValueError("to enable hybrid mode, it must be set in retrieval strategy")
+
+
+class MyElasticsearchStore(BasePydanticVectorStore):
+ """
+ Elasticsearch vector store.
+
+ Args:
+ index_name: Name of the Elasticsearch index.
+ es_client: Optional. Pre-existing AsyncElasticsearch client.
+ es_url: Optional. Elasticsearch URL.
+ es_cloud_id: Optional. Elasticsearch cloud ID.
+ es_api_key: Optional. Elasticsearch API key.
+ es_user: Optional. Elasticsearch username.
+ es_password: Optional. Elasticsearch password.
+ text_field: Optional. Name of the Elasticsearch field that stores the text.
+ vector_field: Optional. Name of the Elasticsearch field that stores the
+ embedding.
+ batch_size: Optional. Batch size for bulk indexing. Defaults to 200.
+ distance_strategy: Optional. Distance strategy to use for similarity search.
+ Defaults to "COSINE".
+ retrieval_strategy: Retrieval strategy to use. AsyncBM25Strategy /
+ AsyncSparseVectorStrategy / AsyncDenseVectorStrategy / AsyncRetrievalStrategy.
+ Defaults to AsyncDenseVectorStrategy.
+
+ Raises:
+ ConnectionError: If AsyncElasticsearch client cannot connect to Elasticsearch.
+ ValueError: If neither es_client nor es_url nor es_cloud_id is provided.
+
+ Examples:
+ `pip install llama-index-vector-stores-elasticsearch`
+
+ ```python
+ from llama_index.vector_stores import ElasticsearchStore
+
+ # Additional setup for ElasticsearchStore class
+ index_name = "my_index"
+ es_url = "http://localhost:9200"
+ es_cloud_id = "" # Found within the deployment page
+ es_user = "elastic"
+ es_password = "" # Provided when creating deployment or can be reset
+ es_api_key = "" # Create an API key within Kibana (Security -> API Keys)
+
+ # Connecting to ElasticsearchStore locally
+ es_local = ElasticsearchStore(
+ index_name=index_name,
+ es_url=es_url,
+ )
+
+ # Connecting to Elastic Cloud with username and password
+ es_cloud_user_pass = ElasticsearchStore(
+ index_name=index_name,
+ es_cloud_id=es_cloud_id,
+ es_user=es_user,
+ es_password=es_password,
+ )
+
+ # Connecting to Elastic Cloud with API Key
+ es_cloud_api_key = ElasticsearchStore(
+ index_name=index_name,
+ es_cloud_id=es_cloud_id,
+ es_api_key=es_api_key,
+ )
+ ```
+
+ """
+
+ class Config:
+ # allow pydantic to tolarate its inability to validate AsyncRetrievalStrategy
+ arbitrary_types_allowed = True
+
+ stores_text: bool = True
+ index_name: str
+ es_client: Optional[Any]
+ es_url: Optional[str]
+ es_cloud_id: Optional[str]
+ es_api_key: Optional[str]
+ es_user: Optional[str]
+ es_password: Optional[str]
+ text_field: str = "content"
+ vector_field: str = "embedding"
+ batch_size: int = 200
+ distance_strategy: Optional[DISTANCE_STRATEGIES] = "COSINE"
+ retrieval_strategy: AsyncRetrievalStrategy
+
+ _store = PrivateAttr()
+
+ def __init__(
+ self,
+ index_name: str,
+ es_client: Optional[Any] = None,
+ es_url: Optional[str] = None,
+ es_cloud_id: Optional[str] = None,
+ es_api_key: Optional[str] = None,
+ es_user: Optional[str] = None,
+ es_password: Optional[str] = None,
+ text_field: str = "content",
+ vector_field: str = "embedding",
+ embedding_dimension: int = 1536,
+ batch_size: int = 200,
+ distance_strategy: Optional[DISTANCE_STRATEGIES] = "COSINE",
+ retrieval_strategy: Optional[AsyncRetrievalStrategy] = None,
+ ) -> None:
+ nest_asyncio.apply()
+
+ if not es_client:
+ es_client = get_elasticsearch_client(
+ url=es_url,
+ cloud_id=es_cloud_id,
+ api_key=es_api_key,
+ username=es_user,
+ password=es_password,
+ )
+
+ if retrieval_strategy is None:
+ retrieval_strategy = AsyncDenseVectorStrategy(
+ distance=DistanceMetric[distance_strategy]
+ )
+
+ metadata_mappings = {
+ "document_id": {"type": "keyword"},
+ "doc_id": {"type": "keyword"},
+ "ref_doc_id": {"type": "keyword"},
+ }
+
+ self._store = AsyncVectorStore(
+ user_agent=get_user_agent(),
+ client=es_client,
+ index=index_name,
+ retrieval_strategy=retrieval_strategy,
+ text_field=text_field,
+ vector_field=vector_field,
+ metadata_mappings=metadata_mappings,
+ num_dimensions=embedding_dimension,
+ )
+ asyncio.get_event_loop().run_until_complete(
+ self._store._create_index_if_not_exists()
+ )
+
+ super().__init__(
+ index_name=index_name,
+ es_client=es_client,
+ es_url=es_url,
+ es_cloud_id=es_cloud_id,
+ es_api_key=es_api_key,
+ es_user=es_user,
+ es_password=es_password,
+ text_field=text_field,
+ vector_field=vector_field,
+ batch_size=batch_size,
+ distance_strategy=distance_strategy,
+ retrieval_strategy=retrieval_strategy,
+ )
+
+ @property
+ def client(self) -> Any:
+ """Get async elasticsearch client."""
+ return self._store.client
+
+ def close(self) -> None:
+ return asyncio.get_event_loop().run_until_complete(self._store.close())
+
+ def add(
+ self,
+ nodes: List[BaseNode],
+ *,
+ create_index_if_not_exists: bool = True,
+ **add_kwargs: Any,
+ ) -> List[str]:
+ """
+ Add nodes to Elasticsearch index.
+
+ Args:
+ nodes: List of nodes with embeddings.
+ create_index_if_not_exists: Optional. Whether to create
+ the Elasticsearch index if it
+ doesn't already exist.
+ Defaults to True.
+
+ Returns:
+ List of node IDs that were added to the index.
+
+ Raises:
+ ImportError: If elasticsearch['async'] python package is not installed.
+ BulkIndexError: If AsyncElasticsearch async_bulk indexing fails.
+ """
+ return asyncio.get_event_loop().run_until_complete(
+ self.async_add(nodes, create_index_if_not_exists=create_index_if_not_exists)
+ )
+
+ async def async_add(
+ self,
+ nodes: List[BaseNode],
+ *,
+ create_index_if_not_exists: bool = True,
+ **add_kwargs: Any,
+ ) -> List[str]:
+ """
+ Asynchronous method to add nodes to Elasticsearch index.
+
+ Args:
+ nodes: List of nodes with embeddings.
+ create_index_if_not_exists: Optional. Whether to create
+ the AsyncElasticsearch index if it
+ doesn't already exist.
+ Defaults to True.
+
+ Returns:
+ List of node IDs that were added to the index.
+
+ Raises:
+ ImportError: If elasticsearch python package is not installed.
+ BulkIndexError: If AsyncElasticsearch async_bulk indexing fails.
+ """
+ if len(nodes) == 0:
+ return []
+
+ add_kwargs.update({"max_retries": 3})
+
+ embeddings: List[List[float]] = []
+ texts: List[str] = []
+ metadatas: List[dict] = []
+ ids: List[str] = []
+ for node in nodes:
+ ids.append(node.node_id)
+ embeddings.append(node.get_embedding())
+ texts.append(node.get_content(metadata_mode=MetadataMode.NONE))
+ metadatas.append(node_to_metadata_dict(node, remove_text=True))
+
+ if not self._store.num_dimensions:
+ self._store.num_dimensions = len(embeddings[0])
+
+ return await self._store.add_texts(
+ texts=texts,
+ metadatas=metadatas,
+ vectors=embeddings,
+ ids=ids,
+ create_index_if_not_exists=create_index_if_not_exists,
+ bulk_kwargs=add_kwargs,
+ )
+
+ def delete(self, ref_doc_id: str, **delete_kwargs: Any) -> None:
+ """
+ Delete node from Elasticsearch index.
+
+ Args:
+ ref_doc_id: ID of the node to delete.
+ delete_kwargs: Optional. Additional arguments to
+ pass to Elasticsearch delete_by_query.
+
+ Raises:
+ Exception: If Elasticsearch delete_by_query fails.
+ """
+ return asyncio.get_event_loop().run_until_complete(
+ self.adelete(ref_doc_id, **delete_kwargs)
+ )
+
+ async def adelete(self, ref_doc_id: str, **delete_kwargs: Any) -> None:
+ """
+ Async delete node from Elasticsearch index.
+
+ Args:
+ ref_doc_id: ID of the node to delete.
+ delete_kwargs: Optional. Additional arguments to
+ pass to AsyncElasticsearch delete_by_query.
+
+ Raises:
+ Exception: If AsyncElasticsearch delete_by_query fails.
+ """
+ await self._store.delete(
+ query={"term": {"metadata.ref_doc_id": ref_doc_id}}, **delete_kwargs
+ )
+
+ def query(
+ self,
+ query: VectorStoreQuery,
+ custom_query: Optional[
+ Callable[[Dict, Union[VectorStoreQuery, None]], Dict]
+ ] = None,
+ es_filter: Optional[List[Dict]] = None,
+ **kwargs: Any,
+ ) -> VectorStoreQueryResult:
+ """
+ Query index for top k most similar nodes.
+
+ Args:
+ query_embedding (List[float]): query embedding
+ custom_query: Optional. custom query function that takes in the es query
+ body and returns a modified query body.
+ This can be used to add additional query
+ parameters to the Elasticsearch query.
+ es_filter: Optional. Elasticsearch filter to apply to the
+ query. If filter is provided in the query,
+ this filter will be ignored.
+
+ Returns:
+ VectorStoreQueryResult: Result of the query.
+
+ Raises:
+ Exception: If Elasticsearch query fails.
+
+ """
+ return asyncio.get_event_loop().run_until_complete(
+ self.aquery(query, custom_query, es_filter, **kwargs)
+ )
+
+ async def aquery(
+ self,
+ query: VectorStoreQuery,
+ custom_query: Optional[
+ Callable[[Dict, Union[VectorStoreQuery, None]], Dict]
+ ] = None,
+ es_filter: Optional[List[Dict]] = None,
+ **kwargs: Any,
+ ) -> VectorStoreQueryResult:
+ """
+ Asynchronous query index for top k most similar nodes.
+
+ Args:
+ query_embedding (VectorStoreQuery): query embedding
+ custom_query: Optional. custom query function that takes in the es query
+ body and returns a modified query body.
+ This can be used to add additional query
+ parameters to the AsyncElasticsearch query.
+ es_filter: Optional. AsyncElasticsearch filter to apply to the
+ query. If filter is provided in the query,
+ this filter will be ignored.
+
+ Returns:
+ VectorStoreQueryResult: Result of the query.
+
+ Raises:
+ Exception: If AsyncElasticsearch query fails.
+
+ """
+ if query.mode == VectorStoreQueryMode.HYBRID:
+ retrieval_strategy = AsyncDenseVectorStrategy(
+ hybrid=True, rrf={"window_size": 50}
+ )
+ elif query.mode == VectorStoreQueryMode.TEXT_SEARCH:
+ retrieval_strategy = AsyncBM25Strategy()
+ else:
+ retrieval_strategy = AsyncDenseVectorStrategy()
+
+ metadata_mappings = {
+ "document_id": {"type": "keyword"},
+ "doc_id": {"type": "keyword"},
+ "ref_doc_id": {"type": "keyword"},
+ }
+ self._store = AsyncVectorStore(
+ user_agent=get_user_agent(),
+ client=self.es_client,
+ index=self.index_name,
+ retrieval_strategy=retrieval_strategy,
+ text_field=self.text_field,
+ vector_field=self.vector_field,
+ metadata_mappings=metadata_mappings,
+ )
+
+ if query.filters is not None and len(query.filters.legacy_filters()) > 0:
+ filter = [_to_elasticsearch_filter(query.filters)]
+ else:
+ filter = es_filter or []
+
+ hits = await self._store.search(
+ query=query.query_str,
+ query_vector=query.query_embedding,
+ k=query.similarity_top_k,
+ num_candidates=query.similarity_top_k * 10,
+ filter=filter,
+ custom_query=custom_query,
+ )
+
+ top_k_nodes = []
+ top_k_ids = []
+ top_k_scores = []
+ for hit in hits:
+ source = hit["_source"]
+ metadata = source.get("metadata", None)
+ text = source.get(self.text_field, None)
+ node_id = hit["_id"]
+
+ try:
+ node = metadata_dict_to_node(metadata)
+ node.text = text
+ except Exception:
+ # Legacy support for old metadata format
+ logger.warning(
+ f"Could not parse metadata from hit {hit['_source']['metadata']}"
+ )
+ node_info = source.get("node_info")
+ relationships = source.get("relationships", {})
+ start_char_idx = None
+ end_char_idx = None
+ if isinstance(node_info, dict):
+ start_char_idx = node_info.get("start", None)
+ end_char_idx = node_info.get("end", None)
+
+ node = TextNode(
+ text=text,
+ metadata=metadata,
+ id_=node_id,
+ start_char_idx=start_char_idx,
+ end_char_idx=end_char_idx,
+ relationships=relationships,
+ )
+ top_k_nodes.append(node)
+ top_k_ids.append(node_id)
+ top_k_scores.append(hit.get("_rank", hit["_score"]))
+
+ if (
+ isinstance(self.retrieval_strategy, AsyncDenseVectorStrategy)
+ and self.retrieval_strategy.hybrid
+ ):
+ total_rank = sum(top_k_scores)
+ top_k_scores = [total_rank - rank / total_rank for rank in top_k_scores]
+
+ return VectorStoreQueryResult(
+ nodes=top_k_nodes,
+ ids=top_k_ids,
+ similarities=_to_llama_similarities(top_k_scores),
+ )
diff --git a/src/pai_rag/modules/retriever/my_vector_index_retriever.py b/src/pai_rag/modules/retriever/my_vector_index_retriever.py
index 16c98946..755f02a6 100644
--- a/src/pai_rag/modules/retriever/my_vector_index_retriever.py
+++ b/src/pai_rag/modules/retriever/my_vector_index_retriever.py
@@ -25,7 +25,7 @@ class MyVectorIndexRetriever(VectorIndexRetriever):
and return the results with the query_result.similarities sorted in descending order.
Args:
- index (VectorStoreIndex): vector store index.
+ index (MyVectorIndexRetriever): vector store index.
similarity_top_k (int): number of top k results to return.
vector_store_query_mode (str): vector store query mode
See reference for VectorStoreQueryMode for full list of supported modes.
diff --git a/src/pai_rag/modules/retriever/retriever.py b/src/pai_rag/modules/retriever/retriever.py
index bc7cbca3..d65b4a33 100644
--- a/src/pai_rag/modules/retriever/retriever.py
+++ b/src/pai_rag/modules/retriever/retriever.py
@@ -3,29 +3,23 @@
import logging
from typing import Dict, List, Any
-import jieba
-from nltk.corpus import stopwords
from llama_index.core.indices.list.base import SummaryIndex
from llama_index.core.retrievers import QueryFusionRetriever
from llama_index.core.tools import RetrieverTool
from llama_index.core.selectors import LLMSingleSelector
from llama_index.core.retrievers import RouterRetriever
+from llama_index.core.vector_stores.types import VectorStoreQueryMode
-# from llama_index.retrievers.bm25 import BM25Retriever
+from pai_rag.utils.tokenizer import jieba_tokenizer
from pai_rag.integrations.retrievers.bm25 import BM25Retriever
from pai_rag.modules.base.configurable_module import ConfigurableModule
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
from pai_rag.utils.prompt_template import QUERY_GEN_PROMPT
+from pai_rag.modules.retriever.my_elasticsearch_store import MyElasticsearchStore
from pai_rag.modules.retriever.my_vector_index_retriever import MyVectorIndexRetriever
logger = logging.getLogger(__name__)
-stopword_list = stopwords.words("chinese") + stopwords.words("english")
-
-
-def jieba_tokenize(text: str) -> List[str]:
- return [w for w in jieba.lcut(text) if w not in stopword_list]
-
class RetrieverModule(ConfigurableModule):
@staticmethod
@@ -37,23 +31,39 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
vector_index = new_params["IndexModule"]
similarity_top_k = config.get("similarity_top_k", 5)
- # vector
+
+ retrieval_mode = config.get("retrieval_mode", "hybrid").lower()
+
+ # Special handle elastic search
+ if isinstance(vector_index.storage_context.vector_store, MyElasticsearchStore):
+ if retrieval_mode == "embedding":
+ query_mode = VectorStoreQueryMode.DEFAULT
+ elif retrieval_mode == "keyword":
+ query_mode = VectorStoreQueryMode.TEXT_SEARCH
+ else:
+ query_mode = VectorStoreQueryMode.HYBRID
+
+ return MyVectorIndexRetriever(
+ index=vector_index,
+ similarity_top_k=similarity_top_k,
+ vector_store_query_mode=query_mode,
+ )
+
vector_retriever = MyVectorIndexRetriever(
index=vector_index, similarity_top_k=similarity_top_k
)
-
# keyword
bm25_retriever = BM25Retriever.from_defaults(
index=vector_index,
similarity_top_k=similarity_top_k,
- tokenizer=jieba_tokenize,
+ tokenizer=jieba_tokenizer,
)
- if config["retrieval_mode"] == "embedding":
+ if retrieval_mode == "embedding":
logger.info(f"MyVectorIndexRetriever used with top_k {similarity_top_k}.")
return vector_retriever
- elif config["retrieval_mode"] == "keyword":
+ elif retrieval_mode == "keyword":
logger.info(f"BM25Retriever used with top_k {similarity_top_k}.")
return bm25_retriever
diff --git a/src/pai_rag/utils/store_utils.py b/src/pai_rag/utils/store_utils.py
index a42bb42c..0873375c 100644
--- a/src/pai_rag/utils/store_utils.py
+++ b/src/pai_rag/utils/store_utils.py
@@ -14,9 +14,9 @@ def get_store_persist_directory_name(storage_config, ndims):
raw_text = "sample_store_key"
vector_store_type = storage_config["vector_store"]["type"].lower()
if vector_store_type == "chroma":
- raw_text = json.dumps(storage_config["vector_store"])
+ raw_text = json.dumps(storage_config["vector_store"], sort_keys=True)
elif vector_store_type == "faiss":
- raw_text = json.dumps(storage_config["vector_store"])
+ raw_text = {"type": "faiss"}
elif vector_store_type == "hologres":
keywords = ["host", "port", "database", "table_name"]
json_data = {k: storage_config["vector_store"][k] for k in keywords}
diff --git a/src/pai_rag/utils/tokenizer.py b/src/pai_rag/utils/tokenizer.py
new file mode 100644
index 00000000..168702b9
--- /dev/null
+++ b/src/pai_rag/utils/tokenizer.py
@@ -0,0 +1,16 @@
+import jieba
+from nltk.corpus import stopwords
+from typing import List
+
+stopword_list = stopwords.words("chinese") + stopwords.words("english")
+
+
+## PUT in utils file and add stopword in TRIE structure.
+def jieba_tokenizer(text: str) -> List[str]:
+ tokens = []
+ for w in jieba.lcut(text):
+ token = w.lower()
+ if token not in stopword_list:
+ tokens.append(token)
+
+ return tokens
diff --git a/tests/core/test_rag_application.py b/tests/core/test_rag_application.py
index 457d599c..ef27858f 100644
--- a/tests/core/test_rag_application.py
+++ b/tests/core/test_rag_application.py
@@ -28,12 +28,9 @@ def rag_app():
# Test load knowledge file
-def test_add_knowledge_file(rag_app: RagApplication):
+async def test_add_knowledge_file(rag_app: RagApplication):
data_dir = os.path.join(BASE_DIR, "tests/testdata/paul_graham")
- print(len(rag_app.index.docstore.docs))
- rag_app.load_knowledge(data_dir)
- print(len(rag_app.index.docstore.docs))
- assert len(rag_app.index.docstore.docs) > 0
+ await rag_app.load_knowledge(data_dir)
# Test rag query
From 0b136e44330370a315109eb09b07d5b35dabe369 Mon Sep 17 00:00:00 2001
From: wwxxzz
Date: Fri, 14 Jun 2024 15:17:36 +0800
Subject: [PATCH 05/69] Modify async upload to sync (#64)
* Modify async upload to sync
* fix failed test
---
src/pai_rag/app/api/query.py | 2 +-
src/pai_rag/app/web/rag_client.py | 12 +++++++-----
src/pai_rag/app/web/tabs/upload_tab.py | 11 +++++++++--
src/pai_rag/core/rag_application.py | 8 +++++++-
src/pai_rag/core/rag_service.py | 4 ++--
tests/core/test_rag_application.py | 4 ++--
6 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/pai_rag/app/api/query.py b/src/pai_rag/app/api/query.py
index dc0ec7fc..8e8192e6 100644
--- a/src/pai_rag/app/api/query.py
+++ b/src/pai_rag/app/api/query.py
@@ -41,7 +41,7 @@ async def aupdate(new_config: Any = Body(None)):
@router.post("/upload_data")
-async def load_data(input: DataInput, background_tasks: BackgroundTasks):
+def load_data(input: DataInput, background_tasks: BackgroundTasks):
task_id = uuid.uuid4().hex
background_tasks.add_task(
rag_service.add_knowledge_async,
diff --git a/src/pai_rag/app/web/rag_client.py b/src/pai_rag/app/web/rag_client.py
index 118b923d..ed2aa3e1 100644
--- a/src/pai_rag/app/web/rag_client.py
+++ b/src/pai_rag/app/web/rag_client.py
@@ -4,6 +4,7 @@
import requests
import html
import markdown
+import httpx
cache_config = None
@@ -95,11 +96,12 @@ def add_knowledge(self, file_dir: str, enable_qa_extraction: bool):
response = dotdict(json.loads(r.text))
return response
- def get_knowledge_state(self, task_id: str):
- r = requests.get(self.get_load_state_url, params={"task_id": task_id})
- r.raise_for_status()
- response = dotdict(json.loads(r.text))
- return response
+ async def get_knowledge_state(self, task_id: str):
+ async with httpx.AsyncClient() as client:
+ r = await client.get(self.get_load_state_url, params={"task_id": task_id})
+ r.raise_for_status()
+ response = dotdict(json.loads(r.text))
+ return response
def reload_config(self, config: Any):
global cache_config
diff --git a/src/pai_rag/app/web/tabs/upload_tab.py b/src/pai_rag/app/web/tabs/upload_tab.py
index 96e4d7a2..b1f6302b 100644
--- a/src/pai_rag/app/web/tabs/upload_tab.py
+++ b/src/pai_rag/app/web/tabs/upload_tab.py
@@ -6,6 +6,7 @@
from pai_rag.app.web.view_model import view_model
from pai_rag.utils.file_utils import MyUploadFile
import pandas as pd
+import asyncio
def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extraction):
@@ -15,7 +16,13 @@ def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extracti
rag_client.reload_config(new_config)
if not upload_files:
- return "No file selected. Please choose at least one file."
+ yield [
+ gr.update(visible=False),
+ gr.update(
+ visible=True,
+ value="No file selected. Please choose at least one file.",
+ ),
+ ]
my_upload_files = []
for file in upload_files:
@@ -28,7 +35,7 @@ def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extracti
result = {"Info": ["StartTime", "EndTime", "Duration(s)", "Status"]}
while not all(file.finished is True for file in my_upload_files):
for file in my_upload_files:
- response = rag_client.get_knowledge_state(str(file.task_id))
+ response = asyncio.run(rag_client.get_knowledge_state(str(file.task_id)))
file.update_state(response["status"])
file.update_process_duration()
result[file.file_name] = file.__info__()
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index cc314882..5f63157a 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -34,12 +34,18 @@ def reload(self, config):
self.logger.info("RagApplication reloaded successfully.")
# TODO: 大量文件上传实现异步添加
- async def load_knowledge(self, file_dir, enable_qa_extraction=False):
+ async def aload_knowledge(self, file_dir, enable_qa_extraction=False):
data_loader = module_registry.get_module_with_config(
"DataLoaderModule", self.config
)
await data_loader.aload(file_dir, enable_qa_extraction)
+ def load_knowledge(self, file_dir, enable_qa_extraction=False):
+ data_loader = module_registry.get_module_with_config(
+ "DataLoaderModule", self.config
+ )
+ data_loader.load(file_dir, enable_qa_extraction)
+
async def aquery_retrieval(self, query: RetrievalQuery) -> RetrievalResponse:
if not query.question:
return RetrievalResponse(docs=[])
diff --git a/src/pai_rag/core/rag_service.py b/src/pai_rag/core/rag_service.py
index 4e768752..dd1f369c 100644
--- a/src/pai_rag/core/rag_service.py
+++ b/src/pai_rag/core/rag_service.py
@@ -51,12 +51,12 @@ def reload(self, new_config: Any):
self.rag.reload(self.rag_configuration.get_value())
self.rag_configuration.persist()
- async def add_knowledge_async(
+ def add_knowledge_async(
self, task_id: str, file_dir: str, enable_qa_extraction: bool = False
):
self.tasks_status[task_id] = "processing"
try:
- await self.rag.load_knowledge(file_dir, enable_qa_extraction)
+ self.rag.load_knowledge(file_dir, enable_qa_extraction)
self.tasks_status[task_id] = "completed"
except Exception as ex:
logger.error(f"Upload failed: {ex}")
diff --git a/tests/core/test_rag_application.py b/tests/core/test_rag_application.py
index ef27858f..83006385 100644
--- a/tests/core/test_rag_application.py
+++ b/tests/core/test_rag_application.py
@@ -28,9 +28,9 @@ def rag_app():
# Test load knowledge file
-async def test_add_knowledge_file(rag_app: RagApplication):
+def test_add_knowledge_file(rag_app: RagApplication):
data_dir = os.path.join(BASE_DIR, "tests/testdata/paul_graham")
- await rag_app.load_knowledge(data_dir)
+ rag_app.load_knowledge(data_dir)
# Test rag query
From 6c5eee4e2ae3c8eaf9f7cbb156c19cc20402582d Mon Sep 17 00:00:00 2001
From: Yue Fei <59813791+moria97@users.noreply.github.com>
Date: Fri, 14 Jun 2024 16:55:03 +0800
Subject: [PATCH 06/69] Fix faiss_path not effective in retrieval (#65)
---
src/pai_rag/core/rag_application.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index 5f63157a..9137bac5 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -50,10 +50,15 @@ async def aquery_retrieval(self, query: RetrievalQuery) -> RetrievalResponse:
if not query.question:
return RetrievalResponse(docs=[])
+ sessioned_config = self.config
+ if query.vector_db and query.vector_db.faiss_path:
+ sessioned_config = self.config.copy()
+ sessioned_config.index.update({"persist_path": query.vector_db.faiss_path})
+
query_bundle = QueryBundle(query.question)
query_engine = module_registry.get_module_with_config(
- "QueryEngineModule", self.config
+ "QueryEngineModule", sessioned_config
)
node_results = await query_engine.aretrieve(query_bundle)
From 435a8b596fdae91cf429072122713c402ad5981f Mon Sep 17 00:00:00 2001
From: wwxxzz
Date: Wed, 19 Jun 2024 14:37:51 +0800
Subject: [PATCH 07/69] Add API to support upload local files (#67)
* support upload file via API
* add Readme for upload API
* refactor query api
* modify load_knowledge with session_config
* use tempfile.mkdtemp() to store upload files
---
README.md | 20 +++++++++++++++++
src/pai_rag/app/api/query.py | 33 ++++++++++++++++++++++++++++-
src/pai_rag/core/rag_application.py | 12 +++++++++--
src/pai_rag/core/rag_service.py | 8 +++++--
4 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 9785d4fd..46d1934e 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,26 @@ curl -X 'POST' http://127.0.0.1:8000/service/batch_evaluate/response
}
```
+3. 上传
+
+支持通过API的方式上传本地文件,并支持指定不同的faiss_path,每次发送API请求会返回一个task_id,之后可以通过task_id来查看文件上传状态(processing、completed、failed)。
+
+- **(1)上传(upload_local_data)**
+
+```bash
+curl -X 'POST' http://127.0.0.1:8000/service/upload_local_data -H 'Content-Type: multipart/form-data' -F 'file=@local_path/PAI.txt' -F 'faiss_path=localdata/storage'
+
+# Return: {"task_id": "2c1e557733764fdb9fefa063538914da"}
+```
+
+- **(2)查看上传状态(upload_local_data)**
+
+```bash
+curl http://127.0.0.1:8077/service/get_upload_state\?task_id\=2c1e557733764fdb9fefa063538914da
+
+# Return: {"task_id":"2c1e557733764fdb9fefa063538914da","status":"completed"}
+```
+
### 独立脚本文件:不依赖于整体服务的启动,可独立运行
1. 向当前索引存储中插入新文件
diff --git a/src/pai_rag/app/api/query.py b/src/pai_rag/app/api/query.py
index 8e8192e6..e008a23b 100644
--- a/src/pai_rag/app/api/query.py
+++ b/src/pai_rag/app/api/query.py
@@ -1,6 +1,8 @@
from typing import Any
-from fastapi import APIRouter, Body, BackgroundTasks
+from fastapi import APIRouter, Body, BackgroundTasks, File, UploadFile, Form
import uuid
+import os
+import tempfile
from pai_rag.core.rag_service import rag_service
from pai_rag.app.api.models import (
RagQuery,
@@ -86,3 +88,32 @@ async def batch_evaluate():
type="all"
)
return {"status": 200, "result": eval_results}
+
+
+@router.post("/upload_local_data")
+async def upload_local_data(
+ file: UploadFile = File(),
+ faiss_path: str = Form(),
+ background_tasks: BackgroundTasks = BackgroundTasks(),
+):
+ task_id = uuid.uuid4().hex
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ fn = file.filename
+ tmpdir = tempfile.mkdtemp()
+ save_file = os.path.join(tmpdir, f"{task_id}_{fn}")
+ with open(save_file, "wb") as f:
+ data = await file.read()
+ f.write(data)
+ f.close()
+
+ background_tasks.add_task(
+ rag_service.add_knowledge_async,
+ task_id=task_id,
+ file_dir=tmpdir,
+ faiss_path=faiss_path,
+ enable_qa_extraction=False,
+ )
+
+ return {"task_id": task_id}
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index 9137bac5..580bdb05 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -40,9 +40,17 @@ async def aload_knowledge(self, file_dir, enable_qa_extraction=False):
)
await data_loader.aload(file_dir, enable_qa_extraction)
- def load_knowledge(self, file_dir, enable_qa_extraction=False):
+ def load_knowledge(self, file_dir, faiss_path=None, enable_qa_extraction=False):
+ sessioned_config = self.config
+ if faiss_path:
+ sessioned_config = self.config.copy()
+ sessioned_config.index.update({"persist_path": faiss_path})
+ self.logger.info(
+ f"Update rag_application config with faiss_persist_path: {faiss_path}"
+ )
+
data_loader = module_registry.get_module_with_config(
- "DataLoaderModule", self.config
+ "DataLoaderModule", sessioned_config
)
data_loader.load(file_dir, enable_qa_extraction)
diff --git a/src/pai_rag/core/rag_service.py b/src/pai_rag/core/rag_service.py
index dd1f369c..fb46f9f1 100644
--- a/src/pai_rag/core/rag_service.py
+++ b/src/pai_rag/core/rag_service.py
@@ -52,11 +52,15 @@ def reload(self, new_config: Any):
self.rag_configuration.persist()
def add_knowledge_async(
- self, task_id: str, file_dir: str, enable_qa_extraction: bool = False
+ self,
+ task_id: str,
+ file_dir: str,
+ faiss_path: str = None,
+ enable_qa_extraction: bool = False,
):
self.tasks_status[task_id] = "processing"
try:
- self.rag.load_knowledge(file_dir, enable_qa_extraction)
+ self.rag.load_knowledge(file_dir, faiss_path, enable_qa_extraction)
self.tasks_status[task_id] = "completed"
except Exception as ex:
logger.error(f"Upload failed: {ex}")
From 1ca838f14dc20ba58ea92d6728dfa4543748a9b1 Mon Sep 17 00:00:00 2001
From: paradiseHIT
Date: Wed, 19 Jun 2024 17:20:02 +0800
Subject: [PATCH 08/69] add docker image timezone for China (#68)
* add image zone for China
* remove unused ENV
---------
Co-authored-by: shubao.sx
Co-authored-by: Yue Fei
---
Dockerfile | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Dockerfile b/Dockerfile
index 549a8a2d..9e6c89fa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,6 +13,9 @@ COPY . .
RUN poetry install && rm -rf $POETRY_CACHE_DIR
FROM python:3.10-slim AS prod
+
+RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Harbin /etc/localtime
+
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
From 5e4b66735ffee0570434f7e501452913fd2c1e5b Mon Sep 17 00:00:00 2001
From: paradiseHIT
Date: Wed, 19 Jun 2024 18:08:23 +0800
Subject: [PATCH 09/69] load data pipeline supports read config (#70)
---
src/pai_rag/data/rag_datapipeline.py | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/pai_rag/data/rag_datapipeline.py b/src/pai_rag/data/rag_datapipeline.py
index 23787e3b..3029d27e 100644
--- a/src/pai_rag/data/rag_datapipeline.py
+++ b/src/pai_rag/data/rag_datapipeline.py
@@ -5,19 +5,19 @@
from pai_rag.core.rag_configuration import RagConfiguration
from pai_rag.modules.module_registry import module_registry
+_BASE_DIR = Path(__file__).parent
+DEFAULT_APPLICATION_CONFIG_FILE = os.path.join(_BASE_DIR, "config/settings.toml")
+
class RagDataPipeline:
def __init__(self, data_loader):
self.data_loader = data_loader
async def ingest_from_folder(self, folder_path: str, enable_qa_extraction: bool):
- await self.data_loader.load(folder_path, enable_qa_extraction)
-
+ await self.data_loader.aload(folder_path, enable_qa_extraction)
-def __init_data_pipeline(use_local_qa_model):
- base_dir = Path(__file__).parent.parent
- config_file = os.path.join(base_dir, "config/settings.toml")
+def __init_data_pipeline(config_file, use_local_qa_model):
config = RagConfiguration.from_file(config_file).get_value()
module_registry.init_modules(config)
@@ -26,6 +26,13 @@ def __init_data_pipeline(use_local_qa_model):
@click.command()
+@click.option(
+ "-c",
+ "--config",
+ show_default=True,
+ help=f"Configuration file. Default: {DEFAULT_APPLICATION_CONFIG_FILE}",
+ default=DEFAULT_APPLICATION_CONFIG_FILE,
+)
@click.option("-d", "--directory", required=True, help="directory path to ingest.")
@click.option(
"-q",
@@ -45,6 +52,6 @@ def __init_data_pipeline(use_local_qa_model):
default=False,
help="use local qa extraction model.",
)
-def run(directory, extract_qa, use_local_qa_model):
- data_pipeline = __init_data_pipeline(use_local_qa_model)
+def run(config, directory, extract_qa, use_local_qa_model):
+ data_pipeline = __init_data_pipeline(config, use_local_qa_model)
asyncio.run(data_pipeline.ingest_from_folder(directory, extract_qa))
From cdeb9e49df5dcaf9f6d556bf41af628002eb5c20 Mon Sep 17 00:00:00 2001
From: paradiseHIT
Date: Thu, 20 Jun 2024 14:44:21 +0800
Subject: [PATCH 10/69] Add gpu docker image timezone for China (#74)
---
Dockerfile_gpu | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Dockerfile_gpu b/Dockerfile_gpu
index d6313cf0..f897a5de 100644
--- a/Dockerfile_gpu
+++ b/Dockerfile_gpu
@@ -15,6 +15,9 @@ RUN mv pyproject_gpu.toml pyproject.toml \
RUN poetry install && rm -rf $POETRY_CACHE_DIR
FROM python:3.10-slim AS prod
+
+RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Harbin /etc/localtime
+
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
From c674f2bdd7c79619a836bc698fe5c663042d9f81 Mon Sep 17 00:00:00 2001
From: Yue Fei
Date: Thu, 20 Jun 2024 15:21:10 +0800
Subject: [PATCH 11/69] Add fast bm25 (#66)
* Add fast bm25
* Fix bm25 bug
* Fix bug
* Fix test
---
src/pai_rag/data/rag_dataloader.py | 14 +-
src/pai_rag/integrations/retrievers/bm25.py | 63 +---
src/pai_rag/modules/__init__.py | 2 +
src/pai_rag/modules/datareader/data_loader.py | 6 +-
src/pai_rag/modules/index/bm25_index.py | 29 ++
src/pai_rag/modules/index/index.py | 62 ++--
src/pai_rag/modules/index/pai_bm25_index.py | 330 ++++++++++++++++++
src/pai_rag/modules/module_registry.py | 1 +
src/pai_rag/modules/retriever/retriever.py | 19 +-
src/pai_rag/utils/store_utils.py | 9 -
src/pai_rag/utils/tokenizer.py | 8 +-
src/pai_rag/utils/trie.py | 43 +++
tests/core/test_rag_application.py | 7 +-
tests/index/test_pai_bm25_index.py | 28 ++
tests/utils/test_trie.py | 32 ++
15 files changed, 542 insertions(+), 111 deletions(-)
create mode 100644 src/pai_rag/modules/index/bm25_index.py
create mode 100644 src/pai_rag/modules/index/pai_bm25_index.py
create mode 100644 src/pai_rag/utils/trie.py
create mode 100644 tests/index/test_pai_bm25_index.py
create mode 100644 tests/utils/test_trie.py
diff --git a/src/pai_rag/data/rag_dataloader.py b/src/pai_rag/data/rag_dataloader.py
index 01246ce1..e8b87a82 100644
--- a/src/pai_rag/data/rag_dataloader.py
+++ b/src/pai_rag/data/rag_dataloader.py
@@ -6,7 +6,6 @@
from llama_index.core.schema import TextNode
from llama_index.llms.huggingface import HuggingFaceLLM
-from pai_rag.utils.store_utils import store_path
from pai_rag.integrations.extractors.html_qa_extractor import HtmlQAExtractor
from pai_rag.integrations.extractors.text_qa_extractor import TextQAExtractor
from pai_rag.modules.nodeparser.node_parser import node_id_hash
@@ -32,6 +31,7 @@ def __init__(
datareader_factory,
node_parser,
index,
+ bm25_index,
oss_cache,
use_local_qa_model=False,
):
@@ -39,6 +39,7 @@ def __init__(
self.node_parser = node_parser
self.oss_cache = oss_cache
self.index = index
+ self.bm25_index = bm25_index
if use_local_qa_model:
# API暂不支持此选项
@@ -48,6 +49,7 @@ def __init__(
)
else:
self.qa_llm = Settings.llm
+
html_extractor = HtmlQAExtractor(llm=self.qa_llm)
txt_extractor = TextQAExtractor(llm=self.qa_llm)
@@ -111,8 +113,13 @@ async def aload(self, file_directory: str, enable_qa_extraction: bool):
logger.info("[DataReader] Start inserting to index.")
- await self.index.insert_nodes_async(nodes)
- self.index.storage_context.persist(persist_dir=store_path.persist_path)
+ await self.index.vector_index.insert_nodes_async(nodes)
+ self.index.vector_index.storage_context.persist(
+ persist_dir=self.index.persist_path
+ )
+
+ if self.bm25_index:
+ self.bm25_index.add_docs(nodes)
logger.info(f"Inserted {len(nodes)} nodes successfully.")
return
@@ -121,4 +128,3 @@ async def aload(self, file_directory: str, enable_qa_extraction: bool):
def load(self, file_directory: str, enable_qa_extraction: bool):
loop = asyncio.get_event_loop()
loop.run_until_complete(self.aload(file_directory, enable_qa_extraction))
- return
diff --git a/src/pai_rag/integrations/retrievers/bm25.py b/src/pai_rag/integrations/retrievers/bm25.py
index c2f4c198..74f98933 100644
--- a/src/pai_rag/integrations/retrievers/bm25.py
+++ b/src/pai_rag/integrations/retrievers/bm25.py
@@ -1,15 +1,13 @@
import logging
-from typing import Callable, List, Optional, cast
+from typing import List, Optional
from llama_index.core.base.base_retriever import BaseRetriever
from llama_index.core.callbacks.base import CallbackManager
from llama_index.core.constants import DEFAULT_SIMILARITY_TOP_K
from llama_index.core.indices.keyword_table.utils import simple_extract_keywords
-from llama_index.core.indices.vector_store.base import VectorStoreIndex
-from llama_index.core.schema import BaseNode, IndexNode, NodeWithScore, QueryBundle
-from llama_index.core.storage.docstore.types import BaseDocumentStore
+from llama_index.core.schema import IndexNode, NodeWithScore, QueryBundle
from nltk.stem import PorterStemmer
-from rank_bm25 import BM25Okapi
+from pai_rag.modules.index.pai_bm25_index import PaiBm25Index
logger = logging.getLogger(__name__)
@@ -25,22 +23,15 @@ def tokenize_remove_stopwords(text: str) -> List[str]:
class BM25Retriever(BaseRetriever):
def __init__(
self,
- nodes: List[BaseNode],
- tokenizer: Optional[Callable[[str], List[str]]],
+ bm25_index: PaiBm25Index,
similarity_top_k: int = DEFAULT_SIMILARITY_TOP_K,
- index: Optional[VectorStoreIndex] = None,
callback_manager: Optional[CallbackManager] = None,
objects: Optional[List[IndexNode]] = None,
object_map: Optional[dict] = None,
verbose: bool = False,
) -> None:
- self._nodes = nodes
- self._index = index
- self._tokenizer = tokenizer or tokenize_remove_stopwords
self._similarity_top_k = similarity_top_k
- self._corpus = [self._tokenizer(node.get_content()) for node in self._nodes]
- if self._corpus:
- self.bm25 = BM25Okapi(self._corpus)
+ self.bm25_index = bm25_index
super().__init__(
callback_manager=callback_manager,
object_map=object_map,
@@ -51,59 +42,23 @@ def __init__(
@classmethod
def from_defaults(
cls,
- index: Optional[VectorStoreIndex] = None,
- nodes: Optional[List[BaseNode]] = None,
- docstore: Optional[BaseDocumentStore] = None,
- tokenizer: Optional[Callable[[str], List[str]]] = None,
+ bm25_index: PaiBm25Index = None,
similarity_top_k: int = DEFAULT_SIMILARITY_TOP_K,
verbose: bool = False,
) -> "BM25Retriever":
- # ensure only one of index, nodes, or docstore is passed
- if sum(bool(val) for val in [index, nodes, docstore]) != 1:
- raise ValueError("Please pass exactly one of index, nodes, or docstore.")
-
- if index is not None:
- docstore = index.docstore
-
- if docstore is not None:
- nodes = cast(List[BaseNode], list(docstore.docs.values()))
-
- assert (
- nodes is not None
- ), "Please pass exactly one of index, nodes, or docstore."
-
- tokenizer = tokenizer or tokenize_remove_stopwords
return cls(
- nodes=nodes,
- index=index,
- tokenizer=tokenizer,
+ bm25_index=bm25_index,
similarity_top_k=similarity_top_k,
verbose=verbose,
)
def _get_scored_nodes(self, query: str) -> List[NodeWithScore]:
- tokenized_query = self._tokenizer(query)
- doc_scores = self.bm25.get_scores(tokenized_query)
-
- nodes: List[NodeWithScore] = []
- for i, node in enumerate(self._nodes):
- nodes.append(NodeWithScore(node=node, score=float(doc_scores[i])))
-
- return nodes
+ return self.bm25_index.query(query_str=query, top_n=self._similarity_top_k)
def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
- if len(self._index.docstore.docs) != len(self._corpus):
- self._nodes = cast(List[BaseNode], list(self._index.docstore.docs.values()))
- self._corpus = [self._tokenizer(node.get_content()) for node in self._nodes]
- if self._corpus:
- self.bm25 = BM25Okapi(self._corpus)
-
- if not self._corpus:
+ if not query_bundle.query_str:
return []
- if query_bundle.custom_embedding_strs or query_bundle.embedding:
- logger.warning("BM25Retriever does not support embeddings, skipping...")
-
scored_nodes = self._get_scored_nodes(query_bundle.query_str)
# Sort and get top_k nodes, score range => 0..1, closer to 1 means more relevant
diff --git a/src/pai_rag/modules/__init__.py b/src/pai_rag/modules/__init__.py
index 3491bb86..6ed43d52 100644
--- a/src/pai_rag/modules/__init__.py
+++ b/src/pai_rag/modules/__init__.py
@@ -14,6 +14,7 @@
from pai_rag.modules.agent.agent import AgentModule
from pai_rag.modules.tool.tool import ToolModule
from pai_rag.modules.cache.oss_cache import OssCacheModule
+from pai_rag.modules.index.bm25_index import BM25IndexModule
ALL_MODULES = [
@@ -33,6 +34,7 @@
"AgentModule",
"ToolModule",
"OssCacheModule",
+ "BM25IndexModule",
]
__all__ = ALL_MODULES + ["ALL_MODULES"]
diff --git a/src/pai_rag/modules/datareader/data_loader.py b/src/pai_rag/modules/datareader/data_loader.py
index 5e5e0383..76b41470 100644
--- a/src/pai_rag/modules/datareader/data_loader.py
+++ b/src/pai_rag/modules/datareader/data_loader.py
@@ -14,6 +14,7 @@ def get_dependencies() -> List[str]:
"DataReaderFactoryModule",
"NodeParserModule",
"IndexModule",
+ "BM25IndexModule",
]
def _create_new_instance(self, new_params: Dict[str, Any]):
@@ -21,5 +22,8 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
data_reader_factory = new_params["DataReaderFactoryModule"]
node_parser = new_params["NodeParserModule"]
index = new_params["IndexModule"]
+ bm25_index = new_params["BM25IndexModule"]
- return RagDataLoader(data_reader_factory, node_parser, index, oss_cache)
+ return RagDataLoader(
+ data_reader_factory, node_parser, index, bm25_index, oss_cache
+ )
diff --git a/src/pai_rag/modules/index/bm25_index.py b/src/pai_rag/modules/index/bm25_index.py
new file mode 100644
index 00000000..493c96f5
--- /dev/null
+++ b/src/pai_rag/modules/index/bm25_index.py
@@ -0,0 +1,29 @@
+import logging
+from typing import Dict, List, Any
+
+from pai_rag.modules.base.configurable_module import ConfigurableModule
+from pai_rag.modules.index.pai_bm25_index import PaiBm25Index
+
+
+logger = logging.getLogger(__name__)
+
+
+class BM25IndexModule(ConfigurableModule):
+ """Class for managing indices.
+
+ RagIndex to manage vector indices for RagApplication.
+ When initializing, the index is empty or load from existing index.
+ User can add nodes to index when needed.
+ """
+
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return ["IndexModule"]
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ index = new_params["IndexModule"]
+ if index.vectordb_type == "elasticsearch":
+ return None
+ else:
+ logger.info("Using BM25 Index.")
+ return PaiBm25Index(index.persist_path)
diff --git a/src/pai_rag/modules/index/index.py b/src/pai_rag/modules/index/index.py
index 84e22697..9bb9b2cf 100644
--- a/src/pai_rag/modules/index/index.py
+++ b/src/pai_rag/modules/index/index.py
@@ -9,7 +9,7 @@
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
from pai_rag.modules.index.store import RagStore
from llama_index.vector_stores.faiss import FaissVectorStore
-from pai_rag.utils.store_utils import get_store_persist_directory_name, store_path
+from pai_rag.utils.store_utils import get_store_persist_directory_name
logging.basicConfig(
stream=sys.stdout,
@@ -20,37 +20,27 @@
DEFAULT_PERSIST_DIR = "./storage"
-class IndexModule(ConfigurableModule):
- """Class for managing indices.
-
- RagIndex to manage vector indices for RagApplication.
- When initializing, the index is empty or load from existing index.
- User can add nodes to index when needed.
- """
-
- @staticmethod
- def get_dependencies() -> List[str]:
- return ["EmbeddingModule"]
-
- def _get_embed_vec_dim(self, embed_model):
- # Get dimension size of embedding vector
- return len(embed_model._get_text_embedding("test"))
-
- def _create_new_instance(self, new_params: Dict[str, Any]):
- config = new_params[MODULE_PARAM_CONFIG]
- embed_model = new_params["EmbeddingModule"]
- embed_dims = self._get_embed_vec_dim(embed_model)
+class RagIndex:
+ def __init__(self, config, embed_model):
+ self.config = config
+ self.embed_model = embed_model
+ self.embed_dims = self._get_embed_vec_dim(embed_model)
persist_path = config.get("persist_path", DEFAULT_PERSIST_DIR)
- folder_name = get_store_persist_directory_name(config, embed_dims)
- store_path.persist_path = os.path.join(persist_path, folder_name)
- is_empty = not os.path.exists(store_path.persist_path)
- rag_store = RagStore(config, store_path.persist_path, is_empty, embed_dims)
+ folder_name = get_store_persist_directory_name(config, self.embed_dims)
+ self.persist_path = os.path.join(persist_path, folder_name)
+ is_empty = not os.path.exists(self.persist_path)
+ rag_store = RagStore(config, self.persist_path, is_empty, self.embed_dims)
storage_context = rag_store.get_storage_context()
+ self.vectordb_type = config["vector_store"].get("type", "faiss").lower()
if is_empty:
- return self.create_indices(storage_context, embed_model)
+ self.vector_index = self.create_indices(storage_context, embed_model)
else:
- return self.load_indices(storage_context, embed_model)
+ self.vector_index = self.load_indices(storage_context, embed_model)
+
+ def _get_embed_vec_dim(self, embed_model):
+ # Get dimension size of embedding vector
+ return len(embed_model._get_text_embedding("test"))
def create_indices(self, storage_context, embed_model):
logging.info("Empty index, need to create indices.")
@@ -73,3 +63,21 @@ def load_indices(self, storage_context, embed_model):
embed_model=embed_model,
)
return vector_index
+
+
+class IndexModule(ConfigurableModule):
+ """Class for managing indices.
+
+ RagIndex to manage vector indices for RagApplication.
+ When initializing, the index is empty or load from existing index.
+ User can add nodes to index when needed.
+ """
+
+ @staticmethod
+ def get_dependencies() -> List[str]:
+ return ["EmbeddingModule"]
+
+ def _create_new_instance(self, new_params: Dict[str, Any]):
+ config = new_params[MODULE_PARAM_CONFIG]
+ embed_model = new_params["EmbeddingModule"]
+ return RagIndex(config, embed_model)
diff --git a/src/pai_rag/modules/index/pai_bm25_index.py b/src/pai_rag/modules/index/pai_bm25_index.py
new file mode 100644
index 00000000..924f914c
--- /dev/null
+++ b/src/pai_rag/modules/index/pai_bm25_index.py
@@ -0,0 +1,330 @@
+import logging
+import os
+import pickle
+import json
+import numpy as np
+from typing import Callable, List, cast, Dict
+from llama_index.core.schema import BaseNode, TextNode, NodeWithScore
+from pai_rag.utils.tokenizer import jieba_tokenizer
+import concurrent.futures
+from scipy.sparse import csr_matrix
+
+
+logger = logging.getLogger(__name__)
+
+MAX_DOC_LIMIT = 9000000000 # 9B
+
+"""
+Store structure:
+000001.json
+000002.json
+...
+999999.json
+"""
+DEFAULT_STORE_DIR = "local_bm25_store"
+
+# PART SIZE
+DEFAULT_FILE_PART_DIR = "parts"
+DEFAULT_PART_SIZE = 10000
+
+"""
+Index structure:
+{
+doc_count: 0,
+doc_len_sum: 0,
+inverted_index: []
+}
+"""
+DEFAULT_INDEX_FILE = "bm25.index.pkl"
+DEFAULT_INDEX_MATRIX_FILE = "bm25.index.matrix.pkl"
+
+
+class LocalBm25Index:
+ def __init__(self):
+ self.doc_count: int = 0
+ self.token_count: int = 0
+ self.doc_lens: np.ndarray = np.array([])
+ self.doc_token_index: List[Dict[str, int]] = []
+ self.inverted_index: List[set[int]] = []
+ self.token_map: Dict[str, int] = {}
+ self.node_id_map: Dict[str, int] = {}
+
+
+class PaiBm25Index:
+ def __init__(
+ self,
+ persist_path: str,
+ k1: int = 1.5,
+ b: int = 0.75,
+ tokenizer: Callable = None,
+ workers: int = 5,
+ ):
+ self.k1 = k1
+ self.b = b
+
+ self.persist_path = os.path.join(persist_path, DEFAULT_STORE_DIR)
+ self.parts_path = os.path.join(self.persist_path, DEFAULT_FILE_PART_DIR)
+ self.index_file = os.path.join(self.persist_path, DEFAULT_INDEX_FILE)
+ self.index_matrix_file = os.path.join(
+ self.persist_path, DEFAULT_INDEX_MATRIX_FILE
+ )
+
+ self.workers = workers
+
+ self.tokenizer = tokenizer or jieba_tokenizer
+
+ logger.info("Start loading local BM25 index!")
+ if os.path.exists(self.parts_path):
+ with open(self.index_file, "rb") as f:
+ self.index: LocalBm25Index = pickle.load(f)
+ with open(self.index_matrix_file, "rb") as f:
+ self.index_matrix = pickle.load(f)
+ else:
+ self.index = LocalBm25Index()
+ self.index_matrix = None
+ self.token_map = {}
+
+ logger.info("Finished loading BM25 index!")
+
+ def split_doc(self, text_list: List[str], tokenizer: Callable):
+ tokens_list = []
+ for text in text_list:
+ tokens = tokenizer(text)
+ tokens_list.append(tokens)
+
+ logger.info(f"Finished {len(text_list)} docs.")
+ return tokens_list
+
+ def persist(self, doc_index_list, doc_list):
+ os.makedirs(self.parts_path, exist_ok=True)
+
+ with open(self.index_file, "wb") as wf:
+ pickle.dump(self.index, wf)
+
+ with open(self.index_matrix_file, "wb") as wf:
+ pickle.dump(self.index_matrix, wf)
+
+ doc_idx_map = dict(zip(doc_index_list, doc_list))
+ bucket_size = DEFAULT_PART_SIZE
+ pre_bucket = -1
+ current_batch = []
+ part_file_name = "default.part"
+ for i in sorted(doc_idx_map):
+ bucket = int(i / bucket_size)
+ if bucket != pre_bucket:
+ if current_batch:
+ part_file_name = os.path.join(
+ self.parts_path, f"{pre_bucket+1:06}.part"
+ )
+ with open(part_file_name, "w") as wf:
+ wf.write("\n".join(current_batch))
+ current_batch = []
+
+ part_file_name = os.path.join(self.parts_path, f"{bucket+1:06}.part")
+ if os.path.exists(part_file_name):
+ current_batch = open(part_file_name, "r").readlines()
+ current_batch = [line.strip() for line in current_batch]
+
+ pre_bucket = bucket
+ doc_i = i % bucket_size
+
+ if doc_i < len(current_batch):
+ current_batch[i % bucket_size] == doc_idx_map[i]
+ else:
+ current_batch.append(doc_idx_map[i])
+
+ if current_batch:
+ part_file_name = os.path.join(self.parts_path, f"{pre_bucket+1:06}.part")
+ with open(part_file_name, "w") as wf:
+ wf.write("\n".join(current_batch))
+ logger.info("Write index succeed!")
+
+ def add_docs(self, nodes: List[BaseNode]):
+ node_index_list = []
+ text_list = []
+ id_list = []
+ metadata_list = []
+
+ for node in nodes:
+ if isinstance(node, TextNode):
+ text_node = cast(TextNode, node)
+ node_id = text_node.node_id
+ id_list.append(node_id)
+ metadata_list.append(text_node.metadata)
+ text_list.append(text_node.get_content())
+
+ if node_id not in self.index.node_id_map:
+ node_index = self.index.doc_count
+ self.index.doc_count += 1
+ self.index.node_id_map[node_id] = node_index
+ node_index_list.append(self.index.node_id_map[node_id])
+
+ else:
+ # don't handle image or graph node
+ pass
+ pad_size = self.index.doc_count - len(self.index.doc_lens)
+ self.index.doc_lens = np.lib.pad(
+ self.index.doc_lens, (0, pad_size), "constant", constant_values=(0)
+ )
+
+ chunk_size = 100000
+ start_pos = 0
+ if len(text_list) < 2 * chunk_size:
+ tokens_list = self.split_doc(text_list, self.tokenizer)
+ self.process_token_list(tokens_list, id_list)
+ else:
+ with concurrent.futures.ProcessPoolExecutor(
+ max_workers=self.workers
+ ) as executor:
+ futures = []
+ future2startpos = {}
+ while start_pos < len(text_list):
+ fut = executor.submit(
+ self.split_doc,
+ text_list[start_pos : start_pos + chunk_size],
+ self.tokenizer,
+ )
+ futures.append(fut)
+ future2startpos[fut] = start_pos
+ start_pos += chunk_size
+
+ i = 0
+ for fut in concurrent.futures.as_completed(futures):
+ start_pos = future2startpos[fut]
+ i += 1
+ tokens_list = fut.result()
+ batch_id_list = id_list[start_pos : start_pos + chunk_size]
+ self.process_token_list(tokens_list, batch_id_list)
+
+ self.construct_index_matrix()
+
+ doc_list = [
+ json.dumps(
+ {"id": id_list[i], "text": text_list[i], "metadata": metadata_list[i]},
+ ensure_ascii=False,
+ )
+ for i in range(len(text_list))
+ ]
+ self.persist(node_index_list, doc_list)
+
+ logger.info("Successfully write to BM25 index.")
+ return
+
+ def construct_index_matrix(self):
+ # m * n matrix
+ m = self.index.doc_count
+ n = self.index.token_count
+
+ df = np.array([len(i) for i in self.index.inverted_index])
+ idf = np.log(1 + (m - df + 0.5) / (df + 0.5))
+
+ avg_dl = np.average(self.index.doc_lens)
+ dl_factor = self.k1 * (1 - self.b + self.b * (self.index.doc_lens) / avg_dl)
+
+ rows = []
+ cols = []
+ data = []
+ for i, doc_token_set in enumerate(self.index.doc_token_index):
+ for token in doc_token_set:
+ rows.append(i)
+ cols.append(token)
+ tf = doc_token_set[token]
+ v = idf[token] * tf * (self.k1 + 1) / (tf + dl_factor[i])
+ data.append(v)
+
+ self.index_matrix = csr_matrix((data, (rows, cols)), shape=(m, n))
+
+ def load_batch_from_part_file(self, batch_ids, part_id):
+ nodes = []
+ part_file_name = os.path.join(self.parts_path, f"{part_id+1:06}.part")
+ with open(part_file_name, "r") as part_file:
+ lines = part_file.readlines()
+ for index in batch_ids:
+ index = index % DEFAULT_PART_SIZE
+ raw_text = lines[index]
+ json_data = json.loads(raw_text)
+ nodes.append(
+ TextNode(
+ id_=json_data["id"],
+ text=json_data["text"],
+ metadata=json_data["metadata"],
+ )
+ )
+ return nodes
+
+ def load_docs_with_index(self, doc_indexes):
+ results = []
+ if len(doc_indexes) == 0:
+ return results
+
+ index2nodes = {}
+ node_indexes = doc_indexes.copy()
+ node_indexes.sort()
+ bucket_size = DEFAULT_PART_SIZE
+ batch_ids = []
+ pre_bucket = -1
+ for i in doc_indexes:
+ bucket = int(i / bucket_size)
+ if bucket != pre_bucket:
+ if batch_ids:
+ batch_nodes = self.load_batch_from_part_file(batch_ids, bucket)
+ index2nodes.update(zip(batch_ids, batch_nodes))
+ batch_ids = []
+ pre_bucket = bucket
+ batch_ids.append(i)
+
+ if batch_ids:
+ batch_nodes = self.load_batch_from_part_file(batch_ids, bucket)
+ index2nodes.update(zip(batch_ids, batch_nodes))
+
+ return [index2nodes[i] for i in doc_indexes]
+
+ def query(self, query_str: str, top_n: int = 5) -> List[NodeWithScore]:
+ results = []
+ if self.index_matrix is None:
+ return results
+
+ tokens = self.tokenizer(query_str)
+ query_vec = np.zeros(self.index.token_count)
+ for token in tokens:
+ if token in self.index.token_map:
+ query_vec[self.index.token_map[token]] += 1
+
+ doc_scores = self.index_matrix.multiply(query_vec).sum(axis=1).getA1()
+ doc_indexes = doc_scores.argsort()[::-1][:top_n]
+ text_nodes = self.load_docs_with_index(doc_indexes)
+ for i, node in enumerate(text_nodes):
+ results.append(NodeWithScore(node=node, score=doc_scores[doc_indexes[i]]))
+
+ return results
+
+ def process_token_list(self, tokens_list, ids_list):
+ for i, tokens in enumerate(tokens_list):
+ token_index_set = {}
+ for token in tokens:
+ if token not in self.index.token_map:
+ self.index.token_map[token] = self.index.token_count
+ self.index.token_count += 1
+ self.index.inverted_index.append(set())
+
+ if self.index.token_map[token] not in token_index_set:
+ token_index_set[self.index.token_map[token]] = 0
+ token_index_set[self.index.token_map[token]] += 1
+
+ doc_i = self.index.node_id_map[ids_list[i]]
+ self.index.doc_lens[doc_i] = len(tokens)
+
+ # 重复
+ if doc_i < len(self.index.doc_token_index):
+ token_index_diff = set(self.index.doc_token_index[doc_i].keys()) - set(
+ token_index_set.keys()
+ )
+ for token_i in token_index_diff:
+ self.index.inverted_index[token_i].discard(doc_i)
+
+ self.index.doc_token_index[doc_i] = token_index_set
+ else:
+ self.index.doc_token_index.append(token_index_set)
+
+ for token_i in token_index_set:
+ self.index.inverted_index[token_i].add(doc_i)
diff --git a/src/pai_rag/modules/module_registry.py b/src/pai_rag/modules/module_registry.py
index cc2dca46..6d6c673b 100644
--- a/src/pai_rag/modules/module_registry.py
+++ b/src/pai_rag/modules/module_registry.py
@@ -21,6 +21,7 @@
"ToolModule": "tool",
"DataLoaderModule": "data_loader",
"OssCacheModule": "cache",
+ "BM25IndexModule": "bm25",
}
diff --git a/src/pai_rag/modules/retriever/retriever.py b/src/pai_rag/modules/retriever/retriever.py
index d65b4a33..2c9193c1 100644
--- a/src/pai_rag/modules/retriever/retriever.py
+++ b/src/pai_rag/modules/retriever/retriever.py
@@ -10,12 +10,10 @@
from llama_index.core.retrievers import RouterRetriever
from llama_index.core.vector_stores.types import VectorStoreQueryMode
-from pai_rag.utils.tokenizer import jieba_tokenizer
from pai_rag.integrations.retrievers.bm25 import BM25Retriever
from pai_rag.modules.base.configurable_module import ConfigurableModule
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
from pai_rag.utils.prompt_template import QUERY_GEN_PROMPT
-from pai_rag.modules.retriever.my_elasticsearch_store import MyElasticsearchStore
from pai_rag.modules.retriever.my_vector_index_retriever import MyVectorIndexRetriever
logger = logging.getLogger(__name__)
@@ -24,18 +22,19 @@
class RetrieverModule(ConfigurableModule):
@staticmethod
def get_dependencies() -> List[str]:
- return ["IndexModule"]
+ return ["IndexModule", "BM25IndexModule"]
def _create_new_instance(self, new_params: Dict[str, Any]):
config = new_params[MODULE_PARAM_CONFIG]
- vector_index = new_params["IndexModule"]
+ index = new_params["IndexModule"]
+ bm25_index = new_params["BM25IndexModule"]
similarity_top_k = config.get("similarity_top_k", 5)
retrieval_mode = config.get("retrieval_mode", "hybrid").lower()
# Special handle elastic search
- if isinstance(vector_index.storage_context.vector_store, MyElasticsearchStore):
+ if index.vectordb_type == "elasticsearch":
if retrieval_mode == "embedding":
query_mode = VectorStoreQueryMode.DEFAULT
elif retrieval_mode == "keyword":
@@ -44,19 +43,19 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
query_mode = VectorStoreQueryMode.HYBRID
return MyVectorIndexRetriever(
- index=vector_index,
+ index=index.vector_index,
similarity_top_k=similarity_top_k,
vector_store_query_mode=query_mode,
)
vector_retriever = MyVectorIndexRetriever(
- index=vector_index, similarity_top_k=similarity_top_k
+ index=index.vector_index, similarity_top_k=similarity_top_k
)
+
# keyword
bm25_retriever = BM25Retriever.from_defaults(
- index=vector_index,
+ bm25_index=bm25_index,
similarity_top_k=similarity_top_k,
- tokenizer=jieba_tokenizer,
)
if retrieval_mode == "embedding":
@@ -89,7 +88,7 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
return fusion_retriever
elif config["retrieval_mode"] == "router":
- nodes = list(vector_index.docstore.docs.values())
+ nodes = list(index.vector_index.docstore.docs.values())
summary_index = SummaryIndex(nodes)
list_retriever = summary_index.as_retriever(
retriever_mode="embedding", similarity_top_k=10
diff --git a/src/pai_rag/utils/store_utils.py b/src/pai_rag/utils/store_utils.py
index 0873375c..6cf79457 100644
--- a/src/pai_rag/utils/store_utils.py
+++ b/src/pai_rag/utils/store_utils.py
@@ -1,7 +1,6 @@
import hashlib
import json
import os
-from dataclasses import dataclass
def get_store_persist_directory_name(storage_config, ndims):
@@ -61,11 +60,3 @@ def read_chat_store_state(persist_dir, file_path):
return json.load(file)
except Exception:
return None # 如果文件不存在/json不合法,则返回None
-
-
-@dataclass
-class StorePath:
- persist_path: str = "./store" # 知识库数据保存地址
-
-
-store_path = StorePath()
diff --git a/src/pai_rag/utils/tokenizer.py b/src/pai_rag/utils/tokenizer.py
index 168702b9..076c76fb 100644
--- a/src/pai_rag/utils/tokenizer.py
+++ b/src/pai_rag/utils/tokenizer.py
@@ -1,8 +1,14 @@
import jieba
from nltk.corpus import stopwords
from typing import List
+from pai_rag.utils.trie import TrieTree
+import string
+
+CHINESE_PUNKTUATION = ""#$%&'()*+,-/:;<=>@[\]^_`{|}~⦅⦆「」、\u3000、〃〈〉《》「」『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘’‛“”„‟…‧﹏﹑﹔·.!?。。"
stopword_list = stopwords.words("chinese") + stopwords.words("english")
+stopword_list += [" "] + list(string.punctuation) + list(CHINESE_PUNKTUATION)
+stop_trie = TrieTree(stopword_list)
## PUT in utils file and add stopword in TRIE structure.
@@ -10,7 +16,7 @@ def jieba_tokenizer(text: str) -> List[str]:
tokens = []
for w in jieba.lcut(text):
token = w.lower()
- if token not in stopword_list:
+ if not stop_trie.match(token):
tokens.append(token)
return tokens
diff --git a/src/pai_rag/utils/trie.py b/src/pai_rag/utils/trie.py
new file mode 100644
index 00000000..caa2eb44
--- /dev/null
+++ b/src/pai_rag/utils/trie.py
@@ -0,0 +1,43 @@
+from typing import Dict
+
+
+class TrieNode:
+ def __init__(self, char, is_word=False):
+ self.char: str = char
+ self.is_word: bool = is_word
+ self.children: Dict[str, TrieNode] = {}
+
+
+class TrieTree:
+ def __init__(self, word_list):
+ self.root = TrieNode("###")
+
+ self.build_tree(word_list)
+
+ def build_tree(self, word_list):
+ for word in word_list:
+ current_node = self.root
+ for w in word:
+ if w not in current_node.children:
+ current_node.children[w] = TrieNode(w)
+ current_node = current_node.children[w]
+ current_node.is_word = True
+
+ def match(self, word):
+ current_node = self.root
+ for w in word:
+ if w not in current_node.children:
+ return False
+ current_node = current_node.children[w]
+
+ return current_node.is_word
+
+
+if __name__ == "__main__":
+ tree = TrieTree(["abc", "Her", "She", "He", "Hereby"])
+ assert tree.match("Her")
+ assert tree.match("Hereby")
+ assert tree.match("She")
+ assert tree.match("abc")
+ assert tree.match("He")
+ assert not tree.match("Here")
diff --git a/tests/core/test_rag_application.py b/tests/core/test_rag_application.py
index 83006385..b0fb0b5d 100644
--- a/tests/core/test_rag_application.py
+++ b/tests/core/test_rag_application.py
@@ -24,14 +24,11 @@ def rag_app():
rag_app = RagApplication()
rag_app.initialize(config)
- return rag_app
-
-
-# Test load knowledge file
-def test_add_knowledge_file(rag_app: RagApplication):
data_dir = os.path.join(BASE_DIR, "tests/testdata/paul_graham")
rag_app.load_knowledge(data_dir)
+ return rag_app
+
# Test rag query
async def test_query(rag_app: RagApplication):
diff --git a/tests/index/test_pai_bm25_index.py b/tests/index/test_pai_bm25_index.py
new file mode 100644
index 00000000..da482076
--- /dev/null
+++ b/tests/index/test_pai_bm25_index.py
@@ -0,0 +1,28 @@
+from pai_rag.modules.index.pai_bm25_index import PaiBm25Index
+from llama_index.core.schema import TextNode
+
+texts = [
+ "人工智能平台PAI是面向开发者和企业的云原生机器学习/深度学习工程平台,服务覆盖AI开发全链路,内置140+种优化算法,具备丰富的行业场景插件。",
+ "面向大规模深度学习及融合智算场景的PaaS平台产品,支持公共云Serverless版、单租版以及混合云产品形态,一站式提供AI工程化全流程平台及软硬一体联合优化的异构融合算力。",
+ "公共云Serverless版: Serverless平台产品,一键快速拉起AI计算任务,复杂异构系统自动运维,轻松管理。与云上的计算、存储、网络等各类产品无缝衔接。",
+ "公共云单租版:云上建立客户专属集群,单个客户独享一套AI平台和运维服务。便捷运营管理,云产品互通,使用云上标准的计算、存储、网络服务。",
+ "飞天混合云版:支持混合云标准架构,提供完整的计算、网络、存储、账号(ASCM),标准SDK/OpenAPI,物理资源独立部署,支持服务商基于客户场景构建业务。",
+ "模型开发: 在模型开发阶段,可通过PAI-Designer、PAI-DSW、PAI-QuickStart 三款工具来完成建模。",
+ "模型训练: 在模型训练阶段,可通过PAI-DLC发起大规模的分布式训练任务;按照使用场景和算力类别,可以分为使用灵骏智算支持大模型的训练任务,和使用阿里云通用算力节点支持通用的训练任务。",
+ "模型部署: 在模型部署阶段,PAI-EAS提供在线预测服务,PAI-Blade提供推理优化服务。",
+]
+
+
+def test_bm25():
+ persist_path = "./tmp/bm25_test"
+
+ bm25 = PaiBm25Index(persist_path)
+
+ nodes = [TextNode(id_=i, text=text) for i, text in enumerate(texts)]
+ bm25.add_docs(nodes)
+
+ nodes_with_score = bm25.query("模型开发")
+ assert nodes_with_score[0].node.get_content() == texts[5]
+
+ nodes_with_score = bm25.query("模型训练")
+ assert nodes_with_score[0].node.get_content() == texts[6]
diff --git a/tests/utils/test_trie.py b/tests/utils/test_trie.py
new file mode 100644
index 00000000..7f686fd6
--- /dev/null
+++ b/tests/utils/test_trie.py
@@ -0,0 +1,32 @@
+from pai_rag.utils.trie import TrieTree
+
+
+def test_trietree():
+ word_list = ["Water", "What", "Watt", "Wow", "Go", "Goose", "Way"]
+ tree = TrieTree(word_list)
+
+ for w in word_list:
+ assert tree.match(w)
+
+ for no_w in ["Wa", "W", "what", "water", "Goos", "G"]:
+ assert not tree.match(no_w)
+
+
+def test_trietree_special_characters():
+ word_list = [
+ "Water",
+ "What",
+ "Watt",
+ "Wow",
+ "Go",
+ "Goose",
+ "Way",
+ ",",
+ "!",
+ "!",
+ "&&",
+ ]
+ tree = TrieTree(word_list)
+
+ for w in word_list:
+ assert tree.match(w)
From 7e98945eb19774c628326c65de39fc97e7f327ae Mon Sep 17 00:00:00 2001
From: paradiseHIT
Date: Fri, 21 Jun 2024 16:39:15 +0800
Subject: [PATCH 12/69] Update readme and configuration (#77)
* fix demo.toml typo, and add comments for settings.toml for embedding
* update readme, add load data
---
README.md | 18 ++++++++++++++----
src/pai_rag/config/settings.toml | 19 ++++++++++++++-----
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 46d1934e..2fdd8202 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,15 @@
## Get Started
-### Step1: Clone Repo
+### 本地启动
+
+#### Step1: Clone Repo
```bash
git clone git@github.com:aigc-apps/PAI-RAG.git
```
-### Step2: 配置环境
+#### Step2: 配置环境
本项目使用poetry进行管理,建议在安装环境之前先创建一个空环境。为了确保环境一致性并避免因Python版本差异造成的问题,我们指定Python版本为3.10。
@@ -26,13 +28,21 @@ pip install poetry
poetry install
```
-### Step3: 启动程序
+#### Step3:加载数据
+
+向当前索引存储中插入directory_path目录下的新文件
+
+```bash
+load_data -c src/pai_rag/config/settings.yaml -d directory_path
+```
+
+#### Step4: 启动程序
使用OpenAI API,需要在命令行引入环境变量 export OPENAI_API_KEY=""
使用DashScope API,需要在命令行引入环境变量 export DASHSCOPE_API_KEY=""
```bash
-# 启动,支持自定义host(默认0.0.0.0), port(默认8000), config(默认config/demo.yaml)
+# 启动,支持自定义host(默认0.0.0.0), port(默认8000), config(默认src/pai_rag/config/settings.yaml)
pai_rag run [--host HOST] [--port PORT] [--config CONFIG_FILE]
```
diff --git a/src/pai_rag/config/settings.toml b/src/pai_rag/config/settings.toml
index 93f12446..b80f753e 100644
--- a/src/pai_rag/config/settings.toml
+++ b/src/pai_rag/config/settings.toml
@@ -19,9 +19,14 @@ persist_path = "localdata/storage"
[rag.data_reader]
type = "SimpleDirectoryReader"
+# embedding configurations, source support API: OpenAI,DashScope; and local model:HuggingFace
+# if use API, need set OPENAI_API_KEY or DASHSCOPE_API_KEY in ENV, If HuggingFace, need set model_name
+# eg.
+# source = "HuggingFace"
+# model_name = "bge-small-zh-v1.5"
+# embed_batch_size = 10
[rag.embedding]
-source = "HuggingFace"
-model_name = "bge-small-zh-v1.5"
+source = "DashScope"
embed_batch_size = 10
[rag.evaluation]
@@ -33,10 +38,14 @@ response = ["Faithfulness", "Answer Relevancy", "Guideline Adherence", "Correctn
persist_path = "localdata/storage"
vector_store.type = "FAISS"
+# llm configurations, source support API: OpenAI,DashScope or PAI-EAS's deployment
+# eg.
+# source = "PaiEas"
+# endpoint = ""
+# token = ""
[rag.llm]
-source = "PaiEas"
-endpoint = ""
-token = ""
+source = "DashScope"
+name = "qwen-turbo"
[rag.llm_chat_engine]
type = "SimpleChatEngine"
From 7c2467ea6a8e6c3ab4c0554aad1fb0c33193ec71 Mon Sep 17 00:00:00 2001
From: Yue Fei
Date: Sat, 22 Jun 2024 00:55:14 +0800
Subject: [PATCH 13/69] Update docker.yml
---
.github/workflows/docker.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index d45cb254..e2e5e756 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -3,6 +3,7 @@ name: Create and publish a Docker image
# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
+ workflow_dispatch:
push:
branches: ["feature"]
From 9e68ac65c1fef30fc6df965dae7921110be2edf9 Mon Sep 17 00:00:00 2001
From: Yue Fei
Date: Mon, 24 Jun 2024 15:56:53 +0800
Subject: [PATCH 14/69] Enable multiple workers to improve perf (#75)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add fast bm25
* Update
* Fix bug
* Fix bm25 bug
* Fix bug
* Refine code
* Update multi-process
* Add API to support upload local files (#67)
* support upload file via API
* add Readme for upload API
* refactor query api
* modify load_knowledge with session_config
* use tempfile.mkdtemp() to store upload files
* add docker image timezone for China (#68)
* add image zone for China
* remove unused ENV
---------
Co-authored-by: shubao.sx
Co-authored-by: Yue Fei
* load data pipeline supports read config (#70)
* Add gpu docker image timezone for China (#74)
* Add fast bm25 (#66)
* Add fast bm25
* Fix bm25 bug
* Fix bug
* Fix test
* Update dockerfile
* Fix bug
* Update
* Update docker file
* Fix empty file bug
* Fix local index error
* Fix lint
* Decouple gradio and backend
* Add ui build
* Add gunicorn
* Fix gunicorn
* Update nginx
* add nginx image
* Fix deployment issue
* Fix upload
---------
Co-authored-by: 筱文
Co-authored-by: paradiseHIT
Co-authored-by: shubao.sx
---
.github/workflows/docker.yml | 18 +
.gitignore | 1 +
Dockerfile | 2 +-
Dockerfile_gpu | 2 +-
Dockerfile_nginx | 3 +
Dockerfile_ui | 27 +
README.md | 23 +-
nginx/default.conf | 67 +
nginx/nginx.conf | 33 +
poetry.lock | 1340 ++++++++++++-----
pyproject.toml | 2 +
pyproject_gpu.toml | 2 +
scripts/locust.py | 46 +
src/pai_rag/app/api/query.py | 7 +-
src/pai_rag/app/{app.py => api/service.py} | 16 +-
src/pai_rag/app/web/rag_client.py | 46 +-
src/pai_rag/app/web/tabs/chat_tab.py | 7 +-
src/pai_rag/app/web/tabs/settings_tab.py | 22 +-
src/pai_rag/app/web/tabs/upload_tab.py | 9 +-
src/pai_rag/app/web/tabs/vector_db_panel.py | 19 +-
src/pai_rag/app/web/view_model.py | 175 ++-
src/pai_rag/app/web/webui.py | 19 +-
src/pai_rag/core/rag_application.py | 10 +-
src/pai_rag/core/rag_configuration.py | 21 +
src/pai_rag/core/rag_service.py | 77 +-
src/pai_rag/data/rag_dataloader.py | 7 +
src/pai_rag/integrations/llms/paieas/base.py | 57 +-
src/pai_rag/main.py | 85 +-
src/pai_rag/modules/index/index.py | 32 +-
src/pai_rag/modules/index/index_daemon.py | 64 +
src/pai_rag/modules/index/index_entry.py | 25 +
src/pai_rag/modules/index/pai_bm25_index.py | 40 +-
src/pai_rag/modules/llm/llm_module.py | 6 +-
src/pai_rag/modules/llm/my_dashscope.py | 365 +++++
src/pai_rag/modules/module_registry.py | 30 +-
.../modules/postprocessor/postprocessor.py | 4 +-
.../queryengine/my_retriever_query_engine.py | 33 +-
37 files changed, 2140 insertions(+), 602 deletions(-)
create mode 100644 Dockerfile_nginx
create mode 100644 Dockerfile_ui
create mode 100644 nginx/default.conf
create mode 100644 nginx/nginx.conf
create mode 100644 scripts/locust.py
rename src/pai_rag/app/{app.py => api/service.py} (52%)
create mode 100644 src/pai_rag/modules/index/index_daemon.py
create mode 100644 src/pai_rag/modules/index/index_entry.py
create mode 100644 src/pai_rag/modules/llm/my_dashscope.py
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index e2e5e756..6ef954d7 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -52,3 +52,21 @@ jobs:
docker tag ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
docker push ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG
docker push ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
+
+ - name: Build and push UI image
+ env:
+ IMAGE_TAG: 0.0.2_ui
+ run: |
+ docker build -t ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG -f Dockerfile_ui .
+ docker tag ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
+ docker push ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG
+ docker push ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
+
+ - name: Build and push nginx image
+ env:
+ IMAGE_TAG: 0.0.2_nginx
+ run: |
+ docker build -t ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG -f Dockerfile_nginx .
+ docker tag ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
+ docker push ${{ env.REGISTRY }}/mybigpai/pairag:$IMAGE_TAG
+ docker push ${{ env.REGISTRY_HZ }}/mybigpai/pairag:$IMAGE_TAG
diff --git a/.gitignore b/.gitignore
index 9f942b55..ea18b748 100644
--- a/.gitignore
+++ b/.gitignore
@@ -222,3 +222,4 @@ output
*.local.toml
localdata/
+*.tmp
diff --git a/Dockerfile b/Dockerfile
index 9e6c89fa..f86927c7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -24,4 +24,4 @@ RUN apt-get update && apt-get install -y libgl1 libglib2.0-0
WORKDIR /app
COPY . .
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
-ENTRYPOINT ["pai_rag", "run"]
+CMD ["pai_rag", "run"]
diff --git a/Dockerfile_gpu b/Dockerfile_gpu
index f897a5de..7388fa4b 100644
--- a/Dockerfile_gpu
+++ b/Dockerfile_gpu
@@ -26,4 +26,4 @@ RUN apt-get update && apt-get install -y libgl1 libglib2.0-0
WORKDIR /app
COPY . .
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
-ENTRYPOINT ["pai_rag", "run"]
+CMD ["pai_rag", "run"]
diff --git a/Dockerfile_nginx b/Dockerfile_nginx
new file mode 100644
index 00000000..a9cdd81a
--- /dev/null
+++ b/Dockerfile_nginx
@@ -0,0 +1,3 @@
+FROM nginx:latest
+COPY ./nginx/default.conf etc/nginx/conf.d/default.conf
+COPY ./nginx/nginx.conf etc/nginx/nginx.conf
diff --git a/Dockerfile_ui b/Dockerfile_ui
new file mode 100644
index 00000000..a2509356
--- /dev/null
+++ b/Dockerfile_ui
@@ -0,0 +1,27 @@
+FROM python:3.10-slim AS builder
+
+RUN pip3 install poetry
+
+ENV POETRY_NO_INTERACTION=1 \
+ POETRY_VIRTUALENVS_IN_PROJECT=1 \
+ POETRY_VIRTUALENVS_CREATE=1 \
+ POETRY_CACHE_DIR=/tmp/poetry_cache
+
+WORKDIR /app
+COPY . .
+
+RUN poetry install && rm -rf $POETRY_CACHE_DIR
+
+FROM python:3.10-slim AS prod
+
+RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Harbin /etc/localtime
+
+ENV VIRTUAL_ENV=/app/.venv \
+ PATH="/app/.venv/bin:$PATH"
+
+RUN apt-get update && apt-get install -y libgl1 libglib2.0-0
+
+WORKDIR /app
+COPY . .
+COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
+CMD ["pai_rag", "ui"]
diff --git a/README.md b/README.md
index 2fdd8202..163d1530 100644
--- a/README.md
+++ b/README.md
@@ -36,19 +36,19 @@ poetry install
load_data -c src/pai_rag/config/settings.yaml -d directory_path
```
-#### Step4: 启动程序
+#### Step4: 启动RAG服务
使用OpenAI API,需要在命令行引入环境变量 export OPENAI_API_KEY=""
使用DashScope API,需要在命令行引入环境变量 export DASHSCOPE_API_KEY=""
```bash
-# 启动,支持自定义host(默认0.0.0.0), port(默认8000), config(默认src/pai_rag/config/settings.yaml)
-pai_rag run [--host HOST] [--port PORT] [--config CONFIG_FILE]
+# 启动,支持自定义host(默认0.0.0.0), port(默认8001), config(默认src/pai_rag/config/settings.yaml)
+pai_rag serve [--host HOST] [--port PORT] [--config CONFIG_FILE]
```
-现在你可以使用命令行向服务侧发送API请求,或者直接打开http://localhost:8000
+你可以使用命令行向服务侧发送API请求。比如调用[Upload API](#upload-api)上传知识库文件。
-1. 对话
+##### Query API
- **Rag Query请求**
@@ -77,7 +77,7 @@ curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application
curl -X 'POST' http://127.0.0.1:8000/service/query/agent -H "Content-Type: application/json" -d '{"question":"今年是2024年,10年前是哪一年?"}'
```
-2. 评估
+##### Evaluation API
支持三种评估模式:全链路评估、检索效果评估、生成效果评估。
@@ -144,7 +144,7 @@ curl -X 'POST' http://127.0.0.1:8000/service/batch_evaluate/response
}
```
-3. 上传
+##### Upload API
支持通过API的方式上传本地文件,并支持指定不同的faiss_path,每次发送API请求会返回一个task_id,之后可以通过task_id来查看文件上传状态(processing、completed、failed)。
@@ -164,6 +164,15 @@ curl http://127.0.0.1:8077/service/get_upload_state\?task_id\=2c1e557733764fdb9f
# Return: {"task_id":"2c1e557733764fdb9fefa063538914da","status":"completed"}
```
+### RAG WEB UI
+
+```bash
+# 启动,支持自定义host(默认0.0.0.0), port(默认8002), config(默认localhost:8001)
+pai_rag ui [--host HOST] [--port PORT] [rag-url RAG_URL]
+```
+
+你也可以打开http://127.0.0.1:8002/ 来配置RAG服务以及上传本地数据。
+
### 独立脚本文件:不依赖于整体服务的启动,可独立运行
1. 向当前索引存储中插入新文件
diff --git a/nginx/default.conf b/nginx/default.conf
new file mode 100644
index 00000000..81c6a98d
--- /dev/null
+++ b/nginx/default.conf
@@ -0,0 +1,67 @@
+
+server {
+ listen 8000;
+ listen [::]:8000;
+ server_name localhost;
+ client_max_body_size 50m;
+
+ #access_log /var/log/nginx/host.access.log main;
+
+ location / {
+ proxy_set_header Host \$host;
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+ proxy_pass http://127.0.0.1:8002;
+ }
+
+ #Websocket configuration
+ location /queue/ {
+ proxy_pass http://127.0.0.1:8002/queue/;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+
+ location /service {
+ proxy_set_header Host \$host;
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+ proxy_pass http://127.0.0.1:8001;
+ }
+
+ location /docs {
+ proxy_set_header Host \$host;
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+ proxy_pass http://127.0.0.1:8001;
+ }
+
+ #error_page 404 /404.html;
+
+ # redirect server error pages to the static page /50x.html
+ #
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+
+ # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+ #
+ #location ~ \.php$ {
+ # proxy_pass http://127.0.0.1;
+ #}
+
+ # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+ #
+ #location ~ \.php$ {
+ # root html;
+ # fastcgi_pass 127.0.0.1:9000;
+ # fastcgi_index index.php;
+ # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
+ # include fastcgi_params;
+ #}
+
+ # deny access to .htaccess files, if Apache's document root
+ # concurs with nginx's one
+ #
+ #location ~ /\.ht {
+ # deny all;
+ #}
+}
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 00000000..40aafb56
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,33 @@
+
+user nginx;
+daemon off;
+worker_processes auto;
+
+error_log /var/log/nginx/error.log notice;
+pid /var/run/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ include /etc/nginx/conf.d/*.conf;
+}
diff --git a/poetry.lock b/poetry.lock
index b16f163b..13076b5e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,14 +1,14 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "accelerate"
-version = "0.30.1"
+version = "0.31.0"
description = "Accelerate"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "accelerate-0.30.1-py3-none-any.whl", hash = "sha256:8dd4edd532a4dac72558c5fe6fe8cb70d0c8ec9e8733f48db97d51ee41cbe763"},
- {file = "accelerate-0.30.1.tar.gz", hash = "sha256:96779c618889646b86dc928c9e55e86e50a7ccab59e1692e22096481977ae682"},
+ {file = "accelerate-0.31.0-py3-none-any.whl", hash = "sha256:0fc608dc49584f64d04711a39711d73cb0ad4ef3d21cddee7ef2216e29471144"},
+ {file = "accelerate-0.31.0.tar.gz", hash = "sha256:b5199865b26106ccf9205acacbe8e4b3b428ad585e7c472d6a46f6fb75b6c176"},
]
[package.dependencies]
@@ -154,12 +154,12 @@ frozenlist = ">=1.1.0"
[[package]]
name = "alibabacloud-credentials"
-version = "0.3.3"
+version = "0.3.4"
description = "The alibabacloud credentials module of alibabaCloud Python SDK."
optional = false
python-versions = ">=3.6"
files = [
- {file = "alibabacloud_credentials-0.3.3.tar.gz", hash = "sha256:7996cf6d96f242a39978987bdeb5e8c8e802e7b950aa7d983e67ba8001007dc5"},
+ {file = "alibabacloud_credentials-0.3.4.tar.gz", hash = "sha256:c15a34fe782c318d4cf24cb041a0385ac4ccd2548e524e5d7fe1cff56a9a6acc"},
]
[package.dependencies]
@@ -496,13 +496,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
[[package]]
name = "azure-core"
-version = "1.30.1"
+version = "1.30.2"
description = "Microsoft Azure Core Library for Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "azure-core-1.30.1.tar.gz", hash = "sha256:26273a254131f84269e8ea4464f3560c731f29c0c1f69ac99010845f239c1a8f"},
- {file = "azure_core-1.30.1-py3-none-any.whl", hash = "sha256:7c5ee397e48f281ec4dd773d67a0a47a0962ed6fa833036057f9ea067f688e74"},
+ {file = "azure-core-1.30.2.tar.gz", hash = "sha256:a14dc210efcd608821aa472d9fb8e8d035d29b68993819147bc290a8ac224472"},
+ {file = "azure_core-1.30.2-py3-none-any.whl", hash = "sha256:cf019c1ca832e96274ae85abd3d9f752397194d9fea3b41487290562ac8abe4a"},
]
[package.dependencies]
@@ -515,13 +515,13 @@ aio = ["aiohttp (>=3.0)"]
[[package]]
name = "azure-identity"
-version = "1.16.0"
+version = "1.17.0"
description = "Microsoft Azure Identity Library for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "azure-identity-1.16.0.tar.gz", hash = "sha256:6ff1d667cdcd81da1ceab42f80a0be63ca846629f518a922f7317a7e3c844e1b"},
- {file = "azure_identity-1.16.0-py3-none-any.whl", hash = "sha256:722fdb60b8fdd55fa44dc378b8072f4b419b56a5e54c0de391f644949f3a826f"},
+ {file = "azure-identity-1.17.0.tar.gz", hash = "sha256:a1168f223b2d7fa3968362b78affd157a1f3772f310a8cdce883cc515c6c8998"},
+ {file = "azure_identity-1.17.0-py3-none-any.whl", hash = "sha256:501d6c2cbb07826ff5e7cebc05ef319ba45612f31f0bdadca260005798e4ef16"},
]
[package.dependencies]
@@ -529,6 +529,7 @@ azure-core = ">=1.23.0"
cryptography = ">=2.5"
msal = ">=1.24.0"
msal-extensions = ">=0.3.0"
+typing-extensions = ">=4.0.0"
[[package]]
name = "backoff"
@@ -602,6 +603,109 @@ charset-normalizer = ["charset-normalizer"]
html5lib = ["html5lib"]
lxml = ["lxml"]
+[[package]]
+name = "blinker"
+version = "1.8.2"
+description = "Fast, simple object-to-object and broadcast signaling"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
+ {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
+]
+
+[[package]]
+name = "brotli"
+version = "1.1.0"
+description = "Python bindings for the Brotli compression library"
+optional = false
+python-versions = "*"
+files = [
+ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
+ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
+ {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"},
+ {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"},
+ {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"},
+ {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
+ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"},
+ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
+ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
+ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
+ {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
+ {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
+ {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
+ {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"},
+ {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"},
+ {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"},
+ {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"},
+ {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"},
+ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"},
+ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
+ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
+ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
+ {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
+ {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
+ {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
+ {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
+ {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
+ {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
+ {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
+ {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"},
+ {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"},
+ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"},
+ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
+ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
+ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
+ {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
+ {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
+ {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
+ {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"},
+ {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"},
+ {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"},
+ {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"},
+ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"},
+ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
+ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
+ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
+ {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
+ {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
+ {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
+ {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"},
+ {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"},
+ {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"},
+ {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"},
+ {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"},
+ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"},
+ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
+ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
+ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
+ {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
+ {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
+ {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
+ {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"},
+ {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"},
+ {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"},
+ {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"},
+ {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"},
+ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"},
+ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
+ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
+ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
+ {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
+ {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
+ {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
+]
+
[[package]]
name = "build"
version = "1.2.1"
@@ -862,13 +966,13 @@ numpy = "*"
[[package]]
name = "chromadb"
-version = "0.5.0"
+version = "0.5.3"
description = "Chroma."
optional = false
python-versions = ">=3.8"
files = [
- {file = "chromadb-0.5.0-py3-none-any.whl", hash = "sha256:8193dc65c143b61d8faf87f02c44ecfa778d471febd70de517f51c5d88a06009"},
- {file = "chromadb-0.5.0.tar.gz", hash = "sha256:7954af614a9ff7b2902ddbd0a162f33f7ec0669e2429903905c4f7876d1f766f"},
+ {file = "chromadb-0.5.3-py3-none-any.whl", hash = "sha256:b3874f08356e291c68c6d2e177db472cd51f22f3af7b9746215b748fd1e29982"},
+ {file = "chromadb-0.5.3.tar.gz", hash = "sha256:05d887f56a46b2e0fc6ac5ab979503a27b9ee50d5ca9e455f83b2fb9840cd026"},
]
[package.dependencies]
@@ -877,10 +981,11 @@ build = ">=1.0.3"
chroma-hnswlib = "0.7.3"
fastapi = ">=0.95.2"
grpcio = ">=1.58.0"
+httpx = ">=0.27.0"
importlib-resources = "*"
kubernetes = ">=28.1.0"
mmh3 = ">=4.0.1"
-numpy = ">=1.22.5"
+numpy = ">=1.22.5,<2.0.0"
onnxruntime = ">=1.14.1"
opentelemetry-api = ">=1.2.0"
opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0"
@@ -942,6 +1047,21 @@ humanfriendly = ">=9.1"
[package.extras]
cron = ["capturer (>=2.4)"]
+[[package]]
+name = "configargparse"
+version = "1.7"
+description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"},
+ {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"},
+]
+
+[package.extras]
+test = ["PyYAML", "mock", "pytest"]
+yaml = ["PyYAML"]
+
[[package]]
name = "contourpy"
version = "1.2.1"
@@ -1153,12 +1273,12 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "dashscope"
-version = "1.19.2"
+version = "1.19.3"
description = "dashscope client sdk library"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "dashscope-1.19.2-py3-none-any.whl", hash = "sha256:bacfb6b7c2ce273d2f1a75c7a2ecc8142452079dd5efb2bfb77e670742debc62"},
+ {file = "dashscope-1.19.3-py3-none-any.whl", hash = "sha256:dd78d047762dd9826414365534dfc30c79ae7521aba6bc01925fff3e5d0c3539"},
]
[package.dependencies]
@@ -1170,13 +1290,13 @@ tokenizer = ["tiktoken"]
[[package]]
name = "dataclasses-json"
-version = "0.6.6"
+version = "0.6.7"
description = "Easily serialize dataclasses to and from JSON."
optional = false
python-versions = "<4.0,>=3.7"
files = [
- {file = "dataclasses_json-0.6.6-py3-none-any.whl", hash = "sha256:e54c5c87497741ad454070ba0ed411523d46beb5da102e221efb873801b0ba85"},
- {file = "dataclasses_json-0.6.6.tar.gz", hash = "sha256:0c09827d26fffda27f1be2fed7a7a01a29c5ddcd2eb6393ad5ebf9d77e9deae8"},
+ {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"},
+ {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"},
]
[package.dependencies]
@@ -1185,37 +1305,37 @@ typing-inspect = ">=0.4.0,<1"
[[package]]
name = "datasets"
-version = "2.19.2"
+version = "2.20.0"
description = "HuggingFace community-driven open-source library of datasets"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "datasets-2.19.2-py3-none-any.whl", hash = "sha256:e07ff15d75b1af75c87dd96323ba2a361128d495136652f37fd62f918d17bb4e"},
- {file = "datasets-2.19.2.tar.gz", hash = "sha256:eccb82fb3bb5ee26ccc6d7a15b7f1f834e2cc4e59b7cff7733a003552bad51ef"},
+ {file = "datasets-2.20.0-py3-none-any.whl", hash = "sha256:76ac02e3bdfff824492e20678f0b6b1b6d080515957fe834b00c2ba8d6b18e5e"},
+ {file = "datasets-2.20.0.tar.gz", hash = "sha256:3c4dbcd27e0f642b9d41d20ff2efa721a5e04b32b2ca4009e0fc9139e324553f"},
]
[package.dependencies]
aiohttp = "*"
dill = ">=0.3.0,<0.3.9"
filelock = "*"
-fsspec = {version = ">=2023.1.0,<=2024.3.1", extras = ["http"]}
+fsspec = {version = ">=2023.1.0,<=2024.5.0", extras = ["http"]}
huggingface-hub = ">=0.21.2"
multiprocess = "*"
numpy = ">=1.17"
packaging = "*"
pandas = "*"
-pyarrow = ">=12.0.0"
+pyarrow = ">=15.0.0"
pyarrow-hotfix = "*"
pyyaml = ">=5.1"
-requests = ">=2.32.1"
-tqdm = ">=4.62.1"
+requests = ">=2.32.2"
+tqdm = ">=4.66.3"
xxhash = "*"
[package.extras]
apache-beam = ["apache-beam (>=2.26.0)"]
audio = ["librosa", "soundfile (>=0.12.1)"]
benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"]
-dev = ["Pillow (>=9.4.0)", "absl-py", "apache-beam (>=2.26.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "transformers", "typing-extensions (>=4.6.1)", "zstandard"]
+dev = ["Pillow (>=9.4.0)", "absl-py", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "transformers", "typing-extensions (>=4.6.1)", "zstandard"]
docs = ["s3fs", "tensorflow (>=2.6.0)", "torch", "transformers"]
jax = ["jax (>=0.3.14)", "jaxlib (>=0.3.14)"]
metrics-tests = ["Werkzeug (>=1.0.1)", "accelerate", "bert-score (>=0.3.6)", "jiwer", "langdetect", "mauve-text", "nltk", "requests-file (>=1.5.1)", "rouge-score", "sacrebleu", "sacremoses", "scikit-learn", "scipy", "sentencepiece", "seqeval", "six (>=1.15.0,<1.16.0)", "spacy (>=3.0.0)", "texttable (>=1.6.3)", "tldextract", "tldextract (>=3.1.0)", "toml (>=0.10.1)", "typer (<0.5.0)"]
@@ -1223,7 +1343,7 @@ quality = ["ruff (>=0.3.0)"]
s3 = ["s3fs"]
tensorflow = ["tensorflow (>=2.6.0)"]
tensorflow-gpu = ["tensorflow (>=2.6.0)"]
-tests = ["Pillow (>=9.4.0)", "absl-py", "apache-beam (>=2.26.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "transformers", "typing-extensions (>=4.6.1)", "zstandard"]
+tests = ["Pillow (>=9.4.0)", "absl-py", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "transformers", "typing-extensions (>=4.6.1)", "zstandard"]
torch = ["torch"]
vision = ["Pillow (>=9.4.0)"]
@@ -1293,22 +1413,22 @@ files = [
[[package]]
name = "duckduckgo-search"
-version = "6.1.5"
+version = "6.1.7"
description = "Search for words, documents, images, news, maps and text translation using the DuckDuckGo.com search engine."
optional = false
python-versions = ">=3.8"
files = [
- {file = "duckduckgo_search-6.1.5-py3-none-any.whl", hash = "sha256:f0a18fe5f20323ba6bb11865ce32d4520bb90086a6ae62f5da510865f5a7dca8"},
- {file = "duckduckgo_search-6.1.5.tar.gz", hash = "sha256:10e5c4d09a4243fd9d85007dc4fe637456c3c3995fd2e1e9b49ffd6f75bb0afb"},
+ {file = "duckduckgo_search-6.1.7-py3-none-any.whl", hash = "sha256:ec7d5becb8c392c0293ff9464938c1014896e1e14725c05adc306290a636fab2"},
+ {file = "duckduckgo_search-6.1.7.tar.gz", hash = "sha256:c6fd8ba17fe9cd0a4f32e5b96984e959c3da865f9c2864bfcf82bf7ff9b7e8f0"},
]
[package.dependencies]
click = ">=8.1.7"
-orjson = ">=3.10.3"
-pyreqwest-impersonate = ">=0.4.7"
+orjson = ">=3.10.5"
+pyreqwest-impersonate = ">=0.4.8"
[package.extras]
-dev = ["mypy (>=1.10.0)", "pytest (>=8.2.0)", "pytest-asyncio (>=0.23.6)", "ruff (>=0.4.4)"]
+dev = ["mypy (>=1.10.0)", "pytest (>=8.2.2)", "pytest-asyncio (>=0.23.7)", "ruff (>=0.4.8)"]
lxml = ["lxml (>=5.2.2)"]
[[package]]
@@ -1376,13 +1496,13 @@ develop = ["aiohttp", "furo", "httpx", "mock", "opentelemetry-api", "opentelemet
[[package]]
name = "elasticsearch"
-version = "8.13.2"
+version = "8.14.0"
description = "Python client for Elasticsearch"
optional = false
python-versions = ">=3.7"
files = [
- {file = "elasticsearch-8.13.2-py3-none-any.whl", hash = "sha256:7412ceae9c0e437a72854ab3123aa1f37110d1635cc645366988b8c0fee98598"},
- {file = "elasticsearch-8.13.2.tar.gz", hash = "sha256:d51c93431a459b2b7c6c919b6e92a2adc8ac712758de9aeeb16cd4997fc148ad"},
+ {file = "elasticsearch-8.14.0-py3-none-any.whl", hash = "sha256:cef8ef70a81af027f3da74a4f7d9296b390c636903088439087b8262a468c130"},
+ {file = "elasticsearch-8.14.0.tar.gz", hash = "sha256:aa2490029dd96f4015b333c1827aa21fd6c0a4d223b00dfb0fe933b8d09a511b"},
]
[package.dependencies]
@@ -1509,18 +1629,18 @@ files = [
[[package]]
name = "filelock"
-version = "3.14.0"
+version = "3.15.3"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
- {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
- {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
+ {file = "filelock-3.15.3-py3-none-any.whl", hash = "sha256:0151273e5b5d6cf753a61ec83b3a9b7d8821c39ae9af9d7ecf2f9e2f17404103"},
+ {file = "filelock-3.15.3.tar.gz", hash = "sha256:e1199bf5194a2277273dacd50269f0d87d0682088a3c561c15674ea9005d8635"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
@@ -1540,6 +1660,57 @@ sentence_transformers = "*"
torch = ">=1.6.0"
transformers = ">=4.33.0"
+[[package]]
+name = "flask"
+version = "3.0.3"
+description = "A simple framework for building complex web applications."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
+ {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
+]
+
+[package.dependencies]
+blinker = ">=1.6.2"
+click = ">=8.1.3"
+itsdangerous = ">=2.1.2"
+Jinja2 = ">=3.1.2"
+Werkzeug = ">=3.0.0"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "flask-cors"
+version = "4.0.1"
+description = "A Flask extension adding a decorator for CORS support"
+optional = false
+python-versions = "*"
+files = [
+ {file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"},
+ {file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"},
+]
+
+[package.dependencies]
+Flask = ">=0.9"
+
+[[package]]
+name = "flask-login"
+version = "0.6.3"
+description = "User authentication and session management for Flask."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"},
+ {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"},
+]
+
+[package.dependencies]
+Flask = ">=1.0.4"
+Werkzeug = ">=1.0.1"
+
[[package]]
name = "flatbuffers"
version = "24.3.25"
@@ -1704,13 +1875,13 @@ files = [
[[package]]
name = "fsspec"
-version = "2024.3.1"
+version = "2024.5.0"
description = "File-system specification"
optional = false
python-versions = ">=3.8"
files = [
- {file = "fsspec-2024.3.1-py3-none-any.whl", hash = "sha256:918d18d41bf73f0e2b261824baeb1b124bcf771767e3a26425cd7dec3332f512"},
- {file = "fsspec-2024.3.1.tar.gz", hash = "sha256:f39780e282d7d117ffb42bb96992f8a90795e4d0fb0f661a70ca39fe9c43ded9"},
+ {file = "fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c"},
+ {file = "fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a"},
]
[package.dependencies]
@@ -1721,7 +1892,7 @@ abfs = ["adlfs"]
adl = ["adlfs"]
arrow = ["pyarrow (>=1)"]
dask = ["dask", "distributed"]
-devel = ["pytest", "pytest-cov"]
+dev = ["pre-commit", "ruff"]
dropbox = ["dropbox", "dropboxdrivefs", "requests"]
full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"]
fuse = ["fusepy"]
@@ -1738,8 +1909,160 @@ s3 = ["s3fs"]
sftp = ["paramiko"]
smb = ["smbprotocol"]
ssh = ["paramiko"]
+test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"]
+test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"]
+test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"]
tqdm = ["tqdm"]
+[[package]]
+name = "gevent"
+version = "24.2.1"
+description = "Coroutine-based network library"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "gevent-24.2.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07"},
+ {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3"},
+ {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026"},
+ {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5"},
+ {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb"},
+ {file = "gevent-24.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060"},
+ {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98"},
+ {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789"},
+ {file = "gevent-24.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc"},
+ {file = "gevent-24.2.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5"},
+ {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836"},
+ {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c"},
+ {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7"},
+ {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be"},
+ {file = "gevent-24.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91"},
+ {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"},
+ {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d"},
+ {file = "gevent-24.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc"},
+ {file = "gevent-24.2.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40"},
+ {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0"},
+ {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7"},
+ {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f"},
+ {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661"},
+ {file = "gevent-24.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9"},
+ {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f"},
+ {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388"},
+ {file = "gevent-24.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5"},
+ {file = "gevent-24.2.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5"},
+ {file = "gevent-24.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8"},
+ {file = "gevent-24.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19"},
+ {file = "gevent-24.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800"},
+ {file = "gevent-24.2.1-cp38-cp38-win32.whl", hash = "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6"},
+ {file = "gevent-24.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de"},
+ {file = "gevent-24.2.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe"},
+ {file = "gevent-24.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8"},
+ {file = "gevent-24.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7"},
+ {file = "gevent-24.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1"},
+ {file = "gevent-24.2.1-cp39-cp39-win32.whl", hash = "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e"},
+ {file = "gevent-24.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533"},
+ {file = "gevent-24.2.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8"},
+ {file = "gevent-24.2.1.tar.gz", hash = "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
+greenlet = [
+ {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
+ {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
+]
+"zope.event" = "*"
+"zope.interface" = "*"
+
+[package.extras]
+dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"]
+docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"]
+monitor = ["psutil (>=5.7.0)"]
+recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"]
+test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"]
+
+[[package]]
+name = "geventhttpclient"
+version = "2.3.1"
+description = "HTTP client library for gevent"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da22ab7bf5af4ba3d07cffee6de448b42696e53e7ac1fe97ed289037733bf1c2"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2399e3d4e2fae8bbd91756189da6e9d84adf8f3eaace5eef0667874a705a29f8"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e33e87d0d5b9f5782c4e6d3cb7e3592fea41af52713137d04776df7646d71b"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071db313866c3d0510feb6c0f40ec086ccf7e4a845701b6316c82c06e8b9b29"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f36f0c6ef88a27e60af8369d9c2189fe372c6f2943182a7568e0f2ad33bb69f1"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4624843c03a5337282a42247d987c2531193e57255ee307b36eeb4f243a0c21"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d614573621ba827c417786057e1e20e9f96c4f6b3878c55b1b7b54e1026693bc"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d51330a40ac9762879d0e296c279c1beae8cfa6484bb196ac829242c416b709"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc9f2162d4e8cb86bb5322d99bfd552088a3eacd540a841298f06bb8bc1f1f03"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e59d3397e63c65ecc7a7561a5289f0cf2e2c2252e29632741e792f57f5d124"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4436eef515b3e0c1d4a453ae32e047290e780a623c1eddb11026ae9d5fb03d42"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-win32.whl", hash = "sha256:5d1cf7d8a4f8e15cc8fd7d88ac4cdb058d6274203a42587e594cc9f0850ac862"},
+ {file = "geventhttpclient-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4deaebc121036f7ea95430c2d0f80ab085b15280e6ab677a6360b70e57020e7f"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1"},
+ {file = "geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a"},
+ {file = "geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fe912c6456faab196b952adcd63e9353a0d5c8deb31c8d733d38f4f0ab22e359"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b599359779c2278018786c35d70664d441a7cd0d6baef2b2cd0d1685cf478ed"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34107b506e2c40ec7784efa282469bf86888cacddced463dceeb58c201834897"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc34031905b2b31a80d88cd33d7e42b81812950e5304860ab6a65ee2803e2046"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50b54f67ba2087f4d9d2172065c5c5de0f0c7f865ac350116e5452de4be31444"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ddeb431836c2ef7fd33c505a06180dc907b474e0e8537a43ff12e12c9bf0307"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4890713433ca19b081f70b5f7ad258a0979ec3354f9538b50b3ad7d0a86f88de"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8ca7dcbe94cb563341087b00b6fbd0fdd70b2acc1b5d963f9ebbfbc1e5e2893"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05a1bbdd43ae36bcc10b3dbfa0806aefc5033a91efecfddfe56159446a46ea71"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f82c454595a88a5e510ae0985711ef398386998b6f37d90fc30e9ff1a2001280"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b032a5cdb1721921f4cd36aad620af318263b462962cfb23d648cdb93aab232"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-win32.whl", hash = "sha256:ce2c7d18bac7ffdacc4a86cd490bea6136a7d1e1170f8624f2e3bbe3b189d5b8"},
+ {file = "geventhttpclient-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ca50dd9761971d3557b897108933b34fb4a11533d52f0f2753840c740a2861a"},
+ {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c31431e38df45b3c79bf3c9427c796adb8263d622bc6fa25e2f6ba916c2aad93"},
+ {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855ab1e145575769b180b57accb0573a77cd6a7392f40a6ef7bc9a4926ebd77b"},
+ {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a374aad77c01539e786d0c7829bec2eba034ccd45733c1bf9811ad18d2a8ecd"},
+ {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c1e97460608304f400485ac099736fff3566d3d8db2038533d466f8cf5de5a"},
+ {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4f843f81ee44ba4c553a1b3f73115e0ad8f00044023c24db29f5b1df3da08465"},
+ {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:321b73c73d73b85cfeff36b9b5ee04174ec8406fb3dadc129558a26ccb879360"},
+ {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829d03c2a140edbe74ad1fb4f850384f585f3e06fc47cfe647d065412b93926f"},
+ {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994c543f156db7bce3bae15491a0e041eeb3f1cf467e0d1db0c161a900a90bec"},
+ {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4beff505306aa9da5cdfe2f206b403ec7c8d06a22d6b7248365772858c4ee8c"},
+ {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fb0a9673074541ccda09a2423fa16f4528819ceb1ba19d252213f6aca7d4b44a"},
+ {file = "geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185"},
+]
+
+[package.dependencies]
+brotli = "*"
+certifi = "*"
+gevent = "*"
+urllib3 = "*"
+
+[package.extras]
+benchmarks = ["httplib2", "httpx", "requests", "urllib3"]
+dev = ["dpkt", "pytest", "requests"]
+examples = ["oauth2"]
+
[[package]]
name = "google-api-core"
version = "2.19.0"
@@ -1765,13 +2088,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
-version = "2.132.0"
+version = "2.134.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "google-api-python-client-2.132.0.tar.gz", hash = "sha256:d6340dc83b72d72333cee5d50f7dcfecbff66a8783164090e945f985ec4c374d"},
- {file = "google_api_python_client-2.132.0-py2.py3-none-any.whl", hash = "sha256:cde87700bd4d37f39f5e940292c1c6cd0910990b5b01f50b1332a8cea38e8595"},
+ {file = "google-api-python-client-2.134.0.tar.gz", hash = "sha256:4a8f0bea651a212997cc83c0f271fc86f80ef93d1cee9d84de7dfaeef2a858b6"},
+ {file = "google_api_python_client-2.134.0-py2.py3-none-any.whl", hash = "sha256:ba05d60f6239990b7994f6328f17bb154c602d31860fb553016dc9f8ce886945"},
]
[package.dependencies]
@@ -1783,13 +2106,13 @@ uritemplate = ">=3.0.1,<5"
[[package]]
name = "google-auth"
-version = "2.29.0"
+version = "2.30.0"
description = "Google Authentication Library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"},
- {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"},
+ {file = "google-auth-2.30.0.tar.gz", hash = "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688"},
+ {file = "google_auth-2.30.0-py2.py3-none-any.whl", hash = "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5"},
]
[package.dependencies]
@@ -2044,6 +2367,27 @@ files = [
[package.extras]
protobuf = ["grpcio-tools (>=1.63.0)"]
+[[package]]
+name = "gunicorn"
+version = "22.0.0"
+description = "WSGI HTTP Server for UNIX"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"},
+ {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"},
+]
+
+[package.dependencies]
+packaging = "*"
+
+[package.extras]
+eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
+gevent = ["gevent (>=1.4.0)"]
+setproctitle = ["setproctitle"]
+testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
+tornado = ["tornado (>=0.2)"]
+
[[package]]
name = "h11"
version = "0.14.0"
@@ -2180,13 +2524,13 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "huggingface-hub"
-version = "0.23.3"
+version = "0.23.4"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "huggingface_hub-0.23.3-py3-none-any.whl", hash = "sha256:22222c41223f1b7c209ae5511d2d82907325a0e3cdbce5f66949d43c598ff3bc"},
- {file = "huggingface_hub-0.23.3.tar.gz", hash = "sha256:1a1118a0b3dea3bab6c325d71be16f5ffe441d32f3ac7c348d6875911b694b5b"},
+ {file = "huggingface_hub-0.23.4-py3-none-any.whl", hash = "sha256:3a0b957aa87150addf0cc7bd71b4d954b78e749850e1e7fb29ebbd2db64ca037"},
+ {file = "huggingface_hub-0.23.4.tar.gz", hash = "sha256:35d99016433900e44ae7efe1c209164a5a81dbbcd53a52f99c281dcd7ce22431"},
]
[package.dependencies]
@@ -2330,6 +2674,17 @@ files = [
{file = "intel_openmp-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:eef4c8bcc8acefd7f5cd3b9384dbf73d59e2c99fc56545712ded913f43c4a94f"},
]
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+description = "Safely pass data to untrusted environments and back."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
+ {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
+]
+
[[package]]
name = "jieba"
version = "0.42.1"
@@ -2529,13 +2884,13 @@ files = [
[[package]]
name = "kubernetes"
-version = "29.0.0"
+version = "30.1.0"
description = "Kubernetes python client"
optional = false
python-versions = ">=3.6"
files = [
- {file = "kubernetes-29.0.0-py2.py3-none-any.whl", hash = "sha256:ab8cb0e0576ccdfb71886366efb102c6a20f268d817be065ce7f9909c631e43e"},
- {file = "kubernetes-29.0.0.tar.gz", hash = "sha256:c4812e227ae74d07d53c88293e564e54b850452715a59a927e7e1bc6b9a60459"},
+ {file = "kubernetes-30.1.0-py2.py3-none-any.whl", hash = "sha256:e212e8b7579031dd2e512168b617373bc1e03888d41ac4e04039240a292d478d"},
+ {file = "kubernetes-30.1.0.tar.gz", hash = "sha256:41e4c77af9f28e7a6c314e3bd06a8c6229ddd787cad684e0ab9f69b498e98ebc"},
]
[package.dependencies]
@@ -2667,13 +3022,13 @@ wrapt = "*"
[[package]]
name = "llama-index-embeddings-azure-openai"
-version = "0.1.9"
+version = "0.1.10"
description = "llama-index embeddings azure openai integration"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_embeddings_azure_openai-0.1.9-py3-none-any.whl", hash = "sha256:67c91c953e81b9b83fac8385700aa042bf5a410fdc1ac61b73ea810f0e2c313a"},
- {file = "llama_index_embeddings_azure_openai-0.1.9.tar.gz", hash = "sha256:dcc1b5b2b37b7b249ae529731a5ed2bc7d325cb270d6d55dde889474dd997ae2"},
+ {file = "llama_index_embeddings_azure_openai-0.1.10-py3-none-any.whl", hash = "sha256:b100b7338bdfb236ea445eab341c52db8945dac3642141134ec77302ac6fa405"},
+ {file = "llama_index_embeddings_azure_openai-0.1.10.tar.gz", hash = "sha256:e772268d064f082c2d276c26505a3c087973e766d3d411d0e12f14f38dd92eaa"},
]
[package.dependencies]
@@ -2698,19 +3053,19 @@ llama-index-core = ">=0.10.0,<0.11.0"
[[package]]
name = "llama-index-embeddings-huggingface"
-version = "0.2.1"
+version = "0.2.2"
description = "llama-index embeddings huggingface integration"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_embeddings_huggingface-0.2.1-py3-none-any.whl", hash = "sha256:326468966e269acc7fbc77cad4f65ec061133ea91b0063fe181e72d01a6a8511"},
- {file = "llama_index_embeddings_huggingface-0.2.1.tar.gz", hash = "sha256:bac68a13ad5131a055da3ef174cca70e15230426eec7d471b372e81e8489d888"},
+ {file = "llama_index_embeddings_huggingface-0.2.2-py3-none-any.whl", hash = "sha256:3445b1c7823cdb45622f90e79f2540db870ea55b226ec7538be963d340f43240"},
+ {file = "llama_index_embeddings_huggingface-0.2.2.tar.gz", hash = "sha256:43b2978740d29291ae4c7566922d2b1c7543dc979e268794b578e1a2adfb4319"},
]
[package.dependencies]
huggingface-hub = {version = ">=0.19.0", extras = ["inference"]}
llama-index-core = ">=0.10.1,<0.11.0"
-sentence-transformers = ">=2.6.1,<3.0.0"
+sentence-transformers = ">=2.6.1"
[[package]]
name = "llama-index-embeddings-openai"
@@ -2921,13 +3276,13 @@ llama-index-core = ">=0.10.1,<0.11.0"
[[package]]
name = "llama-index-readers-file"
-version = "0.1.23"
+version = "0.1.25"
description = "llama-index readers file integration"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_readers_file-0.1.23-py3-none-any.whl", hash = "sha256:32450d0a3edc6ef6af575f814beec39cd3a3351eaf0e3c97045bdd72a7a7b38d"},
- {file = "llama_index_readers_file-0.1.23.tar.gz", hash = "sha256:fde8ecb588e703849e51dc0f075f56d1f5db3bc1479dd00c21b42e93b81b6267"},
+ {file = "llama_index_readers_file-0.1.25-py3-none-any.whl", hash = "sha256:bc659e432d441c445e110580340675aa60abae1d82add4f65e559dfe8add541b"},
+ {file = "llama_index_readers_file-0.1.25.tar.gz", hash = "sha256:238ddd98aa377d6a44322013eb848056037c80ad84571ea5bf451a640fff4d5c"},
]
[package.dependencies]
@@ -2956,13 +3311,13 @@ llama-parse = ">=0.4.0,<0.5.0"
[[package]]
name = "llama-index-retrievers-bm25"
-version = "0.1.3"
+version = "0.1.4"
description = "llama-index retrievers bm25 integration"
optional = false
-python-versions = ">=3.8.1,<4.0"
+python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_retrievers_bm25-0.1.3-py3-none-any.whl", hash = "sha256:40bb8624000abfef7ee5d1689330b408181ca793d6bfe141cd3310adc1d23b1a"},
- {file = "llama_index_retrievers_bm25-0.1.3.tar.gz", hash = "sha256:d996c731a74b9866ba47827430011ca7fa84f633d8f25c9bb8aaed3767937425"},
+ {file = "llama_index_retrievers_bm25-0.1.4-py3-none-any.whl", hash = "sha256:246fdcda536bd2bff94a5e578d1fb6af8824c95c450c65b34fcc32adf673068c"},
+ {file = "llama_index_retrievers_bm25-0.1.4.tar.gz", hash = "sha256:4949c75628d8e5ead246e2812556aa7d397a9a2a02e99731534d6a190387b573"},
]
[package.dependencies]
@@ -3035,13 +3390,13 @@ llama-index-core = ">=0.10.1,<0.11.0"
[[package]]
name = "llama-index-vector-stores-chroma"
-version = "0.1.8"
+version = "0.1.9"
description = "llama-index vector_stores chroma integration"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_vector_stores_chroma-0.1.8-py3-none-any.whl", hash = "sha256:77f5081a08bcede4fafb3c47b15b3bd5cacaef8d038750207d1858f73bc2e255"},
- {file = "llama_index_vector_stores_chroma-0.1.8.tar.gz", hash = "sha256:9c574baf370faf456bcb67b9d5ea273a6fa1f2b4fd205a59c47b68112364b9e7"},
+ {file = "llama_index_vector_stores_chroma-0.1.9-py3-none-any.whl", hash = "sha256:0d900fe97def537c2dd1c2d155287fae014b63848e3aff28902eb38c45e0bc28"},
+ {file = "llama_index_vector_stores_chroma-0.1.9.tar.gz", hash = "sha256:6a5c27ab3ae25cf504bed9513c1f035365dfb576b886fe334d46908ca24a59cf"},
]
[package.dependencies]
@@ -3080,13 +3435,13 @@ llama-index-core = ">=0.10.1,<0.11.0"
[[package]]
name = "llama-index-vector-stores-milvus"
-version = "0.1.17"
+version = "0.1.20"
description = "llama-index vector_stores milvus integration"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "llama_index_vector_stores_milvus-0.1.17-py3-none-any.whl", hash = "sha256:88cf324ea7540a73a023028358c700f09dae47b55c05a8455322c26bf60b4393"},
- {file = "llama_index_vector_stores_milvus-0.1.17.tar.gz", hash = "sha256:b8f7b1e683cd8477ba2f61ce2607ba34845cbf2760913ba0334d2e8ad8291b4d"},
+ {file = "llama_index_vector_stores_milvus-0.1.20-py3-none-any.whl", hash = "sha256:27a61fd237e67b648f36964c2e25275df4cb20dd740d111f0b75db477259ef5b"},
+ {file = "llama_index_vector_stores_milvus-0.1.20.tar.gz", hash = "sha256:461bccce036be7bb739e57eb3855f64557c506023febfc08f98899778d460602"},
]
[package.dependencies]
@@ -3122,6 +3477,32 @@ files = [
httpx = ">=0.20.0"
pydantic = ">=1.10"
+[[package]]
+name = "locust"
+version = "2.29.0"
+description = "Developer-friendly load testing framework"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "locust-2.29.0-py3-none-any.whl", hash = "sha256:aa9d94d3604ed9f2aab3248460d91e55d3de980a821dffdf8658b439b049d03f"},
+ {file = "locust-2.29.0.tar.gz", hash = "sha256:649c99ce49d00720a3084c0109547035ad9021222835386599a8b545d31ebe51"},
+]
+
+[package.dependencies]
+ConfigArgParse = ">=1.5.5"
+flask = ">=2.0.0"
+Flask-Cors = ">=3.0.10"
+Flask-Login = ">=0.6.3"
+gevent = ">=22.10.2"
+geventhttpclient = ">=2.3.1"
+msgpack = ">=1.0.0"
+psutil = ">=5.9.1"
+pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
+pyzmq = ">=25.0.0"
+requests = ">=2.26.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+Werkzeug = ">=2.0.0"
+
[[package]]
name = "markdown"
version = "3.6"
@@ -3314,14 +3695,14 @@ files = [
[[package]]
name = "milvus-lite"
-version = "2.4.6"
+version = "2.4.7"
description = "A lightweight version of Milvus wrapped with Python."
optional = false
python-versions = ">=3.7"
files = [
- {file = "milvus_lite-2.4.6-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:43ac9f36903b31455e50a8f1d9cb033e18971643029c89eb5c9610f01c1f2e26"},
- {file = "milvus_lite-2.4.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:95afe2ee019c569713926747bbe18ab5944927797374fed796f00fbe564cccd6"},
- {file = "milvus_lite-2.4.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2f9116bfc6a0d95636d3aa144582486b622c492689f3c93c519101bd7158b7db"},
+ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"},
+ {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"},
+ {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"},
]
[[package]]
@@ -3481,22 +3862,22 @@ tests = ["pytest (>=4.6)"]
[[package]]
name = "msal"
-version = "1.28.0"
+version = "1.28.1"
description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect."
optional = false
python-versions = ">=3.7"
files = [
- {file = "msal-1.28.0-py3-none-any.whl", hash = "sha256:3064f80221a21cd535ad8c3fafbb3a3582cd9c7e9af0bb789ae14f726a0ca99b"},
- {file = "msal-1.28.0.tar.gz", hash = "sha256:80bbabe34567cb734efd2ec1869b2d98195c927455369d8077b3c542088c5c9d"},
+ {file = "msal-1.28.1-py3-none-any.whl", hash = "sha256:563c2d70de77a2ca9786aab84cb4e133a38a6897e6676774edc23d610bfc9e7b"},
+ {file = "msal-1.28.1.tar.gz", hash = "sha256:d72bbfe2d5c2f2555f4bc6205be4450ddfd12976610dd9a16a9ab0f05c68b64d"},
]
[package.dependencies]
-cryptography = ">=0.6,<45"
+cryptography = ">=2.5,<45"
PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]}
requests = ">=2.0.0,<3"
[package.extras]
-broker = ["pymsalruntime (>=0.13.2,<0.15)"]
+broker = ["pymsalruntime (>=0.13.2,<0.17)"]
[[package]]
name = "msal-extensions"
@@ -3517,6 +3898,71 @@ portalocker = [
{version = ">=1.6,<3", markers = "platform_system == \"Windows\""},
]
+[[package]]
+name = "msgpack"
+version = "1.0.8"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"},
+ {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"},
+ {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"},
+ {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"},
+ {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"},
+ {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"},
+ {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"},
+ {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"},
+ {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
+ {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
+ {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
+ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
+]
+
[[package]]
name = "multidict"
version = "6.0.5"
@@ -3914,6 +4360,7 @@ description = "Nvidia JIT LTO Library"
optional = false
python-versions = ">=3"
files = [
+ {file = "nvidia_nvjitlink_cu12-12.5.40-py3-none-manylinux2014_aarch64.whl", hash = "sha256:004186d5ea6a57758fd6d57052a123c73a4815adf365eb8dd6a85c9eaa7535ff"},
{file = "nvidia_nvjitlink_cu12-12.5.40-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d9714f27c1d0f0895cd8915c07a87a1d0029a0aa36acaf9156952ec2a8a12189"},
{file = "nvidia_nvjitlink_cu12-12.5.40-py3-none-win_amd64.whl", hash = "sha256:c3401dc8543b52d3a8158007a0c1ab4e9c768fcbd24153a48c86972102197ddd"},
]
@@ -3989,13 +4436,13 @@ sympy = "*"
[[package]]
name = "openai"
-version = "1.31.1"
+version = "1.35.1"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.7.1"
files = [
- {file = "openai-1.31.1-py3-none-any.whl", hash = "sha256:a746cf070798a4048cfea00b0fc7cb9760ee7ead5a08c48115b914d1afbd1b53"},
- {file = "openai-1.31.1.tar.gz", hash = "sha256:a15266827de20f407d4bf9837030b168074b5b29acd54f10bb38d5f53e95f083"},
+ {file = "openai-1.35.1-py3-none-any.whl", hash = "sha256:53ef8935cf916dc7ece67fee5a8a09fc4db5aadf4d6e95b5b7f767f3c4432e4d"},
+ {file = "openai-1.35.1.tar.gz", hash = "sha256:d85973adc2f4fbb11ba20bfd948e3340b8352f6b8a02f1fa1c387c8eefac8d9d"},
]
[package.dependencies]
@@ -4012,48 +4459,48 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
[[package]]
name = "opencv-python"
-version = "4.10.0.82"
+version = "4.10.0.84"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
- {file = "opencv-python-4.10.0.82.tar.gz", hash = "sha256:dbc021eaa310c4145c47cd648cb973db69bb5780d6e635386cd53d3ea76bd2d5"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:5f78652339957ec24b80a782becfb32f822d2008a865512121fad8c3ce233e9a"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:e6be19a0615aa8c4e0d34e0c7b133e26e386f4b7e9b557b69479104ab2c133ec"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b49e530f7fd86f671514b39ffacdf5d14ceb073bc79d0de46bbb6b0cad78eaf"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955c5ce8ac90c9e4636ad7f5c0d9c75b80abbe347182cfd09b0e3eec6e50472c"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:ff54adc9e4daaf438e669664af08bec4a268c7b7356079338b8e4fae03810f2c"},
- {file = "opencv_python-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:2e3c2557b176f1e528417520a52c0600a92c1bb1c359f3df8e6411ab4293f065"},
+ {file = "opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236"},
+ {file = "opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe"},
]
[package.dependencies]
numpy = [
+ {version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
- {version = ">=1.23.5", markers = "python_version >= \"3.11\""},
]
[[package]]
name = "opencv-python-headless"
-version = "4.10.0.82"
+version = "4.10.0.84"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
- {file = "opencv-python-headless-4.10.0.82.tar.gz", hash = "sha256:de9e742c1b9540816fbd115b0b03841d41ed0c65566b0d7a5371f98b131b7e6d"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a09ed50ba21cc5bf5d436cb0e784ad09c692d6b1d1454252772f6c8f2c7b4088"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:977a5fd21e1fe0d3d2134887db4441f8725abeae95150126302f31fcd9f548fa"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4ec6755838b0be12510bfc9ffb014779c612418f11f4f7e6f505c36124a3aa"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37fa5276967ecf6eb297295b16b28b7a2eb3b568ca0ee469fb1a5954de298"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:94736e9b322d13db4768fd35588ad5e8995e78e207263076bfbee18aac835ad5"},
- {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:c1822fa23d1641c0249ed5eb906f4c385f7959ff1bd601a776d56b0c18914af4"},
+ {file = "opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec"},
+ {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05"},
]
[package.dependencies]
numpy = [
+ {version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
- {version = ">=1.23.5", markers = "python_version >= \"3.11\""},
]
[[package]]
@@ -4111,13 +4558,13 @@ files = [
[[package]]
name = "openpyxl"
-version = "3.1.3"
+version = "3.1.4"
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
files = [
- {file = "openpyxl-3.1.3-py2.py3-none-any.whl", hash = "sha256:25071b558db709de9e8782c3d3e058af3b23ffb2fc6f40c8f0c45a154eced2c3"},
- {file = "openpyxl-3.1.3.tar.gz", hash = "sha256:8dd482e5350125b2388070bb2477927be2e8ebc27df61178709bc8c8751da2f9"},
+ {file = "openpyxl-3.1.4-py2.py3-none-any.whl", hash = "sha256:ec17f6483f2b8f7c88c57e5e5d3b0de0e3fb9ac70edc084d28e864f5b33bbefd"},
+ {file = "openpyxl-3.1.4.tar.gz", hash = "sha256:8d2c8adf5d20d6ce8f9bca381df86b534835e974ed0156dacefa76f68c1d69fb"},
]
[package.dependencies]
@@ -4287,67 +4734,67 @@ files = [
[[package]]
name = "orjson"
-version = "3.10.3"
+version = "3.10.5"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = false
python-versions = ">=3.8"
files = [
- {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"},
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"},
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"},
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"},
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"},
- {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"},
- {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"},
- {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"},
- {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"},
- {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"},
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"},
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"},
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"},
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"},
- {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"},
- {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"},
- {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"},
- {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"},
- {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"},
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"},
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"},
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"},
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"},
- {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"},
- {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"},
- {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"},
- {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"},
- {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"},
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"},
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"},
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"},
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"},
- {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"},
- {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"},
- {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"},
- {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"},
- {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"},
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"},
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"},
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"},
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"},
- {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"},
- {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"},
- {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"},
- {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"},
- {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"},
+ {file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"},
+ {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"},
+ {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"},
+ {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"},
+ {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"},
+ {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"},
+ {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"},
+ {file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"},
+ {file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"},
+ {file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"},
+ {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"},
+ {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"},
+ {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"},
+ {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"},
+ {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"},
+ {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"},
+ {file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"},
+ {file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"},
+ {file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"},
+ {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"},
+ {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"},
+ {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"},
+ {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"},
+ {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"},
+ {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"},
+ {file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"},
+ {file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"},
+ {file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"},
+ {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"},
+ {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"},
+ {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"},
+ {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"},
+ {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"},
+ {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"},
+ {file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"},
+ {file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"},
+ {file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"},
+ {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"},
+ {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"},
+ {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"},
+ {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"},
+ {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"},
+ {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"},
+ {file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"},
+ {file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"},
+ {file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"},
]
[[package]]
name = "oss2"
-version = "2.18.5"
+version = "2.18.6"
description = "Aliyun OSS (Object Storage Service) SDK"
optional = false
python-versions = "*"
files = [
- {file = "oss2-2.18.5.tar.gz", hash = "sha256:555c857f4441ae42a2c0abab8fc9482543fba35d65a4a4be73101c959a2b4011"},
+ {file = "oss2-2.18.6.tar.gz", hash = "sha256:a9137269a4466cecd61356fc26b6f5cad24c7e79f49bc1589eefbccaebea5104"},
]
[package.dependencies]
@@ -4371,13 +4818,13 @@ files = [
[[package]]
name = "packaging"
-version = "24.0"
+version = "24.1"
description = "Core utilities for Python packages"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
- {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
@@ -4420,8 +4867,8 @@ files = [
[package.dependencies]
numpy = [
- {version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
+ {version = ">=1.22.4", markers = "python_version < \"3.11\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
@@ -4488,13 +4935,13 @@ image = ["Pillow"]
[[package]]
name = "pdfplumber"
-version = "0.11.0"
+version = "0.11.1"
description = "Plumb a PDF for detailed information about each char, rectangle, and line."
optional = false
python-versions = ">=3.8"
files = [
- {file = "pdfplumber-0.11.0-py3-none-any.whl", hash = "sha256:be41686fe9515da5f54fad254e2b6941a723b9afe99eb92008df30880b6d61b3"},
- {file = "pdfplumber-0.11.0.tar.gz", hash = "sha256:e0027b8fc0ab98329cd8d445f5324d779078a6b58406471234e3e9c265dd8a48"},
+ {file = "pdfplumber-0.11.1-py3-none-any.whl", hash = "sha256:8f3f087001fc3ec72d2c8be23d4ad59a763356e70d659a9180b5250919a3e5a8"},
+ {file = "pdfplumber-0.11.1.tar.gz", hash = "sha256:cb95b4369fa94c3901bc6bce89d81e098afd2bd8d88e0d9706152f4c57e82349"},
]
[package.dependencies]
@@ -4647,20 +5094,20 @@ test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint"
[[package]]
name = "proto-plus"
-version = "1.23.0"
+version = "1.24.0"
description = "Beautiful, Pythonic protocol buffers."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "proto-plus-1.23.0.tar.gz", hash = "sha256:89075171ef11988b3fa157f5dbd8b9cf09d65fffee97e29ce403cd8defba19d2"},
- {file = "proto_plus-1.23.0-py3-none-any.whl", hash = "sha256:a829c79e619e1cf632de091013a4173deed13a55f326ef84f05af6f50ff4c82c"},
+ {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"},
+ {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"},
]
[package.dependencies]
-protobuf = ">=3.19.0,<5.0.0dev"
+protobuf = ">=3.19.0,<6.0.0dev"
[package.extras]
-testing = ["google-api-core[grpc] (>=1.31.5)"]
+testing = ["google-api-core (>=1.31.5)"]
[[package]]
name = "protobuf"
@@ -4684,27 +5131,28 @@ files = [
[[package]]
name = "psutil"
-version = "5.9.8"
+version = "6.0.0"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-files = [
- {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
- {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
- {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
- {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
- {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
- {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
- {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
- {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
- {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
- {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+files = [
+ {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
+ {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
+ {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
+ {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
+ {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
+ {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
+ {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
+ {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
+ {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
+ {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
+ {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
+ {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
+ {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
+ {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
]
[package.extras]
@@ -4982,13 +5430,13 @@ files = [
[[package]]
name = "pydantic"
-version = "2.7.3"
+version = "2.7.4"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"},
- {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"},
+ {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"},
+ {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"},
]
[package.dependencies]
@@ -5137,19 +5585,19 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "pymilvus"
-version = "2.4.3"
+version = "2.4.4"
description = "Python Sdk for Milvus"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pymilvus-2.4.3-py3-none-any.whl", hash = "sha256:38239e89f8d739f665141d0b80908990b5f59681e889e135c234a4a45669a5c8"},
- {file = "pymilvus-2.4.3.tar.gz", hash = "sha256:703ac29296cdce03d6dc2aaebbe959e57745c141a94150e371dc36c61c226cc1"},
+ {file = "pymilvus-2.4.4-py3-none-any.whl", hash = "sha256:073b76bc36f6f4e70f0f0a0023a53324f0ba8ef9a60883f87cd30a44b6c6f2b5"},
+ {file = "pymilvus-2.4.4.tar.gz", hash = "sha256:50c53eb103e034fbffe936fe942751ea3dbd2452e18cf79acc52360ed4987fb7"},
]
[package.dependencies]
environs = "<=9.5.0"
grpcio = ">=1.49.1,<=1.63.0"
-milvus-lite = ">=2.4.0,<2.5.0"
+milvus-lite = {version = ">=2.4.0,<2.5.0", markers = "sys_platform != \"win32\""}
pandas = ">=1.2.4"
protobuf = ">=3.20.0"
setuptools = ">=67"
@@ -5269,59 +5717,59 @@ files = [
[[package]]
name = "pyreqwest-impersonate"
-version = "0.4.7"
+version = "0.4.8"
description = "HTTP client that can impersonate web browsers, mimicking their headers and `TLS/JA3/JA4/HTTP2` fingerprints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c175dfc429c4231a6ce03841630b236f50995ca613ff1eea26fa4c75c730b562"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3f83c50cef2d5ed0a9246318fd3ef3bfeabe286d4eabf92df4835c05a0be7dc"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34930113aa42f47e0542418f6a67bdb2c23fe0e2fa1866f60b29280a036b829"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d2792df548b845edd409a3e4284f76cb4fc2510fe4a69fde9e39d54910b935"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27622d5183185dc63bcab9a7dd1de566688c63b844812b1d9366da7c459a494"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b7bf13d49ef127e659ed134129336e94f7107023ed0138c81a46321b9a580428"},
- {file = "pyreqwest_impersonate-0.4.7-cp310-none-win_amd64.whl", hash = "sha256:0cba006b076b85a875814a4b5dd8cb27f483ebeeb0de83984a3786060fe18e0d"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:370a8cb7a92b15749cbbe3ce7a9f09d35aac7d2a74505eb447f45419ea8ef2ff"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33244ea10ccee08bac7a7ccdc3a8e6bef6e28f2466ed61de551fa24b76ee4b6a"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba24fb6db822cbd9cbac32539893cc19cc06dd1820e03536e685b9fd2a2ffdd"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e001ed09fc364cc00578fd31c0ae44d543cf75daf06b2657c7a82dcd99336ce"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:608525535f078e85114fcd4eeba0f0771ffc7093c29208e9c0a55147502723bf"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:38daedba0fc997e29cbc25c684a42a04aed38bfbcf85d8f1ffe8f87314d5f72f"},
- {file = "pyreqwest_impersonate-0.4.7-cp311-none-win_amd64.whl", hash = "sha256:d21f3e93ee0aecdc43d2914800bdf23501bde858d70ac7c0b06168f85f95bf22"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5caeee29370a06a322ea6951730d21ec3c641ce46417fd2b5805b283564f2fef"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c7aa4b428ed58370975d828a95eaf10561712e79a4e2eafca1746a4654a34a8"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:887249adcab35487a44a5428ccab2a6363642785b36649a732d5e649df568b8e"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60f932de8033c15323ba79a7470406ca8228e07aa60078dee5a18e89f0a9fc88"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2e6332fd6d78623a22f4e747688fe9e6005b61b6f208936d5428d2a65d34b39"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:349b005eef323195685ba5cb2b6f302da0db481e59f03696ef57099f232f0c1f"},
- {file = "pyreqwest_impersonate-0.4.7-cp312-none-win_amd64.whl", hash = "sha256:5620025ac138a10c46a9b14c91b6f58114d50063ff865a2d02ad632751b67b29"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ebf954e09b3dc800a7576c7bde9827b00064531364c7817356c7cc58eb4b46b2"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:112d9561f136548bd67d31cadb6b78d4c31751e526e62e09c6e581c2f1711455"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05213f5f014ecc6732d859a0f51b3dff0424748cc6e2d0d9a42aa1f7108b4eaa"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10fa70529a60fc043650ce03481fab7714e7519c3b06f5e81c95206b8b60aec6"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5b1288881eada1891db7e862c69b673fb159834a41f823b9b00fc52d0f096ccc"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:57ca562229c40615074f36e7f1ae5e57b8164f604eddb042132467c3a00fc2c5"},
- {file = "pyreqwest_impersonate-0.4.7-cp38-none-win_amd64.whl", hash = "sha256:c098ef1333511ea9a43be9a818fcc0866bd2caa63cdc9cf4ab48450ace675646"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:39d961330190bf2d59983ad16dafb4b42d5adcdfe7531ad099c8f3ab53f8d906"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d793591784b89953422b1efaa17460f57f6116de25b3e3065d9fa6cf220ef18"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:945116bb9ffb7e45a87e313f47de28c4da889b14bda620aebc5ba9c3600425cf"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b96a0955c49f346786ee997c755561fecf33b7886cecef861fe4db15c7b23ad3"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ed997197f907ccce9b86a75163b5e78743bc469d2ddcf8a22d4d90c2595573cb"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1f54788f6fb0ee8b31c1eaadba81fb003efb406a768844e2a1a50b855f4806bf"},
- {file = "pyreqwest_impersonate-0.4.7-cp39-none-win_amd64.whl", hash = "sha256:0a679e81b0175dcc670a5ed47a5c184d7031ce16b5c58bf6b2c650ab9f2496c8"},
- {file = "pyreqwest_impersonate-0.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bddb07e04e4006a2184608c44154983fdfa0ce2e230b0a7cec81cd4ba88dd07"},
- {file = "pyreqwest_impersonate-0.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:780c53bfd2fbda151081165733fba5d5b1e17dd61999360110820942e351d011"},
- {file = "pyreqwest_impersonate-0.4.7-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4bfa8ea763e6935e7660f8e885f1b00713b0d22f79a526c6ae6932b1856d1343"},
- {file = "pyreqwest_impersonate-0.4.7-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:96b23b0688a63cbd6c39237461baa95162a69a15e9533789163aabcaf3f572fb"},
- {file = "pyreqwest_impersonate-0.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b0eb56a8ad9d48952c613903d3ef6d8762d48dcec9807a509fee2a43e94ccac"},
- {file = "pyreqwest_impersonate-0.4.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9330176494e260521ea0eaae349ca06128dc527400248c57b378597c470d335c"},
- {file = "pyreqwest_impersonate-0.4.7-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6343bc3392781ff470e5dc47fea9f77bb61d8831b07e901900d31c46decec5d1"},
- {file = "pyreqwest_impersonate-0.4.7-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ecd598e16020a165029647ca80078311bf079e8317bf61c1b2fa824b8967e0db"},
- {file = "pyreqwest_impersonate-0.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a38f3014ac31b08f5fb1ef4e1eb6c6e810f51f6cb815d0066ab3f34ec0f82d98"},
- {file = "pyreqwest_impersonate-0.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db76a97068e5145f5b348037e09a91b2bed9c8eab92e79a3297b1306429fa839"},
- {file = "pyreqwest_impersonate-0.4.7-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1596a8ef8f20bbfe606a90ad524946747846611c8633cbdfbad0a4298b538218"},
- {file = "pyreqwest_impersonate-0.4.7-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dcee18bc350b3d3a0455422c446f1f03f00eb762b3e470066e2bc4664fd7110d"},
- {file = "pyreqwest_impersonate-0.4.7.tar.gz", hash = "sha256:74ba7e6e4f4f753da4f71a7e5dc12625b296bd7d6ddd64093a1fbff14d8d5df7"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:45cad57afe4e6f56078ed9a7a90d0dc839d19d3e7a70175c80af21017f383bfb"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1986600253baf38f25fd07b8bdc1903359c26e5d34beb7d7d084845554b5664d"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cca4e6e59b9ad0cd20bad6caed3ac96992cd9c1d3126ecdfcab2c0ac2b75376"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab6b32544491ee655264dab86fc8a58e47c4f87d196b28022d4007faf971a50"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64bd6299e7fc888bb7f7292cf3e29504c406e5d5d04afd37ca994ab8142d8ee4"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e914b650dd953b8d9b24ef56aa4ecbfc16e399227b68accd818f8bf159e0c558"},
+ {file = "pyreqwest_impersonate-0.4.8-cp310-none-win_amd64.whl", hash = "sha256:cb56a2149b0c4548a8e0158b071a943f33dae9b717f92b5c9ac34ccd1f5a958c"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f62620e023490902feca0109f306e122e427feff7d59e03ecd22c69a89452367"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08d4c01d76da88cfe3d7d03b311b375ce3fb5a59130f93f0637bb755d6e56ff1"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524e276bc460176c79d7ba4b9131d9db73c534586660371ebdf067749252a33"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22863bc0aaf02ca2f5d76c8130929ae680b7d82dfc1c28c1ed5f306ff626928"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cc82d57f6a91037e64a7aa9122f909576ef2a141a42ce599958ef9f8c4bc033"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:da8a053308210e44fd8349f07f45442a0691ac932f2881e98b05cf9ac404b091"},
+ {file = "pyreqwest_impersonate-0.4.8-cp311-none-win_amd64.whl", hash = "sha256:4baf3916c14364a815a64ead7f728afb61b37541933b2771f18dbb245029bb55"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:78db05deed0b32c9c75f2b3168a3a9b7d5e36487b218cb839bfe7e2a143450cb"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9af9446d605903c2b4e94621a9093f8d8a403729bc9cbfbcb62929f8238c838f"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c55890181d8d81e66cac25a95e215dc9680645d01e9091b64449d5407ad9bc6"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69344e7ae9964502a8693da7ad77ebc3e1418ee197e2e394bc23c5d4970772a"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b5db5c957a10d8cc2815085ba0b8fe09245b2f94c2225d9653a854a03b4217e1"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03c19c21f63f9c91c590c4bbcc32cc2d8066b508c683a1d163b8c7d9816a01d5"},
+ {file = "pyreqwest_impersonate-0.4.8-cp312-none-win_amd64.whl", hash = "sha256:0230610779129f74ff802c744643ce7589b1d07cba21d046fe3b574281c29581"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b8cb9471ab4b2fa7e80d3ac4e580249ff988d782f2938ad1f0428433652b170d"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8081a5ace2658be91519902bde9ddc5f94e1f850a39be196007a25e3da5bbfdc"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69eababfa3200459276acd780a0f3eaf41d1fe7c02bd169e714cba422055b5b9"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:632957fa671ebb841166e40913015de457225cb73600ef250c436c280e68bf45"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2ce7ddef334b4e5c68f5ea1da1d65f686b8d84f4443059d128e0f069d3fa499a"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6ce333d450b158d582e36317089a006440b4e66739a8e8849d170e4cb15e8c8d"},
+ {file = "pyreqwest_impersonate-0.4.8-cp38-none-win_amd64.whl", hash = "sha256:9d9c85ce19db92362854f534807e470f03e905f283a7de6826dc79b790a8788e"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2503277f2a95a30e28e498570e2ed03ef4302f873054e8e21d6c0e607cbbc1d1"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8260395ef4ddae325e8b30cef0391adde7bd35e1a1decf8c729e26391f09b52d"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d8066b46d82bbaff5402d767e2f13d3449b8191c37bf8283e91d301a7159869"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9c42f6343cfbd6663fb53edc9eb9feb4ebf6186b284e22368adc1eeb6a33854"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff534f491a059e74fb7f994876df86078b4b125dbecc53c098a298ecd55fa9c6"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b8fbf73b3ac513ddadafd338d61f79cd2370f0691d9175b2b92a45920920d6b"},
+ {file = "pyreqwest_impersonate-0.4.8-cp39-none-win_amd64.whl", hash = "sha256:a26447c82665d0e361207c1a15e56b0ca54974aa6c1fdfa18c68f908dec78cbe"},
+ {file = "pyreqwest_impersonate-0.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24a16b8d55309f0af0db9d04ff442b0c91afccf078a94809e7c3a71747a5c214"},
+ {file = "pyreqwest_impersonate-0.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c8fada56465fc19179404cc9d5d5e1064f5dfe27405cb052f57a5b4fe06aed1"},
+ {file = "pyreqwest_impersonate-0.4.8-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a3d48d5abc146fd804395713427d944757a99254350e6a651e7d776818074aee"},
+ {file = "pyreqwest_impersonate-0.4.8-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:475829fe9994c66258157a8d4adb1c038f44f79f901208ba656d547842337227"},
+ {file = "pyreqwest_impersonate-0.4.8-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef1ec0e97623bc0e18469418cc4dd2c59a2d5fddcae944de61e13c0b46f910e"},
+ {file = "pyreqwest_impersonate-0.4.8-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91857b196de89e9b36d3f8629aa8772c0bbe7efef8334fe266956b1c192ec31c"},
+ {file = "pyreqwest_impersonate-0.4.8-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:63831e407487b8a21bb51f97cd86a616c291d5138f8caec16ab6019cf6423935"},
+ {file = "pyreqwest_impersonate-0.4.8-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c30e61de93bcd0a9d3ca226b1ae5475002afde61e9d85018a6a4a040eeb86567"},
+ {file = "pyreqwest_impersonate-0.4.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c72c37b03bce9900f5dbb4f476af17253ec60c13bf7a7259f71a8dc1b036cb"},
+ {file = "pyreqwest_impersonate-0.4.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f1096165741b5c2178ab15b0eb09b5de16dd39b1cc135767d72471f0a69ce"},
+ {file = "pyreqwest_impersonate-0.4.8-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:70c940c0e4ef335e22a6c705b01f286ee44780b5909065d212d94d82ea2580cb"},
+ {file = "pyreqwest_impersonate-0.4.8-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:81c06f21757602d85f16dbc1cbaee1121cd65455f65aed4c048b7dcda7be85c4"},
+ {file = "pyreqwest_impersonate-0.4.8.tar.gz", hash = "sha256:1eba11d47bd17244c64fec1502cc26ee66cc5c8a3be131e408101ae2b455e5bc"},
]
[package.extras]
@@ -5535,6 +5983,106 @@ files = [
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
+[[package]]
+name = "pyzmq"
+version = "26.0.3"
+description = "Python bindings for 0MQ"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"},
+ {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"},
+ {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"},
+ {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"},
+ {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"},
+ {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"},
+ {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"},
+ {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"},
+ {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"},
+ {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"},
+ {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"},
+ {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"},
+ {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"},
+ {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"},
+ {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"},
+ {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"},
+ {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"},
+ {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"},
+ {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"},
+ {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"},
+ {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"},
+ {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"},
+ {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"},
+ {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"},
+ {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"},
+ {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"},
+ {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"},
+ {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"},
+ {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"},
+ {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"},
+ {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"},
+ {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"},
+ {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"},
+ {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"},
+ {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"},
+ {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"},
+ {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"},
+ {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"},
+ {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"},
+ {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"},
+ {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"},
+ {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"},
+ {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"},
+ {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"},
+ {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"},
+ {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"},
+ {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"},
+ {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"},
+ {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"},
+ {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"},
+ {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"},
+ {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"},
+ {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"},
+ {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"},
+ {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"},
+ {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"},
+ {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"},
+ {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"},
+ {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"},
+ {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"},
+ {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"},
+ {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"},
+ {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"},
+ {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"},
+ {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"},
+ {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"},
+ {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"},
+ {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"},
+ {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"},
+ {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"},
+ {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"},
+ {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"},
+ {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"},
+ {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"},
+ {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"},
+]
+
+[package.dependencies]
+cffi = {version = "*", markers = "implementation_name == \"pypy\""}
+
[[package]]
name = "rank-bm25"
version = "0.2.2"
@@ -5554,13 +6102,13 @@ dev = ["pytest"]
[[package]]
name = "redis"
-version = "5.0.5"
+version = "5.0.6"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
files = [
- {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"},
- {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"},
+ {file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"},
+ {file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"},
]
[package.dependencies]
@@ -5976,27 +6524,32 @@ torch = ["safetensors[numpy]", "torch (>=1.10)"]
[[package]]
name = "scikit-image"
-version = "0.23.2"
+version = "0.24.0"
description = "Image processing in Python"
optional = false
-python-versions = ">=3.10"
+python-versions = ">=3.9"
files = [
- {file = "scikit_image-0.23.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9a8db6c52f8d0e1474ea8320d7b8db442b4d6baa29dd0acbd02f8a49572f18a"},
- {file = "scikit_image-0.23.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:524b51a7440e46ed2ebbde7bc288bf2dde1dee2caafdd9513b2aca38a48223b7"},
- {file = "scikit_image-0.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b335c229170d787b3fb8c60d220f72049ccf862d5191a3cfda6ac84b995ac4e"},
- {file = "scikit_image-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08b10781efbd6b084f3c847ff4049b657241ea866b9e331b14bf791dcb3e6661"},
- {file = "scikit_image-0.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:a207352e9a1956dda1424bbe872c7795345187138118e8be6a421aef3b988c2a"},
- {file = "scikit_image-0.23.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee83fdb1843ee938eabdfeb9498623282935ea30aa20dffc5d5d16698efb4b2a"},
- {file = "scikit_image-0.23.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a158f50d3df4867bbd1c698520ede8bc493e430ad83f54ac1f0d8f57b328779b"},
- {file = "scikit_image-0.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55de3326be124334b89314e9e04c8971ad98d6681e11a243f71bfb85ef9554b0"},
- {file = "scikit_image-0.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fce619a6d84fe40c1208fa579b646e93ce13ef0afc3652a23e9782b2c183291a"},
- {file = "scikit_image-0.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:ee65669aa586e110346f567ed5c92d1bd63799a19e951cb83da3f54b0caf7c52"},
- {file = "scikit_image-0.23.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:15bfb4e8d7bd90a967e6a3c3ab6be678063fc45e950b730684a8db46a02ff892"},
- {file = "scikit_image-0.23.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5736e66d01b11cd90988ec24ab929c80a03af28f690189c951886891ebf63154"},
- {file = "scikit_image-0.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3597ac5d8f51dafbcb7433ef1fdefdefb535f50745b2002ae0a5d651df4f063b"},
- {file = "scikit_image-0.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1978be2abe3c3c3189a99a411d48bbb1306f7c2debb3aefbf426e23947f26623"},
- {file = "scikit_image-0.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:ae32bf0cb02b672ed74d28880ca6f88928ae8dd794d67e04fa3ff4836feb9bd6"},
- {file = "scikit_image-0.23.2.tar.gz", hash = "sha256:c9da4b2c3117e3e30364a3d14496ee5c72b09eb1a4ab1292b302416faa360590"},
+ {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"},
+ {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"},
+ {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8"},
+ {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764"},
+ {file = "scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7"},
+ {file = "scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831"},
+ {file = "scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7"},
+ {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2"},
+ {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c"},
+ {file = "scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c"},
+ {file = "scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3"},
+ {file = "scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c"},
+ {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563"},
+ {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660"},
+ {file = "scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc"},
+ {file = "scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009"},
+ {file = "scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3"},
+ {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7"},
+ {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83"},
+ {file = "scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69"},
+ {file = "scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab"},
]
[package.dependencies]
@@ -6121,13 +6674,13 @@ doc = ["Sphinx", "sphinx-rtd-theme"]
[[package]]
name = "sentence-transformers"
-version = "2.7.0"
+version = "3.0.1"
description = "Multilingual text embeddings"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "sentence_transformers-2.7.0-py3-none-any.whl", hash = "sha256:6a7276b05a95931581bbfa4ba49d780b2cf6904fa4a171ec7fd66c343f761c98"},
- {file = "sentence_transformers-2.7.0.tar.gz", hash = "sha256:2f7df99d1c021dded471ed2d079e9d1e4fc8e30ecb06f957be060511b36f24ea"},
+ {file = "sentence_transformers-3.0.1-py3-none-any.whl", hash = "sha256:01050cc4053c49b9f5b78f6980b5a72db3fd3a0abb9169b1792ac83875505ee6"},
+ {file = "sentence_transformers-3.0.1.tar.gz", hash = "sha256:8a3d2c537cc4d1014ccc20ac92be3d6135420a3bc60ae29a3a8a9b4bb35fbff6"},
]
[package.dependencies]
@@ -6141,7 +6694,8 @@ tqdm = "*"
transformers = ">=4.34.0,<5.0.0"
[package.extras]
-dev = ["pre-commit", "pytest", "ruff (>=0.3.0)"]
+dev = ["accelerate (>=0.20.3)", "datasets", "pre-commit", "pytest", "ruff (>=0.3.0)"]
+train = ["accelerate (>=0.20.3)", "datasets"]
[[package]]
name = "sentencepiece"
@@ -6207,18 +6761,18 @@ files = [
[[package]]
name = "setuptools"
-version = "70.0.0"
+version = "70.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
- {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
+ {file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"},
+ {file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "shapely"
@@ -6323,64 +6877,64 @@ files = [
[[package]]
name = "sqlalchemy"
-version = "2.0.30"
+version = "2.0.31"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"},
- {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"},
- {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"},
- {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"},
- {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"},
- {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"},
- {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"},
- {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"},
- {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"},
-]
-
-[package.dependencies]
-greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""}
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"},
+ {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"},
+ {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"},
+ {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"},
+ {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"},
+ {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"},
+ {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"},
+ {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"},
+ {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"},
+]
+
+[package.dependencies]
+greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""}
typing-extensions = ">=4.6.0"
[package.extras]
@@ -6465,13 +7019,13 @@ files = [
[[package]]
name = "tenacity"
-version = "8.3.0"
+version = "8.4.1"
description = "Retry code until it succeeds"
optional = false
python-versions = ">=3.8"
files = [
- {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"},
- {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"},
+ {file = "tenacity-8.4.1-py3-none-any.whl", hash = "sha256:28522e692eda3e1b8f5e99c51464efcc0b9fc86933da92415168bc1c4e2308fa"},
+ {file = "tenacity-8.4.1.tar.gz", hash = "sha256:54b1412b878ddf7e1f1577cd49527bad8cdef32421bd599beac0c6c3f10582fd"},
]
[package.extras]
@@ -6507,13 +7061,13 @@ files = [
[[package]]
name = "tifffile"
-version = "2024.5.22"
+version = "2024.6.18"
description = "Read and write TIFF files"
optional = false
python-versions = ">=3.9"
files = [
- {file = "tifffile-2024.5.22-py3-none-any.whl", hash = "sha256:e281781c15d7d197d7e12749849c965651413aa905f97a48b0f84bd90a3b4c6f"},
- {file = "tifffile-2024.5.22.tar.gz", hash = "sha256:3a105801d1b86d55692a98812a170c39d3f0447aeacb1d94635d38077cb328c4"},
+ {file = "tifffile-2024.6.18-py3-none-any.whl", hash = "sha256:67299c0445fc47463bbc71f3cb4676da2ab0242b0c6c6542a0680801b4b97d8a"},
+ {file = "tifffile-2024.6.18.tar.gz", hash = "sha256:57e0d2a034bcb6287ea3155d8716508dfac86443a257f6502b57ee7f8a33b3b6"},
]
[package.dependencies]
@@ -7137,13 +7691,13 @@ files = [
[[package]]
name = "typing-extensions"
-version = "4.12.1"
+version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
- {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
@@ -7272,13 +7826,13 @@ files = [
[[package]]
name = "urllib3"
-version = "1.26.18"
+version = "1.26.19"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
- {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
- {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
+ {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"},
+ {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"},
]
[package.extras]
@@ -7548,6 +8102,23 @@ files = [
{file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
]
+[[package]]
+name = "werkzeug"
+version = "3.0.3"
+description = "The comprehensive WSGI web application library."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
+ {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.1.1"
+
+[package.extras]
+watchdog = ["watchdog (>=2.3)"]
+
[[package]]
name = "wrapt"
version = "1.16.0"
@@ -7878,7 +8449,78 @@ files = [
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
+[[package]]
+name = "zope-event"
+version = "5.0"
+description = "Very basic event publishing system"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"},
+ {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"},
+]
+
+[package.dependencies]
+setuptools = "*"
+
+[package.extras]
+docs = ["Sphinx"]
+test = ["zope.testrunner"]
+
+[[package]]
+name = "zope-interface"
+version = "6.4.post2"
+description = "Interfaces for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "zope.interface-6.4.post2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-win_amd64.whl", hash = "sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-win_amd64.whl", hash = "sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-win_amd64.whl", hash = "sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc"},
+ {file = "zope.interface-6.4.post2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1"},
+ {file = "zope.interface-6.4.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-win_amd64.whl", hash = "sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-win_amd64.whl", hash = "sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15"},
+ {file = "zope.interface-6.4.post2.tar.gz", hash = "sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e"},
+]
+
+[package.dependencies]
+setuptools = "*"
+
+[package.extras]
+docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"]
+test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
+testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
+
[metadata]
lock-version = "2.0"
python-versions = ">=3.10.0,<3.12"
-content-hash = "5f079affd17047b2a1e72b8e3c06b3556742c6f982f8b04d1e23a8261688e386"
+content-hash = "ec41485cb8210a18ab1d6b6b546396f5ec4271d57a9a4188cd27253d8c91ab7e"
diff --git a/pyproject.toml b/pyproject.toml
index dc978522..139f9dca 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -72,6 +72,8 @@ pytest-cov = "^5.0.0"
xlrd = "^2.0.1"
markdown = "^3.6"
chardet = "^5.2.0"
+locust = "^2.29.0"
+gunicorn = "^22.0.0"
[tool.poetry.scripts]
pai_rag = "pai_rag.main:main"
diff --git a/pyproject_gpu.toml b/pyproject_gpu.toml
index e68206eb..d9652073 100644
--- a/pyproject_gpu.toml
+++ b/pyproject_gpu.toml
@@ -64,6 +64,8 @@ pytest-cov = "^5.0.0"
xlrd = "^2.0.1"
markdown = "^3.6"
chardet = "^5.2.0"
+locust = "^2.29.0"
+gunicorn = "^22.0.0"
[tool.poetry.scripts]
pai_rag = "pai_rag.main:main"
diff --git a/scripts/locust.py b/scripts/locust.py
new file mode 100644
index 00000000..c998f641
--- /dev/null
+++ b/scripts/locust.py
@@ -0,0 +1,46 @@
+from locust import HttpUser, task, between
+from random import randint
+
+auth_header = {"Authorization": ""}
+
+sample_queries = [
+ "找一部关于下雪的电影。",
+ "找一部适合下雨天看的电影。",
+ "心情不好的时候看什么电影?",
+ "无聊的时候想看什么电影",
+ "压力很大的时候看的电影",
+ "好看的中文警匪片",
+ "校园爱情电影",
+ "金庸小说改编的古装武打剧",
+ "好看的仙侠剧",
+ "搞笑的电影",
+ "评分高的的动画片",
+]
+
+
+class SimpleRagUser(HttpUser):
+ wait_time = between(0, 1)
+
+ @task
+ def qa(self):
+ q_i = randint(0, len(sample_queries) - 1)
+ query = sample_queries[q_i]
+
+ _ = self.client.post(
+ "/service/query", headers=auth_header, json={"question": query}
+ )
+ # sprint(response.content.decode("utf-8"))
+
+
+class SimpleRetrievalUser(HttpUser):
+ wait_time = between(0, 1)
+
+ @task
+ def qa(self):
+ q_i = randint(0, len(sample_queries) - 1)
+ query = sample_queries[q_i]
+
+ _ = self.client.post(
+ "/service/query/retrieval", headers=auth_header, json={"question": query}
+ )
+ # print(response.content.decode("utf-8"))
diff --git a/src/pai_rag/app/api/query.py b/src/pai_rag/app/api/query.py
index e008a23b..d2efa073 100644
--- a/src/pai_rag/app/api/query.py
+++ b/src/pai_rag/app/api/query.py
@@ -42,6 +42,11 @@ async def aupdate(new_config: Any = Body(None)):
return {"msg": "Update RAG configuration successfully."}
+@router.get("/config")
+async def aconfig():
+ return rag_service.get_config()
+
+
@router.post("/upload_data")
def load_data(input: DataInput, background_tasks: BackgroundTasks):
task_id = uuid.uuid4().hex
@@ -93,7 +98,7 @@ async def batch_evaluate():
@router.post("/upload_local_data")
async def upload_local_data(
file: UploadFile = File(),
- faiss_path: str = Form(),
+ faiss_path: str = Form(None),
background_tasks: BackgroundTasks = BackgroundTasks(),
):
task_id = uuid.uuid4().hex
diff --git a/src/pai_rag/app/app.py b/src/pai_rag/app/api/service.py
similarity index 52%
rename from src/pai_rag/app/app.py
rename to src/pai_rag/app/api/service.py
index 4db4797e..73b7d630 100644
--- a/src/pai_rag/app/app.py
+++ b/src/pai_rag/app/api/service.py
@@ -1,12 +1,7 @@
from fastapi import APIRouter, FastAPI
-import gradio as gr
from pai_rag.core.rag_service import rag_service
from pai_rag.app.api import query
from pai_rag.app.api.middleware import init_middleware
-from pai_rag.app.web.webui import create_ui
-from pai_rag.app.web.rag_client import rag_client
-
-UI_PATH = ""
def init_router(app: FastAPI):
@@ -15,16 +10,7 @@ def init_router(app: FastAPI):
app.include_router(api_router, prefix="/service")
-def create_app(config_file: str, app_url: str):
+def configure_app(app: FastAPI, config_file: str):
rag_service.initialize(config_file)
-
- app = FastAPI()
init_middleware(app)
init_router(app)
-
- rag_client.set_endpoint(app_url)
- ui = create_ui()
- ui.queue(concurrency_count=1, max_size=64)
- ui._queue.set_url(app_url)
- app = gr.mount_gradio_app(app, ui, path=UI_PATH)
- return app
diff --git a/src/pai_rag/app/web/rag_client.py b/src/pai_rag/app/web/rag_client.py
index ed2aa3e1..d1681c94 100644
--- a/src/pai_rag/app/web/rag_client.py
+++ b/src/pai_rag/app/web/rag_client.py
@@ -1,12 +1,12 @@
import json
-
from typing import Any
import requests
import html
import markdown
import httpx
-
-cache_config = None
+import os
+import mimetypes
+from pai_rag.app.web.view_model import ViewModel
class dotdict(dict):
@@ -42,7 +42,7 @@ def config_url(self):
@property
def load_data_url(self):
- return f"{self.endpoint}service/upload_data"
+ return f"{self.endpoint}service/upload_local_data"
@property
def get_load_state_url(self):
@@ -89,9 +89,19 @@ def query_vector(self, text: str):
response["answer"] = formatted_text
return response
- def add_knowledge(self, file_dir: str, enable_qa_extraction: bool):
- q = dict(file_path=file_dir, enable_qa_extraction=enable_qa_extraction)
- r = requests.post(self.load_data_url, json=q)
+ def add_knowledge(self, file_name: str, enable_qa_extraction: bool):
+ with open(file_name, "rb") as file_obj:
+ mimetype = mimetypes.guess_type(file_name)[0]
+ # maybe we can upload multiple files in the future
+ files = {"file": (os.path.basename(file_name), file_obj, mimetype)}
+ print(files)
+ # headers = {"content-type": "multipart/form-data"}
+
+ r = requests.post(
+ self.load_data_url,
+ files=files,
+ # headers=headers,
+ )
r.raise_for_status()
response = dotdict(json.loads(r.text))
return response
@@ -103,18 +113,22 @@ async def get_knowledge_state(self, task_id: str):
response = dotdict(json.loads(r.text))
return response
- def reload_config(self, config: Any):
- global cache_config
+ def patch_config(self, update_dict: Any):
+ config = self.get_config()
+ view_model: ViewModel = ViewModel.from_app_config(config)
+ view_model.update(update_dict)
+ new_config = view_model.to_app_config()
- if cache_config == config:
- return
-
- r = requests.patch(self.config_url, json=config)
+ r = requests.patch(self.config_url, json=new_config)
r.raise_for_status()
- print(r.text)
- cache_config = config
-
return
+ def get_config(self):
+ r = requests.get(self.config_url)
+ r.raise_for_status()
+ response = dotdict(json.loads(r.text))
+ print(response)
+ return response
+
rag_client = RagWebClient()
diff --git a/src/pai_rag/app/web/tabs/chat_tab.py b/src/pai_rag/app/web/tabs/chat_tab.py
index 8bb7998b..bbc9b184 100644
--- a/src/pai_rag/app/web/tabs/chat_tab.py
+++ b/src/pai_rag/app/web/tabs/chat_tab.py
@@ -1,7 +1,6 @@
from typing import Dict, Any, List
import gradio as gr
from pai_rag.app.web.rag_client import rag_client
-from pai_rag.app.web.view_model import view_model
from pai_rag.app.web.ui_constants import (
SIMPLE_PROMPTS,
GENERAL_PROMPTS,
@@ -31,9 +30,7 @@ def respond(input_elements: List[Any]):
if not update_dict["question"]:
return "", update_dict["chatbot"], 0
- view_model.update(update_dict)
- new_config = view_model.to_app_config()
- rag_client.reload_config(new_config)
+ rag_client.patch_config(update_dict)
query_type = update_dict["query_type"]
msg = update_dict["question"]
@@ -235,7 +232,7 @@ def change_query_radio(query_type):
respond,
chat_args,
[question, chatbot, cur_tokens],
- api_name="respond",
+ api_name="respond_q",
)
clearBtn.click(clear_history, [chatbot], [chatbot, cur_tokens])
return {
diff --git a/src/pai_rag/app/web/tabs/settings_tab.py b/src/pai_rag/app/web/tabs/settings_tab.py
index acdda1e1..80e0892e 100644
--- a/src/pai_rag/app/web/tabs/settings_tab.py
+++ b/src/pai_rag/app/web/tabs/settings_tab.py
@@ -9,7 +9,6 @@
LLM_MODEL_KEY_DICT,
)
from pai_rag.app.web.rag_client import rag_client
-from pai_rag.app.web.view_model import view_model
from pai_rag.app.web.utils import components_to_dict
from pai_rag.app.web.tabs.vector_db_panel import create_vector_db_panel
import logging
@@ -23,9 +22,7 @@ def connect_vector_db(input_elements: List[Any]):
for element, value in input_elements.items():
update_dict[element.elem_id] = value
- view_model.update(update_dict)
- new_config = view_model.to_app_config()
- rag_client.reload_config(new_config)
+ rag_client.patch_config(update_dict)
return f"[{datetime.datetime.now()}] Connect vector db success!"
except Exception as ex:
logger.critical(f"[Critical] Connect failed. {traceback.format_exc()}")
@@ -59,23 +56,17 @@ def create_setting_tab() -> Dict[str, Any]:
)
def change_emb_source(source):
- view_model.embed_source = source
return {
embed_model: gr.update(visible=(source == "HuggingFace")),
- embed_dim: EMBEDDING_DIM_DICT.get(
- view_model.embed_model, DEFAULT_EMBED_SIZE
- )
+ embed_dim: EMBEDDING_DIM_DICT.get(source, DEFAULT_EMBED_SIZE)
if source == "HuggingFace"
else DEFAULT_EMBED_SIZE,
}
def change_emb_model(model):
- view_model.embed_model = model
return {
- embed_dim: EMBEDDING_DIM_DICT.get(
- view_model.embed_model, DEFAULT_EMBED_SIZE
- )
- if view_model.embed_source == "HuggingFace"
+ embed_dim: EMBEDDING_DIM_DICT.get(model, DEFAULT_EMBED_SIZE)
+ if embed_source == "HuggingFace"
else DEFAULT_EMBED_SIZE,
}
@@ -98,7 +89,7 @@ def change_emb_model(model):
label="LLM Model Source",
elem_id="llm",
)
- with gr.Column(visible=(view_model.llm == "PaiEas")) as eas_col:
+ with gr.Column(visible=(llm == "PaiEas")) as eas_col:
llm_eas_url = gr.Textbox(
label="EAS Url",
elem_id="llm_eas_url",
@@ -112,7 +103,7 @@ def change_emb_model(model):
label="EAS Model name",
elem_id="llm_eas_model_name",
)
- with gr.Column(visible=(view_model.llm != "PaiEas")) as api_llm_col:
+ with gr.Column(visible=(llm != "PaiEas")) as api_llm_col:
llm_api_model_name = gr.Dropdown(
label="LLM Model Name",
elem_id="llm_api_model_name",
@@ -129,7 +120,6 @@ def change_emb_model(model):
)
def change_llm(value):
- view_model.llm = value
eas_visible = value == "PaiEas"
api_visible = value != "PaiEas"
model_options = LLM_MODEL_KEY_DICT.get(value, [])
diff --git a/src/pai_rag/app/web/tabs/upload_tab.py b/src/pai_rag/app/web/tabs/upload_tab.py
index b1f6302b..ef9f45df 100644
--- a/src/pai_rag/app/web/tabs/upload_tab.py
+++ b/src/pai_rag/app/web/tabs/upload_tab.py
@@ -3,17 +3,13 @@
import gradio as gr
import time
from pai_rag.app.web.rag_client import rag_client
-from pai_rag.app.web.view_model import view_model
from pai_rag.utils.file_utils import MyUploadFile
import pandas as pd
import asyncio
def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extraction):
- view_model.chunk_size = chunk_size
- view_model.chunk_overlap = chunk_overlap
- new_config = view_model.to_app_config()
- rag_client.reload_config(new_config)
+ rag_client.patch_config({"chunk_size": chunk_size, "chunk_overlap": chunk_overlap})
if not upload_files:
yield [
@@ -26,8 +22,7 @@ def upload_knowledge(upload_files, chunk_size, chunk_overlap, enable_qa_extracti
my_upload_files = []
for file in upload_files:
- file_dir = os.path.dirname(file.name)
- response = rag_client.add_knowledge(file_dir, enable_qa_extraction)
+ response = rag_client.add_knowledge(file.name, enable_qa_extraction)
my_upload_files.append(
MyUploadFile(os.path.basename(file.name), response["task_id"])
)
diff --git a/src/pai_rag/app/web/tabs/vector_db_panel.py b/src/pai_rag/app/web/tabs/vector_db_panel.py
index fe9da895..a2f196e7 100644
--- a/src/pai_rag/app/web/tabs/vector_db_panel.py
+++ b/src/pai_rag/app/web/tabs/vector_db_panel.py
@@ -1,6 +1,5 @@
import gradio as gr
from typing import Any, Set, Callable, Dict
-from pai_rag.app.web.view_model import view_model
from pai_rag.app.web.utils import components_to_dict
@@ -18,9 +17,7 @@ def create_vector_db_panel(
elem_id="vectordb_type",
)
# Adb
- with gr.Column(
- visible=(view_model.vectordb_type == "AnalyticDB")
- ) as adb_col:
+ with gr.Column(visible=(vectordb_type == "AnalyticDB")) as adb_col:
adb_ak = gr.Textbox(
label="access-key-id",
type="password",
@@ -85,9 +82,7 @@ def create_vector_db_panel(
api_name="connect_adb",
)
# Hologres
- with gr.Column(
- visible=(view_model.vectordb_type == "Hologres")
- ) as holo_col:
+ with gr.Column(visible=(vectordb_type == "Hologres")) as holo_col:
hologres_host = gr.Textbox(
label="Host",
elem_id="hologres_host",
@@ -139,9 +134,7 @@ def create_vector_db_panel(
api_name="connect_hologres",
)
- with gr.Column(
- visible=(view_model.vectordb_type == "ElasticSearch")
- ) as es_col:
+ with gr.Column(visible=(vectordb_type == "ElasticSearch")) as es_col:
es_url = gr.Textbox(label="ElasticSearch Url", elem_id="es_url")
es_index = gr.Textbox(label="Index Name", elem_id="es_index")
es_user = gr.Textbox(label="ES User", elem_id="es_user")
@@ -163,9 +156,7 @@ def create_vector_db_panel(
api_name="connect_elasticsearch",
)
- with gr.Column(
- visible=(view_model.vectordb_type == "Milvus")
- ) as milvus_col:
+ with gr.Column(visible=(vectordb_type == "Milvus")) as milvus_col:
milvus_host = gr.Textbox(label="Host", elem_id="milvus_host")
milvus_port = gr.Textbox(label="Port", elem_id="milvus_port")
@@ -204,7 +195,7 @@ def create_vector_db_panel(
api_name="connect_milvus",
)
- with gr.Column(visible=(view_model.vectordb_type == "FAISS")) as faiss_col:
+ with gr.Column(visible=(vectordb_type == "FAISS")) as faiss_col:
faiss_path = gr.Textbox(label="Path", elem_id="faiss_path")
connect_btn_faiss = gr.Button("Connect Faiss", variant="primary")
con_state_faiss = gr.Textbox(label="Connection Info: ")
diff --git a/src/pai_rag/app/web/view_model.py b/src/pai_rag/app/web/view_model.py
index 3aa1aa73..486d62fa 100644
--- a/src/pai_rag/app/web/view_model.py
+++ b/src/pai_rag/app/web/view_model.py
@@ -108,109 +108,135 @@ def update(self, update_paras: Dict[str, Any]):
if key in attr_set:
setattr(self, key, value)
- def sync_app_config(self, config):
- self.embed_source = config["embedding"].get("source", self.embed_source)
- self.embed_model = config["embedding"].get("model_name", self.embed_model)
- self.embed_api_key = config["embedding"].get("api_key", self.embed_api_key)
- self.embed_batch_size = config["embedding"].get(
- "embed_batch_size", self.embed_batch_size
+ @staticmethod
+ def from_app_config(config):
+ view_model = ViewModel()
+ view_model.embed_source = config["embedding"].get(
+ "source", view_model.embed_source
+ )
+ view_model.embed_model = config["embedding"].get(
+ "model_name", view_model.embed_model
+ )
+ view_model.embed_api_key = config["embedding"].get(
+ "api_key", view_model.embed_api_key
+ )
+ view_model.embed_batch_size = config["embedding"].get(
+ "embed_batch_size", view_model.embed_batch_size
)
- self.llm = config["llm"].get("source", self.llm)
- self.llm_eas_url = config["llm"].get("endpoint", self.llm_eas_url)
- self.llm_eas_token = config["llm"].get("token", self.llm_eas_token)
- self.llm_api_key = config["llm"].get("api_key", self.llm_api_key)
- self.llm_temperature = config["llm"].get("temperature", self.llm_temperature)
- if self.llm == "PaiEAS":
- self.llm_eas_model_name = config["llm"].get("name", self.llm_eas_model_name)
+ view_model.llm = config["llm"].get("source", view_model.llm)
+ view_model.llm_eas_url = config["llm"].get("endpoint", view_model.llm_eas_url)
+ view_model.llm_eas_token = config["llm"].get("token", view_model.llm_eas_token)
+ view_model.llm_api_key = config["llm"].get("api_key", view_model.llm_api_key)
+ view_model.llm_temperature = config["llm"].get(
+ "temperature", view_model.llm_temperature
+ )
+ if view_model.llm == "PaiEAS":
+ view_model.llm_eas_model_name = config["llm"].get(
+ "name", view_model.llm_eas_model_name
+ )
else:
- self.llm_api_model_name = config["llm"].get("name", self.llm_api_model_name)
+ view_model.llm_api_model_name = config["llm"].get(
+ "name", view_model.llm_api_model_name
+ )
- self.vectordb_type = config["index"]["vector_store"].get(
- "type", self.vectordb_type
+ view_model.vectordb_type = config["index"]["vector_store"].get(
+ "type", view_model.vectordb_type
+ )
+ view_model.faiss_path = config["index"].get(
+ "persist_path", view_model.faiss_path
)
- self.faiss_path = config["index"].get("persist_path", self.faiss_path)
- if self.vectordb_type == "AnalyticDB":
- self.adb_ak = config["index"]["vector_store"]["ak"]
- self.adb_sk = config["index"]["vector_store"]["sk"]
- self.adb_region_id = config["index"]["vector_store"]["region_id"]
- self.adb_instance_id = config["index"]["vector_store"]["instance_id"]
- self.adb_account = config["index"]["vector_store"]["account"]
- self.adb_account_password = config["index"]["vector_store"][
+ if view_model.vectordb_type == "AnalyticDB":
+ view_model.adb_ak = config["index"]["vector_store"]["ak"]
+ view_model.adb_sk = config["index"]["vector_store"]["sk"]
+ view_model.adb_region_id = config["index"]["vector_store"]["region_id"]
+ view_model.adb_instance_id = config["index"]["vector_store"]["instance_id"]
+ view_model.adb_account = config["index"]["vector_store"]["account"]
+ view_model.adb_account_password = config["index"]["vector_store"][
"account_password"
]
- self.adb_namespace = config["index"]["vector_store"]["namespace"]
- self.adb_collection = config["index"]["vector_store"]["collection"]
- self.adb_metrics = config["index"]["vector_store"].get("metrics", "cosine")
-
- elif self.vectordb_type == "Hologres":
- self.hologres_host = config["index"]["vector_store"]["host"]
- self.hologres_port = config["index"]["vector_store"]["port"]
- self.hologres_user = config["index"]["vector_store"]["user"]
- self.hologres_password = config["index"]["vector_store"]["password"]
- self.hologres_database = config["index"]["vector_store"]["database"]
- self.hologres_table = config["index"]["vector_store"]["table_name"]
- self.hologres_pre_delete = config["index"]["vector_store"].get(
- "pre_delete_table", False
+ view_model.adb_namespace = config["index"]["vector_store"]["namespace"]
+ view_model.adb_collection = config["index"]["vector_store"]["collection"]
+ view_model.adb_metrics = config["index"]["vector_store"].get(
+ "metrics", "cosine"
)
- elif self.vectordb_type == "ElasticSearch":
- self.es_index = config["index"]["vector_store"]["es_index"]
- self.es_url = config["index"]["vector_store"]["es_url"]
- self.es_user = config["index"]["vector_store"]["es_user"]
- self.es_password = config["index"]["vector_store"]["es_password"]
+ elif view_model.vectordb_type == "Hologres":
+ view_model.hologres_host = config["index"]["vector_store"]["host"]
+ view_model.hologres_port = config["index"]["vector_store"]["port"]
+ view_model.hologres_user = config["index"]["vector_store"]["user"]
+ view_model.hologres_password = config["index"]["vector_store"]["password"]
+ view_model.hologres_database = config["index"]["vector_store"]["database"]
+ view_model.hologres_table = config["index"]["vector_store"]["table_name"]
+ view_model.hologres_pre_delete = config["index"]["vector_store"].get(
+ "pre_delete_table", False
+ )
- elif self.vectordb_type == "Milvus":
- self.milvus_host = config["index"]["vector_store"]["host"]
- self.milvus_port = config["index"]["vector_store"]["port"]
- self.milvus_user = config["index"]["vector_store"]["user"]
- self.milvus_password = config["index"]["vector_store"]["password"]
- self.milvus_database = config["index"]["vector_store"]["database"]
- self.milvus_collection_name = config["index"]["vector_store"][
+ elif view_model.vectordb_type == "ElasticSearch":
+ view_model.es_index = config["index"]["vector_store"]["es_index"]
+ view_model.es_url = config["index"]["vector_store"]["es_url"]
+ view_model.es_user = config["index"]["vector_store"]["es_user"]
+ view_model.es_password = config["index"]["vector_store"]["es_password"]
+
+ elif view_model.vectordb_type == "Milvus":
+ view_model.milvus_host = config["index"]["vector_store"]["host"]
+ view_model.milvus_port = config["index"]["vector_store"]["port"]
+ view_model.milvus_user = config["index"]["vector_store"]["user"]
+ view_model.milvus_password = config["index"]["vector_store"]["password"]
+ view_model.milvus_database = config["index"]["vector_store"]["database"]
+ view_model.milvus_collection_name = config["index"]["vector_store"][
"collection_name"
]
- self.parser_type = config["node_parser"]["type"]
- self.chunk_size = config["node_parser"]["chunk_size"]
- self.chunk_overlap = config["node_parser"]["chunk_overlap"]
+ view_model.parser_type = config["node_parser"]["type"]
+ view_model.chunk_size = config["node_parser"]["chunk_size"]
+ view_model.chunk_overlap = config["node_parser"]["chunk_overlap"]
- self.reader_type = config["data_reader"].get("type", self.reader_type)
- self.enable_qa_extraction = config["data_reader"].get(
- "enable_qa_extraction", self.enable_qa_extraction
+ view_model.reader_type = config["data_reader"].get(
+ "type", view_model.reader_type
+ )
+ view_model.enable_qa_extraction = config["data_reader"].get(
+ "enable_qa_extraction", view_model.enable_qa_extraction
)
- self.similarity_top_k = config["retriever"].get("similarity_top_k", 5)
+ view_model.similarity_top_k = config["retriever"].get("similarity_top_k", 5)
if config["retriever"]["retrieval_mode"] == "hybrid":
- self.retrieval_mode = "Hybrid"
- self.BM25_weight = config["retriever"]["BM25_weight"]
- self.vector_weight = config["retriever"]["vector_weight"]
- self.fusion_mode = config["retriever"]["fusion_mode"]
- self.query_rewrite_n = config["retriever"]["query_rewrite_n"]
+ view_model.retrieval_mode = "Hybrid"
+ view_model.BM25_weight = config["retriever"]["BM25_weight"]
+ view_model.vector_weight = config["retriever"]["vector_weight"]
+ view_model.fusion_mode = config["retriever"]["fusion_mode"]
+ view_model.query_rewrite_n = config["retriever"]["query_rewrite_n"]
elif config["retriever"]["retrieval_mode"] == "embedding":
- self.retrieval_mode = "Embedding Only"
+ view_model.retrieval_mode = "Embedding Only"
elif config["retriever"]["retrieval_mode"] == "keyword":
- self.retrieval_mode = "Keyword Only"
+ view_model.retrieval_mode = "Keyword Only"
# if "Similarity" in config["postprocessor"]:
- # self.similarity_cutoff = config["postprocessor"].get("similarity_cutoff", 0.1)
+ # view_model.similarity_cutoff = config["postprocessor"].get("similarity_cutoff", 0.1)
rerank_model = config["postprocessor"].get("rerank_model", "no-reranker")
if rerank_model == "llm-reranker":
- self.rerank_model = "llm-reranker"
+ view_model.rerank_model = "llm-reranker"
elif rerank_model == "bge-reranker-base":
- self.rerank_model = "bge-reranker-base"
+ view_model.rerank_model = "bge-reranker-base"
elif rerank_model == "bge-reranker-large":
- self.rerank_model = "bge-reranker-large"
+ view_model.rerank_model = "bge-reranker-large"
else:
- self.rerank_model = "no-reranker"
+ view_model.rerank_model = "no-reranker"
- self.synthesizer_type = config["synthesizer"].get("type", "SimpleSummarize")
- self.text_qa_template = config["synthesizer"].get("text_qa_template", None)
+ view_model.synthesizer_type = config["synthesizer"].get(
+ "type", "SimpleSummarize"
+ )
+ view_model.text_qa_template = config["synthesizer"].get(
+ "text_qa_template", None
+ )
if config["query_engine"]["type"] == "TransformQueryEngine":
- self.query_engine_type = "TransformQueryEngine"
+ view_model.query_engine_type = "TransformQueryEngine"
else:
- self.query_engine_type = "RetrieverQueryEngine"
+ view_model.query_engine_type = "RetrieverQueryEngine"
+
+ return view_model
def to_app_config(self):
config = recursive_dict()
@@ -343,7 +369,7 @@ def to_component_settings(self) -> Dict[str, Dict[str, Any]]:
settings["rerank_model"] = {"value": self.rerank_model}
settings["retrieval_mode"] = {"value": self.retrieval_mode}
- prm_type = PROMPT_MAP.get(view_model.text_qa_template, "Custom")
+ prm_type = PROMPT_MAP.get(self.text_qa_template, "Custom")
settings["prm_type"] = {"value": prm_type}
settings["text_qa_template"] = {"value": self.text_qa_template}
@@ -386,6 +412,3 @@ def to_component_settings(self) -> Dict[str, Dict[str, Any]]:
settings["faiss_path"] = {"value": self.faiss_path}
return settings
-
-
-view_model = ViewModel()
diff --git a/src/pai_rag/app/web/webui.py b/src/pai_rag/app/web/webui.py
index c920968a..27e67b7a 100644
--- a/src/pai_rag/app/web/webui.py
+++ b/src/pai_rag/app/web/webui.py
@@ -1,5 +1,7 @@
+from fastapi import FastAPI
import gradio as gr
-from pai_rag.app.web.view_model import view_model
+from pai_rag.app.web.view_model import ViewModel
+from pai_rag.app.web.rag_client import rag_client
from pai_rag.app.web.tabs.settings_tab import create_setting_tab
from pai_rag.app.web.tabs.upload_tab import create_upload_tab
from pai_rag.app.web.tabs.chat_tab import create_chat_tab
@@ -11,11 +13,14 @@
import logging
+DEFAULT_LOCAL_URL = "http://localhost:8001/"
logger = logging.getLogger("WebUILogger")
def resume_ui():
outputs = {}
+ rag_config = rag_client.get_config()
+ view_model = ViewModel.from_app_config(rag_config)
component_settings = view_model.to_component_settings()
for elem in elem_manager.get_elem_list():
@@ -29,7 +34,7 @@ def resume_ui():
return outputs
-def create_ui():
+def make_homepage():
with gr.Blocks(css=DEFAULT_CSS_STYPE) as homepage:
# generate components
gr.Markdown(value=WELCOME_MESSAGE)
@@ -46,3 +51,13 @@ def create_ui():
resume_ui, outputs=elem_manager.get_elem_list(), concurrency_limit=None
)
return homepage
+
+
+def configure_webapp(app: FastAPI, web_url, rag_url=DEFAULT_LOCAL_URL) -> gr.Blocks:
+ rag_client.set_endpoint(rag_url)
+ home = make_homepage()
+ home.queue(concurrency_count=1, max_size=64)
+ home._queue.set_url(web_url)
+ print(web_url)
+ gr.mount_gradio_app(app, home, path="")
+ return home
diff --git a/src/pai_rag/core/rag_application.py b/src/pai_rag/core/rag_application.py
index 580bdb05..2532b679 100644
--- a/src/pai_rag/core/rag_application.py
+++ b/src/pai_rag/core/rag_application.py
@@ -78,6 +78,7 @@ async def aquery_retrieval(self, query: RetrievalQuery) -> RetrievalResponse:
)
for score_node in node_results
]
+
return RetrievalResponse(docs=docs)
async def aquery(self, query: RagQuery) -> RagResponse:
@@ -102,7 +103,6 @@ async def aquery(self, query: RagQuery) -> RagResponse:
if query.vector_db and query.vector_db.faiss_path:
sessioned_config = self.config.copy()
sessioned_config.index.update({"persist_path": query.vector_db.faiss_path})
- print(sessioned_config)
chat_engine_factory = module_registry.get_module_with_config(
"ChatEngineFactoryModule", sessioned_config
@@ -112,10 +112,6 @@ async def aquery(self, query: RagQuery) -> RagResponse:
)
response = await query_chat_engine.achat(query.question)
- chat_store = module_registry.get_module_with_config(
- "ChatStoreModule", sessioned_config
- )
- chat_store.persist()
return RagResponse(answer=response.response, session_id=session_id)
async def aquery_llm(self, query: LlmQuery) -> LlmResponse:
@@ -145,10 +141,6 @@ async def aquery_llm(self, query: LlmQuery) -> LlmResponse:
)
response = await llm_chat_engine.achat(query.question)
- chat_store = module_registry.get_module_with_config(
- "ChatStoreModule", self.config
- )
- chat_store.persist()
return LlmResponse(answer=response.response, session_id=session_id)
async def aquery_agent(self, query: LlmQuery) -> LlmResponse:
diff --git a/src/pai_rag/core/rag_configuration.py b/src/pai_rag/core/rag_configuration.py
index 2c0d01eb..5ce01650 100644
--- a/src/pai_rag/core/rag_configuration.py
+++ b/src/pai_rag/core/rag_configuration.py
@@ -12,6 +12,20 @@ class RagConfiguration:
def __init__(self, config):
self.config = config
+ @classmethod
+ def from_snapshot(cls):
+ try:
+ settings_files = [GENERATED_CONFIG_FILE_NAME]
+ config = Dynaconf(
+ envvar_prefix="PAIRAG",
+ settings_file=settings_files,
+ merge=True,
+ )
+ return cls(config)
+ except Exception as error:
+ logging.critical("Read config file failed.")
+ raise error
+
@classmethod
def from_file(cls, config_file):
try:
@@ -40,3 +54,10 @@ def persist(self):
data = self.config.as_dict()
os.makedirs("localdata", exist_ok=True)
loaders.write(GENERATED_CONFIG_FILE_NAME, DynaBox(data).to_dict(), merge=True)
+
+ def get_config_mtime(self):
+ try:
+ return os.path.getmtime(GENERATED_CONFIG_FILE_NAME)
+ except Exception as ex:
+ print(f"Fail to read config mtime {ex}")
+ return -1
diff --git a/src/pai_rag/core/rag_service.py b/src/pai_rag/core/rag_service.py
index fb46f9f1..dbdb3c22 100644
--- a/src/pai_rag/core/rag_service.py
+++ b/src/pai_rag/core/rag_service.py
@@ -1,4 +1,5 @@
import asyncio
+import os
from asgi_correlation_id import correlation_id
from pai_rag.core.rag_application import RagApplication
from pai_rag.core.rag_configuration import RagConfiguration
@@ -9,11 +10,11 @@
RagResponse,
LlmResponse,
)
-from pai_rag.app.web.view_model import view_model
from openinference.instrumentation import using_attributes
-from typing import Any, Dict
+from typing import Any
import logging
+TASK_STATUS_FILE = "__upload_task_status.tmp"
logger = logging.getLogger(__name__)
@@ -40,16 +41,48 @@ async def _a_trace_correlation_id(*args, **kwargs):
class RagService:
def initialize(self, config_file: str):
+ self.config_file = config_file
self.rag_configuration = RagConfiguration.from_file(config_file)
- view_model.sync_app_config(self.rag_configuration.get_value())
+ self.config_dict_value = self.rag_configuration.get_value().to_dict()
+ self.config_modified_time = self.rag_configuration.get_config_mtime()
+
+ self.rag_configuration.persist()
+
self.rag = RagApplication()
self.rag.initialize(self.rag_configuration.get_value())
- self.tasks_status: Dict[str, str] = {}
- def reload(self, new_config: Any):
- self.rag_configuration.update(new_config)
- self.rag.reload(self.rag_configuration.get_value())
- self.rag_configuration.persist()
+ if os.path.exists(TASK_STATUS_FILE):
+ open(TASK_STATUS_FILE, "w").close()
+
+ def get_config(self):
+ self.check_updates()
+ return self.config_dict_value
+
+ def reload(self, new_config: Any = None):
+ rag_snapshot = RagConfiguration.from_snapshot()
+ if new_config:
+ # 多worker模式,读取最新的setting
+ rag_snapshot.update(new_config)
+ config_snapshot = rag_snapshot.get_value()
+
+ new_dict_value = config_snapshot.to_dict()
+ if self.config_dict_value != new_dict_value:
+ logger.debug("Config changed, reload")
+ self.rag.reload(config_snapshot)
+ self.config_dict_value = new_dict_value
+ self.rag_configuration = rag_snapshot
+ self.rag_configuration.persist()
+ else:
+ logger.debug("Config not changed, not reload")
+
+ def check_updates(self):
+ logger.info("Checking updates")
+ new_modified_time = self.rag_configuration.get_config_mtime()
+ if self.config_modified_time != new_modified_time:
+ self.reload()
+ self.config_modified_time = new_modified_time
+ else:
+ logger.info("No updates")
def add_knowledge_async(
self,
@@ -58,30 +91,50 @@ def add_knowledge_async(
faiss_path: str = None,
enable_qa_extraction: bool = False,
):
- self.tasks_status[task_id] = "processing"
+ self.check_updates()
+ with open(TASK_STATUS_FILE, "a") as f:
+ f.write(f"{task_id} processing\n")
try:
self.rag.load_knowledge(file_dir, faiss_path, enable_qa_extraction)
- self.tasks_status[task_id] = "completed"
+ with open(TASK_STATUS_FILE, "a") as f:
+ f.write(f"{task_id} completed\n")
except Exception as ex:
logger.error(f"Upload failed: {ex}")
- self.tasks_status[task_id] = "failed"
+ with open(TASK_STATUS_FILE, "a") as f:
+ f.write(f"{task_id} failed\n")
+ raise
def get_task_status(self, task_id: str) -> str:
- return self.tasks_status.get(task_id, "unknown")
+ self.check_updates()
+ default_status = "unknown"
+ if not os.path.exists(TASK_STATUS_FILE):
+ return default_status
+
+ lines = open(TASK_STATUS_FILE).readlines()
+ for line in lines[::-1]:
+ if line.startswith(task_id):
+ return line.strip().split(" ")[1]
+
+ return default_status
async def aquery(self, query: RagQuery) -> RagResponse:
+ self.check_updates()
return await self.rag.aquery(query)
async def aquery_llm(self, query: LlmQuery) -> LlmResponse:
+ self.check_updates()
return await self.rag.aquery_llm(query)
async def aquery_retrieval(self, query: RetrievalQuery):
+ self.check_updates()
return await self.rag.aquery_retrieval(query)
async def aquery_agent(self, query: LlmQuery) -> LlmResponse:
+ self.check_updates()
return await self.rag.aquery_agent(query)
async def batch_evaluate_retrieval_and_response(self, type):
+ self.check_updates()
return await self.rag.batch_evaluate_retrieval_and_response(type)
diff --git a/src/pai_rag/data/rag_dataloader.py b/src/pai_rag/data/rag_dataloader.py
index e8b87a82..ee5775a0 100644
--- a/src/pai_rag/data/rag_dataloader.py
+++ b/src/pai_rag/data/rag_dataloader.py
@@ -1,3 +1,5 @@
+import datetime
+import json
import os
from typing import Any, Dict
import asyncio
@@ -118,8 +120,13 @@ async def aload(self, file_directory: str, enable_qa_extraction: bool):
persist_dir=self.index.persist_path
)
+ index_metadata_file = os.path.join(self.index.persist_path, "index.metadata")
if self.bm25_index:
self.bm25_index.add_docs(nodes)
+ metadata_str = json.dumps({"lastUpdated": f"{datetime.datetime.now()}"})
+ with open(index_metadata_file, "w") as wf:
+ wf.write(metadata_str)
+
logger.info(f"Inserted {len(nodes)} nodes successfully.")
return
diff --git a/src/pai_rag/integrations/llms/paieas/base.py b/src/pai_rag/integrations/llms/paieas/base.py
index 40b7edf5..5a78e80d 100644
--- a/src/pai_rag/integrations/llms/paieas/base.py
+++ b/src/pai_rag/integrations/llms/paieas/base.py
@@ -14,11 +14,13 @@
from llama_index.core.base.llms.generic_utils import (
completion_to_chat_decorator,
stream_completion_to_chat_decorator,
+ acompletion_to_chat_decorator,
)
from typing import Any, Dict, Optional, Sequence
from llama_index.core.bridge.pydantic import Field
import json
import requests
+import httpx
DEFAULT_EAS_MODEL_NAME = "pai-eas-custom-llm"
DEFAULT_EAS_MAX_NEW_TOKENS = 512
@@ -103,14 +105,29 @@ def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
return CompletionResponse(text=text)
@llm_completion_callback()
- def stream_complete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
- return self._stream(prompt=prompt, kwargs=kwargs)
+ async def acomplete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
+ params = self._default_params()
+ params.update(kwargs)
+ response = await self._call_eas_async(prompt, params=params)
+ text = self._process_eas_response(response)
+ return CompletionResponse(text=text)
@llm_chat_callback()
def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
chat_fn = completion_to_chat_decorator(self.complete)
return chat_fn(messages, **kwargs)
+ @llm_chat_callback()
+ async def achat(
+ self, messages: Sequence[ChatMessage], **kwargs: Any
+ ) -> ChatResponse:
+ chat_fn = acompletion_to_chat_decorator(self.complete)
+ return await chat_fn(messages, **kwargs)
+
+ @llm_completion_callback()
+ def stream_complete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
+ return self._stream(prompt=prompt, kwargs=kwargs)
+
@llm_chat_callback()
def stream_chat(
self, messages: Sequence[ChatMessage], **kwargs: Any
@@ -158,6 +175,42 @@ def _call_eas(self, prompt: str = "", params: Dict = {}) -> Any:
except Exception as e:
raise e
+ async def _call_eas_async(self, prompt: str = "", params: Dict = {}) -> Any:
+ """Generate text from the eas service."""
+ headers = {
+ "Content-Type": "application/json",
+ "Authorization": f"{self.token}",
+ }
+ if self.version == "1.0":
+ body = {
+ "input_ids": f"{prompt}",
+ }
+ else:
+ body = {
+ "prompt": f"{prompt}",
+ }
+
+ # add params to body
+ for key, value in params.items():
+ body[key] = value
+
+ # make request
+ async with httpx.AsyncClient() as http_client:
+ response = await http_client.post(
+ url=self.endpoint, headers=headers, json=body, timeout=60
+ )
+
+ if response.status_code != 200:
+ raise Exception(
+ f"Request failed with status code {response.status_code}"
+ f" and message {response.text}"
+ )
+
+ try:
+ return json.loads(response.text)
+ except Exception as e:
+ raise e
+
def _stream(
self,
prompt: str,
diff --git a/src/pai_rag/main.py b/src/pai_rag/main.py
index 54e19a12..4a2cfa8a 100644
--- a/src/pai_rag/main.py
+++ b/src/pai_rag/main.py
@@ -1,6 +1,10 @@
+import asyncio
+from contextlib import asynccontextmanager
import click
import uvicorn
-from pai_rag.app.app import create_app
+from fastapi import FastAPI
+from pai_rag.app.api.service import configure_app
+from pai_rag.app.web.webui import configure_webapp
from logging.config import dictConfig
import os
from pathlib import Path
@@ -8,7 +12,9 @@
_BASE_DIR = Path(__file__).parent
DEFAULT_APPLICATION_CONFIG_FILE = os.path.join(_BASE_DIR, "config/settings.toml")
DEFAULT_HOST = "0.0.0.0"
-DEFAULT_PORT = 8000
+DEFAULT_PORT = 8001
+DEFAULT_RAG_URL = f"http://{DEFAULT_HOST}:{DEFAULT_PORT}/"
+DEFAULT_GRADIO_PORT = 8002
def init_log():
@@ -49,6 +55,33 @@ def init_log():
dictConfig(log_config)
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ # Load the ML model
+ from pai_rag.modules.index.index_daemon import index_daemon
+
+ """Start all the non-blocking service tasks, which run in the background."""
+ asyncio.create_task(index_daemon.refresh_async())
+ yield
+
+
+init_log()
+
+app = FastAPI()
+
+is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
+if is_gunicorn:
+ app = FastAPI(lifespan=lifespan)
+ configure_app(app, DEFAULT_APPLICATION_CONFIG_FILE)
+
+
+async def service_tasks_startup():
+ from pai_rag.modules.index.index_daemon import index_daemon
+
+ """Start all the non-blocking service tasks, which run in the background."""
+ asyncio.create_task(index_daemon.refresh_async())
+
+
@click.group(invoke_without_command=True)
@click.pass_context
@click.option("-V", "--version", is_flag=True, help="Show version and exit.")
@@ -59,6 +92,34 @@ def main(ctx, version):
click.echo(ctx.get_help())
+@main.command()
+@click.option(
+ "-h",
+ "--host",
+ show_default=True,
+ help=f"WebApp Host IP. Default: {DEFAULT_HOST}",
+ default=DEFAULT_HOST,
+)
+@click.option(
+ "-p",
+ "--port",
+ show_default=True,
+ type=int,
+ help=f"WebApp Port. Default: {DEFAULT_GRADIO_PORT}",
+ default=DEFAULT_GRADIO_PORT,
+)
+@click.option(
+ "-c",
+ "--rag-url",
+ show_default=True,
+ help=f"PAI-RAG service endpoint. Default: {DEFAULT_RAG_URL}",
+ default=DEFAULT_RAG_URL,
+)
+def ui(host, port, rag_url):
+ configure_webapp(app=app, web_url=f"http://{host}:{port}/", rag_url=rag_url)
+ uvicorn.run(app, host=host, port=port, loop="asyncio")
+
+
@main.command()
@click.option(
"-h",
@@ -77,14 +138,20 @@ def main(ctx, version):
)
@click.option(
"-c",
- "--config",
+ "--config-file",
show_default=True,
help=f"Configuration file. Default: {DEFAULT_APPLICATION_CONFIG_FILE}",
default=DEFAULT_APPLICATION_CONFIG_FILE,
)
-def run(host, port, config):
- init_log()
-
- endpoint = f"http://{host}:{port}/"
- app = create_app(config, endpoint)
- uvicorn.run(app=app, host=host, port=port, loop="asyncio")
+@click.option(
+ "-w",
+ "--workers",
+ show_default=True,
+ help="Worker Number. Default: 1",
+ type=int,
+ default=1,
+)
+def serve(host, port, config_file, workers):
+ app = FastAPI(lifespan=lifespan)
+ configure_app(app, config_file=config_file)
+ uvicorn.run(app=app, host=host, port=port, loop="asyncio", workers=workers)
diff --git a/src/pai_rag/modules/index/index.py b/src/pai_rag/modules/index/index.py
index 9bb9b2cf..c93b2486 100644
--- a/src/pai_rag/modules/index/index.py
+++ b/src/pai_rag/modules/index/index.py
@@ -1,6 +1,5 @@
import logging
import os
-import sys
from typing import Dict, List, Any
from pai_rag.modules.index.my_vector_store_index import MyVectorStoreIndex
@@ -10,15 +9,12 @@
from pai_rag.modules.index.store import RagStore
from llama_index.vector_stores.faiss import FaissVectorStore
from pai_rag.utils.store_utils import get_store_persist_directory_name
-
-logging.basicConfig(
- stream=sys.stdout,
- level=logging.INFO,
- format="%(asctime)s - %(levelname)s - %(message)s",
-)
+from pai_rag.modules.index.index_entry import index_entry
DEFAULT_PERSIST_DIR = "./storage"
+logger = logging.getLogger(__name__)
+
class RagIndex:
def __init__(self, config, embed_model):
@@ -28,15 +24,17 @@ def __init__(self, config, embed_model):
persist_path = config.get("persist_path", DEFAULT_PERSIST_DIR)
folder_name = get_store_persist_directory_name(config, self.embed_dims)
self.persist_path = os.path.join(persist_path, folder_name)
+ index_entry.register(self.persist_path)
+
is_empty = not os.path.exists(self.persist_path)
rag_store = RagStore(config, self.persist_path, is_empty, self.embed_dims)
- storage_context = rag_store.get_storage_context()
+ self.storage_context = rag_store.get_storage_context()
self.vectordb_type = config["vector_store"].get("type", "faiss").lower()
if is_empty:
- self.vector_index = self.create_indices(storage_context, embed_model)
+ self.vector_index = self.create_indices(self.storage_context, embed_model)
else:
- self.vector_index = self.load_indices(storage_context, embed_model)
+ self.vector_index = self.load_indices(self.storage_context, embed_model)
def _get_embed_vec_dim(self, embed_model):
# Get dimension size of embedding vector
@@ -49,7 +47,6 @@ def create_indices(self, storage_context, embed_model):
nodes=[], storage_context=storage_context, embed_model=embed_model
)
logging.info("Created vector_index.")
-
return vector_index
def load_indices(self, storage_context, embed_model):
@@ -64,6 +61,19 @@ def load_indices(self, storage_context, embed_model):
)
return vector_index
+ def reload(self):
+ if isinstance(self.storage_context.vector_store, FaissVectorStore):
+ rag_store = RagStore(self.config, self.persist_path, False, self.embed_dims)
+ self.storage_context = rag_store.get_storage_context()
+
+ self.vector_index = load_index_from_storage(
+ storage_context=self.storage_context
+ )
+ logger.info(
+ f"FaissIndex {self.persist_path} reloaded with {len(self.vector_index.docstore.docs)} nodes."
+ )
+ return
+
class IndexModule(ConfigurableModule):
"""Class for managing indices.
diff --git a/src/pai_rag/modules/index/index_daemon.py b/src/pai_rag/modules/index/index_daemon.py
new file mode 100644
index 00000000..1efeea52
--- /dev/null
+++ b/src/pai_rag/modules/index/index_daemon.py
@@ -0,0 +1,64 @@
+import asyncio
+import datetime
+import os
+import json
+from pai_rag.modules.module_registry import module_registry
+from pai_rag.modules.index.index_entry import index_entry
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class IndexDaemon:
+ def _get_index_lastUpdated(self, index_path):
+ index_metadata_file = os.path.join(index_path, "index.metadata")
+ if os.path.exists(index_metadata_file):
+ with open(index_metadata_file) as f:
+ metadata = json.loads(f.read())
+ return metadata["lastUpdated"]
+ return None
+
+ def register(self, index_path):
+ if index_path not in index_entry.index_entries:
+ index_entry.index_entries[index_path] = self._get_index_lastUpdated(
+ index_path
+ )
+
+ async def refresh_async(self):
+ while True:
+ logger.info(f"{datetime.datetime.now()} Start scan.")
+ bm25_indexes = list(
+ module_registry.get_mod_instances("BM25IndexModule").values()
+ )
+
+ index_map = module_registry.get_mod_instances("IndexModule")
+ for _, index in index_map.items():
+ index_path = index.persist_path
+ if index_path not in index_entry.index_entries:
+ continue
+
+ lastUpdated = self._get_index_lastUpdated(index_path)
+ logging.debug(
+ f"Comparing {lastUpdated} <---> {index_entry.index_entries[index_path]}"
+ )
+ if lastUpdated != index_entry.index_entries[index_path]:
+ logger.info(f"{datetime.datetime.now()} Reloading index.")
+
+ index.reload()
+
+ for bm25_index in bm25_indexes:
+ if bm25_index.persist_path == index_path:
+ logger.info(
+ f"{datetime.datetime.now()} Reloading bm25 index."
+ )
+ bm25_index.reload()
+
+ index_entry.index_entries[index_path] = lastUpdated
+ logger.info(f"{datetime.datetime.now()} Reloaded index.")
+ module_registry.destroy_config_cache()
+
+ logger.info(f"{datetime.datetime.now()} Index scan complete.")
+ await asyncio.sleep(10)
+
+
+index_daemon = IndexDaemon()
diff --git a/src/pai_rag/modules/index/index_entry.py b/src/pai_rag/modules/index/index_entry.py
new file mode 100644
index 00000000..638396a1
--- /dev/null
+++ b/src/pai_rag/modules/index/index_entry.py
@@ -0,0 +1,25 @@
+import os
+import json
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class IndexEntry:
+ def __init__(self):
+ self.index_entries = {}
+
+ def register(self, index_path):
+ if index_path not in self.index_entries:
+ self.index_entries[index_path] = self._get_index_lastUpdated(index_path)
+
+ def _get_index_lastUpdated(self, index_path):
+ index_metadata_file = os.path.join(index_path, "index.metadata")
+ if os.path.exists(index_metadata_file):
+ with open(index_metadata_file) as f:
+ metadata = json.loads(f.read())
+ return metadata["lastUpdated"]
+ return None
+
+
+index_entry = IndexEntry()
diff --git a/src/pai_rag/modules/index/pai_bm25_index.py b/src/pai_rag/modules/index/pai_bm25_index.py
index 924f914c..b72053a2 100644
--- a/src/pai_rag/modules/index/pai_bm25_index.py
+++ b/src/pai_rag/modules/index/pai_bm25_index.py
@@ -62,18 +62,20 @@ def __init__(
self.k1 = k1
self.b = b
- self.persist_path = os.path.join(persist_path, DEFAULT_STORE_DIR)
- self.parts_path = os.path.join(self.persist_path, DEFAULT_FILE_PART_DIR)
- self.index_file = os.path.join(self.persist_path, DEFAULT_INDEX_FILE)
- self.index_matrix_file = os.path.join(
- self.persist_path, DEFAULT_INDEX_MATRIX_FILE
- )
+ self.persist_path = persist_path
+ self.data_path = os.path.join(persist_path, DEFAULT_STORE_DIR)
+ self.parts_path = os.path.join(self.data_path, DEFAULT_FILE_PART_DIR)
+ self.index_file = os.path.join(self.data_path, DEFAULT_INDEX_FILE)
+ self.index_matrix_file = os.path.join(self.data_path, DEFAULT_INDEX_MATRIX_FILE)
self.workers = workers
-
self.tokenizer = tokenizer or jieba_tokenizer
- logger.info("Start loading local BM25 index!")
+ logger.info(f"Start loading local BM25 index @ {self.data_path}!")
+ self.reload()
+ logger.info(f"Finished loading BM25 index @ {self.data_path}!")
+
+ def reload(self):
if os.path.exists(self.parts_path):
with open(self.index_file, "rb") as f:
self.index: LocalBm25Index = pickle.load(f)
@@ -83,8 +85,7 @@ def __init__(
self.index = LocalBm25Index()
self.index_matrix = None
self.token_map = {}
-
- logger.info("Finished loading BM25 index!")
+ self.doc_cache = {}
def split_doc(self, text_list: List[str], tokenizer: Callable):
tokens_list = []
@@ -158,7 +159,7 @@ def add_docs(self, nodes: List[BaseNode]):
self.index.doc_count += 1
self.index.node_id_map[node_id] = node_index
node_index_list.append(self.index.node_id_map[node_id])
-
+ self.doc_cache[self.index.node_id_map[node_id]] = node
else:
# don't handle image or graph node
pass
@@ -253,31 +254,30 @@ def load_batch_from_part_file(self, batch_ids, part_id):
return nodes
def load_docs_with_index(self, doc_indexes):
- results = []
- if len(doc_indexes) == 0:
- return results
+ filtered_doc_indexes = [idx for idx in doc_indexes if idx not in self.doc_cache]
+ if len(filtered_doc_indexes) == 0:
+ return [self.doc_cache[idx] for idx in doc_indexes]
- index2nodes = {}
- node_indexes = doc_indexes.copy()
+ node_indexes = filtered_doc_indexes.copy()
node_indexes.sort()
bucket_size = DEFAULT_PART_SIZE
batch_ids = []
pre_bucket = -1
- for i in doc_indexes:
+ for i in node_indexes:
bucket = int(i / bucket_size)
if bucket != pre_bucket:
if batch_ids:
batch_nodes = self.load_batch_from_part_file(batch_ids, bucket)
- index2nodes.update(zip(batch_ids, batch_nodes))
+ self.doc_cache.update(zip(batch_ids, batch_nodes))
batch_ids = []
pre_bucket = bucket
batch_ids.append(i)
if batch_ids:
batch_nodes = self.load_batch_from_part_file(batch_ids, bucket)
- index2nodes.update(zip(batch_ids, batch_nodes))
+ self.doc_cache.update(zip(batch_ids, batch_nodes))
- return [index2nodes[i] for i in doc_indexes]
+ return [self.doc_cache[i] for i in doc_indexes]
def query(self, query_str: str, top_n: int = 5) -> List[NodeWithScore]:
results = []
diff --git a/src/pai_rag/modules/llm/llm_module.py b/src/pai_rag/modules/llm/llm_module.py
index 44b98758..7511766b 100644
--- a/src/pai_rag/modules/llm/llm_module.py
+++ b/src/pai_rag/modules/llm/llm_module.py
@@ -3,7 +3,9 @@
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.llms.azure_openai import AzureOpenAI
-from llama_index.llms.dashscope import DashScope
+
+# from llama_index.llms.dashscope import DashScope
+from pai_rag.modules.llm.my_dashscope import MyDashScope
from pai_rag.integrations.llms.paieas.base import PaiEAS
from pai_rag.modules.base.configurable_module import ConfigurableModule
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
@@ -56,7 +58,7 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
model = {model_name}
"""
)
- llm = DashScope(
+ llm = MyDashScope(
model_name=model_name, temperature=config.get("temperature", 0.1)
)
elif source == "paieas":
diff --git a/src/pai_rag/modules/llm/my_dashscope.py b/src/pai_rag/modules/llm/my_dashscope.py
new file mode 100644
index 00000000..d6e89d80
--- /dev/null
+++ b/src/pai_rag/modules/llm/my_dashscope.py
@@ -0,0 +1,365 @@
+"""DashScope llm api."""
+
+from http import HTTPStatus
+from typing import Any, Dict, List, Optional, Sequence, Tuple
+
+from llama_index.core.base.llms.types import (
+ ChatMessage,
+ ChatResponse,
+ ChatResponseGen,
+ CompletionResponse,
+ CompletionResponseGen,
+ LLMMetadata,
+ MessageRole,
+)
+from llama_index.core.bridge.pydantic import Field
+from llama_index.core.callbacks import CallbackManager
+from llama_index.core.constants import DEFAULT_NUM_OUTPUTS, DEFAULT_TEMPERATURE
+from llama_index.core.llms.callbacks import (
+ llm_chat_callback,
+ llm_completion_callback,
+)
+from llama_index.core.llms.custom import CustomLLM
+from llama_index.llms.dashscope.utils import (
+ chat_message_to_dashscope_messages,
+ dashscope_response_to_chat_response,
+ dashscope_response_to_completion_response,
+)
+
+
+class DashScopeGenerationModels:
+ """DashScope Qwen serial models."""
+
+ QWEN_TURBO = "qwen-turbo"
+ QWEN_PLUS = "qwen-plus"
+ QWEN_MAX = "qwen-max"
+ QWEN_MAX_1201 = "qwen-max-1201"
+ QWEN_MAX_LONGCONTEXT = "qwen-max-longcontext"
+
+
+DASHSCOPE_MODEL_META = {
+ DashScopeGenerationModels.QWEN_TURBO: {
+ "context_window": 1024 * 8,
+ "num_output": 1024 * 8,
+ "is_chat_model": True,
+ },
+ DashScopeGenerationModels.QWEN_PLUS: {
+ "context_window": 1024 * 32,
+ "num_output": 1024 * 32,
+ "is_chat_model": True,
+ },
+ DashScopeGenerationModels.QWEN_MAX: {
+ "context_window": 1024 * 8,
+ "num_output": 1024 * 8,
+ "is_chat_model": True,
+ },
+ DashScopeGenerationModels.QWEN_MAX_1201: {
+ "context_window": 1024 * 8,
+ "num_output": 1024 * 8,
+ "is_chat_model": True,
+ },
+ DashScopeGenerationModels.QWEN_MAX_LONGCONTEXT: {
+ "context_window": 1024 * 30,
+ "num_output": 1024 * 30,
+ "is_chat_model": True,
+ },
+}
+
+
+def call_with_messages(
+ model: str,
+ messages: List[Dict],
+ parameters: Optional[Dict] = None,
+ api_key: Optional[str] = None,
+ **kwargs: Any,
+) -> Dict:
+ try:
+ from dashscope import Generation
+ except ImportError:
+ raise ValueError(
+ "DashScope is not installed. Please install it with "
+ "`pip install dashscope`."
+ )
+ return Generation.call(
+ model=model, messages=messages, api_key=api_key, **parameters
+ )
+
+
+async def call_with_messages_async(
+ model: str,
+ messages: List[Dict],
+ parameters: Optional[Dict] = None,
+ api_key: Optional[str] = None,
+ **kwargs: Any,
+) -> Dict:
+ try:
+ from dashscope import AioGeneration
+ except ImportError:
+ raise ValueError(
+ "DashScope is not installed. Please install it with "
+ "`pip install dashscope`."
+ )
+ return await AioGeneration.call(
+ model=model, messages=messages, api_key=api_key, **parameters
+ )
+
+
+class MyDashScope(CustomLLM):
+ """DashScope LLM."""
+
+ model_name: str = Field(
+ default=DashScopeGenerationModels.QWEN_MAX,
+ description="The DashScope model to use.",
+ )
+ max_tokens: Optional[int] = Field(
+ description="The maximum number of tokens to generate.",
+ default=DEFAULT_NUM_OUTPUTS,
+ gt=0,
+ )
+ incremental_output: Optional[bool] = Field(
+ description="Control stream output, If False, the subsequent \
+ output will include the content that has been \
+ output previously.",
+ default=True,
+ )
+ enable_search: Optional[bool] = Field(
+ description="The model has a built-in Internet search service. \
+ This parameter controls whether the model refers to \
+ the Internet search results when generating text.",
+ default=False,
+ )
+ stop: Optional[Any] = Field(
+ description="str, list of str or token_id, list of token id. It will automatically \
+ stop when the generated content is about to contain the specified string \
+ or token_ids, and the generated content does not contain \
+ the specified content.",
+ default=None,
+ )
+ temperature: Optional[float] = Field(
+ description="The temperature to use during generation.",
+ default=DEFAULT_TEMPERATURE,
+ gte=0.0,
+ lte=2.0,
+ )
+ top_k: Optional[int] = Field(
+ description="Sample counter when generate.", default=None
+ )
+ top_p: Optional[float] = Field(
+ description="Sample probability threshold when generate."
+ )
+ seed: Optional[int] = Field(
+ description="Random seed when generate.", default=1234, gte=0
+ )
+ repetition_penalty: Optional[float] = Field(
+ description="Penalty for repeated words in generated text; \
+ 1.0 is no penalty, values greater than 1 discourage \
+ repetition.",
+ default=None,
+ )
+ api_key: str = Field(
+ default=None, description="The DashScope API key.", exclude=True
+ )
+
+ def __init__(
+ self,
+ model_name: Optional[str] = DashScopeGenerationModels.QWEN_MAX,
+ max_tokens: Optional[int] = DEFAULT_NUM_OUTPUTS,
+ incremental_output: Optional[int] = True,
+ enable_search: Optional[bool] = False,
+ stop: Optional[Any] = None,
+ temperature: Optional[float] = DEFAULT_TEMPERATURE,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ seed: Optional[int] = 1234,
+ api_key: Optional[str] = None,
+ callback_manager: Optional[CallbackManager] = None,
+ **kwargs: Any,
+ ):
+ super().__init__(
+ model_name=model_name,
+ max_tokens=max_tokens,
+ incremental_output=incremental_output,
+ enable_search=enable_search,
+ stop=stop,
+ temperature=temperature,
+ top_k=top_k,
+ top_p=top_p,
+ seed=seed,
+ api_key=api_key,
+ callback_manager=callback_manager,
+ kwargs=kwargs,
+ )
+
+ @classmethod
+ def class_name(cls) -> str:
+ return "DashScope_LLM"
+
+ @property
+ def metadata(self) -> LLMMetadata:
+ DASHSCOPE_MODEL_META[self.model_name]["num_output"] = (
+ self.max_tokens or DASHSCOPE_MODEL_META[self.model_name]["num_output"]
+ )
+ return LLMMetadata(
+ model_name=self.model_name, **DASHSCOPE_MODEL_META[self.model_name]
+ )
+
+ def _get_default_parameters(self) -> Dict:
+ params: Dict[Any, Any] = {}
+ if self.max_tokens is not None:
+ params["max_tokens"] = self.max_tokens
+ params["incremental_output"] = self.incremental_output
+ params["enable_search"] = self.enable_search
+ if self.stop is not None:
+ params["stop"] = self.stop
+ if self.temperature is not None:
+ params["temperature"] = self.temperature
+
+ if self.top_k is not None:
+ params["top_k"] = self.top_k
+
+ if self.top_p is not None:
+ params["top_p"] = self.top_p
+ if self.seed is not None:
+ params["seed"] = self.seed
+
+ return params
+
+ def _get_input_parameters(
+ self, prompt: str, **kwargs: Any
+ ) -> Tuple[ChatMessage, Dict]:
+ parameters = self._get_default_parameters()
+ parameters.update(kwargs)
+ parameters["stream"] = False
+ # we only use message response
+ parameters["result_format"] = "message"
+ message = ChatMessage(
+ role=MessageRole.USER.value,
+ content=prompt,
+ )
+ return message, parameters
+
+ @llm_completion_callback()
+ def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
+ message, parameters = self._get_input_parameters(prompt=prompt, **kwargs)
+ parameters.pop("incremental_output", None)
+ parameters.pop("stream", None)
+ messages = chat_message_to_dashscope_messages([message])
+ response = call_with_messages(
+ model=self.model_name,
+ messages=messages,
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+ return dashscope_response_to_completion_response(response)
+
+ @llm_completion_callback()
+ def stream_complete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
+ message, parameters = self._get_input_parameters(prompt=prompt, kwargs=kwargs)
+ parameters["incremental_output"] = True
+ parameters["stream"] = True
+ responses = call_with_messages(
+ model=self.model_name,
+ messages=chat_message_to_dashscope_messages([message]),
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+
+ def gen() -> CompletionResponseGen:
+ content = ""
+ for response in responses:
+ if response.status_code == HTTPStatus.OK:
+ top_choice = response.output.choices[0]
+ incremental_output = top_choice["message"]["content"]
+ if not incremental_output:
+ incremental_output = ""
+
+ content += incremental_output
+ yield CompletionResponse(
+ text=content, delta=incremental_output, raw=response
+ )
+ else:
+ yield CompletionResponse(text="", raw=response)
+ return
+
+ return gen()
+
+ @llm_chat_callback()
+ def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
+ parameters = self._get_default_parameters()
+ parameters.update({**kwargs})
+ parameters.pop("stream", None)
+ parameters.pop("incremental_output", None)
+ parameters["result_format"] = "message" # only use message format.
+ response = call_with_messages(
+ model=self.model_name,
+ messages=chat_message_to_dashscope_messages(messages),
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+ return dashscope_response_to_chat_response(response)
+
+ @llm_chat_callback()
+ def stream_chat(
+ self, messages: Sequence[ChatMessage], **kwargs: Any
+ ) -> ChatResponseGen:
+ parameters = self._get_default_parameters()
+ parameters.update({**kwargs})
+ parameters["stream"] = True
+ parameters["incremental_output"] = True
+ parameters["result_format"] = "message" # only use message format.
+ response = call_with_messages(
+ model=self.model_name,
+ messages=chat_message_to_dashscope_messages(messages),
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+
+ def gen() -> ChatResponseGen:
+ content = ""
+ for r in response:
+ if r.status_code == HTTPStatus.OK:
+ top_choice = r.output.choices[0]
+ incremental_output = top_choice["message"]["content"]
+ role = top_choice["message"]["role"]
+ content += incremental_output
+ yield ChatResponse(
+ message=ChatMessage(role=role, content=content),
+ delta=incremental_output,
+ raw=r,
+ )
+ else:
+ yield ChatResponse(message=ChatMessage(), raw=response)
+ return
+
+ return gen()
+
+ @llm_chat_callback()
+ async def achat(
+ self, messages: Sequence[ChatMessage], **kwargs: Any
+ ) -> ChatResponse:
+ parameters = self._get_default_parameters()
+ parameters.update({**kwargs})
+ parameters.pop("stream", None)
+ parameters.pop("incremental_output", None)
+ parameters["result_format"] = "message" # only use message format.
+ response = await call_with_messages_async(
+ model=self.model_name,
+ messages=chat_message_to_dashscope_messages(messages),
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+ return dashscope_response_to_chat_response(response)
+
+ @llm_completion_callback()
+ async def acomplete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
+ message, parameters = self._get_input_parameters(prompt=prompt, **kwargs)
+ parameters.pop("incremental_output", None)
+ parameters.pop("stream", None)
+ messages = chat_message_to_dashscope_messages([message])
+ response = await call_with_messages_async(
+ model=self.model_name,
+ messages=messages,
+ api_key=self.api_key,
+ parameters=parameters,
+ )
+ return dashscope_response_to_completion_response(response)
diff --git a/src/pai_rag/modules/module_registry.py b/src/pai_rag/modules/module_registry.py
index 6d6c673b..4048f3de 100644
--- a/src/pai_rag/modules/module_registry.py
+++ b/src/pai_rag/modules/module_registry.py
@@ -1,4 +1,5 @@
import hashlib
+import json
from typing import Dict, Any
from pai_rag.modules.base.module_constants import MODULE_PARAM_CONFIG
import pai_rag.modules as modules
@@ -30,6 +31,7 @@
class ModuleRegistry:
def __init__(self):
+ self._cache_by_config = {}
self._mod_cls_map = {}
self._mod_deps_map = {}
self._mod_deps_map_inverted = {}
@@ -51,13 +53,25 @@ def __init__(self):
self._mod_deps_map_inverted[dep].append(m_name)
def _get_param_hash(self, params: Dict[str, Any]):
- repr_str = repr(sorted(params.items())).encode("utf-8")
+ repr_str = json.dumps(params, default=repr, sort_keys=True).encode("utf-8")
return hashlib.sha256(repr_str).hexdigest()
def get_module_with_config(self, module_key, config):
- return self._create_mod_lazily(module_key, config)
+ key = repr(config)
+ if key in self._cache_by_config and module_key in self._cache_by_config[key]:
+ return self._cache_by_config[key][module_key]
+
+ else:
+ mod = self._create_mod_lazily(module_key, config)
+ if key not in self._cache_by_config:
+ self._cache_by_config[key] = {}
+
+ self._cache_by_config[key][module_key] = mod
+ return mod
def init_modules(self, config):
+ key = repr(config)
+
mod_cache = {}
mod_stack = []
mod_ref_count = {}
@@ -71,6 +85,9 @@ def init_modules(self, config):
mod = mod_stack.pop()
mod_obj = self._create_mod_lazily(mod, config, mod_cache)
mod_cache[mod] = mod_obj
+ if key not in self._cache_by_config:
+ self._cache_by_config[key] = {}
+ self._cache_by_config[key][mod] = mod_obj
# update module ref count that depends on on
ref_mods = self._mod_deps_map_inverted.get(mod, [])
@@ -102,6 +119,9 @@ def _create_mod_lazily(self, mod_name, config, mod_cache=None):
params[dep] = self._create_mod_lazily(dep, config, mod_cache)
instance_key = self._get_param_hash(params)
+ if mod_name == "IndexModule":
+ print(instance_key, params)
+
if instance_key not in self._mod_instance_map[mod_name]:
logger.info(f"Creating new instance for module {mod_name} {instance_key}.")
self._mod_instance_map[mod_name][instance_key] = mod_cls.get_or_create(
@@ -109,5 +129,11 @@ def _create_mod_lazily(self, mod_name, config, mod_cache=None):
)
return self._mod_instance_map[mod_name][instance_key]
+ def get_mod_instances(self, mod_name: str):
+ return self._mod_instance_map[mod_name]
+
+ def destroy_config_cache(self):
+ self._cache_by_config = {}
+
module_registry = ModuleRegistry()
diff --git a/src/pai_rag/modules/postprocessor/postprocessor.py b/src/pai_rag/modules/postprocessor/postprocessor.py
index ebadfe44..998c8981 100644
--- a/src/pai_rag/modules/postprocessor/postprocessor.py
+++ b/src/pai_rag/modules/postprocessor/postprocessor.py
@@ -53,7 +53,9 @@ def _create_new_instance(self, new_params: Dict[str, Any]):
logger.info(
f"[PostProcessor]: Reranker model used with top_n {top_n}, model {model_name}."
)
- post_processors.append(FlagEmbeddingReranker(model=model, top_n=top_n))
+ post_processors.append(
+ FlagEmbeddingReranker(model=model, top_n=top_n, use_fp16=True),
+ )
else:
logger.info("[PostProcessor]: No Reranker used.")
diff --git a/src/pai_rag/modules/queryengine/my_retriever_query_engine.py b/src/pai_rag/modules/queryengine/my_retriever_query_engine.py
index 8a18ac73..3a615f7a 100644
--- a/src/pai_rag/modules/queryengine/my_retriever_query_engine.py
+++ b/src/pai_rag/modules/queryengine/my_retriever_query_engine.py
@@ -4,7 +4,6 @@
from llama_index.core.callbacks.schema import CBEventType, EventPayload
from llama_index.core.schema import NodeWithScore, QueryBundle
from llama_index.core.query_engine import RetrieverQueryEngine
-
import llama_index.core.instrumentation as instrument
import logging
@@ -24,31 +23,19 @@ class MyRetrieverQueryEngine(RetrieverQueryEngine):
def retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
nodes = self._retriever.retrieve(query_bundle)
- try:
- result = self._retriever._selector.select(
- self._retriever._metadatas, query_bundle
- )
- if result.ind == 0:
- return nodes
- else:
- return self._apply_node_postprocessors(nodes, query_bundle=query_bundle)
- except Exception as ex:
- logger.warn(f"{ex}")
- return self._apply_node_postprocessors(nodes, query_bundle=query_bundle)
+ return self._apply_node_postprocessors(nodes, query_bundle=query_bundle)
+ # 支持异步
async def aretrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
nodes = await self._retriever.aretrieve(query_bundle)
- try:
- result = await self._retriever._selector.aselect(
- self._retriever._metadatas, query_bundle
+
+ for node_postprocessor in self._node_postprocessors:
+ nodes = node_postprocessor.postprocess_nodes(
+ nodes,
+ query_bundle=query_bundle,
)
- if result.ind == 0:
- return nodes
- else:
- return self._apply_node_postprocessors(nodes, query_bundle=query_bundle)
- except Exception as ex:
- logger.warn(f"{ex}")
- return self._apply_node_postprocessors(nodes, query_bundle=query_bundle)
+
+ return nodes
@dispatcher.span
def _query(self, query_bundle: QueryBundle) -> RESPONSE_TYPE:
@@ -72,12 +59,10 @@ async def _aquery(self, query_bundle: QueryBundle) -> RESPONSE_TYPE:
CBEventType.QUERY, payload={EventPayload.QUERY_STR: query_bundle.query_str}
) as query_event:
nodes = await self.aretrieve(query_bundle)
-
response = await self._response_synthesizer.asynthesize(
query=query_bundle,
nodes=nodes,
)
-
query_event.on_end(payload={EventPayload.RESPONSE: response})
return response
From d779dd6282d1ef0308ff350c757bc2115dd2e081 Mon Sep 17 00:00:00 2001
From: wwxxzz
Date: Thu, 27 Jun 2024 15:22:45 +0800
Subject: [PATCH 15/69] Add guides for env and docker (#81)
* Add guides for env
* add guides for docker build
* Add README
---
README.md | 286 +++++++++++++---------
README_zh.md | 286 ++++++++++++++++++++++
docs/docker_build.md | 33 +++
docs/figures/framework.jpg | Bin 0 -> 884977 bytes
{src/pai_rag/docs => docs}/tabular_doc.md | 0
5 files changed, 484 insertions(+), 121 deletions(-)
create mode 100644 README_zh.md
create mode 100644 docs/docker_build.md
create mode 100644 docs/figures/framework.jpg
rename {src/pai_rag/docs => docs}/tabular_doc.md (100%)
diff --git a/README.md b/README.md
index 163d1530..9bf71977 100644
--- a/README.md
+++ b/README.md
@@ -1,154 +1,178 @@
-# PAI-RAG: An easy-to-use framework for modular RAG.
+
+
PAI-RAG: An easy-to-use framework for modular RAG
+
[![PAI-RAG CI](https://github.com/aigc-apps/PAI-RAG/actions/workflows/main.yml/badge.svg)](https://github.com/aigc-apps/PAI-RAG/actions/workflows/main.yml)
-## Get Started
+
+ English |
+ 简体中文 |
+
-### 本地启动
+
+📕 Contents
-#### Step1: Clone Repo
+- 💡 [What is PAI-RAG?](#what-is-pai-rag)
+- 🌟 [Key Features](#key-features)
+- 🔎 [Get Started](#get-started)
+ - [Local](#run-in-local-environment)
+ - [Docker](#run-in-docker)
+- 🔧 [API Service](#api-service)
-```bash
-git clone git@github.com:aigc-apps/PAI-RAG.git
-```
+
-#### Step2: 配置环境
+# 💡 What is PAI-RAG?
-本项目使用poetry进行管理,建议在安装环境之前先创建一个空环境。为了确保环境一致性并避免因Python版本差异造成的问题,我们指定Python版本为3.10。
+PAI-RAG is an easy-to-use opensource framework for modular RAG (Retrieval-Augmented Generation). It combines LLM (Large Language Model) to provide truthful question-answering capabilities, supports flexible configuration and custom development of each module of the RAG system. It offers a production-level RAG workflow for businesses of any scale based on Alibaba Cloud's Platform of Artificial Intelligence (PAI).
-```bash
-conda create -n rag_env python==3.10
-conda activate rag_env
-```
+# 🌟 Key Features
-使用poetry安装项目依赖包
+![framework](docs/figures/framework.jpg)
-```bash
-pip install poetry
-poetry install
-```
+- Modular design, flexible and configurable
+- Built on community open source components, low customization threshold
+- Multi-dimensional automatic evaluation system, easy to grasp the performance quality of each module
+- Integrated llm-based-application tracing and evaluation visualization tools
+- Interactive UI/API calls, convenient iterative tuning experience
+- Alibaba Cloud fast scenario deployment/image custom deployment/open source private deployment
-#### Step3:加载数据
+# 🔎 Get Started
-向当前索引存储中插入directory_path目录下的新文件
+## Run in Local Environment
-```bash
-load_data -c src/pai_rag/config/settings.yaml -d directory_path
-```
+1. Clone Repo
-#### Step4: 启动RAG服务
+ ```bash
+ git clone git@github.com:aigc-apps/PAI-RAG.git
+ ```
-使用OpenAI API,需要在命令行引入环境变量 export OPENAI_API_KEY=""
-使用DashScope API,需要在命令行引入环境变量 export DASHSCOPE_API_KEY=""
+2. Development Environment Settings
-```bash
-# 启动,支持自定义host(默认0.0.0.0), port(默认8001), config(默认src/pai_rag/config/settings.yaml)
-pai_rag serve [--host HOST] [--port PORT] [--config CONFIG_FILE]
-```
+ This project uses poetry for management. To ensure environmental consistency and avoid problems caused by Python version differences, we specify Python version 3.10.
-你可以使用命令行向服务侧发送API请求。比如调用[Upload API](#upload-api)上传知识库文件。
+ ```bash
+ conda create -n rag_env python==3.10
+ conda activate rag_env
+ ```
-##### Query API
+- (1) CPU
-- **Rag Query请求**
+ Use poetry to install project dependency packages directly:
-```bash
-curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"PAI是什么?"}'
-```
+ ```bash
+ pip install poetry
+ poetry install
+ ```
-- **多轮对话请求**
+- (2) GPU
-```bash
-curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"PAI是什么?"}'
+ First replace the default pyproject.toml with the GPU version, and then use poetry to install the project dependency package:
-# 传入session_id:对话历史会话唯一标识,传入session_id后,将对话历史进行记录,调用大模型将自动携带存储的对话历史。
-curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有什么优势?", "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
+ ```bash
+ mv pyproject_gpu.toml pyproject.toml && rm poetry.lock
+ pip install poetry
+ poetry install
+ ```
-# 传入chat_history:用户与模型的对话历史,list中的每个元素是形式为{"user":"用户输入","bot":"模型输出"}的一轮对话,多轮对话按时间顺序排列。
-curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有哪些功能?", "chat_history": [{"user":"PAI是什么?", "bot":"PAI是阿里云的人工智能平台,它提供一站式的机器学习解决方案。这个平台支持各种机器学习任务,包括有监督学习、无监督学习和增强学习,适用于营销、金融、社交网络等多个场景。"}]}'
+- Common network timeout issues
-# 同时传入session_id和chat_history:会用chat_history对存储的session_id所对应的对话历史进行追加更新
-curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有什么优势?", "chat_history": [{"user":"PAI是什么?", "bot":"PAI是阿里云的人工智能平台,它提供一站式的机器学习解决方案。这个平台支持各种机器学习任务,包括有监督学习、无监督学习和增强学习,适用于营销、金融、社交网络等多个场景。"}], "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
-```
+ Note: During the installation, if you encounter a network connection timeout, you can add the Alibaba Cloud or Tsinghua mirror source and append the following lines to the end of the pyproject.toml file:
-- **Agent及调用Fucntion Tool的简单对话**
+ ```bash
+ [[tool.poetry.source]]
+ name = "mirrors"
+ url = "http://mirrors.aliyun.com/pypi/simple/" # Aliyun
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple/" # Qsinghua
+ priority = "default"
+ ```
-```bash
-curl -X 'POST' http://127.0.0.1:8000/service/query/agent -H "Content-Type: application/json" -d '{"question":"今年是2024年,10年前是哪一年?"}'
-```
+ After that, execute the following commands:
-##### Evaluation API
+ ```bash
+ poetry lock
+ poetry install
+ ```
-支持三种评估模式:全链路评估、检索效果评估、生成效果评估。
+3. Load Data
-初次调用时会在 localdata/evaluation 下自动生成一个评估数据集(qc_dataset.json, 其中包含了由LLM生成的query、reference_contexts、reference_node_id、reference_answer)。同时评估过程中涉及大量的LLM调用,因此会耗时较久。
+ Insert new files in the directory directory_path into the current index storage:
-- **(1)全链路效果评估(All)**
+ ```bash
+ load_data -c src/pai_rag/config/settings.yaml -d directory_path
+ ```
-```bash
-curl -X 'POST' http://127.0.0.1:8000/service/batch_evaluate
-```
+4. Run RAG Service
-返回示例:
-
-```json
-{
- "status": 200,
- "result": {
- "batch_number": 6,
- "hit_rate_mean": 1.0,
- "mrr_mean": 0.91666667,
- "faithfulness_mean": 0.8333334,
- "correctness_mean": 4.5833333,
- "similarity_mean": 0.88153079
- }
-}
-```
+ To use the OpenAI or DashScope API, you need to introduce environment variables:
-- **(2)检索效果评估(Retrieval)**
+ ```bash
+ export OPENAI_API_KEY=""
+ export DASHSCOPE_API_KEY=""
+ ```
-```bash
-curl -X 'POST' http://127.0.0.1:8000/service/batch_evaluate/retrieval
-```
+ ```bash
+ # Support custom host (default 0.0.0.0), port (default 8001), config (default src/pai_rag/config/settings.yaml)
+ pai_rag serve [--host HOST] [--port PORT] [--config CONFIG_FILE]
+ ```
-返回示例:
-
-```json
-{
- "status": 200,
- "result": {
- "batch_number": 6,
- "hit_rate_mean": 1.0,
- "mrr_mean": 0.91667
- }
-}
-```
+5. Run RAG WebUI
-- **(3)生成效果评估(Response)**
+ ```bash
+ # Supports custom host (default 0.0.0.0), port (default 8002), config (default localhost:8001)
+ pai_rag ui [--host HOST] [--port PORT] [rag-url RAG_URL]
+ ```
+
+ You can also open http://127.0.0.1:8002/ to configure the RAG service and upload local data.
+
+## Run in Docker
+
+To make it easier to use and save time on environment installation, we also provide a method to start directly based on the image.
+
+### Use public images directly
+
+1. RAG Service
+
+- CPU
+
+ ```bash
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2
+
+ # -p (port) -v (mount embedding and rerank model directories) -e (set environment variables, if using Dashscope LLM/Embedding, need to be introduced) -w (number of workers, can be specified as the approximate number of CPU cores)
+ docker run -p 8001:8001 -v /huggingface:/huggingface -e DASHSCOPE_API_KEY=sk-xxxx -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2 gunicorn -b 0.0.0.0:8001 -w 16 -k uvicorn.workers.UvicornH11Worker pai_rag.main:app
+ ```
+
+- GPU
+
+ ```bash
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_gpu
+
+ # -p (port) -v (mount embedding and rerank model directories) -e (set environment variables, if using Dashscope LLM/Embedding, you need to introduce it) -w (number of workers, which can be specified as the approximate number of CPU cores)
+ docker run -p 8001:8001 -v /huggingface:/huggingface --gpus all -e DASHSCOPE_API_KEY=sk-xxxx -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_gpu gunicorn -b 0.0.0.0:8001 -w 16 -k uvicorn.workers.UvicornH11Worker pai_rag.main:app
+ ```
+
+2. RAG UI
```bash
-curl -X 'POST' http://127.0.0.1:8000/service/batch_evaluate/response
-```
+docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_ui
-返回示例:
-
-```json
-{
- "status": 200,
- "result": {
- "batch_number": 6,
- "faithfulness_mean": 0.8333334,
- "correctness_mean": 4.58333333,
- "similarity_mean": 0.88153079
- }
-}
+docker run --network host -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_ui
```
-##### Upload API
+### Build your own image based on Dockerfile
+
+You can refer to [How to Build Docker](docs/docker_build.md) to build the image yourself.
+
+After the image is built, you can refer to the above steps to start the Rag service and WebUI.
+
+# 🔧 API Service
+
+You can use the command line to send API requests to the server, for example, calling the [Upload API](#upload-api) to upload a knowledge base file.
+
+## Upload API
-支持通过API的方式上传本地文件,并支持指定不同的faiss_path,每次发送API请求会返回一个task_id,之后可以通过task_id来查看文件上传状态(processing、completed、failed)。
+It supports uploading local files through API and supports specifying different failure_paths. Each time an API request is sent, a task_id will be returned. The file upload status (processing, completed, failed) can then be checked through the task_id.
-- **(1)上传(upload_local_data)**
+- upload_local_data
```bash
curl -X 'POST' http://127.0.0.1:8000/service/upload_local_data -H 'Content-Type: multipart/form-data' -F 'file=@local_path/PAI.txt' -F 'faiss_path=localdata/storage'
@@ -156,7 +180,7 @@ curl -X 'POST' http://127.0.0.1:8000/service/upload_local_data -H 'Content-Type:
# Return: {"task_id": "2c1e557733764fdb9fefa063538914da"}
```
-- **(2)查看上传状态(upload_local_data)**
+- get_upload_state
```bash
curl http://127.0.0.1:8077/service/get_upload_state\?task_id\=2c1e557733764fdb9fefa063538914da
@@ -164,29 +188,49 @@ curl http://127.0.0.1:8077/service/get_upload_state\?task_id\=2c1e557733764fdb9f
# Return: {"task_id":"2c1e557733764fdb9fefa063538914da","status":"completed"}
```
-### RAG WEB UI
+## Query API
+
+- Supports three dialogue modes:
+ - /query/retrieval
+ - /query/llm
+ - /query: (default) RAG (retrieval + llm)
```bash
-# 启动,支持自定义host(默认0.0.0.0), port(默认8002), config(默认localhost:8001)
-pai_rag ui [--host HOST] [--port PORT] [rag-url RAG_URL]
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"PAI是什么?"}'
```
-你也可以打开http://127.0.0.1:8002/ 来配置RAG服务以及上传本地数据。
-
-### 独立脚本文件:不依赖于整体服务的启动,可独立运行
-
-1. 向当前索引存储中插入新文件
+- Multi-round dialogue
```bash
-load_data -d directory_path
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"What is PAI?"}'
```
-2. 生成QA评估测试集和效果评估
+> Parameters: session_id
+>
+> The unique identifier of the conversation history session. After the session_id is passed in, the conversation history will be recorded. Calling the large model will automatically carry the stored conversation history.
+>
+> ```bash
+> curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"What are its advantages?", "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
+> ```
+
+> Parameters: chat_history
+>
+> The conversation history between the user and the model. Each element in the list is a round of conversation in the form of {"user":"user input","bot":"model output"}. Multiple rounds of conversations are arranged in chronological order.
+>
+> ```bash
+> curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"What are its features?", "chat_history": [{"user":"What is PAI?", "bot":"PAI is Alibaba Cloud's artificial intelligence platform, which provides a one-stop machine learning solution. This platform supports various machine learning tasks, including supervised learning, unsupervised learning, and reinforcement learning, and is suitable for multiple scenarios such as marketing, finance, and social networks."}]}'
+> ```
+
+> Parameters: session_id + chat_history
+>
+> Chat_history will be used to append and update the conversation history corresponding to the stored session_id
+>
+> ```bash
+> curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有什么优势?", "chat_history": [{"user":"PAI是什么?", "bot":"PAI是阿里云的人工智能平台,它提供一站式的机器学习解决方案。这个平台支持各种机器学习任务,包括有监督学习、无监督学习和增强学习,适用于营销、金融、社交网络等多个场景。"}], "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
+> ```
-- type(t): 评估类型,可选,['retrieval', 'response', 'all'],默认为'all'
-- overwrite(o): 是否重新生成QA文件,适用于有新增文件的情况,可选 ['True', 'False'],默认为'False'
-- file_path(f): 评估结果的输出文件位置,可选,默认为'localdata/evaluation/batch_eval_results.xlsx'
+- Agent And Function Tool
```bash
-evaluation -t retrieval -o True -f results_output_path
+curl -X 'POST' http://127.0.0.1:8000/service/query/agent -H "Content-Type: application/json" -d '{"question":"This year is 2024. What year was it 10 years ago?"}'
```
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 00000000..a075e8d9
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,286 @@
+
+
PAI-RAG: 一个易于使用的模块化RAG框架
+
+
+[![PAI-RAG CI](https://github.com/aigc-apps/PAI-RAG/actions/workflows/main.yml/badge.svg)](https://github.com/aigc-apps/PAI-RAG/actions/workflows/main.yml)
+
+
+📕 目录
+
+- 💡 [什么是PAI-RAG?](#什么是pai-rag)
+- 🌟 [主要模块和功能](#主要模块和功能)
+- 🔎 [快速开始](#快速开始)
+ - [本地环境](#方式一本地环境)
+ - [Docker镜像](#方式二docker镜像)
+- 🔧 [API服务](#api服务)
+
+
+
+# 💡 什么是PAI-RAG?
+
+PAI-RAG 是一个易于使用的模块化 RAG(检索增强生成)开源框架,结合 LLM(大型语言模型)提供真实问答能力,支持 RAG 系统各模块灵活配置和定制开发,为基于阿里云人工智能平台(PAI)的任何规模的企业提供生产级的 RAG 系统。
+
+# 🌟 主要模块和功能
+
+![framework](docs/figures/framework.jpg)
+
+- 模块化设计,灵活可配置
+- 基于社区开源组件构建,定制化门槛低
+- 多维度自动评估体系,轻松掌握各模块性能质量
+- 集成全链路可观测和评估可视化工具
+- 交互式UI/API调用,便捷的迭代调优体验
+- 阿里云快速场景化部署/镜像自定义部署/开源私有化部署
+
+# 🔎 快速开始
+
+## 方式一:本地环境
+
+1. 克隆仓库
+
+ ```bash
+ git clone git@github.com:aigc-apps/PAI-RAG.git
+ ```
+
+2. 配置开发环境
+
+ 本项目使用poetry进行管理,若在本地环境下使用,建议在安装环境之前先创建一个空环境。为了确保环境一致性并避免因Python版本差异造成的问题,我们指定Python版本为3.10。
+
+ ```bash
+ conda create -n rag_env python==3.10
+ conda activate rag_env
+ ```
+
+- (1) CPU环境
+
+ 直接使用poetry安装项目依赖包:
+
+ ```bash
+ pip install poetry
+ poetry install
+ ```
+
+- (2) GPU环境
+
+ 首先替换默认 pyproject.toml 为 GPU 版本, 再使用poetry安装项目依赖包:
+
+ ```bash
+ mv pyproject_gpu.toml pyproject.toml && rm poetry.lock
+ pip install poetry
+ poetry install
+ ```
+
+- 常见网络超时问题
+
+ 注:在安装过程中,若遇到网络连接超时的情况,可以添加阿里云或清华的镜像源,在 pyproject.toml 文件末尾追加以下几行:
+
+ ```bash
+ [[tool.poetry.source]]
+ name = "mirrors"
+ url = "http://mirrors.aliyun.com/pypi/simple/" # 阿里云
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple/" # 清华
+ priority = "default"
+ ```
+
+ 之后,再依次执行以下命令:
+
+ ```bash
+ poetry lock
+ poetry install
+ ```
+
+3. 加载数据
+
+ 向当前索引存储中插入directory_path目录下的新文件
+
+ ```bash
+ load_data -c src/pai_rag/config/settings.yaml -d directory_path
+ ```
+
+4. 启动RAG服务
+
+ 使用OpenAI API,需要在命令行引入环境变量
+
+ ```bash
+ export OPENAI_API_KEY=""
+ ```
+
+ 使用DashScope API,需要在命令行引入环境变量
+
+ ```bash
+ export DASHSCOPE_API_KEY=""
+ ```
+
+ ```bash
+ # 启动,支持自定义host(默认0.0.0.0), port(默认8001), config(默认src/pai_rag/config/settings.yaml)
+ pai_rag serve [--host HOST] [--port PORT] [--config CONFIG_FILE]
+ ```
+
+5. 启动RAG WebUI
+
+ ```bash
+ # 启动,支持自定义host(默认0.0.0.0), port(默认8002), config(默认localhost:8001)
+ pai_rag ui [--host HOST] [--port PORT] [rag-url RAG_URL]
+ ```
+
+ 你也可以打开http://127.0.0.1:8002/ 来配置RAG服务以及上传本地数据。
+
+## 方式二:Docker镜像
+
+为了更方便使用,节省较长时间的环境安装问题,我们也提供了直接基于镜像启动的方式。
+
+### 使用公开镜像
+
+1. 启动RAG服务
+
+- CPU
+
+ ```bash
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2
+
+ # 启动: -p(端口) -v(挂载embedding和rerank模型目录) -e(设置环境变量,若使用Dashscope LLM/Embedding,需要引入) -w(worker数量,可以指定为近似cpu核数)
+ docker run -p 8001:8001 -v /huggingface:/huggingface -e DASHSCOPE_API_KEY=sk-xxxx -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2 gunicorn -b 0.0.0.0:8001 -w 16 -k uvicorn.workers.UvicornH11Worker pai_rag.main:app
+ ```
+
+- GPU
+
+ ```bash
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_gpu
+
+ # 启动: -p(端口) -v(挂载embedding和rerank模型目录) -e(设置环境变量,若使用Dashscope LLM/Embedding,需要引入) -w(worker数量,可以指定为近似cpu核数)
+ docker run -p 8001:8001 -v /huggingface:/huggingface --gpus all -e DASHSCOPE_API_KEY=sk-xxxx -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_gpu gunicorn -b 0.0.0.0:8001 -w 16 -k uvicorn.workers.UvicornH11Worker pai_rag.main:app
+ ```
+
+2. 启动RAG WebUI
+
+```bash
+docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_ui
+
+docker run --network host -d mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/mybigpai/pairag:0.0.2_ui
+```
+
+### 基于Dockerfile自行构建镜像
+
+可以参考[How to Build Docker](docs/docker_build.md)来自行构建镜像。
+
+镜像构建完成后可参考【使用公开镜像】的步骤启动RAG服务和WebUI。
+
+# 🔧 API服务
+
+你可以使用命令行向服务侧发送API请求。比如调用[Upload API](#upload-api)上传知识库文件。
+
+## Upload API
+
+支持通过API的方式上传本地文件,并支持指定不同的faiss_path,每次发送API请求会返回一个task_id,之后可以通过task_id来查看文件上传状态(processing、completed、failed)。
+
+- 上传(upload_local_data)
+
+```bash
+curl -X 'POST' http://127.0.0.1:8000/service/upload_local_data -H 'Content-Type: multipart/form-data' -F 'file=@local_path/PAI.txt' -F 'faiss_path=localdata/storage'
+
+# Return: {"task_id": "2c1e557733764fdb9fefa063538914da"}
+```
+
+- 查看上传状态(get_upload_state)
+
+```bash
+curl http://127.0.0.1:8077/service/get_upload_state\?task_id\=2c1e557733764fdb9fefa063538914da
+
+# Return: {"task_id":"2c1e557733764fdb9fefa063538914da","status":"completed"}
+```
+
+## Query API
+
+- Rag Query请求
+
+```bash
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"PAI是什么?"}'
+```
+
+- 多轮对话请求
+
+```bash
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"PAI是什么?"}'
+
+# 传入session_id:对话历史会话唯一标识,传入session_id后,将对话历史进行记录,调用大模型将自动携带存储的对话历史。
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有什么优势?", "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
+
+# 传入chat_history:用户与模型的对话历史,list中的每个元素是形式为{"user":"用户输入","bot":"模型输出"}的一轮对话,多轮对话按时间顺序排列。
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有哪些功能?", "chat_history": [{"user":"PAI是什么?", "bot":"PAI是阿里云的人工智能平台,它提供一站式的机器学习解决方案。这个平台支持各种机器学习任务,包括有监督学习、无监督学习和增强学习,适用于营销、金融、社交网络等多个场景。"}]}'
+
+# 同时传入session_id和chat_history:会用chat_history对存储的session_id所对应的对话历史进行追加更新
+curl -X 'POST' http://127.0.0.1:8000/service/query -H "Content-Type: application/json" -d '{"question":"它有什么优势?", "chat_history": [{"user":"PAI是什么?", "bot":"PAI是阿里云的人工智能平台,它提供一站式的机器学习解决方案。这个平台支持各种机器学习任务,包括有监督学习、无监督学习和增强学习,适用于营销、金融、社交网络等多个场景。"}], "session_id": "1702ffxxad3xxx6fxxx97daf7c"}'
+```
+
+- Agent及调用Function Tool的简单对话
+
+```bash
+curl -X 'POST' http://127.0.0.1:8000/service/query/agent -H "Content-Type: application/json" -d '{"question":"今年是2024年,10年前是哪一年?"}'
+```
+
+
diff --git a/docs/docker_build.md b/docs/docker_build.md
new file mode 100644
index 00000000..5d9f8b51
--- /dev/null
+++ b/docs/docker_build.md
@@ -0,0 +1,33 @@
+# Docker build
+
+## Server
+
+### CPU
+
+```bash
+docker build -f Dockerfile -t rag_serve:0.1 .
+```
+
+### GPU
+
+```bash
+docker build -f Dockerfile_gpu -t rag_serve:0.1_gpu .
+```
+
+## UI
+
+```bash
+docker build -f Dockerfile_ui -t rag_ui:0.1 .
+```
+
+## Nginx
+
+```bash
+docker build -f Dockerfile_nginx -t rag_nginx:0.1 .
+```
+
+# 常见问题
+
+## docker pull timeout
+
+建议更换docker镜像源为阿里云镜像,在阿里云在容器镜像服务 -> 镜像工具 -> 镜像加速器 中可以找到阿里云的专属镜像加速器,按照指示说明修改daemon配置文件来使用加速器即可。
diff --git a/docs/figures/framework.jpg b/docs/figures/framework.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c53aa093fa1c3cea893405cfcb41fa0f79aea31e
GIT binary patch
literal 884977
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