Metadata-Version: 2.4
Name: cortexa_v2_sdk
Version: 0.1.3
Summary: Python SDK for querying published Cortexa archives and downloading immutable archive or dataset exports.
Author: Cortexa
Project-URL: Homepage, https://github.com/verteklab/cortexa
Project-URL: Repository, https://github.com/verteklab/cortexa
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests

# Cortexa V2 SDK

`cortexa_v2_sdk` 用于让鼎算、Ray Worker 和其他 Python 服务查询数基已发布的不可变归档，浏览归档样本元数据，并在训练开始时按 `archive_id` 下载训练包。SDK 同时保留传统 `dataset_id` 查询、导出和评测数据解析能力。

- PyPI 发行包：`cortexa_v2_sdk`
- Python 导入包：`cortexa_sdk`
- 命令行程序：`cortexa-sdk`
- 当前版本：`0.1.3`
- Python：`>=3.10`
- PyPI：https://pypi.org/project/cortexa-v2-sdk/

训练平台业务代码只调用 SDK 的公开方法。SDK 内部封装 Cortexa HTTP API、API Key、导出任务轮询和 ZIP 下载；鼎算不直接拼接数基接口，也不扫描共享数据库、对象存储或 `manifest.json`。

## 功能概览

| 场景 | API | 说明 |
|---|---|---|
| 查询训练可用归档 | `CortexaClient.list_training_archives()` | 只返回数基已登记为训练可用的归档 |
| 查询归档详情 | `CortexaClient.archive_detail()` | 返回不可变归档、来源数据集和资产快照 |
| 浏览归档样本 | `CortexaClient.archive_samples()` | 按 `archive_id` 请求服务端分页，不下载媒体 |
| 下载归档训练包 | `CortexaClient.download_archive()` | 支持 YOLO、COCO 和三种训练/验证比例 |
| 查询传统数据集 | `list_datasets()`、`dataset_detail()` | 支持名称和模板标注筛选 |
| 下载传统数据集 | `download_dataset()` | 支持 NATIVE、JSON、YOLO、COCO、LABELME |
| 解压和解析 | `extract_zip()`、`load_dataset()` | 解析模板组或标准 JSON 评测数据 |
| 转换评测输入 | `to_eval_json()` | 生成 `question` / `expected` 数据 |
| 命令行下载 | `cortexa-sdk` | 初始化配置或按 `dataset_id` 下载 |

## 安装

安装固定版本：

```bash
python3 -m pip install "cortexa_v2_sdk==0.1.3"
```

升级：

```bash
python3 -m pip install --upgrade "cortexa_v2_sdk==0.1.3"
```

验证发行包、导入包和命令行程序：

```bash
python3 -c "from importlib.metadata import version; print(version('cortexa_v2_sdk'))"
python3 -c "from cortexa_sdk import CortexaClient, ExportType; print(ExportType.YOLO.value)"
cortexa-sdk --help
```

发布 `0.1.3` 后，期望前两条分别输出 `0.1.3` 和 `YOLO`。发布前可使用仓库内开发安装验证当前代码。

仓库内开发安装：

```bash
git clone https://github.com/verteklab/cortexa.git
cd cortexa
python3 -m pip install -e .
```

> 安装名使用 `cortexa_v2_sdk`，代码导入名仍是 `cortexa_sdk`。

## 快速开始

下面代码完成鼎算的主要调用链：发现归档、查看样本、按需下载并解压。

```python
from cortexa_sdk import CortexaClient, ExportType
from cortexa_sdk.parser import extract_zip

client = CortexaClient(
    api_key="<CORTEXA_API_KEY>",
    base_url="http://127.0.0.1:8000/api/v1",
)

result = client.list_training_archives(page=1, page_size=20)
archives = result.get("list", [])
if not archives:
    raise RuntimeError("当前没有已发布给训练平台的归档")

archive_id = archives[0]["_id"]
archive = client.archive_detail(archive_id)
print(archive["name"], archive.get("dataset_id"))

sample_page = client.archive_samples(archive_id, page=1, page_size=20)
for sample in sample_page["samples"]:
    print(sample["asset_id"], sample["name"], sample["kind"])

zip_path = client.download_archive(
    archive_id=archive_id,
    export_type=ExportType.YOLO,
    split_ratio="8:2",
    download_dir="/data/ray/cortexa-cache",
    assets_included=True,
)

dataset_root = extract_zip(zip_path)
print(dataset_root)
```

