chore: import upstream snapshot with attribution
tests / ragflow_tests_infinity (push) Has been cancelled
tests / ragflow_tests_elasticsearch (push) Has been cancelled
sep-tests / ragflow_preflight (push) Has been cancelled
sep-tests / ragflow_tests_infinity (push) Has been cancelled
sep-tests / ragflow_tests_elasticsearch (push) Has been cancelled
tests / ragflow_preflight (push) Has been cancelled
tests / ragflow_tests_infinity (push) Has been cancelled
tests / ragflow_tests_elasticsearch (push) Has been cancelled
sep-tests / ragflow_preflight (push) Has been cancelled
sep-tests / ragflow_tests_infinity (push) Has been cancelled
sep-tests / ragflow_tests_elasticsearch (push) Has been cancelled
tests / ragflow_preflight (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import ragflow_sdk
|
||||
|
||||
print(ragflow_sdk.__version__)
|
||||
@@ -0,0 +1,31 @@
|
||||
[project]
|
||||
name = "ragflow-sdk"
|
||||
version = "0.26.4"
|
||||
description = "Python client sdk of [RAGFlow](https://github.com/infiniflow/ragflow). RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding."
|
||||
authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }]
|
||||
license = { text = "Apache License, Version 2.0" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13,<3.14"
|
||||
dependencies = ["requests>=2.30.0,<3.0.0", "beartype>=0.20.0,<1.0.0"]
|
||||
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
"hypothesis>=6.131.9",
|
||||
"openpyxl>=3.1.5",
|
||||
"pillow>=11.1.0",
|
||||
"pytest>=8.3.5",
|
||||
"python-docx>=1.1.2",
|
||||
"python-pptx>=1.0.2",
|
||||
"reportlab>=4.3.1",
|
||||
"requests>=2.32.3",
|
||||
"requests-toolbelt>=1.0.0",
|
||||
]
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
markers = [
|
||||
"p1: high priority test cases",
|
||||
"p2: medium priority test cases",
|
||||
"p3: low priority test cases",
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from beartype.claw import beartype_this_package
|
||||
|
||||
beartype_this_package()
|
||||
|
||||
import importlib.metadata
|
||||
|
||||
from .ragflow import RAGFlow
|
||||
from .modules.dataset import DataSet
|
||||
from .modules.chat import Chat
|
||||
from .modules.session import Session
|
||||
from .modules.document import Document
|
||||
from .modules.chunk import Chunk
|
||||
from .modules.agent import Agent
|
||||
from .modules.memory import Memory
|
||||
|
||||
__version__ = importlib.metadata.version("ragflow_sdk")
|
||||
|
||||
__all__ = ["RAGFlow", "DataSet", "Chat", "Session", "Document", "Chunk", "Agent", "Memory"]
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
@@ -0,0 +1,69 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from .base import Base
|
||||
from .session import Session
|
||||
|
||||
|
||||
class Agent(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = None
|
||||
self.avatar = None
|
||||
self.canvas_type = None
|
||||
self.description = None
|
||||
self.dsl = None
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
class Dsl(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.answer = []
|
||||
self.components = {"begin": {"downstream": ["Answer:China"], "obj": {"component_name": "Begin", "params": {}}, "upstream": []}}
|
||||
self.graph = {
|
||||
"edges": [],
|
||||
"nodes": [{"data": {"label": "Begin", "name": "begin"}, "id": "begin", "position": {"x": 50, "y": 200}, "sourcePosition": "left", "targetPosition": "right", "type": "beginNode"}],
|
||||
}
|
||||
self.history = []
|
||||
self.messages = []
|
||||
self.path = []
|
||||
self.reference = []
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def create_session(self, **kwargs) -> Session:
|
||||
res = self.post(f"/agents/{self.id}/sessions", json=kwargs)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Session(self.rag, res.get("data"))
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def list_sessions(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", desc: bool = True, id: str = None) -> list[Session]:
|
||||
res = self.get(f"/agents/{self.id}/sessions", {"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id})
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
result_list = []
|
||||
for data in res.get("data"):
|
||||
temp_agent = Session(self.rag, data)
|
||||
result_list.append(temp_agent)
|
||||
return result_list
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def delete_sessions(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
payload = {"ids": ids}
|
||||
if delete_all:
|
||||
payload["delete_all"] = True
|
||||
res = self.rm(f"/agents/{self.id}/sessions", payload)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
class Base:
|
||||
def __init__(self, rag, res_dict):
|
||||
self.rag = rag
|
||||
self._update_from_dict(rag, res_dict)
|
||||
|
||||
def _update_from_dict(self, rag, res_dict):
|
||||
for k, v in res_dict.items():
|
||||
if isinstance(v, dict):
|
||||
self.__dict__[k] = Base(rag, v)
|
||||
else:
|
||||
self.__dict__[k] = v
|
||||
|
||||
def to_json(self):
|
||||
pr = {}
|
||||
for name in dir(self):
|
||||
value = getattr(self, name)
|
||||
if not name.startswith("__") and not callable(value) and name != "rag":
|
||||
if isinstance(value, Base):
|
||||
pr[name] = value.to_json()
|
||||
else:
|
||||
pr[name] = value
|
||||
return pr
|
||||
|
||||
def post(self, path, json=None, stream=False, files=None):
|
||||
res = self.rag.post(path, json, stream=stream, files=files)
|
||||
return res
|
||||
|
||||
def get(self, path, params=None):
|
||||
res = self.rag.get(path, params)
|
||||
return res
|
||||
|
||||
def rm(self, path, json):
|
||||
res = self.rag.delete(path, json)
|
||||
return res
|
||||
|
||||
def put(self, path, json):
|
||||
res = self.rag.put(path, json)
|
||||
return res
|
||||
|
||||
def patch(self, path, json):
|
||||
res = self.rag.patch(path, json)
|
||||
return res
|
||||
|
||||
def __str__(self):
|
||||
return str(self.to_json())
|
||||
@@ -0,0 +1,67 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
from .base import Base
|
||||
from .session import Session
|
||||
|
||||
|
||||
class Chat(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = ""
|
||||
self.name = "assistant"
|
||||
self.icon = ""
|
||||
self.dataset_ids = []
|
||||
self.llm_id = None
|
||||
self.llm_setting = {}
|
||||
self.prompt_config = {}
|
||||
self.similarity_threshold = 0.2
|
||||
self.vector_similarity_weight = 0.3
|
||||
self.top_n = 6
|
||||
self.top_k = 1024
|
||||
self.rerank_id = ""
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def update(self, update_message: dict):
|
||||
if not isinstance(update_message, dict):
|
||||
raise Exception("ValueError('`update_message` must be a dict')")
|
||||
res = self.patch(f"/chats/{self.id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def create_session(self, name: str = "New session") -> Session:
|
||||
res = self.post(f"/chats/{self.id}/sessions", {"name": name})
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Session(self.rag, res["data"])
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def list_sessions(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", desc: bool = True, id: str = None, name: str = None, user_id: str = None) -> list[Session]:
|
||||
res = self.get(f"/chats/{self.id}/sessions", {"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name, "user_id": user_id})
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
result_list = []
|
||||
for data in res["data"]:
|
||||
result_list.append(Session(self.rag, data))
|
||||
return result_list
|
||||
raise Exception(res["message"])
|
||||
|
||||
def delete_sessions(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
res = self.rm(f"/chats/{self.id}/sessions", {"ids": ids, "delete_all": delete_all})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
@@ -0,0 +1,61 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class ChunkUpdateError(Exception):
|
||||
def __init__(self, code=None, message=None, details=None):
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class Chunk(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = ""
|
||||
self.content = ""
|
||||
self.important_keywords = []
|
||||
self.tag_kwd = []
|
||||
self.questions = []
|
||||
self.create_time = ""
|
||||
self.create_timestamp = 0.0
|
||||
self.dataset_id = None
|
||||
self.document_name = ""
|
||||
self.document_keyword = ""
|
||||
self.document_id = ""
|
||||
self.available = True
|
||||
# Additional fields for retrieval results
|
||||
self.similarity = 0.0
|
||||
self.vector_similarity = 0.0
|
||||
self.term_similarity = 0.0
|
||||
self.positions = []
|
||||
self.doc_type = ""
|
||||
for k in list(res_dict.keys()):
|
||||
if k not in self.__dict__:
|
||||
res_dict.pop(k)
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
# for backward compatibility
|
||||
if not self.document_name:
|
||||
self.document_name = self.document_keyword
|
||||
|
||||
def update(self, update_message: dict):
|
||||
res = self.patch(f"/datasets/{self.dataset_id}/documents/{self.document_id}/chunks/{self.id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise ChunkUpdateError(code=res.get("code"), message=res.get("message"), details=res.get("details"))
|
||||
@@ -0,0 +1,183 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from typing import Any
|
||||
|
||||
from .base import Base
|
||||
from .document import Document
|
||||
|
||||
|
||||
class DataSet(Base):
|
||||
class ParserConfig(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = ""
|
||||
self.name = ""
|
||||
self.avatar = ""
|
||||
self.tenant_id = None
|
||||
self.description = ""
|
||||
self.embedding_model = ""
|
||||
self.permission = "me"
|
||||
self.document_count = 0
|
||||
self.chunk_count = 0
|
||||
self.chunk_method = "naive"
|
||||
self.parser_config = None
|
||||
self.pagerank = 0
|
||||
for k in list(res_dict.keys()):
|
||||
if k not in self.__dict__:
|
||||
res_dict.pop(k)
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def update(self, update_message: dict):
|
||||
res = self.put(f"/datasets/{self.id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
self._update_from_dict(self.rag, res.get("data", {}))
|
||||
return self
|
||||
|
||||
def upload_documents(self, document_list: list[dict]):
|
||||
url = f"/datasets/{self.id}/documents"
|
||||
files = [("file", (ele["display_name"], ele["blob"])) for ele in document_list]
|
||||
res = self.post(path=url, json=None, files=files)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
doc_list = []
|
||||
for doc in res["data"]:
|
||||
document = Document(self.rag, doc)
|
||||
doc_list.append(document)
|
||||
return doc_list
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def list_documents(
|
||||
self,
|
||||
id: str | None = None,
|
||||
ids: list[str] | None = None,
|
||||
name: str | None = None,
|
||||
keywords: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 30,
|
||||
orderby: str = "create_time",
|
||||
desc: bool = True,
|
||||
create_time_from: int = 0,
|
||||
create_time_to: int = 0,
|
||||
):
|
||||
# Validate that id and ids are not used together
|
||||
if id and ids:
|
||||
raise ValueError("Cannot use both 'id' and 'ids' parameters at the same time.")
|
||||
|
||||
params = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"keywords": keywords,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"orderby": orderby,
|
||||
"desc": desc,
|
||||
"create_time_from": create_time_from,
|
||||
"create_time_to": create_time_to,
|
||||
}
|
||||
# Handle ids parameter - convert to multiple query params
|
||||
if ids:
|
||||
for doc_id in ids:
|
||||
params.append(("ids", doc_id))
|
||||
res = self.get(f"/datasets/{self.id}/documents", params=params)
|
||||
res = res.json()
|
||||
documents = []
|
||||
if res.get("code") == 0:
|
||||
for document in res["data"].get("docs"):
|
||||
documents.append(Document(self.rag, document))
|
||||
return documents
|
||||
raise Exception(res["message"])
|
||||
|
||||
def delete_documents(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
res = self.rm(f"/datasets/{self.id}/documents", {"ids": ids, "delete_all": delete_all})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def _get_documents_status(self, document_ids):
|
||||
import time
|
||||
|
||||
terminal_states = {"DONE", "FAIL", "CANCEL"}
|
||||
interval_sec = 1
|
||||
pending = set(document_ids)
|
||||
finished = []
|
||||
while pending:
|
||||
for doc_id in list(pending):
|
||||
|
||||
def fetch_doc(doc_id: str) -> Document | None:
|
||||
try:
|
||||
docs = self.list_documents(id=doc_id)
|
||||
return docs[0] if docs else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
doc = fetch_doc(doc_id)
|
||||
if doc is None:
|
||||
continue
|
||||
if isinstance(doc.run, str) and doc.run.upper() in terminal_states:
|
||||
finished.append((doc_id, doc.run, doc.chunk_count, doc.token_count))
|
||||
pending.discard(doc_id)
|
||||
elif float(doc.progress or 0.0) >= 1.0:
|
||||
finished.append((doc_id, "DONE", doc.chunk_count, doc.token_count))
|
||||
pending.discard(doc_id)
|
||||
if pending:
|
||||
time.sleep(interval_sec)
|
||||
return finished
|
||||
|
||||
def async_parse_documents(self, document_ids):
|
||||
res = self.post(f"/datasets/{self.id}/chunks", {"document_ids": document_ids})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def parse_documents(self, document_ids):
|
||||
try:
|
||||
self.async_parse_documents(document_ids)
|
||||
self._get_documents_status(document_ids)
|
||||
except KeyboardInterrupt:
|
||||
self.async_cancel_parse_documents(document_ids)
|
||||
|
||||
return self._get_documents_status(document_ids)
|
||||
|
||||
def async_cancel_parse_documents(self, document_ids):
|
||||
res = self.rm(f"/datasets/{self.id}/chunks", {"document_ids": document_ids})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def get_auto_metadata(self) -> dict[str, Any]:
|
||||
"""
|
||||
Retrieve auto-metadata configuration for a dataset via SDK.
|
||||
"""
|
||||
res = self.get(f"/datasets/{self.id}/metadata/config")
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return res["data"]
|
||||
raise Exception(res["message"])
|
||||
|
||||
def update_auto_metadata(self, **config: Any) -> dict[str, Any]:
|
||||
"""
|
||||
Update auto-metadata configuration for a dataset via SDK.
|
||||
"""
|
||||
res = self.put(f"/datasets/{self.id}/metadata/config", config)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return res["data"]
|
||||
raise Exception(res["message"])
|
||||
@@ -0,0 +1,104 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import json
|
||||
|
||||
from .base import Base
|
||||
from .chunk import Chunk
|
||||
|
||||
|
||||
class Document(Base):
|
||||
class ParserConfig(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = ""
|
||||
self.name = ""
|
||||
self.thumbnail = None
|
||||
self.dataset_id = None
|
||||
self.chunk_method = "naive"
|
||||
self.parser_config = {"pages": [[1, 1000000]]}
|
||||
self.source_type = "local"
|
||||
self.type = ""
|
||||
self.created_by = ""
|
||||
self.size = 0
|
||||
self.token_count = 0
|
||||
self.chunk_count = 0
|
||||
self.progress = 0.0
|
||||
self.progress_msg = ""
|
||||
self.process_begin_at = None
|
||||
self.process_duration = 0.0
|
||||
self.run = "0"
|
||||
self.status = "1"
|
||||
self.meta_fields = {}
|
||||
for k in list(res_dict.keys()):
|
||||
if k not in self.__dict__:
|
||||
res_dict.pop(k)
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def update(self, update_message: dict):
|
||||
if "meta_fields" in update_message:
|
||||
if not isinstance(update_message["meta_fields"], dict):
|
||||
raise Exception("meta_fields must be a dictionary")
|
||||
res = self.patch(f"/datasets/{self.dataset_id}/documents/{self.id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
self._update_from_dict(self.rag, res.get("data", {}))
|
||||
return self
|
||||
|
||||
def download(self):
|
||||
res = self.get(f"/datasets/{self.dataset_id}/documents/{self.id}")
|
||||
error_keys = set(["code", "message"])
|
||||
try:
|
||||
response = res.json()
|
||||
actual_keys = set(response.keys())
|
||||
if actual_keys == error_keys:
|
||||
raise Exception(response.get("message"))
|
||||
else:
|
||||
return res.content
|
||||
except json.JSONDecodeError:
|
||||
return res.content
|
||||
|
||||
def list_chunks(self, page=1, page_size=30, keywords="", id=""):
|
||||
data = {"keywords": keywords, "page": page, "page_size": page_size, "id": id}
|
||||
res = self.get(f"/datasets/{self.dataset_id}/documents/{self.id}/chunks", data)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
chunks = []
|
||||
for data in res["data"].get("chunks"):
|
||||
chunk = Chunk(self.rag, data)
|
||||
chunks.append(chunk)
|
||||
return chunks
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def add_chunk(self, content: str, important_keywords: list[str] = [], questions: list[str] = [], image_base64: str | None = None, *, tag_kwd: list[str] = []):
|
||||
body = {"content": content, "important_keywords": important_keywords, "tag_kwd": tag_kwd, "questions": questions}
|
||||
if image_base64 is not None:
|
||||
body["image_base64"] = image_base64
|
||||
res = self.post(f"/datasets/{self.dataset_id}/documents/{self.id}/chunks", body)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Chunk(self.rag, res["data"].get("chunk"))
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def delete_chunks(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
res = self.rm(f"/datasets/{self.dataset_id}/documents/{self.id}/chunks", {"chunk_ids": ids, "delete_all": delete_all})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
@@ -0,0 +1,87 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Memory(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = ""
|
||||
self.name = ""
|
||||
self.avatar = None
|
||||
self.tenant_id = None
|
||||
self.owner_name = ""
|
||||
self.memory_type = ["raw"]
|
||||
self.storage_type = "table"
|
||||
self.embd_id = ""
|
||||
self.llm_id = ""
|
||||
self.permissions = "me"
|
||||
self.description = ""
|
||||
self.memory_size = 5 * 1024 * 1024
|
||||
self.forgetting_policy = "FIFO"
|
||||
self.temperature = (0.5,)
|
||||
self.system_prompt = ""
|
||||
self.user_prompt = ""
|
||||
for k in list(res_dict.keys()):
|
||||
if k not in self.__dict__:
|
||||
res_dict.pop(k)
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def update(self, update_dict: dict):
|
||||
res = self.put(f"/memories/{self.id}", update_dict)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
self._update_from_dict(self.rag, res.get("data", {}))
|
||||
return self
|
||||
|
||||
def get_config(self):
|
||||
res = self.get(f"/memories/{self.id}/config")
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
self._update_from_dict(self.rag, res.get("data", {}))
|
||||
return self
|
||||
|
||||
def list_memory_messages(self, agent_id: str | list[str] = None, keywords: str = None, page: int = 1, page_size: int = 50):
|
||||
params = {"agent_id": agent_id, "keywords": keywords, "page": page, "page_size": page_size}
|
||||
res = self.get(f"/memories/{self.id}", params)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return res["data"]
|
||||
|
||||
def forget_message(self, message_id: int):
|
||||
res = self.rm(f"/messages/{self.id}:{message_id}", {})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return True
|
||||
|
||||
def update_message_status(self, message_id: int, status: bool):
|
||||
update_message = {"status": status}
|
||||
res = self.put(f"/messages/{self.id}:{message_id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return True
|
||||
|
||||
def get_message_content(self, message_id: int) -> dict:
|
||||
res = self.get(f"/messages/{self.id}:{message_id}/content")
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return res["data"]
|
||||
@@ -0,0 +1,186 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .base import Base
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Session(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.id = None
|
||||
self.name = "New session"
|
||||
self.messages = [{"role": "assistant", "content": "Hi! I am your assistant, can I help you?"}]
|
||||
for key, value in res_dict.items():
|
||||
if key == "chat_id" and value is not None:
|
||||
self.chat_id = None
|
||||
self.__session_type = "chat"
|
||||
if key == "agent_id" and value is not None:
|
||||
self.agent_id = None
|
||||
self.__session_type = "agent"
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
def ask(
|
||||
self,
|
||||
question="",
|
||||
stream=False,
|
||||
inputs=None,
|
||||
release=None,
|
||||
return_trace=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Ask a question to the session.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
question : str
|
||||
The user's question. May be empty when the agent is driven solely by
|
||||
Begin component inputs.
|
||||
stream : bool
|
||||
If ``True``, yields ``Message`` objects as they arrive (SSE streaming).
|
||||
If ``False``, yields a single ``Message`` with the final answer.
|
||||
inputs : dict, optional
|
||||
Values for variables declared on the agent's **Begin** component. Each
|
||||
value must be a dict containing at least a ``"value"`` key, and may
|
||||
include ``"type"``. Example::
|
||||
|
||||
session.ask(
|
||||
"",
|
||||
stream=False,
|
||||
inputs={"key1": {"type": "line", "value": "hello"}},
|
||||
)
|
||||
|
||||
Only meaningful for agent sessions; ignored for chat sessions.
|
||||
release : bool, optional
|
||||
If ``True``, run against the latest published agent version instead of
|
||||
the editable draft. Only meaningful for agent sessions.
|
||||
return_trace : bool, optional
|
||||
If ``True``, include execution trace information in the response.
|
||||
Only meaningful for agent sessions.
|
||||
**kwargs
|
||||
Additional fields forwarded verbatim to the completion endpoint
|
||||
(e.g. ``session_id``, ``files``, ``user_id``, ``custom_header``).
|
||||
See the HTTP API reference for the full list.
|
||||
"""
|
||||
if inputs is not None:
|
||||
kwargs["inputs"] = inputs
|
||||
if release is not None:
|
||||
kwargs["release"] = release
|
||||
if return_trace is not None:
|
||||
kwargs["return_trace"] = return_trace
|
||||
|
||||
if inputs is not None or release is not None or return_trace is not None:
|
||||
logger.debug(
|
||||
"Session.ask explicit-params session_type=%s session_id=%s input_keys=%s release=%s return_trace=%s",
|
||||
self.__session_type,
|
||||
getattr(self, "id", None),
|
||||
list(inputs.keys()) if isinstance(inputs, dict) else None,
|
||||
release,
|
||||
return_trace,
|
||||
)
|
||||
|
||||
if self.__session_type == "agent":
|
||||
res = self._ask_agent(question, stream, **kwargs)
|
||||
elif self.__session_type == "chat":
|
||||
res = self._ask_chat(question, stream, **kwargs)
|
||||
else:
|
||||
raise Exception(f"Unknown session type: {self.__session_type}")
|
||||
|
||||
if stream:
|
||||
for line in res.iter_lines(decode_unicode=True):
|
||||
if not line:
|
||||
continue # Skip empty lines
|
||||
line = line.strip()
|
||||
if line.startswith("data:"):
|
||||
content = line[len("data:") :].strip()
|
||||
if content == "[DONE]":
|
||||
break # End of stream
|
||||
else:
|
||||
content = line
|
||||
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError:
|
||||
continue # Skip lines that are not valid JSON
|
||||
|
||||
event = json_data.get("event", None)
|
||||
if event and event != "message":
|
||||
continue
|
||||
|
||||
if (self.__session_type == "agent" and event == "message_end") or (self.__session_type == "chat" and json_data.get("data") is True):
|
||||
return
|
||||
if self.__session_type == "agent":
|
||||
yield self._structure_answer(json_data)
|
||||
else:
|
||||
yield self._structure_answer(json_data["data"])
|
||||
else:
|
||||
try:
|
||||
json_data = res.json()
|
||||
except ValueError:
|
||||
raise Exception(f"Invalid response {res}")
|
||||
yield self._structure_answer(json_data["data"])
|
||||
|
||||
def _structure_answer(self, json_data):
|
||||
answer = ""
|
||||
if self.__session_type == "agent":
|
||||
answer = json_data["data"]["content"]
|
||||
elif self.__session_type == "chat":
|
||||
answer = json_data["answer"]
|
||||
reference = json_data.get("reference", {})
|
||||
temp_dict = {"content": answer, "role": "assistant"}
|
||||
if reference and "chunks" in reference:
|
||||
chunks = reference["chunks"]
|
||||
temp_dict["reference"] = chunks
|
||||
message = Message(self.rag, temp_dict)
|
||||
return message
|
||||
|
||||
def _ask_chat(self, question: str, stream: bool, **kwargs):
|
||||
json_data = {"question": question, "stream": stream, "session_id": self.id}
|
||||
json_data.update(kwargs)
|
||||
res = self.post(f"/chats/{self.chat_id}/completions", json_data, stream=stream)
|
||||
return res
|
||||
|
||||
def _ask_agent(self, question: str, stream: bool, **kwargs):
|
||||
json_data = {
|
||||
"agent_id": self.agent_id,
|
||||
"query": question,
|
||||
"stream": stream,
|
||||
"session_id": self.id,
|
||||
"openai-compatible": False,
|
||||
}
|
||||
json_data.update(kwargs)
|
||||
res = self.post("/agents/chat/completions", json_data, stream=stream)
|
||||
return res
|
||||
|
||||
def update(self, update_message):
|
||||
res = self.patch(f"/chats/{self.chat_id}/sessions/{self.id}", update_message)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
|
||||
class Message(Base):
|
||||
def __init__(self, rag, res_dict):
|
||||
self.content = "Hi! I am your assistant, can I help you?"
|
||||
self.reference = None
|
||||
self.role = "assistant"
|
||||
self.prompt = None
|
||||
self.id = None
|
||||
super().__init__(rag, res_dict)
|
||||
@@ -0,0 +1,392 @@
|
||||
#
|
||||
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Optional, Any
|
||||
|
||||
import requests
|
||||
|
||||
from .modules.agent import Agent
|
||||
from .modules.chat import Chat
|
||||
from .modules.chunk import Chunk
|
||||
from .modules.dataset import DataSet
|
||||
from .modules.memory import Memory
|
||||
|
||||
|
||||
class RAGFlow:
|
||||
def __init__(self, api_key, base_url, version="v1"):
|
||||
"""
|
||||
api_url: http://<host_address>/api/v1
|
||||
"""
|
||||
self.user_key = api_key
|
||||
self.api_url = f"{base_url}/api/{version}"
|
||||
self.authorization_header = {"Authorization": "{} {}".format("Bearer", self.user_key)}
|
||||
|
||||
def post(self, path, json=None, stream=False, files=None):
|
||||
res = requests.post(url=self.api_url + path, json=json, headers=self.authorization_header, stream=stream, files=files)
|
||||
return res
|
||||
|
||||
def get(self, path, params=None, json=None):
|
||||
res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header, json=json)
|
||||
return res
|
||||
|
||||
def delete(self, path, json):
|
||||
res = requests.delete(url=self.api_url + path, json=json, headers=self.authorization_header)
|
||||
return res
|
||||
|
||||
def put(self, path, json):
|
||||
res = requests.put(url=self.api_url + path, json=json, headers=self.authorization_header)
|
||||
return res
|
||||
|
||||
def patch(self, path, json):
|
||||
res = requests.patch(url=self.api_url + path, json=json, headers=self.authorization_header)
|
||||
return res
|
||||
|
||||
def create_dataset(
|
||||
self,
|
||||
name: str,
|
||||
avatar: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
embedding_model: Optional[str] = None,
|
||||
permission: str = "me",
|
||||
chunk_method: str = "naive",
|
||||
parser_config: Optional[DataSet.ParserConfig] = None,
|
||||
auto_metadata_config: Optional[dict[str, Any]] = None,
|
||||
) -> DataSet:
|
||||
payload = {
|
||||
"name": name,
|
||||
"avatar": avatar,
|
||||
"description": description,
|
||||
"embedding_model": embedding_model,
|
||||
"permission": permission,
|
||||
"chunk_method": chunk_method,
|
||||
}
|
||||
if parser_config is not None:
|
||||
payload["parser_config"] = parser_config.to_json()
|
||||
if auto_metadata_config is not None:
|
||||
payload["auto_metadata_config"] = auto_metadata_config
|
||||
|
||||
res = self.post("/datasets", payload)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return DataSet(self, res["data"])
|
||||
raise Exception(res["message"])
|
||||
|
||||
def delete_datasets(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
res = self.delete("/datasets", {"ids": ids, "delete_all": delete_all})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def get_dataset(self, name: str):
|
||||
_list = self.list_datasets(name=name)
|
||||
if len(_list) > 0:
|
||||
return _list[0]
|
||||
raise Exception("Dataset %s not found" % name)
|
||||
|
||||
def list_datasets(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", desc: bool = True, id: str | None = None, name: str | None = None) -> list[DataSet]:
|
||||
res = self.get(
|
||||
"/datasets",
|
||||
{
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"orderby": orderby,
|
||||
"desc": desc,
|
||||
"id": id,
|
||||
"name": name,
|
||||
},
|
||||
)
|
||||
res = res.json()
|
||||
result_list = []
|
||||
if res.get("code") == 0:
|
||||
for data in res["data"]:
|
||||
result_list.append(DataSet(self, data))
|
||||
return result_list
|
||||
raise Exception(res["message"])
|
||||
|
||||
def create_chat(
|
||||
self,
|
||||
name: str,
|
||||
icon: str = "",
|
||||
dataset_ids: list[str] | None = None,
|
||||
llm_id: str | None = None,
|
||||
llm_setting: dict | None = None,
|
||||
prompt_config: dict | None = None,
|
||||
**kwargs,
|
||||
) -> Chat:
|
||||
payload = {"name": name, "icon": icon, "dataset_ids": dataset_ids or []}
|
||||
if llm_id is not None:
|
||||
payload["llm_id"] = llm_id
|
||||
if llm_setting is not None:
|
||||
payload["llm_setting"] = llm_setting
|
||||
if prompt_config is not None:
|
||||
payload["prompt_config"] = prompt_config
|
||||
payload.update(kwargs)
|
||||
res = self.post("/chats", payload)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Chat(self, res["data"])
|
||||
raise Exception(res["message"])
|
||||
|
||||
def delete_chats(self, ids: list[str] | None = None, delete_all: bool = False):
|
||||
res = self.delete("/chats", {"ids": ids, "delete_all": delete_all})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def get_chat(self, chat_id: str) -> Chat:
|
||||
res = self.get(f"/chats/{chat_id}")
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Chat(self, res["data"])
|
||||
raise Exception(res["message"])
|
||||
|
||||
def list_chats(
|
||||
self,
|
||||
page: int = 1,
|
||||
page_size: int = 30,
|
||||
orderby: str = "create_time",
|
||||
desc: bool = True,
|
||||
id: str | None = None,
|
||||
name: str | None = None,
|
||||
keywords: str | None = None,
|
||||
owner_ids: str | list[str] | None = None,
|
||||
) -> list[Chat]:
|
||||
res = self.get(
|
||||
"/chats",
|
||||
{
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"orderby": orderby,
|
||||
"desc": desc,
|
||||
"id": id,
|
||||
"name": name,
|
||||
"keywords": keywords,
|
||||
"owner_ids": owner_ids,
|
||||
},
|
||||
)
|
||||
res = res.json()
|
||||
result_list = []
|
||||
if res.get("code") == 0:
|
||||
for data in res["data"]["chats"]:
|
||||
result_list.append(Chat(self, data))
|
||||
return result_list
|
||||
raise Exception(res["message"])
|
||||
|
||||
def retrieve(
|
||||
self,
|
||||
dataset_ids,
|
||||
document_ids=None,
|
||||
question="",
|
||||
page=1,
|
||||
page_size=30,
|
||||
similarity_threshold=0.2,
|
||||
vector_similarity_weight=0.3,
|
||||
top_k=1024,
|
||||
rerank_id: str | None = None,
|
||||
keyword: bool = False,
|
||||
cross_languages: list[str] | None = None,
|
||||
metadata_condition: dict | None = None,
|
||||
use_kg: bool = False,
|
||||
toc_enhance: bool = False,
|
||||
):
|
||||
if document_ids is None:
|
||||
document_ids = []
|
||||
data_json = {
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"similarity_threshold": similarity_threshold,
|
||||
"vector_similarity_weight": vector_similarity_weight,
|
||||
"top_k": top_k,
|
||||
"rerank_id": rerank_id,
|
||||
"keyword": keyword,
|
||||
"question": question,
|
||||
"dataset_ids": dataset_ids,
|
||||
"document_ids": document_ids,
|
||||
"cross_languages": cross_languages,
|
||||
"metadata_condition": metadata_condition,
|
||||
"use_kg": use_kg,
|
||||
"toc_enhance": toc_enhance,
|
||||
}
|
||||
# Send a POST request to the backend service (using requests library as an example, actual implementation may vary)
|
||||
res = self.post("/retrieval", json=data_json)
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
chunks = []
|
||||
for chunk_data in res["data"].get("chunks"):
|
||||
chunk = Chunk(self, chunk_data)
|
||||
chunks.append(chunk)
|
||||
return chunks
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
def list_agents(self, page: int = 1, page_size: int = 30, orderby: str = "update_time", desc: bool = True) -> list[Agent]:
|
||||
res = self.get(
|
||||
"/agents",
|
||||
{
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"orderby": orderby,
|
||||
"desc": desc,
|
||||
},
|
||||
)
|
||||
res = res.json()
|
||||
result_list = []
|
||||
if res.get("code") == 0:
|
||||
data = res.get("data") or {}
|
||||
data_list = data.get("canvas", [])
|
||||
for data in data_list:
|
||||
result_list.append(Agent(self, data))
|
||||
return result_list
|
||||
raise Exception(res["message"])
|
||||
|
||||
def get_agent(self, agent_id: str) -> Agent:
|
||||
res = self.get(f"/agents/{agent_id}")
|
||||
res = res.json()
|
||||
if res.get("code") == 0:
|
||||
return Agent(self, res["data"])
|
||||
raise Exception(res["message"])
|
||||
|
||||
def create_agent(
|
||||
self,
|
||||
title: str,
|
||||
dsl: dict,
|
||||
description: str | None = None,
|
||||
canvas_type: str | None = None,
|
||||
) -> None:
|
||||
req = {"title": title, "dsl": dsl}
|
||||
|
||||
if description is not None:
|
||||
req["description"] = description
|
||||
|
||||
if canvas_type is not None:
|
||||
req["canvas_type"] = canvas_type
|
||||
|
||||
res = self.post("/agents", req)
|
||||
res = res.json()
|
||||
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def update_agent(
|
||||
self,
|
||||
agent_id: str,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
dsl: dict | None = None,
|
||||
canvas_type: str | None = None,
|
||||
) -> None:
|
||||
req = {}
|
||||
|
||||
if title is not None:
|
||||
req["title"] = title
|
||||
|
||||
if description is not None:
|
||||
req["description"] = description
|
||||
|
||||
if dsl is not None:
|
||||
req["dsl"] = dsl
|
||||
|
||||
if canvas_type is not None:
|
||||
req["canvas_type"] = canvas_type
|
||||
|
||||
res = self.put(f"/agents/{agent_id}", req)
|
||||
res = res.json()
|
||||
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def delete_agent(self, agent_id: str) -> None:
|
||||
res = self.delete(f"/agents/{agent_id}", {})
|
||||
res = res.json()
|
||||
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def create_memory(self, name: str, memory_type: list[str], embd_id: str, llm_id: str):
|
||||
payload = {"name": name, "memory_type": memory_type, "embd_id": embd_id, "llm_id": llm_id}
|
||||
res = self.post("/memories", payload)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return Memory(self, res["data"])
|
||||
|
||||
def list_memory(self, page: int = 1, page_size: int = 50, tenant_id: str | list[str] = None, memory_type: str | list[str] = None, storage_type: str = None, keywords: str = None) -> dict:
|
||||
res = self.get(
|
||||
"/memories",
|
||||
{
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"tenant_id": tenant_id,
|
||||
"memory_type": memory_type,
|
||||
"storage_type": storage_type,
|
||||
"keywords": keywords,
|
||||
},
|
||||
)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
result_list = []
|
||||
for data in res["data"]["memory_list"]:
|
||||
result_list.append(Memory(self, data))
|
||||
return {"code": res.get("code", 0), "message": res.get("message"), "memory_list": result_list, "total_count": res["data"]["total_count"]}
|
||||
|
||||
def delete_memory(self, memory_id: str):
|
||||
res = self.delete(f"/memories/{memory_id}", {})
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
|
||||
def add_message(self, memory_id: list[str], agent_id: str, session_id: str, user_input: str, agent_response: str, user_id: str = "") -> str:
|
||||
"""Append messages to memories; ``user_id`` is forwarded only for API-key auth (external subject)."""
|
||||
payload = {"memory_id": memory_id, "agent_id": agent_id, "session_id": session_id, "user_input": user_input, "agent_response": agent_response, "user_id": user_id}
|
||||
res = self.post("/messages", payload)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return res["message"]
|
||||
|
||||
def search_message(
|
||||
self,
|
||||
query: str,
|
||||
memory_id: list[str],
|
||||
agent_id: str = None,
|
||||
session_id: str = None,
|
||||
user_id: str = None,
|
||||
similarity_threshold: float = 0.2,
|
||||
keywords_similarity_weight: float = 0.7,
|
||||
top_n: int = 10,
|
||||
) -> list[dict]:
|
||||
params = {
|
||||
"query": query,
|
||||
"memory_id": memory_id,
|
||||
"agent_id": agent_id,
|
||||
"session_id": session_id,
|
||||
"user_id": user_id,
|
||||
"similarity_threshold": similarity_threshold,
|
||||
"keywords_similarity_weight": keywords_similarity_weight,
|
||||
"top_n": top_n,
|
||||
}
|
||||
res = self.get("/messages/search", params)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return res["data"]
|
||||
|
||||
def get_recent_messages(self, memory_id: list[str], agent_id: str = None, session_id: str = None, limit: int = 10) -> list[dict]:
|
||||
params = {"memory_id": memory_id, "agent_id": agent_id, "session_id": session_id, "limit": limit}
|
||||
res = self.get("/messages", params)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res["message"])
|
||||
return res["data"]
|
||||
@@ -0,0 +1,17 @@
|
||||
from .ragflow_sdk import RAGFlow
|
||||
|
||||
rag_object = RAGFlow(api_key="ragflow-FDfRECsXDRagsKPxb_EfZdDPcmngavSgYEzbU_Blgq4", base_url="http://localhost:9222")
|
||||
assistant = rag_object.get_agent("b0bc46e43dfc11f1b4ff84ba59bc54d9")
|
||||
session = assistant.create_session()
|
||||
|
||||
print("\n==================== Miss R =====================\n")
|
||||
print("Hello. What can I do for you?")
|
||||
|
||||
while True:
|
||||
question = input("\n==================== User =====================\n> ")
|
||||
print("\n==================== Miss R =====================\n")
|
||||
|
||||
cont = ""
|
||||
for ans in session.ask(question, stream=True):
|
||||
print(ans.content[len(cont) :], end="", flush=True)
|
||||
cont = ans.content
|
||||
@@ -0,0 +1,152 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
HOST_ADDRESS = os.getenv("HOST_ADDRESS", "http://127.0.0.1:9380")
|
||||
ZHIPU_AI_API_KEY = os.getenv("ZHIPU_AI_API_KEY")
|
||||
if ZHIPU_AI_API_KEY is None:
|
||||
pytest.exit("Error: Environment variable ZHIPU_AI_API_KEY must be set")
|
||||
|
||||
# def generate_random_email():
|
||||
# return 'user_' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))+'@1.com'
|
||||
|
||||
|
||||
def generate_email():
|
||||
return "user_123@1.com"
|
||||
|
||||
|
||||
EMAIL = generate_email()
|
||||
# password is "123"
|
||||
PASSWORD = """ctAseGvejiaSWWZ88T/m4FQVOpQyUvP+x7sXtdv3feqZACiQleuewkUi35E16wSd5C5QcnkkcV9cYc8TKPTRZlxappDuirxghxoOvFcJxFU4ixLsD
|
||||
fN33jCHRoDUW81IH9zjij/vaw8IbVyb6vuwg6MX6inOEBRRzVbRYxXOu1wkWY6SsI8X70oF9aeLFp/PzQpjoe/YbSqpTq8qqrmHzn9vO+yvyYyvmDsphXe
|
||||
X8f7fp9c7vUsfOCkM+gHY3PadG+QHa7KI7mzTKgUTZImK6BZtfRBATDTthEUbbaTewY4H0MnWiCeeDhcbeQao6cFy1To8pE3RpmxnGnS8BsBn8w=="""
|
||||
|
||||
|
||||
def register():
|
||||
url = HOST_ADDRESS + "/api/v1/users"
|
||||
name = "user"
|
||||
register_data = {"email": EMAIL, "nickname": name, "password": PASSWORD}
|
||||
res = requests.post(url=url, json=register_data)
|
||||
res = res.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
|
||||
|
||||
def login():
|
||||
url = HOST_ADDRESS + "/api/v1/auth/login"
|
||||
login_data = {"email": EMAIL, "password": PASSWORD}
|
||||
response = requests.post(url=url, json=login_data)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
auth = response.headers["Authorization"]
|
||||
return auth
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_api_key_fixture():
|
||||
try:
|
||||
register()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
auth = login()
|
||||
url = HOST_ADDRESS + "/v1/system/tokens"
|
||||
auth = {"Authorization": auth}
|
||||
response = requests.post(url=url, headers=auth)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
return res["data"].get("token")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_auth():
|
||||
try:
|
||||
register()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
auth = login()
|
||||
return auth
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_email():
|
||||
return EMAIL
|
||||
|
||||
|
||||
def get_added_models(auth, factory_name):
|
||||
url = HOST_ADDRESS + "/api/v1/models"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
added_factory = {model["provider_name"] for model in res.get("data", [])}
|
||||
if factory_name in added_factory:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_model_instance(auth):
|
||||
add_provider_api = HOST_ADDRESS + "/api/v1/providers"
|
||||
authorization = {"Authorization": auth}
|
||||
add_provider_response = requests.put(url=add_provider_api, headers=authorization, json={"provider_name": "ZHIPU-AI"})
|
||||
add_provider_res = add_provider_response.json()
|
||||
if add_provider_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model provider: {add_provider_res.get('message')}")
|
||||
|
||||
add_instance_api = HOST_ADDRESS + "/api/v1/providers/ZHIPU-AI/instances"
|
||||
add_instance_response = requests.post(url=add_instance_api, headers=authorization, json={"instance_name": "CI", "api_key": ZHIPU_AI_API_KEY, "region": "default", "base_url": ""})
|
||||
add_instance_res = add_instance_response.json()
|
||||
if add_instance_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model instance: {add_instance_res.get('message')}")
|
||||
|
||||
add_success = get_added_models(auth, "ZHIPU-AI")
|
||||
if not add_success:
|
||||
pytest.exit("Critical error in check added model: add model failed")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def set_tenant_info(get_auth):
|
||||
auth = get_auth
|
||||
if not get_added_models(auth, "ZHIPU-AI"):
|
||||
try:
|
||||
add_model_instance(auth)
|
||||
except Exception as e:
|
||||
pytest.exit(f"Error in set_tenant_info: {str(e)}")
|
||||
url = HOST_ADDRESS + "/api/v1/models/default"
|
||||
authorization = {"Authorization": get_auth}
|
||||
# set chat model
|
||||
set_default_llm_response = requests.patch(url=url, headers=authorization, json={"model_provider": "ZHIPU-AI", "model_instance": "CI", "model_type": "chat", "model_name": "glm-4-flash"})
|
||||
llm_res = set_default_llm_response.json()
|
||||
if llm_res.get("code") != 0:
|
||||
raise Exception(llm_res.get("message"))
|
||||
# set embedding model
|
||||
set_default_embedding_response = requests.patch(
|
||||
url=url, headers=authorization, json={"model_provider": "Builtin", "model_instance": "Local", "model_type": "embedding", "model_name": "BAAI/bge-small-en-v1.5"}
|
||||
)
|
||||
embd_res = set_default_embedding_response.json()
|
||||
if embd_res.get("code") != 0:
|
||||
raise Exception(embd_res.get("message"))
|
||||
# set image to text model
|
||||
set_default_img2txt_response = requests.patch(url=url, headers=authorization, json={"model_provider": "ZHIPU-AI", "model_instance": "CI", "model_type": "vision", "model_name": "glm-4v"})
|
||||
img2txt_res = set_default_img2txt_response.json()
|
||||
if img2txt_res.get("code") != 0:
|
||||
raise Exception(img2txt_res.get("message"))
|
||||
@@ -0,0 +1,115 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
HOST_ADDRESS = os.getenv("HOST_ADDRESS", "http://127.0.0.1:9380")
|
||||
API_VERSION = "v1"
|
||||
DATASETS_API_URL = f"/api/{API_VERSION}/datasets"
|
||||
|
||||
DATASET_NAME_LIMIT = 128
|
||||
|
||||
|
||||
def create_dataset(auth, payload=None):
|
||||
url = f"{HOST_ADDRESS}{DATASETS_API_URL}"
|
||||
res = requests.post(url=url, headers={"Content-Type": "application/json"}, auth=auth, json=payload)
|
||||
return res.json()
|
||||
|
||||
|
||||
def list_dataset(auth, params=None):
|
||||
url = f"{HOST_ADDRESS}{DATASETS_API_URL}"
|
||||
res = requests.get(url=url, headers={"Content-Type": "application/json"}, auth=auth, params=params)
|
||||
return res.json()
|
||||
|
||||
|
||||
def rm_dataset(auth, dataset_ids):
|
||||
url = f"{HOST_ADDRESS}{DATASETS_API_URL}"
|
||||
res = requests.delete(url=url, headers={"Content-Type": "application/json"}, auth=auth, json={"ids": dataset_ids})
|
||||
return res.json()
|
||||
|
||||
|
||||
def update_dataset(auth, dataset_id, payload=None):
|
||||
url = f"{HOST_ADDRESS}{DATASETS_API_URL}/{dataset_id}"
|
||||
res = requests.put(url=url, headers={"Content-Type": "application/json"}, auth=auth, json=payload)
|
||||
return res.json()
|
||||
|
||||
|
||||
def upload_file(auth, dataset_id, path):
|
||||
authorization = {"Authorization": auth}
|
||||
url = f"{HOST_ADDRESS}/v1/document/upload"
|
||||
json_req = {
|
||||
"kb_id": dataset_id,
|
||||
}
|
||||
|
||||
file = {"file": open(f"{path}", "rb")}
|
||||
|
||||
res = requests.post(url=url, headers=authorization, files=file, data=json_req)
|
||||
return res.json()
|
||||
|
||||
|
||||
def list_document(auth, dataset_id):
|
||||
authorization = {"Authorization": auth}
|
||||
url = f"{HOST_ADDRESS}/v1/document/list?id={dataset_id}"
|
||||
json = {}
|
||||
res = requests.post(url=url, headers=authorization, json=json)
|
||||
return res.json()
|
||||
|
||||
|
||||
def get_docs_info(auth, dataset_id, doc_ids=None, doc_id=None):
|
||||
"""
|
||||
Get document information by IDs.
|
||||
|
||||
Args:
|
||||
auth: Authorization header
|
||||
dataset_id: Dataset ID
|
||||
doc_ids: List of document IDs (use for multiple) - exclusive with doc_id
|
||||
doc_id: Single document ID (use for one) - exclusive with doc_ids
|
||||
|
||||
Raises:
|
||||
ValueError: If both doc_id and doc_ids are provided
|
||||
"""
|
||||
# Validate that id and ids are not used together
|
||||
if doc_id and doc_ids:
|
||||
raise ValueError("Cannot use both 'id' and 'ids' parameters at the same time.")
|
||||
|
||||
authorization = {"Authorization": auth}
|
||||
params = {}
|
||||
if doc_ids:
|
||||
# Multiple IDs
|
||||
for id in doc_ids:
|
||||
params.append(("ids", id))
|
||||
elif doc_id:
|
||||
# Single ID
|
||||
params["id"] = doc_id
|
||||
|
||||
# Use /api/v1 prefix for dataset API
|
||||
url = f"{HOST_ADDRESS}/api/v1/datasets/{dataset_id}/documents"
|
||||
res = requests.get(url=url, headers=authorization, params=params)
|
||||
return res.json()
|
||||
|
||||
|
||||
def parse_docs(auth, doc_ids):
|
||||
authorization = {"Authorization": auth}
|
||||
json_req = {"doc_ids": doc_ids, "run": 1}
|
||||
url = f"{HOST_ADDRESS}/api/v1/documents/ingest"
|
||||
res = requests.post(url=url, headers=authorization, json=json_req)
|
||||
return res.json()
|
||||
|
||||
|
||||
def parse_file(auth, document_id):
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
def test_get_email(get_email):
|
||||
print("\nEmail account:", flush=True)
|
||||
print(f"{get_email}\n", flush=True)
|
||||
@@ -0,0 +1,74 @@
|
||||
#
|
||||
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from common import create_dataset, list_dataset, rm_dataset, upload_file
|
||||
from common import list_document, get_docs_info, parse_docs
|
||||
from time import sleep
|
||||
from timeit import default_timer as timer
|
||||
|
||||
|
||||
def test_parse_txt_document(get_auth):
|
||||
# create dataset
|
||||
res = create_dataset(get_auth, {"name": "test_parse_txt_document"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
page_number = 1
|
||||
dataset_list = []
|
||||
dataset_id = None
|
||||
while True:
|
||||
res = list_dataset(get_auth, {"page": page_number, "page_size": 150})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
for item in data:
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
if len(dataset_list) < page_number * 150:
|
||||
break
|
||||
page_number += 1
|
||||
|
||||
filename = "ragflow_test.txt"
|
||||
res = upload_file(get_auth, dataset_id, f"../test_sdk_api/test_data/{filename}")
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
res = list_document(get_auth, dataset_id)
|
||||
|
||||
doc_id_list = []
|
||||
for doc in res["data"]["docs"]:
|
||||
doc_id_list.append(doc["id"])
|
||||
|
||||
res = get_docs_info(get_auth, dataset_id, doc_ids=doc_id_list)
|
||||
print(doc_id_list)
|
||||
doc_count = len(doc_id_list)
|
||||
res = parse_docs(get_auth, doc_id_list)
|
||||
|
||||
start_ts = timer()
|
||||
while True:
|
||||
res = get_docs_info(get_auth, dataset_id, doc_ids=doc_id_list)
|
||||
finished_count = 0
|
||||
for doc_info in res["data"]:
|
||||
if doc_info["progress"] == 1:
|
||||
finished_count += 1
|
||||
if finished_count == doc_count:
|
||||
break
|
||||
sleep(1)
|
||||
print("time cost {:.1f}s".format(timer() - start_ts))
|
||||
|
||||
# delete dataset
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
@@ -0,0 +1,198 @@
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from common import create_dataset, list_dataset, rm_dataset, update_dataset, DATASET_NAME_LIMIT
|
||||
import re
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def test_dataset(get_auth):
|
||||
# create dataset
|
||||
res = create_dataset(get_auth, {"name": "test_create_dataset"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
page_number = 1
|
||||
dataset_list = []
|
||||
while True:
|
||||
res = list_dataset(get_auth, {"page": page_number, "page_size": 150})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
for item in data:
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
if len(dataset_list) < page_number * 150:
|
||||
break
|
||||
page_number += 1
|
||||
|
||||
print(f"found {len(dataset_list)} datasets")
|
||||
# delete dataset
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
|
||||
|
||||
def test_dataset_1k_dataset(get_auth):
|
||||
# create dataset
|
||||
for i in range(1000):
|
||||
res = create_dataset(get_auth, {"name": f"test_create_dataset_{i}"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
page_number = 1
|
||||
dataset_list = []
|
||||
while True:
|
||||
res = list_dataset(get_auth, {"page": page_number, "page_size": 150})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
for item in data:
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
if len(dataset_list) < page_number * 150:
|
||||
break
|
||||
page_number += 1
|
||||
|
||||
print(f"found {len(dataset_list)} datasets")
|
||||
# delete dataset
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
|
||||
|
||||
def test_duplicated_name_dataset(get_auth):
|
||||
# create dataset
|
||||
for i in range(20):
|
||||
res = create_dataset(get_auth, {"name": "test_create_dataset"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
res = list_dataset(get_auth, {"page": 1})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
dataset_list = []
|
||||
pattern = r"^test_create_dataset.*"
|
||||
for item in data:
|
||||
dataset_name = item.get("name")
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
match = re.match(pattern, dataset_name)
|
||||
assert match is not None
|
||||
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
|
||||
|
||||
def test_invalid_name_dataset(get_auth):
|
||||
# create dataset
|
||||
res = create_dataset(get_auth, {"name": 0})
|
||||
assert res["code"] != 0
|
||||
|
||||
res = create_dataset(get_auth, {"name": ""})
|
||||
assert res["code"] != 0
|
||||
|
||||
long_string = ""
|
||||
|
||||
while len(long_string.encode("utf-8")) <= DATASET_NAME_LIMIT:
|
||||
long_string += random.choice(string.ascii_letters + string.digits)
|
||||
|
||||
res = create_dataset(get_auth, {"name": long_string})
|
||||
assert res["code"] != 0
|
||||
print(res)
|
||||
|
||||
|
||||
def test_update_different_params_dataset_success(get_auth):
|
||||
# create dataset
|
||||
res = create_dataset(get_auth, {"name": "test_create_dataset"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
page_number = 1
|
||||
dataset_list = []
|
||||
while True:
|
||||
res = list_dataset(get_auth, {"page": page_number, "page_size": 150})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
for item in data:
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
if len(dataset_list) < page_number * 150:
|
||||
break
|
||||
page_number += 1
|
||||
|
||||
print(f"found {len(dataset_list)} datasets")
|
||||
dataset_id = dataset_list[0]
|
||||
|
||||
res = update_dataset(
|
||||
get_auth,
|
||||
dataset_id,
|
||||
{
|
||||
"name": "test_update_dataset",
|
||||
"description": "test",
|
||||
"permission": "me",
|
||||
"chunk_method": "presentation",
|
||||
"language": "spanish",
|
||||
},
|
||||
)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# delete dataset
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
|
||||
|
||||
# update dataset with different parameters
|
||||
def test_update_different_params_dataset_fail(get_auth):
|
||||
# create dataset
|
||||
res = create_dataset(get_auth, {"name": "test_create_dataset"})
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
|
||||
# list dataset
|
||||
page_number = 1
|
||||
dataset_list = []
|
||||
while True:
|
||||
res = list_dataset(get_auth, {"page": page_number, "page_size": 150})
|
||||
data = res.get("data")
|
||||
if isinstance(data, dict):
|
||||
data = data.get("kbs", [])
|
||||
for item in data:
|
||||
dataset_id = item.get("id")
|
||||
dataset_list.append(dataset_id)
|
||||
if len(dataset_list) < page_number * 150:
|
||||
break
|
||||
page_number += 1
|
||||
|
||||
print(f"found {len(dataset_list)} datasets")
|
||||
dataset_id = dataset_list[0]
|
||||
|
||||
res = update_dataset(get_auth, dataset_id, {"id": "xxx"})
|
||||
assert res.get("code") == 101
|
||||
|
||||
# delete dataset
|
||||
if dataset_list:
|
||||
res = rm_dataset(get_auth, dataset_list)
|
||||
assert res.get("code") == 0, f"{res.get('message')}"
|
||||
print(f"{len(dataset_list)} datasets are deleted")
|
||||
Generated
+363
@@ -0,0 +1,363 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = "==3.13.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "25.4.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beartype"
|
||||
version = "0.22.6"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/e2/105ceb1704cb80fe4ab3872529ab7b6f365cf7c74f725e6132d0efcf1560/beartype-0.22.6.tar.gz", hash = "sha256:97fbda69c20b48c5780ac2ca60ce3c1bb9af29b3a1a0216898ffabdd523e48f4", size = 1588975, upload-time = "2025-11-20T04:47:14.736Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/c9/ceecc71fe2c9495a1d8e08d44f5f31f5bca1350d5b2e27a4b6265424f59e/beartype-0.22.6-py3-none-any.whl", hash = "sha256:0584bc46a2ea2a871509679278cda992eadde676c01356ab0ac77421f3c9a093", size = 1324807, upload-time = "2025-11-20T04:47:11.837Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.10.5"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hypothesis"
|
||||
version = "6.142.3"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "sortedcontainers" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/c9/03b5177dcd0224338c9ef63890bc52c0b0fbc86fba7c2c8a8523c0f02833/hypothesis-6.142.3.tar.gz", hash = "sha256:f1aaf83f6cc0c50f1b61e167974a8a67377dce13e0ea628b67a83f574ef30b85", size = 466042, upload-time = "2025-10-22T19:22:16.689Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/42/7422624c9079865a094e3e13014ecf21f07f07b190df09e1feaaaa687891/hypothesis-6.142.3-py3-none-any.whl", hash = "sha256:2fc19a2824c9bdc3f8e39d87861fbdf1d766982b20d54646a642bce82bcac179", size = 533464, upload-time = "2025-10-22T19:22:13.051Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "et-xmlfile" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-docx"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-pptx"
|
||||
version = "1.0.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
{ name = "pillow" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "xlsxwriter" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ragflow-sdk"
|
||||
version = "0.26.4"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "beartype" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
test = [
|
||||
{ name = "hypothesis" },
|
||||
{ name = "openpyxl" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pytest" },
|
||||
{ name = "python-docx" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "reportlab" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "beartype", specifier = ">=0.20.0,<1.0.0" },
|
||||
{ name = "requests", specifier = ">=2.30.0,<3.0.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
test = [
|
||||
{ name = "hypothesis", specifier = ">=6.131.9" },
|
||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||
{ name = "pillow", specifier = ">=11.1.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
{ name = "python-docx", specifier = ">=1.1.2" },
|
||||
{ name = "python-pptx", specifier = ">=1.0.2" },
|
||||
{ name = "reportlab", specifier = ">=4.3.1" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
{ name = "requests-toolbelt", specifier = ">=1.0.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reportlab"
|
||||
version = "4.4.4"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/fa/ed71f3e750afb77497641eb0194aeda069e271ce6d6931140f8787e0e69a/reportlab-4.4.4.tar.gz", hash = "sha256:cb2f658b7f4a15be2cc68f7203aa67faef67213edd4f2d4bdd3eb20dab75a80d", size = 3711935, upload-time = "2025-09-19T10:43:36.502Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/66/e040586fe6f9ae7f3a6986186653791fb865947f0b745290ee4ab026b834/reportlab-4.4.4-py3-none-any.whl", hash = "sha256:299b3b0534e7202bb94ed2ddcd7179b818dcda7de9d8518a57c85a58a1ebaadb", size = 1954981, upload-time = "2025-09-19T10:43:33.589Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests-toolbelt"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xlsxwriter"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user