# Python SDK（clore-ai）

该 **clore-ai** 软件包是官方的 Python SDK，用于 [Clore.ai](https://clore.ai) GPU 市场。它将完整的 REST API 封装为一个简洁、类型安全的接口，内置速率限制、自动重试和结构化错误处理 —— 让你可以专注于租用 GPU，而不是处理 HTTP 细节。

***

## 安装

```bash
pip install clore-ai
```

**要求：** Python 3.9+

该软件包同时安装 Python SDK 和 [`clore` 命令行工具（CLI）](https://docs.clore.ai/clore.ai/clore.ai-eng-zh/kai-fa-zhe/cli-guide).

***

## 认证

从 [Clore.ai 仪表板](https://clore.ai) → **API** 部分获取你的 API 密钥。

### 选项 1：环境变量（推荐）

```bash
export CLORE_API_KEY=your_api_key_here
```

SDK 会自动读取 `CLORE_API_KEY` —— 无需更改代码。

### 选项 2：CLI 配置文件

```bash
clore config set api_key YOUR_API_KEY
```

这会将密钥存储在 `~/.clore/config.json`.

### 选项 3：在代码中直接传入

```python
from clore_ai import CloreAI

client = CloreAI(api_key="your_api_key_here")
```

> ⚠️ **重要：** Clore.ai API 使用 `auth` 头进行认证， **而不是** `Authorization: Bearer`。SDK 会自动处理这一点。

***

## 快速开始

```python
from clore_ai import CloreAI

client = CloreAI()
servers = client.marketplace(gpu="RTX 4090", max_price_usd=5.0)
for s in servers:
    print(f"Server {s.id}: {s.gpu_model} — ${s.price_usd:.4f}/h")
```

***

## 同步客户端（`CloreAI`)

### 构造函数

```python
CloreAI(
    api_key: str | None = None,       # 回退到 CLORE_API_KEY 环境变量 / 配置
    base_url: str | None = None,       # 默认： https://api.clore.ai/v1
    timeout: float = 30.0,             # 请求超时（秒）
    max_retries: int = 3               # 在速率限制 / 网络错误时的重试次数
)
```

客户端支持上下文管理器以自动清理：

```python
with CloreAI() as client:
    wallets = client.wallets()
    # client.close() 会自动调用
```

***

### `wallets()`

获取你的钱包余额和充值地址。

```python
wallets = client.wallets()

for wallet in wallets:
    print(f"{wallet.name}: {wallet.balance:.8f}")
    if wallet.deposit:
        print(f"  Deposit: {wallet.deposit}")
```

**返回：** `List[Wallet]`

| 字段               | 类型              | 描述                                                             |
| ---------------- | --------------- | -------------------------------------------------------------- |
| `name`           | `str`           | 货币名称（例如 `"bitcoin"`, `"CLORE-Blockchain"`, `"USD-Blockchain"`) |
| `balance`        | `float \| None` | 当前余额                                                           |
| `deposit`        | `str \| None`   | 充值地址                                                           |
| `withdrawal_fee` | `float \| None` | 提现费用                                                           |

***

### `marketplace()`

使用可选的客户端过滤器搜索 GPU 市场。

```python
# 所有可用服务器
servers = client.marketplace()

# 按 GPU 型号和最高价格过滤
servers = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=5.0
)

# 多 GPU 机组并配备大量内存
servers = client.marketplace(
    min_gpu_count=4,
    min_ram_gb=128.0
)
```

**参数：**

| 参数               | 类型              | 默认     | 描述                        |
| ---------------- | --------------- | ------ | ------------------------- |
| `gpu`            | `str \| None`   | `None` | 按 GPU 型号过滤（不区分大小写的子字符串匹配） |
| `min_gpu_count`  | `int \| None`   | `None` | 最少 GPU 数量                 |
| `min_ram_gb`     | `float \| None` | `None` | 最小内存（GB）                  |
| `max_price_usd`  | `float \| None` | `None` | 每小时最高价格（美元）               |
| `available_only` | `bool`          | `True` | 仅返回可租用的服务器                |

**返回：** `List[MarketplaceServer]`

每个 `MarketplaceServer` 为最常用字段提供便捷属性，并可访问完整的嵌套数据：

| 属性               | 类型              | 描述                                          |
| ---------------- | --------------- | ------------------------------------------- |
| `id`             | `int`           | 唯一服务器 ID                                    |
| `gpu_model`      | `str \| None`   | 主 GPU 描述（例如 `"1x NVIDIA GeForce RTX 4090"`) |
| `gpu_count`      | `int`           | GPU 数量（来自 `gpu_array`)                      |
| `ram_gb`         | `float \| None` | 内存（GB）                                      |
| `price_usd`      | `float \| None` | 按需价格（美元）                                    |
| `spot_price_usd` | `float \| None` | 竞价价格（美元）                                    |
| `available`      | `bool`          | 服务器是否可用（未被租用）                               |
| `location`       | `str \| None`   | 来自网络规格的国家代码                                 |

对于高级用例，你可以访问完整的嵌套结构：

| 字段            | 类型                     | 描述                                                                          |
| ------------- | ---------------------- | --------------------------------------------------------------------------- |
| `specs`       | `ServerSpecs \| None`  | 完整硬件规格（`specs.gpu`, `specs.ram`, `specs.cpu`, `specs.disk`, `specs.net`，等等） |
| `price`       | `ServerPrice \| None`  | 完整价格对象（`price.usd.on_demand_usd`, `price.usd.spot`, `price.on_demand`，等等）   |
| `rented`      | `bool \| None`         | 服务器当前是否被租用                                                                  |
| `reliability` | `float \| None`        | 服务器可靠性评分                                                                    |
| `rating`      | `ServerRating \| None` | 服务器评分（`rating.avg`, `rating.cnt`)                                           |

> **注意：** 该 `marketplace()` 该端点是公开的 — 无需 API 密钥即可使用。

***

### `my_servers()`

列出你在 Clore.ai 市场上提供的服务器。

```python
my_servers = client.my_servers()

for server in my_servers:
    print(f"{server.name}: {server.gpu_model} [{server.status}]")
```

**返回：** `List[MyServer]`

| 属性           | 类型              | 描述                                                                 |
| ------------ | --------------- | ------------------------------------------------------------------ |
| `id`         | `int`           | 服务器 ID                                                             |
| `name`       | `str \| None`   | 服务器名称                                                              |
| `gpu_model`  | `str \| None`   | 主 GPU 描述                                                           |
| `ram_gb`     | `float \| None` | 内存（GB）                                                             |
| `status`     | `str`           | 可读的状态： `"Online"`, `"Offline"`, `"Disconnected"`，或 `"Not Working"` |
| `connected`  | `bool \| None`  | 服务器是否已连接                                                           |
| `online`     | `bool \| None`  | 服务器是否在线                                                            |
| `visibility` | `str \| None`   | `"public"` 或 `"private"`                                           |

***

### `server_config(server_name)`

获取你托管的特定服务器的配置。

```python
config = client.server_config("MyGPU")

print(f"Server: {config.name}")
print(f"GPU: {config.gpu_model}")
print(f"Min rental: {config.mrl}h")
print(f"On-demand: ${config.on_demand_price}")
print(f"Spot: ${config.spot_price}")
```

**参数：**

| 参数            | 类型    | 描述    |
| ------------- | ----- | ----- |
| `server_name` | `str` | 服务器名称 |

**返回：** `ServerConfig`

| 属性                | 类型                    | 描述                       |
| ----------------- | --------------------- | ------------------------ |
| `name`            | `str \| None`         | 服务器名称                    |
| `gpu_model`       | `str \| None`         | 主 GPU 描述                 |
| `mrl`             | `int \| None`         | 最短租期（小时）                 |
| `on_demand_price` | `float \| None`       | 可用的首个按需美元价格              |
| `spot_price`      | `float \| None`       | 可用的首个竞价美元价格              |
| `specs`           | `ServerSpecs \| None` | 完整硬件规格                   |
| `connected`       | `bool \| None`        | 服务器是否已连接                 |
| `visibility`      | `str \| None`         | `"public"` 或 `"private"` |

***

### `my_orders(include_completed)`

获取你当前的订单，可选择包含已完成/已过期的订单。

```python
# 仅活动订单
orders = client.my_orders()

# 包含已完成的订单
all_orders = client.my_orders(include_completed=True)

for order in orders:
    print(f"Order {order.id}: {order.type} — {order.status}")
    if order.pub_cluster:
        print(f"  IP: {order.pub_cluster}")
    if order.tcp_ports:
        print(f"  Ports: {order.tcp_ports}")
```