训练任务必须保存归档记录的 `_id` 作为 `archive_id`。归档中的 `dataset_id` 只用于追溯来源，不能替代不可变归档作为训练输入。

## 集成流程

```text
数基管理员
  1. 完成归档审批
  2. 在归档详情选择“同步到鼎算”
  3. 数基只登记 archive_id，不预生成训练 ZIP

鼎算服务
  4. list_training_archives() 查询可用归档
  5. archive_detail() / archive_samples() 展示归档与样本信息
  6. 创建训练任务并保存 archive_id

Ray Worker
  7. download_archive(archive_id) 按需创建导出任务
  8. SDK 等待导出完成并下载 ZIP
  9. Worker 解压后开始训练
```

这套流程不会维护统一 `manifest.json`，也不会在“同步到鼎算”时复制数据。ZIP 是训练执行时的按需传输产物，不是两个平台之间的发布索引。

## 配置

### 配置优先级

SDK 按以下顺序解析配置，前面的值覆盖后面的值：

1. 方法参数或 `CortexaClient(...)` 参数
2. JSON 配置文件
3. 环境变量
4. SDK 内置下载目录

`base_url` 没有内置默认值。参数、配置文件和环境变量都未提供时，创建客户端会抛出 `ValueError`。

### 环境变量

```bash
export CORTEXA_API_KEY="<CORTEXA_API_KEY>"
export CORTEXA_BASE_URL="http://127.0.0.1:8000/api/v1"
export CORTEXA_DATASET_DIR="/data/ray/cortexa-cache"
export CORTEXA_CONFIG="$HOME/.cortexa/config.json"
```

| 环境变量 | 默认值 | 说明 |
|---|---|---|
| `CORTEXA_API_KEY` | 无 | 作为 `X-API-KEY` 请求头发送 |
| `CORTEXA_BASE_URL` | 无 | Cortexa API 基址，必填 |
| `CORTEXA_DATASET_DIR` | `~/.cortexa/datasets` | ZIP 默认下载目录 |
| `CORTEXA_CONFIG` | `~/.cortexa/config.json` | JSON 配置文件路径 |

### JSON 配置文件

生成配置模板：

```bash
cortexa-sdk --init-config
```

默认写入 `~/.cortexa/config.json`：

```json
{
  "api_key": "<CORTEXA_API_KEY>",
  "base_url": "http://127.0.0.1:8000/api/v1",
  "dataset_dir": "/data/ray/cortexa-cache"
}
```

指定其他配置文件：

```python
from cortexa_sdk import CortexaClient

client = CortexaClient(config_file="/etc/dingsuan/cortexa.json")
```

不要把 API Key 写入 Git、Dockerfile、训练参数或日志。生产环境应通过 Kubernetes Secret、容器环境变量或密钥管理服务注入。

### API 地址转换

传统数据集方法直接使用 `base_url`。归档方法会自动切换到 workflow v2：

| `base_url` | 归档 API 基址 |
|---|---|
| `http://host/api/v1` | `http://host/api/v2` |
| `http://host/api/v2` | `http://host/api/v2` |
| `http://host` | `http://host/api/v2` |

同时使用归档和传统数据集能力时，建议配置以 `/api/v1` 结尾的地址。

## 公开类型

### `ExportType`

```python
from cortexa_sdk import ExportType

ExportType.NATIVE
ExportType.JSON
ExportType.YOLO
ExportType.COCO
ExportType.LABELME
```

| 格式 | 归档下载 | 传统数据集下载 | CLI | 内置评测解析器 |
|---|---:|---:|---:|---:|
| NATIVE | 否 | 是 | 否 | 否 |
| JSON | 否 | 是 | 是 | 是 |
| YOLO | 是 | 是 | 是 | 否 |
| COCO | 是 | 是 | 是 | 否 |
| LABELME | 否 | 是 | 是 | 否 |

