支持表格
This commit is contained in:
parent
c410bf704b
commit
27bf97d3d6
|
|
@ -364,6 +364,17 @@ async def upload_file(
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"❌ 文本文件字数超限: {file.filename}, {e}")
|
logger.warning(f"❌ 文本文件字数超限: {file.filename}, {e}")
|
||||||
raise BadRequestError(str(e)) from e
|
raise BadRequestError(str(e)) from e
|
||||||
|
|
||||||
|
existing_file = await KnowledgeBaseFileService.get_active_file_by_name(
|
||||||
|
conn, kb_id, file.filename
|
||||||
|
)
|
||||||
|
if existing_file and existing_file.status in ("processing", "completed"):
|
||||||
|
raise BadRequestError(f"文件 '{file.filename}' 已存在于该知识库中")
|
||||||
|
retry_old_file_path = (
|
||||||
|
existing_file.file_path
|
||||||
|
if existing_file and existing_file.status == "failed"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
# 生成唯一文件名
|
# 生成唯一文件名
|
||||||
timestamp = int(time.time() * 1000)
|
timestamp = int(time.time() * 1000)
|
||||||
|
|
@ -374,6 +385,7 @@ async def upload_file(
|
||||||
oss_service = get_oss_service()
|
oss_service = get_oss_service()
|
||||||
file_path = None
|
file_path = None
|
||||||
file_url = None
|
file_url = None
|
||||||
|
uploaded_oss_object_name = None
|
||||||
|
|
||||||
logger.info(f"☁️ 开始上传文件,OSS 状态: {'已启用' if oss_service.enabled else '未启用'}")
|
logger.info(f"☁️ 开始上传文件,OSS 状态: {'已启用' if oss_service.enabled else '未启用'}")
|
||||||
|
|
||||||
|
|
@ -382,6 +394,7 @@ async def upload_file(
|
||||||
file_url = oss_service.upload_file_from_bytes(content, oss_object_name, file.filename)
|
file_url = oss_service.upload_file_from_bytes(content, oss_object_name, file.filename)
|
||||||
if file_url:
|
if file_url:
|
||||||
file_path = file_url
|
file_path = file_url
|
||||||
|
uploaded_oss_object_name = oss_object_name
|
||||||
logger.info(f"✅ 文件已上传到 OSS: {file_url}")
|
logger.info(f"✅ 文件已上传到 OSS: {file_url}")
|
||||||
|
|
||||||
# 🔑 图片审核:在创建文件记录前进行审核
|
# 🔑 图片审核:在创建文件记录前进行审核
|
||||||
|
|
@ -439,12 +452,25 @@ async def upload_file(
|
||||||
file_path = str(local_path)
|
file_path = str(local_path)
|
||||||
logger.info(f"💾 文件已保存到本地: {file_path}")
|
logger.info(f"💾 文件已保存到本地: {file_path}")
|
||||||
|
|
||||||
# 创建文件记录
|
try:
|
||||||
logger.info(f"📝 创建文件记录: {file.filename}")
|
# 创建文件记录(处理失败的可复用原记录重试)
|
||||||
file_record = await KnowledgeBaseFileService.create_file_record(
|
logger.info(f"📝 创建文件记录: {file.filename}")
|
||||||
conn, kb_id, current_user.id, file.filename, file_path, file_size, file_type
|
file_record = await KnowledgeBaseFileService.create_file_record(
|
||||||
)
|
conn, kb_id, current_user.id, file.filename, file_path, file_size, file_type
|
||||||
logger.info(f"✅ 文件记录已创建: ID={file_record.id}, 状态={file_record.status}")
|
)
|
||||||
|
logger.info(f"✅ 文件记录已创建: ID={file_record.id}, 状态={file_record.status}")
|
||||||
|
except ValueError:
|
||||||
|
if uploaded_oss_object_name and oss_service.enabled:
|
||||||
|
oss_service.delete_file(uploaded_oss_object_name)
|
||||||
|
raise
|
||||||
|
|
||||||
|
if retry_old_file_path and retry_old_file_path != file_path and retry_old_file_path.startswith(
|
||||||
|
("http://", "https://")
|
||||||
|
):
|
||||||
|
old_oss_object = oss_service.extract_object_name_from_url(retry_old_file_path, kb_id)
|
||||||
|
if old_oss_object:
|
||||||
|
oss_service.delete_file(old_oss_object)
|
||||||
|
logger.info(f"🗑️ 已清理失败重试前的 OSS 文件: {old_oss_object}")
|
||||||
|
|
||||||
# 审计日志:上传
|
# 审计日志:上传
|
||||||
await AuditService.write(
|
await AuditService.write(
|
||||||
|
|
@ -479,7 +505,6 @@ async def upload_file(
|
||||||
except BadRequestError:
|
except BadRequestError:
|
||||||
raise
|
raise
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# 文件名重复等业务错误
|
|
||||||
logger.warning(f"文件上传验证失败: {e}")
|
logger.warning(f"文件上传验证失败: {e}")
|
||||||
raise BadRequestError(str(e))
|
raise BadRequestError(str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,8 @@ def build_chat_model(
|
||||||
base_url = llm_env.resolved_deepseek_chat_base_url().strip().rstrip("/")
|
base_url = llm_env.resolved_deepseek_chat_base_url().strip().rstrip("/")
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"未知提供方: {provider}")
|
raise ValueError(f"未知提供方: {provider}")
|
||||||
|
|
||||||
|
print("-----------------------------------走到这里了-----------------------------------",extra_kwargs)
|
||||||
return ChatOpenAI(
|
return ChatOpenAI(
|
||||||
model=api_model,
|
model=api_model,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,18 @@ def dashscope_native_http_api_base() -> str:
|
||||||
def resolved_deepseek_chat_base_url() -> str:
|
def resolved_deepseek_chat_base_url() -> str:
|
||||||
"""DeepSeek OpenAI 兼容 base:仅从 ``DEEPSEEK_API_BASE`` 读取,无内置默认。"""
|
"""DeepSeek OpenAI 兼容 base:仅从 ``DEEPSEEK_API_BASE`` 读取,无内置默认。"""
|
||||||
return _getenv_nonempty("DEEPSEEK_API_BASE", "deepseek_api_base").strip().rstrip("/")
|
return _getenv_nonempty("DEEPSEEK_API_BASE", "deepseek_api_base").strip().rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def tongyi_embedding_api_key() -> str:
|
||||||
|
"""通义 Embedding:优先 ``ZL_DASHSCOPE_API_KEY``,否则回退 ``DASHSCOPE_API_KEY``。"""
|
||||||
|
return _getenv_nonempty("ZL_DASHSCOPE_API_KEY", "zl_dashscope_api_key") or _getenv_nonempty(
|
||||||
|
"DASHSCOPE_API_KEY", "dashscope_api_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tongyi_embedding_api_base() -> str:
|
||||||
|
"""通义 Embedding OpenAI 兼容 base:优先 ``ZL_DASHSCOPE_API_BASE``,否则 ``DASHSCOPE_API_BASE``。"""
|
||||||
|
zl = _getenv_nonempty("ZL_DASHSCOPE_API_BASE", "zl_dashscope_api_base").strip().rstrip("/")
|
||||||
|
if zl:
|
||||||
|
return zl
|
||||||
|
return tongyi_openai_compatible_base_url()
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ dependencies = [
|
||||||
"sse-starlette>=3.0.3",
|
"sse-starlette>=3.0.3",
|
||||||
"streamlit>=1.52.0",
|
"streamlit>=1.52.0",
|
||||||
"tavily-python>=0.7.13",
|
"tavily-python>=0.7.13",
|
||||||
"unstructured[docx]>=0.18.21",
|
"unstructured[docx,xlsx]>=0.18.21",
|
||||||
"uvicorn>=0.38.0",
|
"uvicorn>=0.38.0",
|
||||||
"redis>=5.0.0",
|
"redis>=5.0.0",
|
||||||
"alibabacloud-dysmsapi20170525>=3.0.0",
|
"alibabacloud-dysmsapi20170525>=3.0.0",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,25 @@ logger = get_logger(__name__)
|
||||||
class KnowledgeBaseFileService:
|
class KnowledgeBaseFileService:
|
||||||
"""知识库文件服务类"""
|
"""知识库文件服务类"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_active_file_by_name(
|
||||||
|
conn: asyncpg.Connection,
|
||||||
|
knowledge_base_id: int,
|
||||||
|
file_name: str,
|
||||||
|
) -> Optional[KnowledgeBaseFile]:
|
||||||
|
"""按知识库 + 文件名查询未删除的文件记录。"""
|
||||||
|
row = await conn.fetchrow(
|
||||||
|
"""
|
||||||
|
SELECT id, knowledge_base_id, user_id, file_name, file_path, file_size,
|
||||||
|
file_type, status, chunk_count, created_at, updated_at, is_deleted, deleted_at
|
||||||
|
FROM knowledge_base_file
|
||||||
|
WHERE knowledge_base_id = $1 AND file_name = $2 AND is_deleted = FALSE
|
||||||
|
""",
|
||||||
|
knowledge_base_id,
|
||||||
|
file_name,
|
||||||
|
)
|
||||||
|
return KnowledgeBaseFile(**dict(row)) if row else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def create_file_record(
|
async def create_file_record(
|
||||||
conn: asyncpg.Connection,
|
conn: asyncpg.Connection,
|
||||||
|
|
@ -43,18 +62,35 @@ class KnowledgeBaseFileService:
|
||||||
KnowledgeBaseFile: 创建的文件记录
|
KnowledgeBaseFile: 创建的文件记录
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 检查文件名是否已存在
|
existing = await KnowledgeBaseFileService.get_active_file_by_name(
|
||||||
existing = await conn.fetchrow(
|
conn, knowledge_base_id, file_name
|
||||||
"""
|
|
||||||
SELECT id FROM knowledge_base_file
|
|
||||||
WHERE knowledge_base_id = $1 AND file_name = $2 AND is_deleted = FALSE
|
|
||||||
""",
|
|
||||||
knowledge_base_id, file_name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
|
if existing.status == "failed":
|
||||||
|
row = await conn.fetchrow(
|
||||||
|
"""
|
||||||
|
UPDATE knowledge_base_file
|
||||||
|
SET file_path = $1, file_size = $2, file_type = $3,
|
||||||
|
status = 'processing', chunk_count = 0,
|
||||||
|
user_id = $4, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $5
|
||||||
|
RETURNING id, knowledge_base_id, user_id, file_name, file_path, file_size,
|
||||||
|
file_type, status, chunk_count, created_at, updated_at, is_deleted, deleted_at
|
||||||
|
""",
|
||||||
|
file_path,
|
||||||
|
file_size,
|
||||||
|
file_type,
|
||||||
|
user_id,
|
||||||
|
existing.id,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"重试处理失败文件: {file_name}, 知识库 ID: {knowledge_base_id}, file_id={existing.id}"
|
||||||
|
)
|
||||||
|
return KnowledgeBaseFile(**dict(row))
|
||||||
|
|
||||||
raise ValueError(f"文件 '{file_name}' 已存在于该知识库中")
|
raise ValueError(f"文件 '{file_name}' 已存在于该知识库中")
|
||||||
|
|
||||||
# 插入文件记录
|
# 插入文件记录
|
||||||
row = await conn.fetchrow(
|
row = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -71,17 +71,61 @@ except ImportError:
|
||||||
fitz = None
|
fitz = None
|
||||||
|
|
||||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||||
from langchain_ollama import OllamaEmbeddings
|
from langchain_core.embeddings import Embeddings
|
||||||
from langchain_openai import OpenAIEmbeddings
|
from langchain_openai import OpenAIEmbeddings
|
||||||
from langchain_chroma import Chroma
|
from langchain_chroma import Chroma
|
||||||
import bs4
|
import bs4
|
||||||
|
|
||||||
from logger.logging import get_logger
|
from logger.logging import get_logger
|
||||||
from core.config import settings
|
from core.config import settings
|
||||||
|
from core.llm_env import tongyi_embedding_api_base, tongyi_embedding_api_key
|
||||||
from services.kb_text_limits import validate_kb_text_length, validate_chat_file_text_length
|
from services.kb_text_limits import validate_kb_text_length, validate_chat_file_text_length
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# 通义 text-embedding-v4(OpenAI 兼容)单次请求最多 10 条,超出会 400/500
|
||||||
|
_TONGYI_EMBEDDING_MAX_BATCH = 10
|
||||||
|
|
||||||
|
|
||||||
|
class TongyiEmbeddings(Embeddings):
|
||||||
|
"""通义千问 Embedding 封装:固定走 ZL/DashScope 网关,并按 API 上限分批请求。"""
|
||||||
|
|
||||||
|
def __init__(self, *, model: str, api_key: str, base_url: str, dimensions: int) -> None:
|
||||||
|
self._inner = OpenAIEmbeddings(
|
||||||
|
model=model,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
check_embedding_ctx_length=False,
|
||||||
|
dimensions=dimensions,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _batched(self, texts: List[str]) -> List[List[float]]:
|
||||||
|
if not texts:
|
||||||
|
return []
|
||||||
|
vectors: List[List[float]] = []
|
||||||
|
for start in range(0, len(texts), _TONGYI_EMBEDDING_MAX_BATCH):
|
||||||
|
batch = texts[start : start + _TONGYI_EMBEDDING_MAX_BATCH]
|
||||||
|
vectors.extend(self._inner.embed_documents(batch))
|
||||||
|
return vectors
|
||||||
|
|
||||||
|
def embed_documents(self, texts: List[str]) -> List[List[float]]:
|
||||||
|
return self._batched(texts)
|
||||||
|
|
||||||
|
def embed_query(self, text: str) -> List[float]:
|
||||||
|
return self._inner.embed_query(text)
|
||||||
|
|
||||||
|
async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
|
||||||
|
if not texts:
|
||||||
|
return []
|
||||||
|
vectors: List[List[float]] = []
|
||||||
|
for start in range(0, len(texts), _TONGYI_EMBEDDING_MAX_BATCH):
|
||||||
|
batch = texts[start : start + _TONGYI_EMBEDDING_MAX_BATCH]
|
||||||
|
vectors.extend(await self._inner.aembed_documents(batch))
|
||||||
|
return vectors
|
||||||
|
|
||||||
|
async def aembed_query(self, text: str) -> List[float]:
|
||||||
|
return await self._inner.aembed_query(text)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ProcessResult:
|
class ProcessResult:
|
||||||
|
|
@ -125,16 +169,28 @@ class VectorService:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""初始化向量服务"""
|
"""初始化向量服务"""
|
||||||
# 初始化嵌入模型
|
embedding_api_key = tongyi_embedding_api_key()
|
||||||
# self.embedding = OllamaEmbeddings(model="nomic-embed-text")
|
embedding_api_base = tongyi_embedding_api_base()
|
||||||
# DashScope 兼容网关只接受字符串 input;默认 check_embedding_ctx_length=True
|
if not embedding_api_key:
|
||||||
# 会用 tiktoken 转成 token id 列表再请求,导致 400:contents is neither str nor list of str
|
raise ValueError(
|
||||||
print(settings.dashscope_api_key, settings.dashscope_api_base)
|
"未配置通义 Embedding API Key,请设置 ZL_DASHSCOPE_API_KEY 或 DASHSCOPE_API_KEY"
|
||||||
self.embedding = OpenAIEmbeddings(
|
)
|
||||||
model="text-embedding-v4",
|
if not embedding_api_base:
|
||||||
api_key=os.getenv("ZL_DASHSCOPE_API_KEY"), # 如果您没有配置环境变量,请在此处用您的API Key进行替换
|
raise ValueError(
|
||||||
base_url=os.getenv("ZL_DASHSCOPE_API_BASE"),
|
"未配置通义 Embedding API Base,请设置 ZL_DASHSCOPE_API_BASE 或 DASHSCOPE_API_BASE"
|
||||||
check_embedding_ctx_length=False,
|
)
|
||||||
|
# 通义 text-embedding-v4(OpenAI 兼容 /embeddings);check_embedding_ctx_length=False
|
||||||
|
# 避免 tiktoken 将输入转成 token id 列表导致 DashScope 返回 400
|
||||||
|
self.embedding = TongyiEmbeddings(
|
||||||
|
model=settings.embedding_model,
|
||||||
|
api_key=embedding_api_key,
|
||||||
|
base_url=embedding_api_base,
|
||||||
|
dimensions=settings.embedding_dimension,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"通义 Embedding 已初始化: model={}, base_url={}",
|
||||||
|
settings.embedding_model,
|
||||||
|
embedding_api_base,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 文本分割器配置(参考 server:增大 chunk_size 保留更多上下文)
|
# 文本分割器配置(参考 server:增大 chunk_size 保留更多上下文)
|
||||||
|
|
|
||||||
|
|
@ -1124,7 +1124,7 @@ dependencies = [
|
||||||
{ name = "sse-starlette" },
|
{ name = "sse-starlette" },
|
||||||
{ name = "streamlit" },
|
{ name = "streamlit" },
|
||||||
{ name = "tavily-python" },
|
{ name = "tavily-python" },
|
||||||
{ name = "unstructured", extra = ["docx"] },
|
{ name = "unstructured", extra = ["docx", "xlsx"] },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1188,7 +1188,7 @@ requires-dist = [
|
||||||
{ name = "sse-starlette", specifier = ">=3.0.3" },
|
{ name = "sse-starlette", specifier = ">=3.0.3" },
|
||||||
{ name = "streamlit", specifier = ">=1.52.0" },
|
{ name = "streamlit", specifier = ">=1.52.0" },
|
||||||
{ name = "tavily-python", specifier = ">=0.7.13" },
|
{ name = "tavily-python", specifier = ">=0.7.13" },
|
||||||
{ name = "unstructured", extras = ["docx"], specifier = ">=0.18.21" },
|
{ name = "unstructured", extras = ["docx", "xlsx"], specifier = ">=0.18.21" },
|
||||||
{ name = "uvicorn", specifier = ">=0.38.0" },
|
{ name = "uvicorn", specifier = ">=0.38.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1848,6 +1848,19 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "msoffcrypto-tool"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
{ name = "olefile" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a6/34/6250bdddaeaae24098e45449ea362fb3555a65fba30cad0ad5630ea48d1a/msoffcrypto_tool-6.0.0.tar.gz", hash = "sha256:9a5ebc4c0096b42e5d7ebc2350afdc92dc511061e935ca188468094fdd032bbe", size = 40593, upload-time = "2026-01-12T08:59:56.73Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/85/9e359fa9279e1d6861faaf9b6f037a3226374deb20a054c3937be6992013/msoffcrypto_tool-6.0.0-py3-none-any.whl", hash = "sha256:46c394ed5d9641e802fc79bf3fb0666a53748b23fa8c4aa634ae9d30d46fe397", size = 48791, upload-time = "2026-01-12T08:59:55.394Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multidict"
|
name = "multidict"
|
||||||
version = "6.7.1"
|
version = "6.7.1"
|
||||||
|
|
@ -1905,6 +1918,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/cf/1c3795866cefaac6e648d4e98c373cafd97810f6e317c307371007ab4abb/neo4j-6.2.0-py3-none-any.whl", hash = "sha256:b87abdd13a5cc2e3bd51026926c2f20ac38fa3febe98c340520dce19e97388d0", size = 327824, upload-time = "2026-05-04T07:35:39.604Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/cf/1c3795866cefaac6e648d4e98c373cafd97810f6e317c307371007ab4abb/neo4j-6.2.0-py3-none-any.whl", hash = "sha256:b87abdd13a5cc2e3bd51026926c2f20ac38fa3febe98c340520dce19e97388d0", size = 327824, upload-time = "2026-05-04T07:35:39.604Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "networkx"
|
||||||
|
version = "3.6.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nltk"
|
name = "nltk"
|
||||||
version = "3.9.4"
|
version = "3.9.4"
|
||||||
|
|
@ -3353,6 +3375,13 @@ wheels = [
|
||||||
docx = [
|
docx = [
|
||||||
{ name = "python-docx" },
|
{ name = "python-docx" },
|
||||||
]
|
]
|
||||||
|
xlsx = [
|
||||||
|
{ name = "msoffcrypto-tool" },
|
||||||
|
{ name = "networkx" },
|
||||||
|
{ name = "openpyxl" },
|
||||||
|
{ name = "pandas" },
|
||||||
|
{ name = "xlrd" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unstructured-client"
|
name = "unstructured-client"
|
||||||
|
|
@ -3594,6 +3623,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" },
|
{ url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xlrd"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xxhash"
|
name = "xxhash"
|
||||||
version = "3.7.0"
|
version = "3.7.0"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue