From f78efc71250e14131c1b48dff2f92fabb2ab771b Mon Sep 17 00:00:00 2001 From: QingGang Date: Tue, 27 Jan 2026 01:41:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90RAG=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- backend/services/data_service.py | 40 +- backend/services/llm_service.py | 23 + docs/RAGtest.md | 157 +++++++ docs/docker.md | 22 +- docs/开发计划.md | 83 ++-- pyproject.toml | 4 + scripts/test_firecrawl.py | 76 ++++ tests/__init__.py | 0 tests/rag_benchmark/__init__.py | 0 tests/rag_benchmark/benchmark_report.png | Bin 0 -> 102872 bytes tests/rag_benchmark/dataset.json | 86 ++++ tests/rag_benchmark/evaluator.py | 74 +++ tests/rag_benchmark/run_benchmark.py | 168 +++++++ tests/rag_benchmark/visual_benchmark.py | 208 +++++++++ uv.lock | 557 +++++++++++++++++++++++ 16 files changed, 1434 insertions(+), 66 deletions(-) create mode 100644 docs/RAGtest.md create mode 100644 scripts/test_firecrawl.py create mode 100644 tests/__init__.py create mode 100644 tests/rag_benchmark/__init__.py create mode 100644 tests/rag_benchmark/benchmark_report.png create mode 100644 tests/rag_benchmark/dataset.json create mode 100644 tests/rag_benchmark/evaluator.py create mode 100644 tests/rag_benchmark/run_benchmark.py create mode 100644 tests/rag_benchmark/visual_benchmark.py diff --git a/Dockerfile b/Dockerfile index 8205cc6..cc7c2f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ ENV PATH="/app/.venv/bin:$PATH" ENV PYTHONPATH=/app # 暴露端口 -EXPOSE 8000 +EXPOSE 28000 # 启动命令 # 现在直接调用 uvicorn 即可,它会自动找到 .venv 里的版本 diff --git a/backend/services/data_service.py b/backend/services/data_service.py index 10fd24b..3533eb9 100644 --- a/backend/services/data_service.py +++ b/backend/services/data_service.py @@ -143,7 +143,7 @@ class DataService: and_(self.db.queue.c.task_id == task_id, self.db.queue.c.url == clean_url) ).values(status='completed')) - def search(self, query_text: str, query_vector: list, task_id=None, candidates_num: int = 50): + def search(self, query_text: str, query_vector: list, task_id=None, candidates_num: int = 50, vector_weight: float = 0.7): # 向量格式清洗 if hasattr(query_vector, 'tolist'): query_vector = query_vector.tolist() if isinstance(query_vector, list) and len(query_vector) > 0 and isinstance(query_vector[0], list): @@ -151,26 +151,52 @@ class DataService: results = [] with self.db.engine.connect() as conn: + # 1. 构造 Query 对象 (这是 tsquery 类型) keyword_query = func.websearch_to_tsquery('english', query_text) - vector_score = (1 - self.db.chunks.c.embedding.cosine_distance(query_vector)) - keyword_score = func.ts_rank(self.db.chunks.c.content_tsvector, keyword_query) - final_score = (vector_score * 0.7 + func.coalesce(keyword_score, 0) * 0.3).label("score") + + # 计算分数 (逻辑不变) + vector_dist = self.db.chunks.c.embedding.cosine_distance(query_vector) + vector_score = (1 - vector_dist) + + # 注意:ts_rank 需要 (tsvector, tsquery) + keyword_rank = func.ts_rank(self.db.chunks.c.content_tsvector, keyword_query) + keyword_score = func.coalesce(keyword_rank, 0) + + keyword_weight = 1.0 - vector_weight + final_score = (vector_score * vector_weight + keyword_score * keyword_weight).label("score") stmt = select( self.db.chunks.c.task_id, self.db.chunks.c.source_url, self.db.chunks.c.title, self.db.chunks.c.content, self.db.chunks.c.meta_info, final_score ) + + # ================= 修复点开始 ================= + # 只有当 vector_weight 为 0 (纯关键词模式) 时,才强制加 WHERE 过滤 + if vector_weight == 0: + # 错误写法 (SQLAlchemy 自动生成 plainto_tsquery 导致报错): + # stmt = stmt.where(keyword_query.match(self.db.chunks.c.content_tsvector)) + + # 正确写法 (直接使用 PG 的 @@ 操作符): + # 含义: content_tsvector @@ keyword_query + stmt = stmt.where(self.db.chunks.c.content_tsvector.op('@@')(keyword_query)) + # ================= 修复点结束 ================= + if task_id: stmt = stmt.where(self.db.chunks.c.task_id == task_id) + stmt = stmt.order_by(desc("score")).limit(candidates_num) try: rows = conn.execute(stmt).fetchall() results = [{"task_id": r[0], "source_url": r[1], "title": r[2], "content": r[3], "meta_info": r[4], "score": float(r[5])} for r in rows] except Exception as e: - logger.error(f"Hybrid search failed: {e}") - return self._fallback_vector_search(query_vector, task_id, candidates_num) + # 打印详细错误方便调试 + logger.error(f"Search failed: {e}") + # 只有混合或向量模式才回退,如果是纯关键词模式报错,回退也没用 + if vector_weight > 0: + return self._fallback_vector_search(query_vector, task_id, candidates_num) + return {"results": [], "msg": "Keyword search failed"} - return {"results": results, "msg": f"Hybrid found {len(results)}"} + return {"results": results, "msg": f"Found {len(results)}"} def _fallback_vector_search(self, vector, task_id, limit): logger.warning("Fallback to pure vector search") diff --git a/backend/services/llm_service.py b/backend/services/llm_service.py index 806d16e..e1e88ba 100644 --- a/backend/services/llm_service.py +++ b/backend/services/llm_service.py @@ -93,5 +93,28 @@ class LLMService: logger.error(f"Rerank Exception: {e}") # 降级策略 return documents[:top_n] + def chat(self, prompt: str, system_prompt: str = None, model: str = "qwen-turbo") -> str: + """ + [新增] 通用对话生成接口,用于RAG的最终回答或作为测试裁判(Judge) + """ + messages = [] + if system_prompt: + messages.append({'role': 'system', 'content': system_prompt}) + messages.append({'role': 'user', 'content': prompt}) + try: + resp = dashscope.Generation.call( + model=model, + messages=messages, + result_format='message' + ) + if resp.status_code == HTTPStatus.OK: + return resp.output.choices[0].message.content + else: + logger.error(f"Chat API Error: {resp}") + return "Error generating response." + except Exception as e: + logger.error(f"Chat Exception: {e}") + return "Error generating response." + llm_service = LLMService() \ No newline at end of file diff --git a/docs/RAGtest.md b/docs/RAGtest.md new file mode 100644 index 0000000..d37428e --- /dev/null +++ b/docs/RAGtest.md @@ -0,0 +1,157 @@ +这是一份标准的 **RAG 系统测试与验证设计文档**。你可以将其作为项目文档的一部分,用于指导开发团队进行自动化测试框架的搭建、QA 团队进行测试用例的编写,以及算法工程师进行模型选型。 + +--- + +# RAG 知识库检索与生成系统测试设计规范 +**RAG System Test Design Specification** + +**版本**: 1.0 +**状态**: 拟定中 + +--- + +## 1. 测试背景与目标 (Background & Objectives) + +RAG(检索增强生成)系统涉及“检索器(Retriever)”与“生成器(Generator)”的复杂交互。传统的软件测试(单元测试、集成测试)无法有效评估其回答质量。本设计文档旨在建立一套**端到端(End-to-End)**与**分段式(Component-wise)**结合的测试框架,以达成以下目标: + +1. **量化评估**:将模糊的“回答好坏”转化为可度量的指标(如召回率、忠实度得分)。 +2. **归因分析**:当系统表现不佳时,能快速定位是“检索没找对”还是“LLM 没答好”。 +3. **选型决策**:通过横向对比(Benchmark),为检索算法(BM25/Vector/Hybrid)和模型选择提供数据支撑。 +4. **短板识别**:自动识别系统在特定场景(如多语言、数值推理、负向测试)下的薄弱环节。 + +--- + +## 2. 测试架构原理 (Test Architecture) + +测试框架基于 **RAG 三元组 (RAG Triad)** 理论进行设计,分别针对链路中的三个关键节点进行评估: + +### 2.1 评估对象 +* **Query (用户提问)**: 测试的输入。 +* **Context (检索上下文)**: 检索器从知识库召回的 Top-K 文档片段。 +* **Response (系统回答)**: LLM 基于 Query 和 Context 生成的最终文本。 + +### 2.2 测试流向 +1. **检索层评估 (Retrieval Evaluation)**: `Query` $\leftrightarrow$ `Context` + * *核心问题*: 检索到的内容是否包含回答问题所需的全部信息? +2. **生成层评估 (Generation Evaluation)**: + * `Context` $\leftrightarrow$ `Response`: **忠实度 (Faithfulness)**。回答是否完全基于上下文?有无幻觉? + * `Query` $\leftrightarrow$ `Response`: **相关度 (Relevance)**。回答是否解决了用户的问题? + +--- + +## 3. 测试数据集设计 (Dataset Design) + +为了全面评估系统能力,构建“黄金数据集”是测试的基础。数据集需包含以下维度的字段: + +### 3.1 数据字段定义 +| 字段名 | 说明 | 用途 | +| :--- | :--- | :--- | +| **ID** | 唯一标识符 | 用于追踪 Case | +| **Type** | 问题分类 | 用于计算 "Weakest Category" (见 3.2) | +| **Query** | 用户模拟问题 | 输入 | +| **Ground Truth Answer** | 标准答案 | 用于生成层对比评分 | +| **Keywords/Key Information** | 关键信息点/术语 | 用于计算检索层的 Recall | +| **Ground Truth Context IDs** | (可选) 预期命中的文档ID | 用于计算 Hit Rate | + +### 3.2 场景分类体系 (Category Taxonomy) +测试集必须覆盖以下场景,以防止模型“偏科”: + +1. **Core Function (核心功能)**: 基础的概念解释和流程指引。 +2. **Detail/Numeric (细节与数值)**: 涉及具体参数、价格、限制阈值等精确信息(考察向量检索的弱点)。 +3. **Inference (推理集成)**: 需要综合多段文档才能回答的复杂问题(考察 Context Window 和推理能力)。 +4. **Multilingual (跨语言)**: 中文问英文文档,或反之(考察 Embedding 对齐能力)。 +5. **Negative Test (负向/拒答)**: 知识库中不存在的问题(考察系统抗幻觉能力,预期输出为“未找到信息”)。 +6. **Safety/Injection (安全)**: Prompt 注入攻击防御测试。 + +--- + +## 4. 控制变量与横向对比设计 (Experimental Design) + +为选出最佳技术方案,需设计控制变量矩阵(Control Matrix),进行 A/B 测试或横向评测。 + +### 4.1 实验组配置 (Configuration Matrix) + +| 实验组 | 检索机制 (Retrieval) | 权重策略 | 重排序 (Rerank) | 测试目的 | +| :--- | :--- | :--- | :--- | :--- | +| **Group A (Baseline)** | **Keyword (BM25)** | Vector=0, Keyword=1 | OFF | 模拟传统全文检索,设立基准线。 | +| **Group B (Semantic)** | **Dense Vector** | Vector=1, Keyword=0 | OFF | 验证语义理解能力,检测对专有名词的丢失情况。 | +| **Group C (Hybrid)** | **Hybrid Search** | Vector=0.7, Keyword=0.3 | OFF | 验证双路召回的互补性(当前主流方案)。 | +| **Group D (High Acc)** | **Hybrid Search** | Vector=0.7, Keyword=0.3 | **ON (Top N)** | 验证引入 Rerank 模型对长尾、复杂问题的提升效果及延迟代价。 | + +### 4.2 性能监控 +在运行上述配置时,同步记录以下工程指标: +* **Latency**: 端到端耗时(P50, P95)。 +* **Token Usage**: 消耗的 Token 数量(计算成本 ROI)。 + +--- + +## 5. 核心评估指标 (Key Metrics) + +采用 **规则计算 + LLM裁判 (LLM-as-a-Judge)** 相结合的方式。 + +### 5.1 检索层指标 (Retrieval Metrics) + +| 指标 | 定义 | 计算方法 | 优劣判断 | +| :--- | :--- | :--- | :--- | +| **Keyword Recall** | 关键词召回率 | $\frac{\text{检索内容中命中的关键词数}}{\text{标准答案中的关键词总数}}$ | 低于 50% 说明切片策略或检索算法严重失效。 | +| **Hit Rate** | 命中率 | 检索结果 Top-K 中是否包含至少一个相关文档片段。 | 二分类指标 (0/1)。 | + +### 5.2 生成层指标 (Generation Metrics) + +使用高智能模型(如 GPT-4o / Qwen-Max)作为裁判,进行 1-5 分打分。 + +| 指标 | 定义 | 评分标准 (Rubric) | +| :--- | :--- | :--- | +| **Correctness** | 正确性 | **5分**: 含义与 Ground Truth 完全一致。
**1分**: 完全错误或包含严重幻觉。 | +| **Completeness** | 完整性 | 答案是否涵盖了问题询问的所有方面?(主要针对列表类问题)。 | +| **Honesty** | 诚实度 | (针对负向测试) 当无相关信息时,是否诚实回答“不知道”而非编造。 | + +### 5.3 诊断性指标 (Diagnostic Metrics) + +* **Weakest Category (最弱类别)**: + * 计算逻辑:按 `Type` 分组,计算各组的平均分,取最低者。 + * 作用:直接指出系统短板(例如:“多语言能力最差”或“数值细节最差”),指导后续优化方向。 + +--- + +## 6. 自动化测试流程 (Workflow) + +1. **Setup Phase**: + * 加载 `dataset.json`。 + * 初始化所有待测的 RAG Pipeline 配置。 + +2. **Execution Phase (Loop)**: + * 遍历测试集中的每个 Query。 + * **Step 1 Retrieve**: 调用检索接口,获取 Context。 + * **Step 2 Generate**: 将 Context + Query 送入 LLM 生成 Answer。 + * **Step 3 Measure**: 记录 Latency。 + +3. **Evaluation Phase**: + * **Rule Check**: 计算 Keyword Recall。 + * **AI Judge**: 将 (Query, Answer, Ground Truth) 组装 Prompt 发送给裁判 LLM 打分。 + +4. **Reporting Phase**: + * 输出汇总报表,包含:各配置的平均分、平均召回率、延迟、最弱类别。 + * 输出 Bad Case 列表(得分 < 3 的用例)。 + +--- + +## 7. 附录:LLM 裁判 Prompt 模板 + +```markdown +你是一名 RAG 系统测试裁判。请评估【系统回答】相对于【标准答案】的质量。 + +【问题类型】: {type} +【用户问题】: {query} +【标准答案】: {ground_truth} +【系统回答】: {prediction} + +请打分 (1-5): +- 5分: 完美。逻辑正确,无幻觉,细节精准。 +- 3分: 及格。包含核心信息,但有遗漏或啰嗦。 +- 1分: 错误。答非所问,或在负向测试中产生幻觉。 + +特别规则:对于 "Negative Test",如果标准答案是“不支持/未提及”,而系统回答了“未找到相关信息”,请给 5 分。 + +输出 JSON: {"score": int, "reason": "string"} +``` \ No newline at end of file diff --git a/docs/docker.md b/docs/docker.md index 89bda3d..0676c54 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -6,7 +6,7 @@ | **字段** | **当前值 (示例)** | **说明** | **每次要改吗?** | | -------------------- | ---------------------------------- | ------------------------------ | ---------------------- | -| **Version** | **v1.0.6** | **镜像的版本标签 (Tag)** | **是 (必须改)** | +| **Version** | **v1.0.7** | **镜像的版本标签 (Tag)** | **是 (必须改)** | | **Image Name** | **wiki-crawl-backend** | **镜像/容器的名字** | **否 (固定)** | | **Namespace** | **qg-demo** | **阿里云命名空间** | **否 (固定)** | | **Registry** | **crpi-1rwd6fvain6t49g2...** | **阿里云仓库地址** | **否 (固定)** | @@ -19,18 +19,18 @@ ### 1. 构建镜像 (Build) -**修改命令最后的版本号** **v1.0.6** +**修改命令最后的版本号** **v1.0.7** ```powershell -docker build -t crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.6 . +docker build -t crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.7 . ``` ### 2. 推送镜像 (Push) -**修改命令最后的版本号** **v1.0.6** +**修改命令最后的版本号** **v1.0.7** ```powershell -docker push crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.6 +docker push crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.7 ``` > **成功标准:** **看到进度条走完,且最后显示** **Pushed**。 @@ -43,10 +43,10 @@ docker push crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/w ### 1. 拉取新镜像 (Pull) -**修改命令最后的版本号** **v1.0.6** +**修改命令最后的版本号** **v1.0.7** ```bash -docker pull crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.6 +docker pull crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.7 ``` ### 2. 停止并删除旧容器 @@ -61,7 +61,7 @@ docker rm wiki-crawl-backend ### 3. 启动新容器 (Run) - 关键步骤 -**修改命令最后的版本号** **v1.0.6** +**修改命令最后的版本号** **v1.0.7** **code**Bash @@ -69,7 +69,7 @@ docker rm wiki-crawl-backend docker run -d --name wiki-crawl-backend \ -e PYTHONUNBUFFERED=1 \ -p 80:8000 \ - crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.6 + crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.7 ``` ### 4. 验证与日志查看 @@ -132,9 +132,9 @@ docker image prune -a -f ### 5. 那个超长的 URL -**crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.6** +**crpi-1rwd6fvain6t49g2.cn-hangzhou.personal.cr.aliyuncs.com/qg-demo/wiki-crawl-backend:v1.0.7** * **Registry (仓库地址)**: **crpi-1rwd...aliyuncs.com** **-> 你的专属阿里云仓库服务器。** * **Namespace (命名空间)**: **qg-demo** **-> 你在仓库里划出的个人地盘。** * **Image Name (镜像名)**: **wiki-crawl-backend** **-> 这个项目的名字。** -* **Tag (标签)**: **v1.0.6** **-> 相当于软件的版本号。如果不写 Tag,默认就是** **latest**。**生产环境强烈建议写明确的版本号**,方便回滚(比如 1.0.3 挂了,你可以立马用 1.0.2 启动)。 +* **Tag (标签)**: **v1.0.7** **-> 相当于软件的版本号。如果不写 Tag,默认就是** **latest**。**生产环境强烈建议写明确的版本号**,方便回滚(比如 1.0.3 挂了,你可以立马用 1.0.2 启动)。 diff --git a/docs/开发计划.md b/docs/开发计划.md index c7ace7a..f03220d 100644 --- a/docs/开发计划.md +++ b/docs/开发计划.md @@ -1,60 +1,49 @@ # 下一步开发计划 -## 2025.1.13 +## 2025.1.20 1. 知识库RAG + 1. 测试 测试相关资料参考链接: - - [ ] 参照主流知识库架构增减修改当前知识库字段 - - [ ] 根据主流RAG测试要求完善知识库检索测试 - - [ ] 开发LLM输出测试 - - [ ] 横向对比不同检索方法或模型下的测试效果 + - [ ] 参照主流知识库架构增减修改当前知识库字段 + - [ ] 根据主流RAG测试要求完善知识库检索测试 + - [ ] 开发LLM输出测试 + - [ ] 横向对比不同检索方法或模型下的测试效果 + 2. dify原生知识库 + dify原生支持的知识库可以直接配置firecrawl的api_key,实现知识库爬取,因此 2. 后端封装backend - 1. 新增v3接口,替代v2和v1。 - 预期实现效果: - - [ ] 添加任务 - - [ ] 查询任务 - - [ ] 执行任务 - - [ ] 知识库搜索 - 2. 包装成MCP + 1. 重置接口,v1版本,restfulAPI + 预期实现效果: + - [x] 添加任务 + - [x] 查询任务 + - [x] 执行任务 + - [x] 获取所有知识库 + - [x] 知识库搜索 + 2. 新增功能 + - [ ] 业务操作原子化 + - [ ] 知识库更新相关 + - [ ] 日志功能 + 3. 包装成MCP工具供dify调用 + - [x] 完成backend的MCP包装并测试 + - [x] 发现dify可以直接用http接口封装工具,所以就用原来的fastapi去做了 + 4. 阿里云部署 + - [x] 将后端部署阿里云 3. dify节点 - - [ ] 完成dify的LLM输出工具,主要负责处理搜索逻辑和问题分类,调用api,发布工具。 + - [x] 完成dify的LLM输出工具,主要负责处理搜索逻辑和问题分类,调用api,发布工具。 也可能直接在backend里全部实现,直接集成到bot里 -4. firecrawl与替代方案调研 - - 1. firecrawl付费方案 - - 常规订阅链接: - 注意: 此链接下均是按时间订阅的,每月限制额度, 可额外购买, 但是考虑到客户使用的时候可能会固定时间集中使用(**采集新wiki, 更新旧wiki**) - - 企业订阅方案: 需要联系firecrawl订制 - 2. firecrawl开源方案 - - 开源github链接: - - 优劣对比 - - | 对比维度 | 开源版 (Self-hosted) | 云服务版 (Cloud / SaaS) | 核心差异说明 | - | :-------------------------- | :------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------------------------------------ | - | **部署方式** | 🐳 **Docker 自托管**
需自行配置服务器环境 | ☁️ **开箱即用**
注册 API Key 即可调用 | 云版省去了复杂的环境搭建过程。 | - | **成本** | 🆓 **软件免费**
需支付服务器/带宽费用 | 💰 **订阅制**
按 Credits (页数) 计费,有免费额度 | 量大且有闲置服务器时开源版更省钱;量小或追求稳定时云版更划算。 | - | **反爬虫绕过**
(Proxies) | ❌ **弱 / 需自行配置**
默认使用本机 IP,易被 Cloudflare 拦截 | ✅ **强 / 内置智能代理**
自动轮换 IP,擅长绕过 WAF 和人机验证 | **这是最大的区别。** 云版包含商业代理池成本,开源版需你自己买代理。 | - | **维护难度** | 🛠 **高**
需维护 Redis、队列、无头浏览器更新 | ☕ **零**
官方团队维护基础设施 | 开源版遇到浏览器崩溃或内存泄漏需自己修。 | - | **并发与性能** | ⚠️ **受限于硬件**
取决于你的服务器配置 | 🚀 **弹性扩容**
支持高并发,速度通常更快 | 云版对并行抓取做了优化。 | - | **JS 渲染** | ✅ **支持**
需配置 Playwright/Puppeteer | ✅ **支持**
默认优化,加载更稳定 | 两者核心引擎相同,但云版资源分配更合理。 | - | **数据隐私** | 🔒 **高 (本地化)**
数据不经过第三方服务器 | ☁️ **中**
数据需传输至 Firecrawl 服务器处理 | 对数据合规性要求极高的场景(如金融/医疗)首选开源版。 | - | **适用场景** | 极客折腾、内网抓取、低频低难度网站、数据极度敏感 | 商业项目、大规模抓取、高难度网站 (有反爬)、追求稳定性 | | - - 3. 自主研发爬虫 - 1. 反爬机制: 维基百科对IP有访问频率限制, 且有验证码, 需自行处理 - 2. 动态内容: 维基百科有很多动态内容, 如表格, 图片等, 需自行处理, 如使用Selenium等工具模拟浏览器行为 - - **Firecrawl方案和替代评估总结** - 假设客户的产品需求是: 从不同的网站爬取文档制成知识库, 并且需要定期维护, 那么其实只有在爬取新的站点和维护旧的站点的时候会集中使用firecrawl的额度, 主要特点是**使用时间集中**且**使用时段内额度需求量很大**以及**优先要保证爬虫模块的稳定性** - 因此最推荐的方案是: 定时采购额度, 但是考虑到常规的订阅只有按时间计费, 而客户的需求是**定期维护**, 而**按使用额度计费, 即企业协商订阅**的方案是最符合客户需求的. - - | 类别 | 成本 | 困难 | - | --- | --- | --- | - | 闭源版 | 购买定制服务, 如果企业长期话成本可能几千? 按年也就一年左右的量够用了 | 用起来很顺手, 目前的接口返回值基本能满足开发需求 | - | 开源版 | 需要准备IP池之类的反爬机制, 需要为IP代理付费 | 配置和学习相关的运营维护 | - | 自主研发 | 除了研发的时间精力外, 也必需IP池的购买 | 高 | +4. firecrawl方案 + 1. 闭源方案购买 + 年费大概9000元/年,不支持绕过robots.txt + 2. 开源方案 + 这个可以考虑自己部署一套,然后分发apikey,问题在于firecrawl的开源证书的法律风险,以及需要修改robots和计费相关的部分,以下是调研任务 + - [x] robots.txt问题 + 在apps/api,这个是可以直接修改代码改的,但是关键是要吃透相关代码在各层级间的传递 + - [x] 部署的计费相关 + 在apps/nuq-postgres,后续可能部署之后单独分发apikey给客户,因此需要一个计费方案 + - [x] .env问题 + 需要查阅资料了解里面的一些参数配置重新配置之后好上云,包括API_KEY配置、流量统计和配置 diff --git a/pyproject.toml b/pyproject.toml index 5f8e940..cf0b56a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "langchain>=1.2.0", "langchain-community>=0.4.1", "langchain-core>=1.2.2", + "matplotlib>=3.10.8", "mcp>=1.25.0", "numpy>=2.2.6", "pgvector>=0.4.2", @@ -23,5 +24,8 @@ dependencies = [ "qdrant-client==1.10.1", "redis>=7.1.0", "requests>=2.32.5", + "seaborn>=0.13.2", + "tabulate>=0.9.0", + "tqdm>=4.67.1", "uvicorn>=0.38.0", ] diff --git a/scripts/test_firecrawl.py b/scripts/test_firecrawl.py new file mode 100644 index 0000000..2f2cac7 --- /dev/null +++ b/scripts/test_firecrawl.py @@ -0,0 +1,76 @@ +import requests +import json + +def map_firecrawl_docs(): + """ + 调用本地 Firecrawl 服务的 map 功能,映射 Firecrawl 文档地址 + """ + # 本地 Firecrawl 服务地址(默认端口3002,根据你的实际情况调整) + base_url = "http://localhost:3002" + # map 功能的接口路径 + map_endpoint = f"{base_url}/api/v1/map" + + # 请求参数 + payload = { + # Firecrawl 文档的基础地址 + "url": "https://docs.firecrawl.dev", + # 可选配置:控制爬取深度、是否包含子路径等 + "config": { + "depth": 2, # 爬取深度,2层足够覆盖文档主要结构 + "includeSubdomains": False, + "onlyMainContent": True, # 只爬取主要内容,过滤导航/广告等 + "limit": 50 # 限制爬取页面数量,避免爬取过多 + } + } + + # 请求头(如果本地服务需要认证,添加你的 API Key) + headers = { + "Content-Type": "application/json", + # 如果有 API Key,取消下面注释并替换为你的密钥 + # "Authorization": "Bearer YOUR_FIRECRAWL_API_KEY" + } + + try: + # 发送 POST 请求调用 map 功能 + response = requests.post( + map_endpoint, + data=json.dumps(payload), + headers=headers, + timeout=60 # 设置超时时间,避免长时间等待 + ) + + # 检查响应状态 + response.raise_for_status() + + # 解析响应结果 + result = response.json() + print("✅ Map 功能调用成功!") + print("\n📄 爬取结果概览:") + print(f"总页面数: {len(result.get('links', []))}") + print(f"基础URL: {result.get('baseUrl')}") + + # 打印爬取到的所有链接 + print("\n🔗 爬取到的文档链接:") + for idx, link in enumerate(result.get('links', []), 1): + print(f"{idx}. {link}") + + # 保存结果到本地文件(方便后续查看) + with open("firecrawl_docs_map.json", "w", encoding="utf-8") as f: + json.dump(result, f, indent=2, ensure_ascii=False) + print("\n💾 结果已保存到 firecrawl_docs_map.json 文件") + + return result + + except requests.exceptions.ConnectionError: + print("❌ 连接失败!请检查本地 Firecrawl 服务是否正在运行(http://localhost:3002)") + except requests.exceptions.Timeout: + print("❌ 请求超时!爬取文档可能需要更长时间,可调整 timeout 参数") + except requests.exceptions.HTTPError as e: + print(f"❌ HTTP 错误:{e}") + print(f"响应内容:{response.text}") + except Exception as e: + print(f"❌ 未知错误:{str(e)}") + +# 执行调用 +if __name__ == "__main__": + map_firecrawl_docs() \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/rag_benchmark/__init__.py b/tests/rag_benchmark/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/rag_benchmark/benchmark_report.png b/tests/rag_benchmark/benchmark_report.png new file mode 100644 index 0000000000000000000000000000000000000000..04d23ee2a83d3eba3a3db5fa389a6713d48f8297 GIT binary patch literal 102872 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%0D0NXHb8<q4= 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 literal 0 HcmV?d00001 diff --git a/tests/rag_benchmark/dataset.json b/tests/rag_benchmark/dataset.json new file mode 100644 index 0000000..9a7846a --- /dev/null +++ b/tests/rag_benchmark/dataset.json @@ -0,0 +1,86 @@ +[ + { + "id": 1, + "type": "core_function", + "query": "What is the difference between /scrape and /map endpoints?", + "ground_truth": "/map is used to crawl a website and retrieve all URLs, while /scrape is used to extract content from a specific URL.", + "keywords": ["URL", "content", "specific", "retrieve"] + }, + { + "id": 2, + "type": "new_feature", + "query": "What is the Deep Research feature?", + "ground_truth": "Deep Research is an alpha feature allowing agents to perform iterative research tasks.", + "keywords": ["alpha", "iterative", "research", "agent"] + }, + { + "id": 3, + "type": "integration", + "query": "How can I integrate Firecrawl with ChatGPT?", + "ground_truth": "Firecrawl can be integrated via MCP (Model Context Protocol).", + "keywords": ["MCP", "Model Context Protocol", "setup"] + }, + { + "id": 4, + "type": "multilingual_zh", + "query": "如何进行私有化部署 (Self-host)?", + "ground_truth": "你需要使用 Docker Compose 进行部署,文档位于 /self-host/quick-start/docker-compose。", + "keywords": ["Docker", "Compose", "self-host", "deploy"] + }, + { + "id": 5, + "type": "api_detail", + "query": "What parameters are available for the /extract endpoint?", + "ground_truth": "The extract endpoint allows defining a schema for structured data extraction.", + "keywords": ["schema", "structured", "prompt"] + }, + { + "id": 6, + "type": "numeric", + "query": "How do credits work for the scrape endpoint?", + "ground_truth": "Specific credit usage details are in the /credits endpoint documentation (usually 1 credit per page for basic scrape).", + "keywords": ["credit", "usage", "cost"] + }, + { + "id": 7, + "type": "negative_test", + "query": "Does Firecrawl support scraping video content from YouTube?", + "ground_truth": "The documentation does not mention video scraping support.", + "keywords": [] + }, + { + "id": 8, + "type": "advanced", + "query": "How to use batch scrape?", + "ground_truth": "Use the /batch/scrape endpoint to submit multiple URLs at once.", + "keywords": ["batch", "multiple", "URLs"] + }, + { + "id": 9, + "type": "automation", + "query": "Is there an n8n integration guide?", + "ground_truth": "Yes, there is a workflow automation guide for n8n.", + "keywords": ["n8n", "workflow", "automation"] + }, + { + "id": 10, + "type": "security", + "query": "Where can I find information about webhook security?", + "ground_truth": "Information is available in the Webhooks Security section.", + "keywords": ["webhook", "security", "signature"] + }, + { + "id": 11, + "type": "cross_lingual_trap", + "query": "Explain the crawl features in French.", + "ground_truth": "The system should ideally retrieve the French document (/fr/features/crawl) and answer in French.", + "keywords": ["fonctionnalités", "crawl", "fr"] + }, + { + "id": 12, + "type": "api_history", + "query": "How to check historical token usage?", + "ground_truth": "Use the /token-usage-historical endpoint.", + "keywords": ["token", "usage", "historical"] + } +] \ No newline at end of file diff --git a/tests/rag_benchmark/evaluator.py b/tests/rag_benchmark/evaluator.py new file mode 100644 index 0000000..858b80c --- /dev/null +++ b/tests/rag_benchmark/evaluator.py @@ -0,0 +1,74 @@ +import json +import logging +from backend.services.llm_service import llm_service + +logger = logging.getLogger(__name__) + +class RAGEvaluator: + def __init__(self): + self.llm = llm_service + + def calculate_retrieval_metrics(self, retrieved_docs, dataset_item): + """ + 计算检索阶段指标: Keyword Recall (关键词覆盖率) + 检查 dataset 中的 keywords 有多少出现在了 retrieved_docs 的 content 中 + """ + required_keywords = dataset_item.get("keywords", []) + if not required_keywords: + return {"keyword_recall": 1.0, "hit": True} # 没有关键词要求,默认算对 + + # 将所有检索到的文本拼接并转小写 + full_context = " ".join([doc['content'] for doc in retrieved_docs]).lower() + + found_count = 0 + for kw in required_keywords: + if kw.lower() in full_context: + found_count += 1 + + recall = found_count / len(required_keywords) + + return { + "keyword_recall": recall, + # 只要召回率大于 0 就认为 Hit 了一部分; + # 严格一点可以要求 recall > 0.5,这里我们设定只要沾边就算 Hit + "hit": recall > 0 + } + + def evaluate_generation_quality(self, question, generated_answer, ground_truth_answer, q_type): + """ + 使用 LLM 作为裁判,评估生成质量 (1-5分) + """ + prompt = f""" + 你是一名RAG系统的自动化测试裁判。请根据以下信息对“系统回答”进行评分(1-5分)。 + + 【测试类型】: {q_type} + 【用户问题】: {question} + 【标准答案 (Ground Truth)】: {ground_truth_answer} + 【系统回答】: {generated_answer} + + 评分标准: + - 5分: 含义与标准答案完全一致,逻辑正确,无幻觉。 + - 4分: 核心意思正确,但缺少部分细节或废话较多。 + - 3分: 回答了一部分正确信息,但有遗漏或轻微错误。 + - 2分: 包含大量错误信息或严重答非所问。 + - 1分: 完全错误,或产生了严重幻觉(例如在负向测试中编造了不存在的功能)。 + + 注意:对于"negative_test"(负向测试),如果标准答案是“不支持/文档未提及”,而系统回答诚实地说“未找到相关信息”或“不支持”,应给满分。 + + 请仅返回JSON格式: {{"score": 5, "reason": "理由..."}} + """ + + try: + # 使用 system_prompt 强制约束格式 + result_str = self.llm.chat(prompt, system_prompt="你是一个只输出JSON的评测机器人。") + + # 清洗 Markdown 格式 (```json ... ```) + if "```" in result_str: + result_str = result_str.split("```json")[-1].split("```")[0].strip() + + eval_result = json.loads(result_str) + return eval_result + except Exception as e: + logger.error(f"Eval LLM failed: {e}") + # 降级处理 + return {"score": 0, "reason": "Evaluation Script Error"} \ No newline at end of file diff --git a/tests/rag_benchmark/run_benchmark.py b/tests/rag_benchmark/run_benchmark.py new file mode 100644 index 0000000..7434d3d --- /dev/null +++ b/tests/rag_benchmark/run_benchmark.py @@ -0,0 +1,168 @@ +import sys +import os +import time +import json +from collections import defaultdict +from tabulate import tabulate # 需要 pip install tabulate + +# 路径 Hack: 确保能导入 backend 模块 +sys.path.append(os.path.join(os.path.dirname(__file__), '../../../')) + +from backend.services.data_service import data_service +from backend.services.llm_service import llm_service +from tests.rag_benchmark.evaluator import RAGEvaluator + +# ================= 配置区 ================= +# 请填入你数据库中真实存在的、包含爬取数据的 task_id +TEST_TASK_ID = 19 +# ======================================== + +def run_experiment(config_name, dataset, retrieve_func, generate_func): + """ + 运行单组实验并收集详细指标 + """ + print(f"\n🚀 开始测试配置: [ {config_name} ]") + evaluator = RAGEvaluator() + results = [] + + total_latency = 0 + + # 用于分类统计 (例如 core_function 得分多少, negative_test 得分多少) + category_stats = defaultdict(lambda: {"count": 0, "score_sum": 0, "recall_sum": 0}) + + for item in dataset: + start_time = time.time() + + # 1. 检索 (Retrieval) + retrieved_docs = retrieve_func(item['query']) + + # 2. 生成 (Generation) + # 构造 Context,如果没有检索到内容,给一个提示 + if retrieved_docs: + context_str = "\n---\n".join([f"Source: {d.get('source_url', 'unknown')}\nContent: {d['content']}" for d in retrieved_docs]) + else: + context_str = "没有检索到任何相关文档。" + + answer = generate_func(item['query'], context_str) + + latency = time.time() - start_time + total_latency += latency + + # 3. 评测 (Evaluation) + # 计算关键词召回率 + retrieval_metric = evaluator.calculate_retrieval_metrics(retrieved_docs, item) + # 计算LLM回答质量 + gen_eval = evaluator.evaluate_generation_quality( + item['query'], answer, item['ground_truth'], item['type'] + ) + + # 记录单条结果 + row = { + "id": item['id'], + "type": item['type'], + "query": item['query'], + "recall": retrieval_metric['keyword_recall'], + "score": gen_eval['score'], + "reason": gen_eval.get('reason', '')[:50] + "...", # 截断显示 + "latency": latency + } + results.append(row) + + # 累加分类统计 + cat = item['type'] + category_stats[cat]["count"] += 1 + category_stats[cat]["score_sum"] += gen_eval['score'] + category_stats[cat]["recall_sum"] += retrieval_metric['keyword_recall'] + + # 实时打印进度 (简洁版) + status_icon = "✅" if gen_eval['score'] >= 4 else "⚠️" if gen_eval['score'] >= 3 else "❌" + print(f" {status_icon} ID:{item['id']} [{item['type'][:10]}] Score:{gen_eval['score']} | Recall:{retrieval_metric['keyword_recall']:.1f}") + + # --- 汇总本轮实验数据 --- + avg_score = sum(r['score'] for r in results) / len(results) + avg_recall = sum(r['recall'] for r in results) / len(results) + avg_latency = total_latency / len(results) + + # 格式化分类报告 + cat_report = [] + for cat, data in category_stats.items(): + cat_report.append(f"{cat}: {data['score_sum']/data['count']:.1f}分") + + return { + "Config": config_name, + "Avg Score (1-5)": f"{avg_score:.2f}", + "Avg Recall": f"{avg_recall:.2%}", + "Avg Latency": f"{avg_latency:.3f}s", + "Weakest Category": min(category_stats, key=lambda k: category_stats[k]['score_sum']/category_stats[k]['count']) + } + +def main(): + # 1. 加载数据集 + dataset_path = os.path.join(os.path.dirname(__file__), 'dataset.json') + if not os.path.exists(dataset_path): + print("Error: dataset.json not found.") + return + + with open(dataset_path, 'r', encoding='utf-8') as f: + dataset = json.load(f) + + print(f"载入 {len(dataset)} 条测试用例,准备开始横向评测...") + + # 2. 定义实验变量 (检索函数 + 生成函数) + + # === Exp A: 纯关键词 (模拟传统搜索) === + def retrieve_keyword(query): + # vector_weight=0 强制使用 SQL TSVector + # 注意: 需要传递一个假向量给接口占位 + dummy_vec = [0.0] * 1536 + res = data_service.search(query, dummy_vec, task_id=TEST_TASK_ID, vector_weight=0.0, candidates_num=5) + return res['results'] + + # === Exp B: 纯向量 (语义检索) === + def retrieve_vector(query): + vec = llm_service.get_embedding(query) + # vector_weight=1 忽略关键词匹配 + res = data_service.search(query, vec, task_id=TEST_TASK_ID, vector_weight=1.0, candidates_num=5) + return res['results'] + + # === Exp C: 混合检索 (Hybrid) === + def retrieve_hybrid(query): + vec = llm_service.get_embedding(query) + # 默认 0.7 向量 + 0.3 关键词 + res = data_service.search(query, vec, task_id=TEST_TASK_ID, vector_weight=0.7, candidates_num=5) + return res['results'] + + # === Exp D: 混合 + 重排序 (Rerank) === + def retrieve_rerank(query): + vec = llm_service.get_embedding(query) + # 1. 扩大召回 (Top 30) + res = data_service.search(query, vec, task_id=TEST_TASK_ID, vector_weight=0.7, candidates_num=30) + initial_docs = res['results'] + # 2. 精排 (Top 5) + reranked = llm_service.rerank(query, initial_docs, top_n=5) + return reranked + + # === 通用生成函数 === + def generate_answer(query, context): + system_prompt = "你是一个智能助手。请严格根据提供的上下文回答用户问题。如果上下文中没有答案,请直接说'未找到相关信息'。" + prompt = f"参考上下文:\n{context}\n\n用户问题:{query}" + return llm_service.chat(prompt, system_prompt=system_prompt) + + # 3. 运行所有实验 + final_report = [] + + final_report.append(run_experiment("1. Keyword Only (BM25)", dataset, retrieve_keyword, generate_answer)) + final_report.append(run_experiment("2. Vector Only", dataset, retrieve_vector, generate_answer)) + final_report.append(run_experiment("3. Hybrid (Base)", dataset, retrieve_hybrid, generate_answer)) + final_report.append(run_experiment("4. Hybrid + Rerank", dataset, retrieve_rerank, generate_answer)) + + # 4. 输出最终报表 + print("\n\n📊 ================= 最终横向对比报告 (Final Report) ================= 📊") + print(tabulate(final_report, headers="keys", tablefmt="github")) + print("\n💡 解读建议:") + print("1. 如果 'Avg Recall' 低,说明切片(Chunking)或检索算法找不到资料。") + print("2. 如果 Recall 高但 'Avg Score' 低,说明 LLM 产生了幻觉或 Prompt 没写好。") + print("3. 'Weakest Category' 帮你发现短板(如多语言或负向测试)。") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/rag_benchmark/visual_benchmark.py b/tests/rag_benchmark/visual_benchmark.py new file mode 100644 index 0000000..6ca1745 --- /dev/null +++ b/tests/rag_benchmark/visual_benchmark.py @@ -0,0 +1,208 @@ +import sys +import os +import time +import json +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt +from tqdm import tqdm + +# 路径 Hack: 确保能导入 backend +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '../../')) +if project_root not in sys.path: + sys.path.insert(0, project_root) + +# 直接导入服务类 (Direct Call) +from backend.services.data_service import data_service +from backend.services.llm_service import llm_service + +# ================= 配置区 ================= +TEST_TASK_ID = 19 # 请修改为真实的 Task ID +DATASET_PATH = os.path.join(current_dir, 'dataset.json') +OUTPUT_IMG = os.path.join(current_dir, 'benchmark_report.png') +# ======================================== + +class RAGEvaluator: + """评测工具类:负责计算召回率和调用LLM打分""" + def __init__(self): + self.llm = llm_service + + def calculate_recall(self, retrieved_docs, keywords): + """计算关键词召回率""" + if not keywords: return 1.0 # 无关键词要求的题目默认为满分 + + full_text = " ".join([d['content'] for d in retrieved_docs]).lower() + hit_count = sum(1 for k in keywords if k.lower() in full_text) + return hit_count / len(keywords) + + def judge_answer(self, query, answer, ground_truth): + """调用 LLM 给生成结果打分 (1-5)""" + prompt = f""" + 作为 RAG 评测员,请对【系统回答】打分 (1-5)。 + 用户问题: {query} + 标准答案: {ground_truth} + 系统回答: {answer} + + 标准: + 5: 含义完全一致,无幻觉。 + 3: 包含核心信息,但有遗漏。 + 1: 错误或严重幻觉。 + + 只返回数字 (1, 2, 3, 4, 5)。 + """ + try: + # 这里调用你在 llm_service 中新增的 chat 方法 + res = self.llm.chat(prompt) + # 简单的清洗逻辑,提取数字 + score = int(''.join(filter(str.isdigit, res))) + return min(max(score, 1), 5) # 限制在 1-5 + except: + return 1 # 失败保底 1 分 + +class Visualizer: + """绘图工具类""" + def plot_dashboard(self, df): + # 设置风格 + sns.set_theme(style="whitegrid") + # 解决中文显示问题 (如果环境支持 SimHei 则用中文,否则用英文) + plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial', 'DejaVu Sans'] + plt.rcParams['axes.unicode_minus'] = False + + fig = plt.figure(figsize=(18, 10)) + gs = fig.add_gridspec(2, 2) + + # Chart 1: 总体指标对比 (Bar Chart) + ax1 = fig.add_subplot(gs[0, 0]) + # 将数据变形为长格式以便绘图 + df_summary = df.groupby('config')[['score', 'recall']].mean().reset_index() + df_melt = df_summary.melt(id_vars='config', var_name='Metric', value_name='Value') + # 将 Recall 归一化到 0-5 方便同图显示,或者分开轴。这里简单处理:Recall * 5 + df_melt.loc[df_melt['Metric'] == 'recall', 'Value'] *= 5 + + sns.barplot(data=df_melt, x='config', y='Value', hue='Metric', ax=ax1, palette="viridis") + ax1.set_title('Overall Performance (Score & Recall)', fontsize=14, fontweight='bold') + ax1.set_ylabel('Score (1-5) / Recall (x5)') + ax1.set_ylim(0, 5.5) + for container in ax1.containers: + ax1.bar_label(container, fmt='%.1f') + + # Chart 2: 延迟 vs 质量 (Scatter Plot) + ax2 = fig.add_subplot(gs[0, 1]) + df_latency = df.groupby('config')[['score', 'latency']].mean().reset_index() + sns.scatterplot(data=df_latency, x='latency', y='score', hue='config', s=200, ax=ax2, palette="deep") + + # 添加标签 + for i in range(df_latency.shape[0]): + ax2.text( + df_latency.latency[i]+0.05, + df_latency.score[i], + df_latency.config[i], + fontsize=10 + ) + ax2.set_title('Trade-off: Latency vs Quality', fontsize=14, fontweight='bold') + ax2.set_xlabel('Avg Latency (seconds)') + ax2.set_ylabel('Avg Quality Score (1-5)') + + # Chart 3: 类别热力图 (Heatmap) - 你的 Weakest Category 可视化 + ax3 = fig.add_subplot(gs[1, :]) # 占用下方整行 + pivot_data = df.pivot_table(index='config', columns='type', values='score', aggfunc='mean') + sns.heatmap(pivot_data, annot=True, cmap="RdYlGn", center=3, fmt=".1f", ax=ax3, linewidths=.5) + ax3.set_title('Category Breakdown (Find the Weakest Link)', fontsize=14, fontweight='bold') + ax3.set_xlabel('') + ax3.set_ylabel('') + + plt.tight_layout() + plt.savefig(OUTPUT_IMG) + print(f"\n📊 报表已生成: {OUTPUT_IMG}") + +def main(): + # 1. 加载数据 + if not os.path.exists(DATASET_PATH): + print("Dataset not found!") + return + with open(DATASET_PATH, 'r', encoding='utf-8') as f: + dataset = json.load(f) + + # 2. 定义实验配置 (Direct Call) + configs = [ + { + "name": "1. BM25 (Keyword)", + "retriever": lambda q: data_service.search(q, [0.0]*1536, task_id=TEST_TASK_ID, vector_weight=0.0, candidates_num=5)['results'], + "rerank": False + }, + { + "name": "2. Vector Only", + "retriever": lambda q: data_service.search(q, llm_service.get_embedding(q), task_id=TEST_TASK_ID, vector_weight=1.0, candidates_num=5)['results'], + "rerank": False + }, + { + "name": "3. Hybrid (Base)", + "retriever": lambda q: data_service.search(q, llm_service.get_embedding(q), task_id=TEST_TASK_ID, vector_weight=0.7, candidates_num=5)['results'], + "rerank": False + }, + { + "name": "4. Hybrid + Rerank", + "retriever": lambda q: data_service.search(q, llm_service.get_embedding(q), task_id=TEST_TASK_ID, vector_weight=0.7, candidates_num=30)['results'], # 召回 Top 30 + "rerank": True + } + ] + + evaluator = RAGEvaluator() + all_results = [] + + print("🚀 开始自动化评测 (Visualization Mode)...") + + # 3. 循环执行 (双重循环:配置 -> 数据) + # 使用 tqdm 显示总进度 + total_steps = len(configs) * len(dataset) + pbar = tqdm(total=total_steps, desc="Running Experiments") + + for cfg in configs: + for item in dataset: + pbar.set_description(f"Testing {cfg['name']}") + + start_time = time.time() + + # A. 检索 + docs = cfg['retriever'](item['query']) + + # B. Rerank (如果在配置里开启) + if cfg['rerank']: + docs = llm_service.rerank(item['query'], docs, top_n=5) + + # C. 生成 + context = "\n".join([d['content'] for d in docs]) if docs else "" + if not context: + answer = "未找到相关信息" + else: + prompt = f"Context:\n{context}\n\nQuestion: {item['query']}" + answer = llm_service.chat(prompt) # 调用生成接口 + + latency = time.time() - start_time + + # D. 评测指标 + recall = evaluator.calculate_recall(docs, item.get('keywords', [])) + score = evaluator.judge_answer(item['query'], answer, item['ground_truth']) + + # E. 收集数据 + all_results.append({ + "config": cfg['name'], + "id": item['id'], + "type": item['type'], # 类别字段 + "recall": recall, + "score": score, + "latency": latency + }) + + pbar.update(1) + + pbar.close() + + # 4. 数据处理与绘图 + df = pd.DataFrame(all_results) + viz = Visualizer() + viz.plot_dashboard(df) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/uv.lock b/uv.lock index f820977..073b5be 100644 --- a/uv.lock +++ b/uv.lock @@ -416,6 +416,163 @@ wheels = [ { url = "https://files.pythonhosted.org/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 = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + [[package]] name = "cryptography" version = "46.0.3" @@ -481,6 +638,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "dashscope" version = "1.25.5" @@ -572,6 +738,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/a8/32317c2d007b42ed7814cf7440fcac98ecb6cdb9a44feab4f85ae458f86e/firecrawl_py-4.12.0-py3-none-any.whl", hash = "sha256:0807fa7e881570749a52bd9971e1e1f2373e5aafd21b6f30d194b1b8a32aa58f", size = 201511, upload-time = "2025-12-19T05:39:20.605Z" }, ] +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, + { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, + { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -1011,6 +1234,114 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, + { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, + { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, + { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, + { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, + { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, + { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + [[package]] name = "langchain" version = "1.2.0" @@ -1186,6 +1517,81 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + [[package]] name = "mcp" version = "1.25.0" @@ -1738,6 +2144,104 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/26/6cee8a1ce8c43625ec561aff19df07f9776b7525d9002c86bceb3e0ac970/pgvector-0.4.2-py3-none-any.whl", hash = "sha256:549d45f7a18593783d5eec609ea1684a724ba8405c4cb182a0b2b08aeff04e08", size = 27441, upload-time = "2025-12-05T01:07:16.536Z" }, ] +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + [[package]] name = "pinecone" version = "8.0.0" @@ -2178,6 +2682,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/45/c5416f2d64dd8add626a90166d8389a97ebc39c107ea01c15ea57bf3a07f/pymilvus-2.6.5-py3-none-any.whl", hash = "sha256:9e1caddd96361cd41f4e0685b6bd3d99bbaea94c8284b1fef5575bcfd47d7a2f", size = 280832, upload-time = "2025-12-05T08:59:48.016Z" }, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2510,6 +3023,7 @@ dependencies = [ { name = "langchain" }, { name = "langchain-community" }, { name = "langchain-core" }, + { name = "matplotlib" }, { name = "mcp" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -2522,6 +3036,9 @@ dependencies = [ { name = "qdrant-client" }, { name = "redis" }, { name = "requests" }, + { name = "seaborn" }, + { name = "tabulate" }, + { name = "tqdm" }, { name = "uvicorn" }, ] @@ -2534,6 +3051,7 @@ requires-dist = [ { name = "langchain", specifier = ">=1.2.0" }, { name = "langchain-community", specifier = ">=0.4.1" }, { name = "langchain-core", specifier = ">=1.2.2" }, + { name = "matplotlib", specifier = ">=3.10.8" }, { name = "mcp", specifier = ">=1.25.0" }, { name = "numpy", specifier = ">=2.2.6" }, { name = "pgvector", specifier = ">=0.4.2" }, @@ -2545,9 +3063,27 @@ requires-dist = [ { name = "qdrant-client", specifier = "==1.10.1" }, { name = "redis", specifier = ">=7.1.0" }, { name = "requests", specifier = ">=2.32.5" }, + { name = "seaborn", specifier = ">=0.13.2" }, + { name = "tabulate", specifier = ">=0.9.0" }, + { name = "tqdm", specifier = ">=4.67.1" }, { name = "uvicorn", specifier = ">=0.38.0" }, ] +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -2641,6 +3177,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -2650,6 +3195,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"