### `AnnotationType`

```python
from cortexa_sdk import AnnotationType

AnnotationType.RECT       # rect
AnnotationType.POLYGON    # polygon
AnnotationType.CUBOID     # cuboid
AnnotationType.POLYLINE   # line
```

YOLO 数据集下载必须提供 `annotation_type`。LabelMe 后端仅接受 `line` 标注类型；不传标注类型时由后端按数据集内容校验。

## 客户端 API

### 创建客户端

```python
from cortexa_sdk import CortexaClient

client = CortexaClient(
    api_key="<CORTEXA_API_KEY>",
    base_url="http://127.0.0.1:8000/api/v1",
    config_file=None,
)
```

构造参数：

| 参数 | 类型 | 说明 |
|---|---|---|
| `api_key` | `str` 或 `None` | API Key，覆盖配置文件和环境变量 |
| `base_url` | `str` 或 `None` | API 基址，覆盖配置文件和环境变量 |
| `config_file` | `str` 或 `None` | 自定义 JSON 配置文件路径 |

当前实现每次请求通过 `requests` 独立发送，不维护长连接 Session。

## 归档 API

归档 API 是鼎算集成的主路径。

### `list_training_archives()`

```python
result = client.list_training_archives(
    page=1,
    page_size=20,
    keyword="桥梁",
)
```

参数：

| 参数 | 默认值 | 说明 |
|---|---|---|
| `page` | `1` | 页码 |
| `page_size` | `20` | 每页数量 |
| `keyword` | `None` | 按归档名称做不区分大小写的子串查询 |

返回值结构：

```json
{
  "list": [
    {
      "_id": "archive-id",
      "name": "桥梁病害训练集-v1",
      "dataset_id": "dataset-id",
      "status": "approved",
      "training_registration": {
        "registered": true,
        "training_name": "桥梁病害训练集"
      }
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 20
}
```

SDK 固定发送 `training_registered=true`，不会把未发布归档混入鼎算列表。

### `archive_detail()`

```python
archive = client.archive_detail("archive-id")
```

返回归档完整数据，包括：

- `_id`：不可变归档 ID
- `name`：归档名称
- `status`：归档状态
- `dataset_id`：来源数据集 ID
- `dataset_snapshot`：归档时的数据集快照
- `asset_snapshot`：归档时的资产快照
- `training_registration`：训练平台登记信息
- `display`：前端展示用格式、样本数、大小、版本等字段

归档不存在或鉴权失败时，底层 `requests` 会抛出 HTTP 异常。

### `archive_samples()`

```python
page = client.archive_samples(
    archive_id="archive-id",
    page=1,
    page_size=50,
)
```

该方法请求 `GET /api/v2/archives/{archive_id}/samples`。数基后端直接从不可变 `asset_snapshot` 中截取当前页，SDK 不会先下载完整归档详情，也不会查询实时数据集或下载媒体文件。

分页约束：

| 参数 | 默认值 | 约束 |
|---|---|---|
| `page` | `1` | 正整数 |
| `page_size` | `20` | `1` 到 `100` 的整数 |

返回示例：

```json
{
  "archive_id": "archive-id",
  "dataset_id": "dataset-id",
  "page": 1,
  "page_size": 50,
  "total": 8600,
  "samples": [
    {
      "index": 0,
      "asset_id": "asset-id",
      "name": "bridge_0001.jpg",
      "kind": "image",
      "media_type": "image/jpeg",
      "size_bytes": 128034,
      "split": "train",
      "width": 1920,
      "height": 1080,
      "uri": "datasets/bridge_0001.jpg"
    }
  ]
}
```

`kind` 可能是 `image`、`video`、`audio`、`text` 或 `file`。SDK 优先根据 `media_type` 判断，缺失时使用文件扩展名。

页码超过最后一页时，`samples` 返回空列表，`total` 仍返回归档样本总数。`index` 是样本在完整归档快照中的全局下标，不会从每页的零重新开始。

