Skip to content

Commit cb34b01

Browse files
committed
feat: ship v0.7.0 onboarding and diagnostics
1 parent 89b8ccb commit cb34b01

27 files changed

Lines changed: 3112 additions & 566 deletions

README.md

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,25 @@ UncommonRoute picks the cheapest model that still gets the job done — automati
4040
pip install uncommon-route
4141
```
4242

43-
### 2. Connect an upstream — pick one
44-
45-
**Option A — Commonstack (recommended, one key covers all providers)**
46-
47-
Sign up at [commonstack.ai](https://commonstack.ai) and copy your API key from the dashboard. Then:
43+
### 2. Run the guided setup
4844

4945
```bash
50-
export UNCOMMON_ROUTE_UPSTREAM="https://api.commonstack.ai/v1"
51-
export UNCOMMON_ROUTE_API_KEY="csk-your-key"
52-
uncommon-route serve
46+
uncommon-route init
5347
```
5448

55-
One key gives you OpenAI, Anthropic, Google, xAI, MiniMax, Moonshot, DeepSeek, and more — consolidated billing, no per-provider setup.
49+
The wizard walks you through:
5650

57-
**Option B — Bring your own keys (BYOK)**
51+
- choosing a connection path: Commonstack, local/custom upstream, or BYOK
52+
- saving upstream credentials locally
53+
- configuring Claude Code, Codex, or OpenAI SDK / Cursor
54+
- optionally starting the proxy in background
5855

59-
Register any subset of providers you have direct keys for:
56+
If you prefer to sanity-check before starting the proxy:
6057

6158
```bash
62-
uncommon-route provider add openai sk-...
63-
uncommon-route provider add anthropic sk-ant-...
64-
uncommon-route provider add google AIza...
65-
# also supported: xai, minimax, moonshot, deepseek
66-
uncommon-route serve
59+
uncommon-route doctor
6760
```
6861

69-
Auto-routing will only consider models backed by a registered provider.
70-
71-
> **Note:** UncommonRoute does **not** auto-read `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`. Use one of the two paths above.
72-
7362
### 3. Point your client at the proxy
7463

7564
| Client | Change |
@@ -88,6 +77,35 @@ resp = client.chat.completions.create(model="uncommon-route/auto", messages=msgs
8877

8978
Works with **Claude Code**, **Codex**, **Cursor**, the **OpenAI SDK**, and **OpenClaw**.
9079

80+
<details>
81+
<summary><strong>Manual setup (advanced)</strong></summary>
82+
83+
**Commonstack managed upstream**
84+
85+
```bash
86+
export UNCOMMON_ROUTE_UPSTREAM="https://api.commonstack.ai/v1"
87+
export UNCOMMON_ROUTE_API_KEY="csk-your-key"
88+
uncommon-route serve
89+
```
90+
91+
One key gives you OpenAI, Anthropic, Google, xAI, MiniMax, Moonshot, DeepSeek, and more — consolidated billing, no per-provider setup.
92+
93+
**Bring your own keys (BYOK)**
94+
95+
```bash
96+
uncommon-route provider add openai sk-...
97+
uncommon-route provider add anthropic sk-ant-...
98+
uncommon-route provider add google AIza...
99+
# also supported: xai, minimax, moonshot, deepseek
100+
uncommon-route serve
101+
```
102+
103+
Auto-routing will only consider models backed by a registered provider.
104+
105+
> **Note:** UncommonRoute does **not** auto-read `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`. Use `uncommon-route init`, a saved connection, or one of the manual paths above.
106+
107+
</details>
108+
91109
---
92110

93111
## How It Works
@@ -161,6 +179,19 @@ Real-time monitoring, interactive playground, cost tracking, and model routing c
161179

162180
---
163181

182+
## Diagnostics
183+
184+
When a user hits a routing or upstream issue, you can export a local support bundle without guessing which logs to collect:
185+
186+
```bash
187+
uncommon-route support bundle
188+
uncommon-route support request <request_id>
189+
```
190+
191+
The bundle includes recent request traces, recent errors, stats summaries, provider/config snapshots, and redacted local state. It stays on your machine until you choose to share it.
192+
193+
---
194+
164195
## Configuration
165196

166197
### Routing modes
@@ -209,6 +240,8 @@ Runs entirely on your machine. No data leaves unless you opt in.
209240
uncommon-route telemetry status
210241
```
211242

243+
Diagnostics exports are also local-first: `uncommon-route support bundle` writes a redacted zip under `~/.uncommon-route/support/` by default.
244+
212245
---
213246

214247
## Development

