# GMICLOUD-IEOPS Python SDK

> 用于构建可扩展 AI 模型推理服务的统一框架

**[English](README_EN.md)** | 中文

## 目录

- [概览](#概览)
- [安装](#安装)
- [快速开始](#快速开始)
- [两种集成模式](#两种集成模式)
- [配置管理](#配置管理)
- [分布式存储](#分布式存储)
- [API 参考](#api-参考)
- [示例](#示例)
- [详细文档](#详细文档)

---

## 概览

GMICLOUD-IEOPS Python SDK 提供构建推理服务的统一框架：

- **透明代理模式 (推荐)**: 使用 Engine 无缝集成任意 Web 框架
- **统一服务器基础设施**: 基于 FastAPI 的服务器，支持自动路由注册
- **灵活路由**: 支持 REST API、SSE 流式和 WebSocket
- **配置管理**: 集中式环境变量管理，支持类型安全
- **服务注册**: 自动注册到 IEOPS 代理进行负载均衡

### 架构

**推荐: Engine 模式 (透明代理)**

```
┌─────────────────────────────────────────────────────────────────┐
│  Worker 应用                                                      │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  Engine (子进程)                                             ││
│  │  ┌─────────────┐    ┌─────────────┐                         ││
│  │  │  Register   │    │ Interceptor │ ←── Unix Socket         ││
│  │  │  (心跳注册)  │    │ (透明代理)   │                         ││
│  │  └─────────────┘    └──────┬──────┘                         ││
│  └────────────────────────────┼────────────────────────────────┘│
│                               │                                  │
│  ┌────────────────────────────▼────────────────────────────────┐│
│  │  用户服务 (主进程)                                           ││
│  │  Flask / FastAPI / Django / 任意框架                        ││
│  │  http://127.0.0.1:{port}                                    ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  IEOPS Proxy (负载均衡器)                                        │
└─────────────────────────────────────────────────────────────────┘
```

**可选: Handler + Server 模式 (SDK 路由)**

```
┌─────────────────────────────────────────────────────────────────┐
│  Worker 应用                                                      │
│  ┌───────────────┐     ┌───────────────┐     ┌───────────────┐ │
│  │    Handler    │────▶│    Server     │────▶│   FastAPI     │ │
│  │ (你的逻辑)    │     │ (RouterDef)   │     │   Application │ │
│  └───────────────┘     └───────────────┘     └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```

---

## 安装

### 从 PyPI (推荐)

```bash
pip install gmi-ieops
```

### 从源码 (开发)

```bash
cd sdk
pip install -e .
```

### 依赖

- Python 3.10+
- aiohttp (透明代理)
- FastAPI (可选，Handler+Server 模式)
- Uvicorn

---

## 快速开始

### Engine 模式 (推荐)

使用任意 Web 框架，Engine 提供透明代理和服务注册：

```python
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port
from flask import Flask, jsonify

# 获取可用端口
port = get_service_port()

# 启动 Engine (子进程运行 Register + Interceptor)
engine = Engine(service_port=port)
engine.start()

# 你的服务 (主进程，使用任意框架)
app = Flask(__name__)

@app.route("/health")
def health():
    return jsonify({"status": "ok"})

@app.route("/api/chat", methods=["POST"])
def chat():
    # 你的推理逻辑
    return jsonify({"message": "Hello!"})

app.run(host="127.0.0.1", port=port)
```

**FastAPI 版本:**

```python
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port
from fastapi import FastAPI
import uvicorn

port = get_service_port()
engine = Engine(service_port=port)
engine.start()

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

@app.post("/api/chat")
def chat(data: dict):
    return {"message": "Hello!", "received": data}

uvicorn.run(app, host="127.0.0.1", port=port)
```

### Handler + Server 模式 (可选)

如果需要 SDK 的路由管理功能：

```python
from gmi_ieops.handler import Handler, Server, RouterDef, RouterKind
from gmi_ieops.config import env

class MyHandler:
    async def chat_stream(self, query):
        """流式输出"""
        for char in "Hello!":
            yield {"content": char, "stopped": False}
        yield {"stopped": True}
    
    def chat_complete(self, query):
        """单次响应"""
        return {"message": "Hello!"}

def main():
    handler = MyHandler()
    
    server = Server(
        routers={
            "chat": [
                RouterDef(path="stream", handler=handler.chat_stream, kind=RouterKind.SSE),
                RouterDef(path="complete", handler=handler.chat_complete, kind=RouterKind.API),
            ],
        },
        app_name=env.app.name,
    )
    
    Handler(server=server).serve()

if __name__ == "__main__":
    main()
```

---

## 两种集成模式

### 模式对比

| 特性 | Engine 模式 (推荐) | Handler + Server 模式 |
|------|-------------------|----------------------|
| **灵活性** | ✅ 任意 Web 框架 | ⚠️ 仅 SDK 路由 |
| **侵入性** | ✅ 零侵入 | ⚠️ 需要适配 RouterDef |
| **SSE 支持** | ✅ 框架原生 | ✅ SDK 封装 |
| **WebSocket** | ✅ 框架原生 | ✅ SDK 封装 |
| **学习成本** | ✅ 低 | ⚠️ 需要学习 RouterDef |
| **适用场景** | 新项目、迁移现有服务 | 需要 SDK 高级路由功能 |

### Engine 模式详解

Engine 在子进程中运行，提供：
- **Register**: 向 IEOPS Proxy 注册服务，心跳保活
- **Interceptor**: 透明代理，转发请求到用户服务
- **Model Sync Wait**: 等待模型同步完成（当 MODEL_FROM 设置时）

```python
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port

port = get_service_port()

engine = Engine(
    service_port=port,           # 用户服务端口
    service_host="127.0.0.1",    # 用户服务地址 (默认)
    register_enable=True,        # 启用服务注册 (默认)
    interceptor_enable=True,     # 启用透明代理 (默认)
    timeout=600,                 # 请求超时 (秒)
    wait_model_sync=True,        # 等待模型同步完成 (默认)
)
engine.start()
```

**使用模式:**

```python
# 1. 完整模式 (默认): Register + Interceptor
engine = Engine(service_port=port)

# 2. 仅注册: 用户服务直接暴露 (不推荐)
engine = Engine(register_enable=True, interceptor_enable=False)

# 3. 仅代理: 本地开发/测试
engine = Engine(service_port=port, register_enable=False, interceptor_enable=True)
```

### Handler + Server 模式详解

适用于需要 SDK 路由管理的场景：

```python
from gmi_ieops.handler import RouterDef, RouterKind, HTTPMethod

RouterDef(
    path="chat",                    # 路由路径
    handler=model.chat,             # 处理函数
    kind=RouterKind.SSE,            # 类型: API, SSE, WS
    method=HTTPMethod.POST,         # HTTP 方法
    summary="Chat endpoint",        # OpenAPI 描述
    timeout=300,                    # 请求超时
)
```

| RouterKind | 说明 | 用途 |
|------------|------|------|
| `RouterKind.API` | REST API，返回 JSON | 单次响应端点 |
| `RouterKind.SSE` | Server-Sent Events | 流式文本生成 |
| `RouterKind.WS` | WebSocket | 双向通信 |

---

## 配置管理

### 环境变量

SDK 使用集中式环境变量管理器。通过 `env` 对象访问：

```python
from gmi_ieops.config import env

# 应用配置
env.app.name              # APP_NAME (默认: "ieops")

# 服务器配置
env.server.host           # MODEL_SERVER_HOST (默认: "127.0.0.1")
env.server.port           # MODEL_SERVER_PORT (默认: 8001)
env.server.socket         # MODEL_SERVER_SOCKET (默认: "")

# 模型配置
env.model.path            # MODEL_PATH (默认: "")
env.model.name            # MODEL_NAME (默认: "model")
env.model.timeout         # MODEL_TIMEOUT (默认: 600)
env.model.concurrency     # MODEL_THREAD_CONCURRENCY (默认: 8)

# 设备配置
env.device.cuda_visible   # CUDA_VISIBLE_DEVICES (默认: "0")
env.device.device         # DEVICE (默认: "auto")
env.device.torch_dtype    # TORCH_DTYPE (默认: "float16")

# 存储配置 (WebDAV)
env.storage.webdav_url        # IEOPS_WEBDAV_URL (默认: "")
env.storage.webdav_user       # IEOPS_WEBDAV_USER (默认: "")
env.storage.webdav_pass       # IEOPS_WEBDAV_PASS (默认: "")
env.storage.webdav_timeout    # IEOPS_WEBDAV_TIMEOUT (默认: 30)
env.storage.oid               # IEOPS_STORAGE_OID (默认: "gmicloud.ieops")
env.storage.uid               # IEOPS_STORAGE_UID (默认: "gmicloud.ieops")

# 模型同步配置 (由 brslet 管理)
env.model_sync.model_from         # MODEL_FROM (默认: "")
env.model_sync.model_path         # MODEL_PATH (默认: "/data/models")
env.model_sync.sync_result_file   # MODEL_SYNC_RESULT_FILE (默认: ".model_sync_status")
env.model_sync.wait_enabled       # MODEL_SYNC_WAIT_ENABLED (默认: True)
env.model_sync.wait_timeout       # MODEL_SYNC_WAIT_TIMEOUT (默认: 86400)
env.model_sync.wait_interval      # MODEL_SYNC_WAIT_INTERVAL (默认: 1)

# SSL/TLS 配置
env.ssl.cert_file             # SSL_CERT_FILE (默认: "")
env.ssl.cert_dir              # SSL_CERT_DIR (默认: "")
env.ssl.requests_ca_bundle    # REQUESTS_CA_BUNDLE (默认: "")
```

### 自定义环境变量

```python
from gmi_ieops.config import env

# 获取字符串
value = env.get("MY_CUSTOM_VAR", "default")

# 带类型转换
value_int = env.get_int("MY_INT_VAR", 10)
value_bool = env.get_bool("MY_BOOL_VAR", False)
value_float = env.get_float("MY_FLOAT_VAR", 0.5)
value_list = env.get_list("MY_LIST_VAR", ["a", "b"])
```

---

## 分布式存储

SDK 提供透明的分布式存储访问，使用 `gmifs://` 协议前缀将文件写入远程存储。

> **注意**: NFS 支持已禁用，当前仅支持 WebDAV 后端。

### 基本用法

```python
# 设置环境变量启用 WebDAV 存储
# IEOPS_WEBDAV_URL=http://10.0.0.1:5000
# IEOPS_WEBDAV_USER=user
# IEOPS_WEBDAV_PASS=password

# 导入 handler 模块时会自动启用存储（无需手动导入 storage）
from gmi_ieops.handler import Engine

# 使用 gmifs:// 前缀写入远程存储
with open("gmifs://output/image.png", "wb") as f:
    f.write(image_data)

# 不带前缀的路径仍然访问本地文件系统
with open("/data/models/model.bin", "rb") as f:
    data = f.read()
```

### 工作原理

- `gmifs://path` → WebDAV: `/{oid}/{uid}/path`
- 其他路径 → 本地文件系统（不受影响）

### 高级用法

```python
from gmi_ieops.storage import FileSystem

# 直接使用 FileSystem API (WebDAV 后端)
fs = FileSystem(backend="webdav", hostname="http://10.0.0.1:5000")

# 创建目录
fs.mkdir("data/images", parents=True, exist_ok=True)

# 读写文件
with fs.open("data/result.json", "w") as f:
    f.write('{"status": "ok"}')

# 列出目录
for name in fs.listdir("data"):
    print(name)
```

---

## API 参考

### gmi_ieops.handler

| 类/函数 | 说明 |
|---------|------|
| `Engine` | **推荐** - 统一管理 Register 和 Interceptor |
| `Interceptor` | 透明代理 (底层，通常通过 Engine 使用) |
| `Server` | FastAPI 服务器，带路由管理 |
| `Handler` | 服务生命周期管理器 |
| `RouterDef` | 路由定义数据类 |
| `RouterConfig` | 全局路由配置 |
| `RouterKind` | 路由类型枚举 (API, SSE, WS) |
| `HTTPMethod` | HTTP 方法枚举 |
| `generate_trace_id()` | 生成唯一跟踪 ID |
| `format_error_response()` | 创建统一错误响应 |
| `TokenizerPool` | Tokenizer 连接池 |
| `TokenizerBase` | Tokenizer 基类 |
| `SyncStatus` | 模型同步状态枚举 |
| `ModelSyncStatus` | 模型同步状态信息类 |
| `check_model_sync_status()` | 检查当前模型同步状态 |
| `wait_for_model_sync()` | 等待模型同步完成 |
| `Metrics` | 指标收集类 |
| `MetricType` | 指标类型枚举 (COUNTER, GAUGE, HISTOGRAM, SUMMARY) |
| `get_metrics()` | 获取全局指标实例 |
| `reset_global_metrics()` | 重置全局指标 |

### gmi_ieops.config

| 类/函数 | 说明 |
|---------|------|
| `env` | 全局环境变量实例 |
| `Env` | 环境变量管理器类 |
| `EnvVar` | 环境变量描述符 |
| `EnvGroup` | 环境变量分组基类 |

### gmi_ieops.utils

| 类/函数 | 说明 |
|---------|------|
| `get_service_port()` | 获取可用服务端口 |
| `get_socket_path()` | 获取 Unix socket 路径 |
| `log` | 日志工具 |
| `randstr()`, `arandstr()` | 随机字符串生成器 |
| `SubprocessManager` | 子进程生命周期管理器 |

### gmi_ieops.storage

| 类/函数 | 说明 |
|---------|------|
| `FileSystem` | 用户隔离文件系统 (WebDAV 后端) |
| `GMIFS_PROTOCOL` | gmifs:// 协议常量 |
| `enable_storage_intercept()` | 启用存储拦截 (自动调用) |
| `disable_storage_intercept()` | 禁用存储拦截 |

---

## 示例

### Engine + Flask

```python
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port
from flask import Flask, jsonify, request

port = get_service_port()
engine = Engine(service_port=port)
engine.start()

app = Flask(__name__)

@app.route("/health")
def health():
    return jsonify({"status": "ok"})

@app.route("/v1/chat/completions", methods=["POST"])
def chat():
    data = request.json
    messages = data.get("messages", [])
    return jsonify({
        "id": "chatcmpl-xxx",
        "object": "chat.completion",
        "choices": [{"message": {"role": "assistant", "content": "Hello!"}}],
    })

app.run(host="127.0.0.1", port=port)
```

### Engine + FastAPI (流式)

```python
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import uvicorn
import json

port = get_service_port()
engine = Engine(service_port=port)
engine.start()

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

@app.post("/v1/chat/completions")
async def chat(data: dict):
    if data.get("stream"):
        async def generate():
            for token in ["Hello", " ", "World", "!"]:
                chunk = {"choices": [{"delta": {"content": token}}]}
                yield f"data: {json.dumps(chunk)}\n\n"
            yield "data: [DONE]\n\n"
        return StreamingResponse(generate(), media_type="text/event-stream")
    return {"choices": [{"message": {"content": "Hello World!"}}]}

uvicorn.run(app, host="127.0.0.1", port=port)
```

### Handler + Server 模式示例

```python
from gmi_ieops.handler import Handler, Server, RouterDef, RouterKind, HTTPMethod
from gmi_ieops.config import env

class ChatHandler:
    async def chat_completions(self, query):
        return {
            "id": "chatcmpl-xxx",
            "object": "chat.completion",
            "choices": [{"message": {"role": "assistant", "content": "Hello!"}}],
        }
    
    async def chat_stream(self, query):
        for token in ["Hello", " ", "World", "!"]:
            yield {"choices": [{"delta": {"content": token}}]}

def main():
    handler = ChatHandler()
    
    server = Server(
        routers={
            "chat/completions": [
                RouterDef(path="", handler=handler.chat_completions, kind=RouterKind.API),
            ],
            "chat/completions/stream": [
                RouterDef(path="", handler=handler.chat_stream, kind=RouterKind.SSE),
            ],
        },
        prefix="/v1",
        app_name=env.app.name,
    )
    
    Handler(server=server).serve()

if __name__ == "__main__":
    main()
```

---

## 最佳实践

### 1. 优先使用 Engine 模式

```python
# ✅ 推荐: Engine 模式
from gmi_ieops.handler import Engine
from gmi_ieops.utils import get_service_port

port = get_service_port()
engine = Engine(service_port=port)
engine.start()

# 使用你熟悉的框架
app = Flask(__name__)  # 或 FastAPI, Django 等
```

### 2. 流式输出

```python
# ✅ 好: 增量产出
async def stream():
    async for chunk in generate():
        yield f"data: {json.dumps(chunk)}\n\n"
    yield "data: [DONE]\n\n"

# ❌ 坏: 收集所有再产出
async def bad_stream():
    results = []
    async for chunk in generate():
        results.append(chunk)
    yield f"data: {json.dumps(results)}\n\n"
```

### 3. 错误处理

```python
from fastapi import HTTPException

@app.post("/api/chat")
async def chat(data: dict):
    if not data.get("messages"):
        raise HTTPException(status_code=400, detail="Messages required")
    
    try:
        result = await inference(data)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
```

### 4. 指标收集

```python
from gmi_ieops.handler import get_metrics

metrics = get_metrics()

# 计数器 - 累计计数
metrics.inc("requests_total")
metrics.inc("tokens_total", value=128)

# 仪表盘 - 瞬时值
metrics.set_gauge("active_requests", 5)
metrics.inc_gauge("active_requests")
metrics.dec_gauge("active_requests")

# 直方图 - 延迟跟踪
metrics.observe("request_latency_seconds", 0.123)

# 计时器 - 自动记录耗时
with metrics.timer("inference_latency_seconds"):
    result = model.generate(prompt)

# 便捷方法 - 跟踪完整请求
metrics.track_request(
    endpoint="/v1/chat",
    success=True,
    latency=0.5,
    prompt_tokens=100,
    completion_tokens=50,
)

# 获取所有指标
all_metrics = metrics.get_all()
```

---

## 详细文档

| 文档 | 说明 |
|------|------|
| [Engine 模式指南](docs/engine-mode-guide.md) | **推荐** - 透明代理模式完整指南 |
| [Handler+Server 模式指南](docs/handler-server-mode-guide.md) | SDK 路由管理模式指南 |
| [Mamba 镜像构建指南](docs/mamba-image-build-guide.md) | 容器镜像构建教程 |
| [架构文档](docs/ARCHITECTURE.md) | SDK 内部架构详解 |
| [WebDAV 存储指南](docs/webdav-storage-guide.md) | WebDAV 分布式存储访问 |
| [Engine 示例](example_engine.py) | Engine 模式完整示例 |
| [Worker 示例](../workers/) | 参考实现 |
