Metadata-Version: 2.4
Name: octopus-router
Version: 0.3.0
Summary: Octopus Router: BYOK-first model gateway with constraint-filtered decision routing
License-Expression: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiosqlite>=0.20.0
Requires-Dist: cryptography>=43.0.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: prometheus-client>=0.21.0
Requires-Dist: pydantic>=2.9.0
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: structlog>=24.4.0
Requires-Dist: uvicorn>=0.32.0
Provides-Extra: client
Requires-Dist: openai==3.35.1; extra == "client"
Provides-Extra: dev
Requires-Dist: pytest>=8.3.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
Requires-Dist: ruff>=0.7.0; extra == "dev"
Dynamic: license-file

# Octopus Router（八爪鱼路由）

面向独立开发者、小团队和 AI agent 用户的统一模型入口：客户端只需指向一个 OpenAI 兼容地址，选 `auto` 或 `eco`，由路由器在硬约束过滤后的候选里决定用哪个模型。Python/FastAPI/SQLite，单进程，本机运行。BYOK（用户自带 API key 与本机端点）优先；免费本地核心无需平台订阅、云账号或训练贡献。

## 免费版：一条命令启动

需要 [uv](https://docs.astral.sh/uv/)（会自动准备 Python 3.12）。

```bash
# 发布到 PyPI 后（尚未发布）：
uv tool install octopus-router     # 安装 octopus 命令；升级用 uv tool upgrade octopus-router
octopus start                      # 只监听 127.0.0.1:8787，数据在 ~/.octopus（可用 --data-dir 或 OCTOPUS_HOME 指定）

# 现在：从源码目录安装或直接运行
uv tool install .                  # 在仓库根目录执行
uv sync --all-extras --locked && uv run octopus start   # 开发方式
```

`uv run python scripts/package_smoke.py` 会构建 wheel、装进干净虚拟环境、在仓库外启动安装版 `octopus start`，并经模拟本机上游跑通一次 `auto` 和 Claude Code（Messages）请求；CI 每次都跑。它不调用真实厂商。

首次启动会创建本机账号（own-first，无平台余额、无付费回退），把 Octopus key 存到数据目录的 `account.json`（权限 0600），并打开 Portal：

1. 在“我的模型资源 → 快速添加厂商 API”选厂商（OpenAI、Gemini、Grok、DeepSeek、Qwen、GLM、Kimi、MiniMax，含国际/中国大陆端点），填模型 ID 和自己的 API key。key 只写入、加密保存在本机，不会再显示，也不进日志和请求记录。其他 OpenAI 兼容服务可在下方表单按自定义端点添加。
2. 在“接入”页复制配置：OpenAI 兼容客户端用 `http://127.0.0.1:8787/v1`，Claude Code 用 `ANTHROPIC_BASE_URL=http://127.0.0.1:8787`，模型都填 `auto`。`uv run octopus connect` 也会打印这些设置。

免费版只开放 `auto`（及其实时流式变体 `auto-realtime`）和指定模型直连；`eco` 等模式需要订阅，未订阅时返回 402 `subscription_required`。决策器为本地运行，目前是启发式基线，Laya 微调模型通过评测后替换。

## 虚拟模型

| `model` | 目标 | 行为 |
|---|---|---|
| `auto` | 尽量答上 | 能力/上下文/预算/可用性硬过滤 → 决策模型按拒答风险排序 → 按优先级。无工具时先缓冲，拒答会换另一个合规模型重试一次；带工具时走实时流式 |
| `auto-realtime` | 尽量答上，实时输出 | 同上，但实时流式 |
| `eco`（订阅） | 省钱优先 | 同样的硬过滤和拒答风险判断，然后选估算价格最低的候选；价格未知者排最后 |
| 具体模型名 | 指定模型 | 直连，不会悄悄换模型 |

合规边界：标记为 `safety_modified`（去除安全训练的 uncensored/abliterated 变体）的模型不会进入 `auto`/`eco`，也不会成为拒答重试目标；所有合规候选都拒答时按拒答返回。

可靠性：上游**确定未受理**（连接失败、429、504 以外的 5xx）且客户端尚未收到任何内容时，按顺序切换到下一个候选，每个根请求最多 3 次上游调用；429 按 `Retry-After` 冷却，连续失败指数退避。可能已受理的调用（超时、流中途出错）不重放，标记待结算并返回明确中断事件。决策模型超时、出错或给出候选外的结果时回退到默认顺序，原因记在 trace 上。

## 2026-11-08 实现状态

本轮新增 `eco`、决策器超时/异常/无效输出回退、未受理前故障转移与冷却（平台与 BYOK 两条路径）、`auto` 支持工具、逐次尝试的延迟/token/结果记录与 Portal 展示。全部用合成上游和本机回环服务验证（截至第二批 `uv run pytest` 362 项通过），**没有调用任何真实模型**。

10-21 的状态仍然成立：自有资源管理、租户凭据引用、有上限的付费回退、受预算保护的远程联调入口、官方 OpenAI Python SDK **3.35.1** 的 Chat/无状态 Responses 子集。Laya 仍是 **`laya-baseline-v0` 启发式**；没有加载或训练 Laya 权重，微调方案见 [Laya 微调方案](docs/laya-finetune-plan.md)。Anthropic Messages 桥已接通，Claude Code 2.1.317 已在合成上游上实测（见 [客户端兼容](docs/client-compatibility.md)）；Codex 产品兼容尚未验证。没有启用真实收费、公开部署或调用付费模型。

当前证据与交接见 [开发进度](docs/development-progress.md)。显卡故障不阻断 CPU 回归；10 月 15 日的本地模型结果仅是历史证据。

## 安装与无 GPU 验收

在项目根目录使用 Python 3.12+：

```bash
uv sync --all-extras --locked
uv run ruff check .
uv run pytest -q -rs
uv run python -m scripts.local_smoke --registry configs/registry.yaml --report artifacts/mock-smoke-2026-10-21.json
uv run python -m scripts.client_smoke --report artifacts/client-smoke.json
```

`client` 可选依赖锁定实际验证的 SDK；`--all-extras` 包含它。无 `uv` 时使用 `.venv/bin/python -m ...`。Prometheus 规则另外运行 `promtool check rules observability/alerts.yml`；工具缺失时对应测试明确跳过。

这些检查使用进程内 mock 或真实 loopback socket 上的合成服务，证明协议、权限、流式/取消和账本行为，不证明真实模型质量。实际 SDK 的验证范围见 [客户端兼容](docs/client-compatibility.md)。

## 本地核心：接自己的资源

启动一个隔离实例，然后创建**不发平台测试余额、不授权付费回退**的本地账号：

```bash
uv run python main.py --registry configs/registry.yaml --data-dir data/owned-local --port 8082
# 保持以上进程运行，在另一个终端执行：
uv run python -m scripts.local_account --url http://127.0.0.1:8082 --data-dir data/owned-local --own-first
```

账号和 API key 仅保存到权限 0600 的 `data/owned-local/test-account.json`。这是本机账号，不需要外部云账号。打开 `http://127.0.0.1:8082/dashboard/login`，使用该 key；在“我的模型资源”添加端点及策略。保存配置不会自动调用或探测上游。

- 本机端点只允许显式 loopback，使用 Chat Completions `/v1` 基础 URL。Ollama 原生 `/api/chat` 不属于这个协议。客户端应指向 Router。
- 资源默认关闭。明确允许推理、确认能力和手动健康状态后才调用；健康状态不是自动供应商探针。未知配额默认停止，或仅在你明确设置的小次数上限内尝试。
- own-first 先过滤能力、key 权限、隐私、配额和预算，再按资源顺序选择。local-only 不外发到远程资源，也不允许付费回退。没有符合要求的资源时解释失败。
- 自有资源的平台本次推理收费为零；上游 API、订阅、电力和设备成本由用户承担。配置价格计算的自有成本标为估计；未知 usage/费用保持未知。资源用量与平台钱包分开记录。
- 自有资源路径使用本地规则，不发正文到云分类器，也不采集训练样本。普通推理 JSON 不能授予付费权限。

完整配置、BYO 凭据、付费授权和限制见 [自有资源使用](docs/owned-resources.md)。云服务器上的 localhost 指云服务器；当前没有用户电脑连接器或通用消费订阅 OAuth 接入。

## BYO API 凭据与配置

凭据只使用环境变量引用；不能在资源 JSON 或面板填写原始 key。服务器必须显式把变量绑定给具体租户，不能借此使用其他租户或平台的凭据。管理员从本地账号文件取得 tenant ID 后，以如下参数重启**本项目实例**：

```bash
uv run python main.py --registry configs/registry.yaml --data-dir data/owned-local --port 8082 \
  --owned-secret-env TENANT_ID:MY_BYO_API_KEY --env-file /path/to/project-provider.env
```

加载器只读取注册表引用和明确绑定的变量，不执行 shell、不覆盖已有环境变量，也不搜寻其他凭据文件。远程 BYO 还需 HTTPS、精确域名、资源外发许可和策略域名白名单。仅配置引用不代表已经核验供应商条款或真实能力。

`--registry`、`--rate-cards` 选择独立配置；`--data-dir` 隔离钱包、trace、资源账本、学习库和本地密钥。随机 `local-secrets.json` 跨重启复用，须与 SQLite 数据一起备份。`STATION_ADMIN_KEY`/`STATION_HMAC_SECRET`/`STATION_FERNET_KEY` 可显式覆盖；改变密钥需要相应迁移，不能恢复已丢失的旧解密密钥。

## 远程模型联调

```bash
uv run python -m scripts.remote_smoke \
  --plan configs/remote-validation/plan.example.yaml \
  --report artifacts/remote-preflight-2026-10-21.json
```

模板默认禁用，预期 `blocked`/退出码 1；不发送请求。可执行路径包含精确目标白名单、真实报价预检、持久请求/token/费用预占、超时/取消、脱敏报告和失败非零退出。真实执行还需专用凭据、核验过的模型/费率，以及具体预算授权。文档中的 $100 是建议，不能据此花费。旧 DeepInfra 示例使用 mock 费率，不能直接启用。

详见 [远程联调](docs/remote-validation.md)。`local_smoke` 的 mock/loopback 限制保留。

## 数据与离线训练准备

学习设施默认关闭，须由 `--learning-capture` 开启；租户仍须独立授权。失败样本与正常对照分别同意，`X-Training-Consent: deny` 阻止当次采集。仅管理员注册表执行路径支持这项采集。用户面板可分别授权/撤回，并明确选择是否允许人工查看。

候选正文加密且先隔离；生产审核记录权利依据、人工拒答标签和保留期限。`approve_for_test` 只能生成合成测试候选，不能进入正式格式转换。统一 manifest 冻结内容 hash、分组和 train/dev/calibration/test/challenge；所有导出使用同一分区。撤回阻断新导出、使版本失效并列出已登记导出副本；副本处置是有记录的人工操作，不宣称权重已遗忘。

不接触用户数据的可重复演练（输出目录须是新的）：

```bash
uv run python -m scripts.training_prepare --synthetic-rehearsal \
  --output data/training-rehearsal-new --dataset-id synthetic-pipeline
```

它验证 Laya **0.4.12** 官方 JSONL 格式、本地分区和发布/回滚演练，不训练、不上传、不替换线上基线。另外已在独立 CPU 环境对 100 条合成数据运行官方 `dry_run`，锁定 multilingual revision，只下载公开配置/tokenizer，没有下载或加载权重、没有更新优化器或重新切分 calibration。复现命令和真实数据门禁见 [学习管线](docs/learning-pipeline.md)。

## 历史本地模型与开发边界

`configs/registry.local.yaml` 保留历史模型/端点（`main.py` 默认改读 `configs/registry.yaml`）。GPU 已损坏，不能把注册表中的 enabled 或旧报告理解为当前服务可用。硬件恢复后才在新目录重新运行 `scripts.local_smoke --env-file /path/to/project-provider.env`。不传 `--own-first` 的账号工具仍保留 1 USD 合成测试余额流程，用于历史 mock/钱包验收。

`auto`/`eco` 无工具时提供最多一次文本缓冲补救，带工具时走实时路径；固定模型和 `auto-realtime` 可实时流式。工具能力须明确声明，已曝光工具不自动重放。默认最多输出 256 token；缺可靠 usage 保持待结算。单进程 SQLite 是当前运行边界，多实例部署、供应商完整对账、真实收款和商业发布另受门禁约束。

商业方向以 [商业计划 v4](docs/business-plan-v4.md) 为准；[v3](routing-station-development-plan-v3.md) 的账本、权限、同意与补救契约继续有效。参考 [开发环境](docs/development-environments.md)、[Laya 计划](docs/laya-decision-flywheel.md)、[收费前检查](docs/business-readiness.md)。

## 许可证

Apache License 2.0，见 [LICENSE](LICENSE)。