### `download_archive()`

```python
zip_path = client.download_archive(
    archive_id="archive-id",
    export_type=ExportType.YOLO,
    split_ratio="8:2",
    download_dir="/data/ray/cortexa-cache",
    assets_included=True,
)
```

参数：

| 参数 | 默认值 | 约束 |
|---|---|---|
| `archive_id` | 必填 | 必须使用归档 `_id` |
| `export_type` | `ExportType.YOLO` | 仅支持 YOLO、COCO |
| `split_ratio` | `"8:2"` | 支持 `8:2`、`7:3`、`9:1` |
| `download_dir` | 配置值或 `~/.cortexa/datasets` | ZIP 保存目录 |
| `assets_included` | `True` | 是否包含媒体资产 |

执行过程：

1. 创建 `training` 模式归档导出任务。
2. 每两秒查询一次任务状态。
3. 导出失败时抛出 `RuntimeError`。
4. 导出完成后流式下载 ZIP。
5. 保存为 `{download_dir}/{archive_id}.zip`。

相同 `archive_id` 再次下载会覆盖同名本地文件。SDK 当前没有超时参数；调用方应在 Worker 层设置任务超时和重试策略。

## 模块级归档函数

不需要复用客户端时，可以直接调用模块级函数。

### `archive_samples()`

```python
from cortexa_sdk import archive_samples

page = archive_samples(
    archive_id="archive-id",
    page=1,
    page_size=20,
    api_key="<CORTEXA_API_KEY>",
    base_url="http://127.0.0.1:8000/api/v1",
)
```

### `download_archive()`

```python
from cortexa_sdk import ExportType, download_archive

zip_path = download_archive(
    archive_id="archive-id",
    export_type=ExportType.COCO,
    split_ratio="7:3",
    api_key="<CORTEXA_API_KEY>",
    base_url="http://127.0.0.1:8000/api/v1",
    download_dir="/data/ray/cortexa-cache",
)
```

模块级函数会为每次调用创建一个新的 `CortexaClient`。

## 传统数据集 API

这些方法用于仍以可变 `dataset_id` 工作的兼容场景。新的鼎算训练任务应优先使用归档 API。

### `list_datasets()`

```python
result = client.list_datasets(
    name="桥梁",
    is_template_annotation=True,
    page=1,
    page_size=10,
)
```

返回后端响应中的 `data` 字段，通常包含 `datasets`、`total` 和分页信息。

### `dataset_detail()`

```python
detail = client.dataset_detail(
    dataset_id="dataset-id",
    page=1,
    page_size=20,
    group="train",
)
```

详情中的 `_id` 会规范化为 `id`。后端业务响应为 `404` 时返回 `None`。

### `download_dataset()`

```python
zip_path = client.download_dataset(
    dataset_id="dataset-id",
    export_type=ExportType.JSON,
    annotation_type=None,
    download_dir="/data/cortexa-cache",
    assets_included=True,
    project_id=None,
    label_list=None,
    filter_by_labels=False,
    pack_by_template_group=False,
)
```

参数：

| 参数 | 默认值 | 说明 |
|---|---|---|
| `dataset_id` | 必填 | 数据集 ID |
| `export_type` | `ExportType.JSON` | NATIVE、JSON、YOLO、COCO、LABELME |
| `annotation_type` | `None` | YOLO 必填 |
| `download_dir` | 配置值 | ZIP 保存目录 |
| `assets_included` | `True` | 是否包含媒体资产 |
| `project_id` | `None` | 项目 ID；模板组打包时必填 |
| `label_list` | `None` | 标签名称列表，仅 JSON、YOLO |
| `filter_by_labels` | `False` | 是否过滤为仅包含指定标签的样本 |
| `pack_by_template_group` | `False` | 是否按自定义模板组打包 |

格式约束：

| 组合 | 结果 |
|---|---|
| YOLO 未传 `annotation_type` | `ValueError` |
| LABELME 且 `assets_included=False` | `ValueError` |
| `label_list` 用于 COCO、LABELME | `ValueError` |
| `filter_by_labels=True` 但没有 `label_list` | `ValueError` |
| `pack_by_template_group=True` 但不是 JSON | `ValueError` |
| 模板组打包同时启用标签过滤 | `ValueError` |