**参数：**

| 参数                  | 类型     | 默认      | 描述             |
| ------------------- | ------ | ------- | -------------- |
| `include_completed` | `bool` | `False` | 是否包含已完成/已过期的订单 |

**返回：** `List[Order]`

| 字段            | 类型              | 描述                       |
| ------------- | --------------- | ------------------------ |
| `id`          | `int`           | 唯一订单 ID                  |
| `server_id`   | `int \| None`   | 服务器 ID                   |
| `type`        | `str`           | `"on-demand"` 或 `"spot"` |
| `status`      | `str \| None`   | 订单状态                     |
| `image`       | `str \| None`   | Docker 镜像                |
| `currency`    | `str \| None`   | 支付货币                     |
| `price`       | `float \| None` | 订单每日价格                   |
| `pub_cluster` | `str \| None`   | 用于访问的公共主机名 / IP          |
| `tcp_ports`   | `dict \| None`  | TCP 端口映射                 |

***

### `spot_marketplace(server_id)`

查看特定服务器的竞价市场报价。

```python
spot = client.spot_marketplace(server_id=6)

if spot.offers:
    for offer in spot.offers:
        print(f"Order {offer.order_id}: ${offer.price}/day (server {offer.server_id})")

if spot.currency_rates_in_usd:
    for coin, rate in spot.currency_rates_in_usd.items():
        print(f"  {coin}: ${rate}")
```

**参数：**

| 参数          | 类型    | 描述         |
| ----------- | ----- | ---------- |
| `server_id` | `int` | 要检查的服务器 ID |

**返回：** `SpotMarket`

| 字段                      | 类型                         | 描述                                       |
| ----------------------- | -------------------------- | ---------------------------------------- |
| `offers`                | `List[SpotOffer] \| None`  | 竞价报价列表（`order_id`, `price`, `server_id`) |
| `server`                | `SpotServerInfo \| None`   | 服务器信息（最低定价、可见性、在线状态）                     |
| `currency_rates_in_usd` | `Dict[str, float] \| None` | 以美元计的货币汇率                                |

***

### `create_order(...)`

创建新的按需或竞价订单。这是你租用 GPU 的方式。

#### 按需订单

```python
order = client.create_order(
    server_id=123,
    image="cloreai/ubuntu22.04-cuda12",
    type="on-demand",
    currency="bitcoin",
    ssh_password="MySecurePass123",
    ports={"22": "tcp", "8888": "http"}
)

print(f"Order created: {order.id}")
print(f"Connect: {order.pub_cluster}")
```

#### 竞价订单

```python
order = client.create_order(
    server_id=123,
    image="cloreai/pytorch",
    type="spot",
    currency="bitcoin",
    spot_price=0.000005,
    ssh_password="MySecurePass123",
    ports={"22": "tcp"}
)
```

**参数：**

| 参数                   | 类型      | 必需   | 描述                                           |
| -------------------- | ------- | ---- | -------------------------------------------- |
| `server_id`          | `int`   | 是    | 要租用的服务器 ID                                   |
| `image`              | `str`   | 是    | Docker 镜像（例如 `"cloreai/ubuntu22.04-cuda12"`) |
| `type`               | `str`   | 是    | `"on-demand"` 或 `"spot"`                     |
| `currency`           | `str`   | 是    | 支付货币（例如 `"bitcoin"`)                         |
| `ssh_password`       | `str`   | 否    | SSH 密码（字母数字，最多 32 个字符）                       |
| `ssh_key`            | `str`   | 否    | SSH 公钥（最多 3072 个字符）                          |
| `ports`              | `dict`  | 否    | 端口映射，例如 `{"22": "tcp", "8888": "http"}`      |
| `env`                | `dict`  | 否    | 环境变量                                         |
| `jupyter_token`      | `str`   | 否    | Jupyter 笔记本令牌（最多 32 个字符）                     |
| `command`            | `str`   | 否    | 容器启动后要运行的 shell 命令                           |
| `spot_price`         | `float` | 仅限竞价 | 竞价订单的每日价格                                    |
| `required_price`     | `float` | 否    | 锁定特定价格（仅限按需）                                 |
| `autossh_entrypoint` | `str`   | 否    | 使用 Clore.ai SSH 入口点                          |

