Metadata-Version: 2.4
Name: ai-center-runtime
Version: 0.2.0
Summary: Relay-only model access SDK for AI Center applications (invocation binding, allowlist, stable errors)
Author: 01JohnMa
Project-URL: Homepage, https://github.com/01JohnMa/ai_center
Project-URL: Changelog, https://github.com/01JohnMa/ai_center/commits/main/packages/ai-center-runtime
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: openai==2.54.0
Requires-Dist: httpx==0.28.1
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == "fastapi"

# ai-center-runtime

Python 应用接入 AI Center 模型网关的最小 SDK。

**Relay-only 合同（ADR-0015，2026-09-29 修订）**：应用不直连 LiteLLM，也不手工拼装归因。
部署时平台注入三元组环境变量，应用把模型请求发到平台 Relay，归因（spend logs
metadata）由平台在转发时注入，ModelCall/Token/费用由 AI Center 对账生成。

| 环境变量 | 含义 |
|---|---|
| `AI_CENTER_MODEL_GATEWAY_URL_TEMPLATE` | 含 `{invocation_id}` 占位符的 Relay 端点模板 |
| `AI_CENTER_RUNTIME_CREDENTIAL` | per-Deployment 运行时凭据（随部署轮换） |
| `AI_CENTER_MODEL_OPERATIONS` | 允许的 operation 白名单（逗号分隔；缺省一律拒绝） |

## 安装

从仓库根目录执行（当前无需发布 PyPI）：

```sh
uv pip install ./packages/ai-center-runtime
```

## 接入：异步 Job 中绑定 Invocation，再调用模型

```python
from ai_center_runtime import (
    PlatformModelClient, PlatformModelError,
    bind_invocation, model_access_mode,
)

# 1) 部署受理时把平台 Invocation 绑定到当前任务上下文（如异步 Job）。
binding = bind_invocation(platform_invocation_id)
try:
    if model_access_mode() != "relay":
        raise RuntimeError("平台模型访问未配置 Relay；direct 仅限本地开发")

    # 2) 客户端读取三元组环境变量；白名单外的 operation 在发请求前失败。
    with PlatformModelClient() as models:
        answer = models.chat_completion(
            [{"role": "user", "content": "你好"}],
            model="deepseek-flash",
            json_mode=True,
        )
        print(answer["choices"][0]["message"]["content"])

        vectors = models.text_embeddings(["一段文本"], model="text-embedding")
finally:
    binding.restore()
```

要点：

- **FastAPI 一行接入**（可选依赖）：同步接口不需要手写"读 header + bind +
  try/finally restore"——用依赖注入器，请求进入自动绑定、响应后自动解绑：

  ```python
  from fastapi import Depends
  from ai_center_runtime import model_invocation, PlatformModelClient

  @app.post("/extract")
  async def extract(req: ExtractRequest, _b=Depends(model_invocation())):
      # 本 handler 内的 Relay 调用自动携带 Invocation
      with PlatformModelClient() as models:
          return models.chat_completion([...], model="deepseek-flash")
  ```

  安装：`pip install 'ai-center-runtime[fastapi]'`。缺
  `x-ai-center-invocation-id` 头时返回 422；`model_invocation(required=False)`
  放行无 Invocation 请求（调用点 fail-closed 不变）。

- **mode 三态，绝不回退**：`model_access_mode()` 返回 `relay` / `direct` /
  `unconfigured`；`direct` 只有应用显式设置 `AI_CENTER_LLM_MODE=direct`（本地开发）
  时成立，平台从不注入它。
- **fail closed**：白名单缺失或 operation 越界抛 `MODEL_CAPABILITY_UNAVAILABLE`；
  未绑定 Invocation 抛 `MODEL_INVOCATION_MISSING`。构造
  `PlatformModelClient()` 本身就是模式检查（未配置时构造即失败），无需再手写
  mode 判断。
- **稳定错误**：`PlatformModelError.reason` ∈ `MODEL_CAPABILITY_UNAVAILABLE` /
  `MODEL_INVOCATION_MISSING` / `MODEL_ACCESS_EXPIRED`（异步窗口过期）/
  `MODEL_TIMEOUT` / `MODEL_UPSTREAM_ERROR`；`status` 携带 HTTP 状态。
- **logical_call_id**：默认按 `{invocation_id}:{scope}:{sha256[:24]}` 生成并经
  `x-request-id` 透传；同一逻辑调用的重试应显式传同一个 `logical_call_id`。
- **OpenAI 兼容生态**：`relay_connection()` 返回 `{"base_url", "api_key"}`，
  可直接喂给 `openai.OpenAI(...)`、llama_index `OpenAI(...)` 等。

## 旧合同（Deprecated）

`InvocationContext` / `ModelClient` 是直连 LiteLLM 时代的合同：平台已不再注入
`LITELLM_BASE_URL` / `LITELLM_API_KEY`，新应用禁止使用；它们仅为尚未迁移的
历史应用保留，迁移路径是把 `InvocationContext.from_headers(...)` +
`ModelClient.from_runtime_context(...)` 替换为 `bind_invocation(...)` +
`PlatformModelClient(...)`。

## 限制与证据边界

- 只支持 blocking JSON（chat.completions / embeddings）；SSE 与
  responses 端点的客户端封装暂未提供。同步调用用 `PlatformModelClient`，
  异步应用（FastAPI 路由、async Worker）用 `AsyncPlatformModelClient`，
  两者接口语义一致。
- 不自动重试、不跟随重定向；Provider 路由与重试由 LiteLLM owner 负责。
- 归因字段由平台 Relay 注入，SDK 与应用都不能覆盖；应用也不应伪造
  `x-ai-center-*` header。
- SDK 不能在恶意代码面前充当沙箱；凭据模型限制、Relay 窗口与白名单、
  Runtime 网络隔离负责服务端强制治理。

## 验证

```sh
PYTHONPATH=packages/ai-center-runtime/src uv run --project packages/ai-center-runtime \
  python -m unittest discover -s packages/ai-center-runtime/tests
```

协议测试通过 httpx MockTransport 校验三元组解析、模板物化、白名单 fail-closed、
错误映射与绑定隔离，不需要真实平台。真实链路探针见 `scripts/smoke_relay.py`。

合同来源：[ADR 0013](../../docs/adr/0013-runtime-v2-unified-invocation-and-usage.md)、
[ADR 0015](../../docs/adr/0015-platform-model-gateway-relay.md)。