JSON 示例：

```python
zip_path = client.download_dataset(
    "dataset-id",
    export_type=ExportType.JSON,
)
```

YOLO 示例：

```python
zip_path = client.download_dataset(
    "dataset-id",
    export_type=ExportType.YOLO,
    annotation_type=AnnotationType.RECT,
)
```

按标签过滤：

```python
zip_path = client.download_dataset(
    "dataset-id",
    export_type=ExportType.JSON,
    label_list=["裂缝", "锈蚀"],
    filter_by_labels=True,
)
```

按自定义模板组打包：

```python
zip_path = client.download_dataset(
    "dataset-id",
    export_type=ExportType.JSON,
    project_id="project-id",
    pack_by_template_group=True,
)
```

模块级调用：

```python
from cortexa_sdk import ExportType, download_dataset

zip_path = download_dataset(
    dataset_id="dataset-id",
    export_type=ExportType.JSON,
    api_key="<CORTEXA_API_KEY>",
    base_url="http://127.0.0.1:8000/api/v1",
)
```

## ZIP 解析工具

解析工具位于 `cortexa_sdk.parser`。它们面向 JSON 类问答评测数据，不是通用 YOLO、COCO 或 LabelMe 解析器。

### `extract_zip()`

```python
from cortexa_sdk.parser import extract_zip

root = extract_zip(
    zip_path="/data/cortexa-cache/archive-id.zip",
    target_dir="/data/cortexa-cache/archive-id",
)
```

`target_dir` 未传时，默认解压到 ZIP 同级同名目录。如果解压目录中只有一个根目录，函数返回该根目录；否则返回目标目录。

### `load_dataset()`

```python
from cortexa_sdk.parser import load_dataset

samples = load_dataset("/data/cortexa-cache/archive-id")
```

自动识别顺序：

1. 查找包含 `答题.json` 的模板组目录。
2. 找到时使用 `parse_template_groups()`。
3. 否则使用 `parse_standard_json()`。

### `parse_template_groups()`

支持两种目录：

```text
train/
  group_001/
    答题.json
    image.jpg

group_001/
  答题.json
  image.jpg
```

`答题.json` 中每个资源的 `result.tempAnntation` 被转换为问答对。后端会把相同答案同步到组内资源，因此解析器只读取组内第一个有效资源，避免重复。

### `parse_standard_json()`

支持标准 JSON 目录：

```text
annotations/
  train/
    dataset.json
  validate/
    dataset.json
assets/
  train/
    image.jpg
  validate/
    image.jpg
```

解析器支持一个 JSON 文件包含多个资源，也支持单资源 JSON。

### `to_eval_json()`

```python
from cortexa_sdk.parser import load_dataset, to_eval_json

samples = load_dataset("/data/cortexa-cache/archive-id")
payload = to_eval_json(samples)
```

返回：

```json
{
  "samples": [
    {
      "question": "桥面是否存在裂缝？",
      "expected": "是"
    }
  ]
}
```

该函数返回 Python `dict`，不会自动写文件。没有问答标注的样本不会进入结果。

## 数据模型

```python
from cortexa_sdk.models import EvalSample, QAPair
```

`QAPair`：

| 字段 | 类型 | 说明 |
|---|---|---|
| `question` | `str` | 问题 |
| `answer` | `str` | 期望答案 |

`EvalSample`：

| 字段 | 类型 | 说明 |
|---|---|---|
| `sample_id` | `str` | 资源 ID 或模板组 ID |
| `split` | `str` | train、validate 或 test |
| `image_paths` | `list[Path]` | 图片路径 |
| `video_paths` | `list[Path]` | 视频路径 |
| `audio_paths` | `list[Path]` | 音频路径 |
| `text_paths` | `list[Path]` | 文本路径 |
| `ground_truth` | `list[QAPair]` | 问答标注 |
| `raw_annotation` | `dict` | 原始标注 |