**返回：** `订单`

> **速率限制：** `create_order` 在调用之间有一个特殊的 5 秒冷却时间。SDK 会自动强制执行。

***

### `cancel_order(order_id, issue)`

取消活动订单或竞价报价。可选地报告服务器问题。

```python
# 简单取消
client.cancel_order(order_id=38)

# 带问题报告的取消
client.cancel_order(
    order_id=38,
    issue="GPU #1 was overheating and throttling"
)
```

**参数：**

| 参数         | 类型    | 必需 | 描述                       |
| ---------- | ----- | -- | ------------------------ |
| `order_id` | `int` | 是  | 要取消的订单 ID                |
| `issue`    | `str` | 否  | 取消原因 / 问题报告（最多 2048 个字符） |

**返回：** `Dict[str, Any]`

***

### `set_server_settings(...)`

更新你在市场上托管的服务器的设置。

```python
client.set_server_settings(
    name="MyGPU",
    availability=True,
    mrl=96,
    on_demand=0.0001,
    spot=0.00000113
)
```

**参数：**

| 参数             | 类型      | 必需 | 描述         |
| -------------- | ------- | -- | ---------- |
| `name`         | `str`   | 是  | 服务器名称      |
| `availability` | `bool`  | 否  | 服务器是否可以被租用 |
| `mrl`          | `int`   | 否  | 最短租期（小时）   |
| `on_demand`    | `float` | 否  | 按需每日价格     |
| `spot`         | `float` | 否  | 最低竞价每日价格   |

**返回：** `Dict[str, Any]`

***

### `set_spot_price(order_id, price)`

更新你在竞价市场报价的价格。

```python
client.set_spot_price(order_id=39, price=0.000003)
```

**参数：**

| 参数         | 类型      | 描述         |
| ---------- | ------- | ---------- |
| `order_id` | `int`   | 竞价订单/报价 ID |
| `price`    | `float` | 新的每日价格     |

**返回：** `Dict[str, Any]`

> **注意：** 你只能每 600 秒降低一次竞价价格，且降幅有步进限制。如果超出这些限制，API 会返回 `code: 6` 并提供详细信息。

***

## 异步客户端（`AsyncCloreAI`)

该 `AsyncCloreAI` 客户端提供与 `CloreAI`相同的方法，但所有方法都返回协程。当你需要并发 API 调用或在异步应用中工作时使用它。

### 基本用法

```python
import asyncio
from clore_ai import AsyncCloreAI

async def main():
    async with AsyncCloreAI(api_key="your_key") as client:
        wallets = await client.wallets()
        for w in wallets:
            print(f"{w.name}: {w.balance:.8f}")

asyncio.run(main())
```

### 并发操作

使用 `asyncio.gather`:

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # 并发搜索多个 GPU 型号
        rtx4090, rtx3090, a100 = await asyncio.gather(
            client.marketplace(gpu="RTX 4090"),
            client.marketplace(gpu="RTX 3090"),
            client.marketplace(gpu="A100"),
        )

        for name, servers in [("RTX 4090", rtx4090), ("RTX 3090", rtx3090), ("A100", a100)]:
            if servers:
                cheapest = min(s.price_usd or float('inf') for s in servers)
                print(f"{name}: {len(servers)} available, cheapest ${cheapest:.4f}/h")
            else:
                print(f"{name}: none available")

asyncio.run(compare_gpus())
```

### 可用方法

`AsyncCloreAI` 支持与 `CloreAI`:

| 相同的所有方法     | 描述                                  |
| ----------- | ----------------------------------- |
| `方法`        | await wallets()                     |
| `获取钱包余额`    | await marketplace(...)              |
| `搜索市场`      | await my\_servers()                 |
| `列出你托管的服务器` | await server\_config(name)          |
| `获取服务器配置`   | await my\_orders(...)               |
| `列出你的订单`    | await spot\_marketplace(server\_id) |
| `获取竞价市场报价`  | await create\_order(...)            |
| `创建新订单`     | await cancel\_order(...)            |
| `取消订单`      | await set\_server\_settings(...)    |
| `更新服务器设置`   | await set\_spot\_price(...)         |

***

## 更新竞价价格

错误处理

```python
from clore_ai import CloreAI
SDK 为每个 API 错误代码提供结构化的异常类。
    from clore_ai.exceptions import (
    CloreAPIError,      # 所有 API 错误的基类
    AuthError,          # 代码 3 — 无效的 API 密钥
    RateLimitError,     # 代码 5 — 超出速率限制
    InvalidInputError,  # 代码 2 — 错误的请求数据
    DBError,            # 代码 1 — 数据库错误（DBError）或数据库相关错误）
    FieldError，        # 代码 6 — 字段特定错误
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=123,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin",
    )