README.zh-CN.md

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,25 @@ UncommonRoute 自动选最便宜、又能完成任务的模型。
4040
pip install uncommon-route
4141
```
4242

43-
### 2. 接入上游——二选一
44-
45-
**方式 A — Commonstack 托管(推荐,一把 key 覆盖所有 provider)**
46-
47-
[commonstack.ai](https://commonstack.ai) 注册并从 dashboard 拿到你的 API key,然后:
43+
### 2. 运行引导式配置
4844

4945
```bash
50-
export UNCOMMON_ROUTE_UPSTREAM="https://api.commonstack.ai/v1"
51-
export UNCOMMON_ROUTE_API_KEY="csk-your-key"
52-
uncommon-route serve
46+
uncommon-route init
5347
```
5448

55-
一把 key 打通 OpenAI、Anthropic、Google、xAI、MiniMax、Moonshot、DeepSeek 等所有主流 provider——统一账单,无需逐家申请。
49+
这个向导会一步步帮你:
5650

57-
**方式 B — 自带 key(BYOK)**
51+
- 选择接入方式:Commonstack、BYOK,或者本地 / 自定义 upstream
52+
- 把 upstream 凭据保存到本地
53+
- 配置 Claude Code、Codex、OpenAI SDK / Cursor
54+
- 可选地直接把代理以后台方式启动起来
5855

59-
注册你已经有 key 的任意 provider(可选任意子集)
56+
如果你想在启动前先检查一遍
6057

6158
```bash
62-
uncommon-route provider add openai sk-...
63-
uncommon-route provider add anthropic sk-ant-...
64-
uncommon-route provider add google AIza...
65-
# 同样支持:xai、minimax、moonshot、deepseek
66-
uncommon-route serve
59+
uncommon-route doctor
6760
```
6861

69-
Auto 路由只会在已注册的 provider 范围内选模型。
70-
71-
> **注意:** UncommonRoute **不会**自动读取 `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` 环境变量。请用上面两种方式之一。
72-
7362
### 3. 把客户端指向代理
7463

7564
| 客户端 | 改动 |
@@ -88,6 +77,35 @@ resp = client.chat.completions.create(model="uncommon-route/auto", messages=msgs
8877

8978
支持 **Claude Code****Codex****Cursor****OpenAI SDK****OpenClaw**
9079

80+
<details>
81+
<summary><strong>手动配置(进阶)</strong></summary>
82+
83+
**方式 A — Commonstack 托管(推荐,一把 key 覆盖所有 provider)**
84+
85+
```bash
86+
export UNCOMMON_ROUTE_UPSTREAM="https://api.commonstack.ai/v1"
87+
export UNCOMMON_ROUTE_API_KEY="csk-your-key"
88+
uncommon-route serve
89+
```
90+
91+
一把 key 打通 OpenAI、Anthropic、Google、xAI、MiniMax、Moonshot、DeepSeek 等所有主流 provider——统一账单,无需逐家申请。
92+
93+
**方式 B — 自带 key(BYOK)**
94+
95+
```bash
96+
uncommon-route provider add openai sk-...
97+
uncommon-route provider add anthropic sk-ant-...
98+
uncommon-route provider add google AIza...
99+
# 同样支持:xai、minimax、moonshot、deepseek
100+
uncommon-route serve
101+
```
102+
103+
Auto 路由只会在已注册的 provider 范围内选模型。
104+
105+
> **注意:** UncommonRoute **不会**自动读取 `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` 环境变量。请使用 `uncommon-route init`、本地保存的连接配置,或上面的手动方式。
106+
107+
</details>
108+
91109
---
92110

93111
## 工作原理
@@ -104,7 +122,7 @@ resp = client.chat.completions.create(model="uncommon-route/auto", messages=msgs
104122
| 信号 | 分析内容 | 耗时 |
105123
|---|---|---|
106124
| **元数据信号** | 对话轮次、工具调用、上下文深度 | <1ms |
107-
| **语义信号** | 与已知任务模式的相似度(embedding) | ~10ms |
125+
| **语义信号** | 与已知任务模式的相似度(embedding) | ~20ms |
108126
| **结构信号** | 文本复杂度特征(影子模式运行) | <1ms |
109127

110128
三个信号投票,集成模型决定 tier,路由器在对应 tier 中选最便宜的模型。遇到不确定的情况,宁可多花一点也不冒失败风险。
@@ -139,7 +157,7 @@ v1 分类器在干净的 benchmark 上跑出了 88.5% 的准确率,我们直
139157
|---|---|
140158
| **成本节省** | **82%**(对比全程顶配) |
141159
| **任务完成率** | **93.4%** |
142-
| **路由延迟** | **<10ms** |
160+
| **路由延迟** | **~20–25ms**(warm process, CPU, bge-small embedding) |
143161
| **准确率** | **78%** tier 匹配 |
144162

145163
```bash
@@ -159,6 +177,19 @@ uncommon-route serve
159177

160178
---
161179