## CLI

CLI 当前只支持传统 `dataset_id` 下载。归档发现、样本浏览和 `archive_id` 下载应使用 Python API。

初始化配置：

```bash
cortexa-sdk --init-config
```

下载 JSON：

```bash
cortexa-sdk \
  --dataset-id "dataset-id" \
  --export-type JSON \
  --download-dir ./tmp/datasets
```

下载 YOLO：

```bash
cortexa-sdk \
  -d "dataset-id" \
  -t YOLO \
  --annotation-type rect \
  --download-dir ./tmp/datasets
```

按标签过滤：

```bash
cortexa-sdk \
  -d "dataset-id" \
  -t JSON \
  --label-list "裂缝" "锈蚀" \
  --filter-by-labels
```

主要选项：

| 选项 | 说明 |
|---|---|
| `-d, --dataset-id` | 数据集 ID |
| `-t, --export-type` | JSON、YOLO、COCO、LABELME |
| `--annotation-type` | rect、polygon、cuboid、line |
| `--assets-included` | 包含资源文件 |
| `--api-key` | API Key |
| `--base-url` | API 基址 |
| `--download-dir` | 下载目录 |
| `--project-id` | 项目 ID |
| `--label-list` | 空格或逗号分隔的标签 |
| `--filter-by-labels` | 启用标签过滤 |
| `--init-config` | 初始化配置文件 |

## Ray Worker 示例

推荐在任务调度阶段只保存 `archive_id`、导出格式和划分比例。Worker 获得任务后再调用 SDK。

```python
from pathlib import Path

from cortexa_sdk import CortexaClient, ExportType
from cortexa_sdk.parser import extract_zip


def prepare_training_dataset(
    archive_id: str,
    cache_dir: str,
) -> Path:
    client = CortexaClient()
    zip_path = client.download_archive(
        archive_id=archive_id,
        export_type=ExportType.YOLO,
        split_ratio="8:2",
        download_dir=cache_dir,
    )
    return extract_zip(
        zip_path,
        Path(cache_dir) / archive_id,
    )
```

Worker 层建议：

- 为整个下载阶段设置明确超时。
- 对网络失败和临时后端失败做有限次数重试。
- 以 `archive_id` 作为缓存键。
- 多 Worker 共享缓存时使用文件锁或原子目录切换。
- 训练结束后按磁盘策略清理 ZIP 和解压目录。
- 日志记录 `archive_id`、导出格式和划分比例，不记录 API Key。

## 异常处理

```python
import requests

from cortexa_sdk import CortexaClient, ExportType

client = CortexaClient()

try:
    path = client.download_archive(
        "archive-id",
        export_type=ExportType.YOLO,
        split_ratio="8:2",
    )
except ValueError as exc:
    print("参数或配置错误:", exc)
except requests.HTTPError as exc:
    print("HTTP 请求失败:", exc.response.status_code)
except RuntimeError as exc:
    print("Cortexa 业务或导出任务失败:", exc)
```

常见异常：

| 异常 | 常见原因 |
|---|---|
| `ValueError` | 缺少 `base_url`、格式不支持、划分比例错误、参数组合非法 |
| `requests.HTTPError` | API Key 无效、无权限、归档不存在、网关返回错误 |
| `requests.ConnectionError` | Cortexa 或文件服务不可达 |
| `RuntimeError` | 业务响应失败、导出任务失败、任务响应缺少必要字段 |

## 常见问题

### 为什么安装名和导入名不同？

PyPI 发行包使用新名称 `cortexa_v2_sdk`，Python 包为兼容已有代码继续使用 `cortexa_sdk`：

```bash
python3 -m pip install cortexa_v2_sdk
```

```python
from cortexa_sdk import CortexaClient
```

### 鼎算查询不到刚登记的归档

确认数基归档已经审批，并在归档详情成功保存“同步到鼎算”。`list_training_archives()` 固定查询 `training_registered=true`；传入 `keyword` 时还要确保名称匹配。

### 为什么不用 `dataset_id` 创建训练任务？