except AuthError:
    print("无效的 API 密钥。请检查您的 CLORE_API_KEY。")
except RateLimitError:
    print("已触发速率限制。SDK 会自动重试，但您已达到最大重试次数。")
except InvalidInputError as e:
    print(f"错误的请求：{e}")
except FieldError as e:
    # 代码 6 的错误在响应中包含详细信息
    print(f"字段错误：{e}（详细信息：{e.response}）")
except CloreAPIError as e:
    print(f"API 错误：{e}（代码：{e.code}）")
```

### 错误代码

| 代码 | 异常                     | 描述                             |
| -- | ---------------------- | ------------------------------ |
| 0  | —                      | 成功                             |
| 1  | `DBError`              | 数据库错误                          |
| 2  | `InvalidInputError`    | 无效的输入数据                        |
| 3  | `AuthError`            | 无效的 API 令牌                     |
| 4  | `InvalidEndpointError` | 无效的端点                          |
| 5  | `RateLimitError`       | 超出速率限制                         |
| 6  | `FieldError`           | 特定字段的错误（参见 `error` 响应中的 field） |

所有异常类都继承自 `CloreAPIError` 并包含：

* `e.code` — 数字错误代码
* `e.response` — 完整的 API 响应字典（如可用）

***

## 速率限制

SDK 包含内置速率限制器，会自动强制执行 Clore.ai 的限制：

| 端点             | 限制              |
| -------------- | --------------- |
| 大多数端点          | **每秒 1 次请求**    |
| `create_order` | **每 5 秒 1 次请求** |

当 API 返回速率限制错误（代码 5）时，SDK 会应用 **指数退避** 并重试最多 `max_retries` 次（默认：3）。您无需在调用之间添加 `time.sleep()` 。

### 工作原理

1. 在每次请求之前，速率限制器会等待直到最小间隔过去。
2. `create_order` 调用会强制额外的 5 秒冷却期。
3. 在速率限制错误时，SDK 会以指数方式退避：1s → 2s → 4s → ...
4. 在 `max_retries` 次失败尝试后，会引发 `RateLimitError` 。

### 自定义重试行为

```python
client = CloreAI(
    max_retries=5,    # 长时间运行脚本可增加重试次数
    timeout=60.0      # 网络较慢时可延长超时时间
)
```

***

## 配置

### 配置文件

CLI 将配置存储在 `~/.clore/config.json`:

```json
{
  "api_key": "your_api_key_here"
}
```

### 解析顺序

SDK 按以下顺序解析 API 密钥：

1. `api_key` 传递给构造函数的参数
2. `CLORE_API_KEY` 环境变量
3. `api_key` 字段位于 `~/.clore/config.json`

### 环境变量

| 变量              | 描述             |
| --------------- | -------------- |
| `CLORE_API_KEY` | 用于身份验证的 API 密钥 |

***

## 下一步

* [**CLI 参考**](https://docs.clore.ai/clore.ai/clore.ai-eng-zh/kai-fa-zhe/cli-guide) — 从终端使用 Clore.ai
* [**REST API**](https://docs.clore.ai/clore.ai/clore.ai-eng-zh/zhen-dui-zhu-ji/api) — 用于自定义集成的原始 API 文档
* [**按需与抢占式**](https://docs.clore.ai/clore.ai/clore.ai-eng-zh/zhen-dui-zu-yong-zhe/on-demand-vs-spot) — 了解定价模型
* [**可用的 Docker 镜像**](https://docs.clore.ai/clore.ai/clore.ai-eng-zh/zhen-dui-zu-yong-zhe/docker-images) — 用于 GPU 工作负载的预构建镜像