180+
## 诊断与排障
181+
182+
如果用户遇到路由异常、上游报错,或者某次请求行为不对,现在可以直接导出本地诊断包,不需要先猜该收集哪些日志:
183+
184+
```bash
185+
uncommon-route support bundle
186+
uncommon-route support request <request_id>
187+
```
188+
189+
诊断包会包含最近请求 trace、最近错误、统计摘要、provider / 配置快照,以及脱敏后的本地状态。默认只写到你的本机,只有你主动分享时才会离开电脑。
190+
191+
---
192+
162193
## 配置
163194

164195
### 路由模式
@@ -207,6 +238,8 @@ uncommon-route provider remove <name>
207238
uncommon-route telemetry status
208239
```
209240

241+
`uncommon-route support bundle` 生成的诊断包默认也只会保存在 `~/.uncommon-route/support/`
242+
210243
---
211244

212245
## 开发

openclaw-plugin/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This plugin:
1818
- starts `uncommon-route serve`
1919
- registers the local provider with OpenClaw
2020
- exposes the virtual routing modes like `uncommon-route/auto`
21+
- keeps request traces and support diagnostics local on disk
2122

2223
## Install
2324

@@ -85,14 +86,16 @@ If the plugin is installed but responses are failing:
8586
2. Make sure `UNCOMMON_ROUTE_API_KEY` is set if your provider requires one.
8687
3. Open `http://127.0.0.1:8403/health`.
8788
4. Open `http://127.0.0.1:8403/dashboard/`.
89+
5. Run `uncommon-route support bundle` to export recent traces and diagnostics.
8890

8991
## Benchmarks
9092

9193
Current repo benchmarks:
9294

93-
- 92.3% held-out routing accuracy
94-
- ~0.5ms average routing latency
95-
- 67% lower simulated cost than always using Claude Opus in a coding session
95+
- 82% lower cost than always routing to a premium model
96+
- 93.4% task pass rate on CommonRouterBench
97+
- ~20–25ms warm-process routing overhead on CPU
98+
- 78% tier-match accuracy through the production routing path
9699

97100
## Links
98101

openclaw-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@anjieyang/uncommon-route",
3-
"version": "0.6.0",
3+
"version": "0.7.0",
44
"description": "OpenClaw plugin for UncommonRoute, the local LLM router that cuts premium-model spend",
55
"type": "module",
66
"main": "src/index.js",

openclaw-plugin/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import { spawn, execSync } from "node:child_process";
1717
import { setTimeout as sleep } from "node:timers/promises";
1818

19-
const VERSION = "0.4.0";
19+
const VERSION = "0.7.0";
2020
const DEFAULT_PORT = 8403;
2121
const DEFAULT_UPSTREAM = "";
2222
const HEALTH_TIMEOUT_MS = 15_000;

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uncommon-route"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
description = "Local LLM router that cuts premium-model spend with adaptive 3-tier routing, OpenAI + Anthropic compatible"
55
requires-python = ">=3.11"
66
license = "MIT"

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytest
66

7+
from uncommon_route.connections_store import ConnectionsStore, InMemoryConnectionsStorage
78
from uncommon_route.model_experience import (
89
InMemoryModelExperienceStorage,
910
ModelExperienceStore,
@@ -20,6 +21,14 @@ def spend_control() -> SpendControl:
2021

2122
@pytest.fixture(autouse=True)
2223
def _isolate_proxy_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
24+
monkeypatch.setattr(
25+
"uncommon_route.proxy._LOCAL_CLIENT_HOSTS",
26+
{"127.0.0.1", "::1", "localhost", "testclient"},
27+
)
28+
monkeypatch.setattr(
29+
"uncommon_route.proxy.ConnectionsStore",
30+
lambda: ConnectionsStore(storage=InMemoryConnectionsStorage()),
31+
)
2332
monkeypatch.setattr(
2433
"uncommon_route.proxy.load_providers",
2534
lambda: ProvidersConfig(),

tests/test_anthropic_compat.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,18 @@ def test_no_upstream_returns_anthropic_error(self) -> None:
637637
assert data["type"] == "error"
638638
assert data["error"]["type"] == "overloaded_error"
639639

640-
def test_upstream_unreachable_returns_anthropic_error(self, messages_client: TestClient) -> None:
640+
def test_upstream_unreachable_returns_anthropic_error(
641+
self,
642+
messages_client: TestClient,
643+
monkeypatch: pytest.MonkeyPatch,
644+
) -> None:
645+
class FailingClient:
646+
is_closed = False
647+
648+
async def post(self, *args, **kwargs):
649+
raise httpx.ConnectError("connection refused")
650+
651+
monkeypatch.setattr("uncommon_route.proxy._get_client", lambda: FailingClient())
641652
resp = messages_client.post("/v1/messages", json={
642653
"model": "some-model",
643654
"max_tokens": 100,

0 commit comments

Comments
 (0)