`dataset_id` 指向可继续变化的数据集，不能保证训练可复现。`archive_id` 指向审批后的不可变快照，是训练任务和审计记录的输入锚点。

### 为什么同步后没有立即生成 ZIP？

“同步到鼎算”只登记 `archive_id`。Ray Worker 调用 `download_archive()` 时才按格式和划分比例生成 ZIP，避免为未实际使用的归档提前复制数据。

### `archive_samples()` 会下载媒体吗？

不会。它只读取归档的 `asset_snapshot` 并返回元数据；`uri` 也不保证是可直接访问的 HTTP URL。

### 能否用内置解析器读取 YOLO 或 COCO？

不能。内置解析器只支持模板组 JSON 和标准 JSON 评测数据。YOLO、COCO、LabelMe 应使用对应训练框架或成熟解析库。

### 下载文件保存在哪里？

优先使用方法的 `download_dir`，其次读取 JSON 配置中的 `dataset_dir`，再读取 `CORTEXA_DATASET_DIR`，最后使用 `~/.cortexa/datasets`。

## 当前边界

- SDK 查询训练归档时只返回已登记记录。
- SDK 不提供归档登记或撤回接口；该操作由数基界面和权限体系管理。
- SDK 不维护共享 `manifest.json` 或统一对象存储索引。
- SDK 不回写鼎算训练状态和消费状态。
- `archive_samples()` 由数基服务端分页返回样本元数据，不提供单文件下载。
- `download_archive()` 只支持 YOLO 和 COCO。
- CLI 只支持传统数据集下载。
- 下载轮询间隔固定为两秒，当前没有公开超时参数。

## 维护者发版

### 1. 更新版本

修改 `pyproject.toml` 中的版本，并同步更新本 README。PyPI 不允许覆盖已经上传的同版本文件。

### 2. 提交前检查

```bash
python3 -m pytest -q cortexa_sdk/test_sdk.py
black .
python3 .docsmith/scripts/gen.example.py
git diff --check
```

工作区有其他改动时只暂存本次 SDK 文件，不要使用 `git add .`。

### 3. 构建与校验

```bash
RELEASE_DIR="$(mktemp -d)"
python3 -m build --outdir "$RELEASE_DIR"
python3 -m twine check "$RELEASE_DIR"/*
ls -lh "$RELEASE_DIR"
```

`0.1.3` 对应：

- `cortexa_v2_sdk-0.1.3-py3-none-any.whl`
- `cortexa_v2_sdk-0.1.3.tar.gz`

### 4. 隔离安装

```bash
python3 -m venv /tmp/cortexa-v2-sdk-release
/tmp/cortexa-v2-sdk-release/bin/python -m pip install \
  "$RELEASE_DIR/cortexa_v2_sdk-0.1.3-py3-none-any.whl"
/tmp/cortexa-v2-sdk-release/bin/python -c \
  "from cortexa_sdk import CortexaClient, archive_samples, download_archive; print('ok')"
```

### 5. 上传 PyPI

```bash
export TWINE_USERNAME="__token__"
read -s TWINE_PASSWORD
export TWINE_PASSWORD
python3 -m twine upload "$RELEASE_DIR"/*
unset TWINE_PASSWORD TWINE_USERNAME
```

不要把 Token 写入仓库文件、shell 历史、命令参数或 CI 日志。

上传后验证：

```bash
python3 -m pip index versions cortexa_v2_sdk
python3 -m pip install --no-cache-dir "cortexa_v2_sdk==0.1.3"
```

### 6. 创建标签

```bash
git tag -a cortexa-v2-sdk-v0.1.3 -m "cortexa_v2_sdk 0.1.3"
git push origin cortexa-v2-sdk-v0.1.3
```

发版检查：

- [ ] `pyproject.toml` 和 README 版本一致
- [ ] SDK 定向测试通过
- [ ] wheel 和 sdist 通过 `twine check`
- [ ] 隔离环境可导入 `cortexa_sdk`
- [ ] PyPI 安装验证通过
- [ ] 对应提交和版本标签已推送
- [ ] API Key 和 PyPI Token 未进入提交或日志
