router-brain 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- router_brain-1.1.0/LICENSE +21 -0
- router_brain-1.1.0/PKG-INFO +229 -0
- router_brain-1.1.0/README.md +211 -0
- router_brain-1.1.0/pyproject.toml +35 -0
- router_brain-1.1.0/setup.cfg +4 -0
- router_brain-1.1.0/src/router_brain/__init__.py +15 -0
- router_brain-1.1.0/src/router_brain/__main__.py +3 -0
- router_brain-1.1.0/src/router_brain/classifier.py +33 -0
- router_brain-1.1.0/src/router_brain/cli.py +272 -0
- router_brain-1.1.0/src/router_brain/config.py +198 -0
- router_brain-1.1.0/src/router_brain/credentials.py +53 -0
- router_brain-1.1.0/src/router_brain/dashboard.py +150 -0
- router_brain-1.1.0/src/router_brain/degrade.py +278 -0
- router_brain-1.1.0/src/router_brain/executor.py +413 -0
- router_brain-1.1.0/src/router_brain/live.py +56 -0
- router_brain-1.1.0/src/router_brain/llm_api.py +250 -0
- router_brain-1.1.0/src/router_brain/logger.py +45 -0
- router_brain-1.1.0/src/router_brain/models.py +105 -0
- router_brain-1.1.0/src/router_brain/openrouter_sync.py +99 -0
- router_brain-1.1.0/src/router_brain/provider_catalog.py +103 -0
- router_brain-1.1.0/src/router_brain/router.py +48 -0
- router_brain-1.1.0/src/router_brain.egg-info/PKG-INFO +229 -0
- router_brain-1.1.0/src/router_brain.egg-info/SOURCES.txt +30 -0
- router_brain-1.1.0/src/router_brain.egg-info/dependency_links.txt +1 -0
- router_brain-1.1.0/src/router_brain.egg-info/entry_points.txt +2 -0
- router_brain-1.1.0/src/router_brain.egg-info/requires.txt +4 -0
- router_brain-1.1.0/src/router_brain.egg-info/top_level.txt +1 -0
- router_brain-1.1.0/tests/test_classifier.py +61 -0
- router_brain-1.1.0/tests/test_credentials.py +43 -0
- router_brain-1.1.0/tests/test_degrade.py +56 -0
- router_brain-1.1.0/tests/test_executor_plan.py +188 -0
- router_brain-1.1.0/tests/test_router.py +97 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 router-brain contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: router-brain
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: 模型路由大脑:分析任务 → 从模型池选模型 → 派给 DeepSeek Harness headless agent 执行 → 失败降级重试 → 汇总结果
|
|
5
|
+
Author: router-brain contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: llm,routing,agent,deepseek-harness,model-router,multi-model
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: PyYAML>=6.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# router-brain · LLM Routing Brain
|
|
20
|
+
|
|
21
|
+
> **English**: this file · **中文**: [README_CN.md](README_CN.md)
|
|
22
|
+
|
|
23
|
+
> **Separate decision from execution**: a small "brain" model makes decisions (decompose → pick model → dispatch → read feedback); focused workers do one short-context subtask each. Use the right model for the right job, verify across models, fight hallucinations, and save money.
|
|
24
|
+
|
|
25
|
+
> **AI orchestration framework · multi-model router · prompt automation · DeepSeek Harness** — You don't need to know how to write prompts: tell it *what* you want, and the brain decomposes the task, writes the subtask prompts, picks the model, sets the reasoning effort, dispatches workers, reads feedback, and iterates until done.
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<a href="https://github.com/ZzkAItech/router-brain"><img alt="GitHub stars" src="https://img.shields.io/github/stars/ZzkAItech/router-brain?style=flat-square&label=Stars"></a>
|
|
29
|
+
<a href="https://github.com/ZzkAItech/router-brain"><img alt="GitHub forks" src="https://img.shields.io/github/forks/ZzkAItech/router-brain?style=flat-square&label=Forks"></a>
|
|
30
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square"></a>
|
|
31
|
+
<a href="https://www.python.org/"><img alt="Python" src="https://img.shields.io/badge/python-3.10+-blue?style=flat-square"></a>
|
|
32
|
+
<a href="https://github.com/ZzkAItech/router-brain/actions"><img alt="tests" src="https://github.com/ZzkAItech/router-brain/actions/workflows/test.yml/badge.svg"></a>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Problems it solves
|
|
38
|
+
|
|
39
|
+
### 🎯 Problem 1: LLMs lose focus in long contexts
|
|
40
|
+
|
|
41
|
+
**Problem**: Shove a whole long task into one model and it must hold the goal, background, progress, and current step all at once. The longer the context, the more it drifts ("lost in the middle").
|
|
42
|
+
|
|
43
|
+
**router-brain's approach**: **Separate decision from execution.**
|
|
44
|
+
- The **brain model only decides** — it sees "goal + current state + who to dispatch". It never touches execution details, so its attention stays on judgment.
|
|
45
|
+
- Each **worker handles one short-context subtask** — it only focuses on the one thing in front of it.
|
|
46
|
+
|
|
47
|
+
> The brain doesn't do the work, so the brain doesn't tire; the worker does one thing, so it doesn't get lost.
|
|
48
|
+
|
|
49
|
+
### 🌀 Problem 2: Hallucination
|
|
50
|
+
|
|
51
|
+
**Problem**: A single model generates once, with no one checking. In multi-step tasks one hallucinated step corrupts everything downstream.
|
|
52
|
+
|
|
53
|
+
**router-brain's approach**: **Dispatch → feedback → re-dispatch loop.**
|
|
54
|
+
- Brain dispatches → worker finishes → **result returns to the brain**;
|
|
55
|
+
- Brain checks whether it meets the bar; if not, **re-dispatch / switch model** until done or truly unsolvable;
|
|
56
|
+
- Key conclusions are **cross-validated by different models** — one model may fool you, several rarely fool you together.
|
|
57
|
+
|
|
58
|
+
### 💰 Problem 3: Uncontrollable cost
|
|
59
|
+
|
|
60
|
+
**Problem**: Using an expensive model for everything burns money; using cheap ones for everything can't handle hard tasks.
|
|
61
|
+
|
|
62
|
+
**router-brain's approach**: **Right-size the spend, cheap-first.**
|
|
63
|
+
- The **brain uses a cheap/free model** (it only judges, doesn't need the strongest);
|
|
64
|
+
- **Simple tasks (QA/classify/extract) go direct** — instant reply, no agent boot;
|
|
65
|
+
- **Complex tasks dispatch a worker**, trying cheap → expensive in order;
|
|
66
|
+
- Hooks into **free quotas** automatically.
|
|
67
|
+
|
|
68
|
+
### 🔗 And deeper problems it solves
|
|
69
|
+
|
|
70
|
+
- **No vendor lock-in**: models are a pluggable pool — switch providers by editing config, not code.
|
|
71
|
+
- **One API down doesn't stop the task**: multi-channel failover + circuit breaker + retry.
|
|
72
|
+
- **Long tasks survive mid-way failures**: subtasks are independent; retry only the failed step.
|
|
73
|
+
- **Quotas stay managed**: cheap-first + healthcheck + auto-degrade low-quota channels.
|
|
74
|
+
- **Context stays small**: decision/execution separation keeps each worker's context short.
|
|
75
|
+
- **Model lifecycle handled**: deprecated models auto-fuse; `sync-free-models` pulls the latest.
|
|
76
|
+
- **Multi-model as a team**: pick the best for each job, cross-validate key results.
|
|
77
|
+
|
|
78
|
+
### ✍️ Can't write prompts? The brain writes them
|
|
79
|
+
|
|
80
|
+
You only state the **goal** ("refactor this project", "turn this data into a report"). The brain:
|
|
81
|
+
- decomposes into subtasks and **writes each subtask's prompt** (with guardrails, output path, format);
|
|
82
|
+
- **picks the model** — you don't need to know which model is good at what;
|
|
83
|
+
- **sets reasoning effort** — light for simple steps, heavy for deep reasoning.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Architecture in one line
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
You (goal)
|
|
91
|
+
└─> 🧠 Brain (cheap model, decides only) ──decompose──> 👷 Worker 1 (model A, one task)
|
|
92
|
+
├──> 👷 Worker 2 (model B, another task)
|
|
93
|
+
└──> 🔁 feedback back; re-dispatch / switch if not OK
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **Decision plane** (this process): classify → pick model → dispatch → read feedback → decide next.
|
|
97
|
+
- **Execution plane** (DeepSeek Harness headless agent): a real agent with tools (bash/files/workspace) actually does the work.
|
|
98
|
+
|
|
99
|
+
> Every worker is a **real agent** — not "ask a question, get text back", but "write files, run scripts, read results".
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
|
|
105
|
+
### 1. Install
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd router-brain
|
|
109
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
110
|
+
pip install -e .
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Only dependency is PyYAML. Requires **DeepSeek Harness** installed (`dsh` on PATH).
|
|
114
|
+
|
|
115
|
+
### 2. Configure your model pool
|
|
116
|
+
|
|
117
|
+
router-brain ships **no models/channels** — it's a scheduling framework; you add yours.
|
|
118
|
+
|
|
119
|
+
`config/pool.yaml` is a template. Fill in:
|
|
120
|
+
- **providers**: API channels (base_url + credential key name)
|
|
121
|
+
- **models**: the models you can use (id + channel + cost + kind)
|
|
122
|
+
|
|
123
|
+
Credentials go in `~/.dsh/.credentials.yaml` or same-named env vars; the framework reads them and **never prints keys**. Changes take effect immediately — no restart.
|
|
124
|
+
|
|
125
|
+
### 3. Run
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
router-brain list-models # see your pool
|
|
129
|
+
router-brain route "write a python script" # routing decision only (no execution)
|
|
130
|
+
router-brain run "turn /tmp data into a report" # route → dispatch → fallback → summary
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 4. (Optional) Enable the "Router Brain" commander in DeepSeek Harness
|
|
134
|
+
|
|
135
|
+
The repo ships `agent-presets/router-brain/` (commander persona + dispatch skill). Enable it and a new **「路由大脑 / Router Brain」** preset appears in the DSH session picker — it becomes a persistent commander: give it a goal, it decomposes, dispatches, reads feedback, iterates to completion, and gives a final summary.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
mkdir -p ~/.dsh/.agent-presets
|
|
139
|
+
cp -r agent-presets/router-brain ~/.dsh/.agent-presets/
|
|
140
|
+
# restart dsh web, pick 「路由大脑」 in the new-session picker
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
> The preset ships only the commander tools (bash/fs/jobs/goal/todo/skill) and no models — brain and workers both use models from *your* `config/pool.yaml`.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Highlights
|
|
148
|
+
|
|
149
|
+
| Capability | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| Decision/execution separation | Brain judges, worker executes → fights attention drift |
|
|
152
|
+
| Dispatch-feedback-re-dispatch | Results return; re-dispatch/switch on failure → fights hallucination |
|
|
153
|
+
| Cross-model validation | Key results validated by different models, never self-proven |
|
|
154
|
+
| No hardcoded division | Brain decides allocation at runtime |
|
|
155
|
+
| Multi-channel failover | One model on many channels; auto-switch on limit/shutdown |
|
|
156
|
+
| Retry + degrade + fuse | 429/timeout/5xx retry; fallback chain; circuit breaker |
|
|
157
|
+
| Permanent-error fuse | Deprecated/not-found models fuse instantly |
|
|
158
|
+
| Cheap-first | Defaults to free/low-cost available models |
|
|
159
|
+
| Direct fast-answer | QA/classify/extract reply instantly, no agent boot |
|
|
160
|
+
| Worker channel switch | `worker:false` marks brain-only channels |
|
|
161
|
+
| Millisecond classifier | Deterministic rules, zero cost, zero latency |
|
|
162
|
+
| Program-readable decision | `route` emits JSON for the brain or other programs |
|
|
163
|
+
| Full-task trace | `task_id` through everything; JSON logs, no secrets |
|
|
164
|
+
| healthcheck | Probe the whole pool to see what actually works |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Configuration
|
|
169
|
+
|
|
170
|
+
### config/routing.yaml
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
execution:
|
|
174
|
+
max_retries: 2 # transient-failure retries
|
|
175
|
+
max_fallbacks: 4 # model-fallback cap
|
|
176
|
+
timeout_seconds: 600 # per-task timeout
|
|
177
|
+
auto_failover: false # false = stop and let the brain choose
|
|
178
|
+
force_channel: "" # "" = all channels; a name = only that channel
|
|
179
|
+
direct_max_tokens: 2048 # direct-mode token budget
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### config/pool.yaml template
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
providers:
|
|
186
|
+
my_channel:
|
|
187
|
+
base_url: https://api.example.com/v1
|
|
188
|
+
credential_key: MY_API_KEY # put the key in ~/.dsh/.credentials.yaml
|
|
189
|
+
|
|
190
|
+
models:
|
|
191
|
+
my-model:
|
|
192
|
+
kind: general
|
|
193
|
+
cost: low
|
|
194
|
+
context: 131072
|
|
195
|
+
providers:
|
|
196
|
+
- {channel: my_channel, dsh_provider: my_channel}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Directory
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
router-brain/
|
|
205
|
+
├── config/
|
|
206
|
+
│ ├── pool.yaml your model pool (template)
|
|
207
|
+
│ └── routing.yaml routing rules + execution params
|
|
208
|
+
├── agent-presets/
|
|
209
|
+
│ └── router-brain/ DSH preset (copy to ~/.dsh/.agent-presets/ to use)
|
|
210
|
+
├── src/router_brain/
|
|
211
|
+
│ ├── config.py config + pool/channel/degrade management
|
|
212
|
+
│ ├── router.py model selection
|
|
213
|
+
│ ├── degrade.py retry + degrade + circuit breaker
|
|
214
|
+
│ ├── executor.py agent/direct execution
|
|
215
|
+
│ ├── llm_api.py direct-mode client (error classification)
|
|
216
|
+
│ └── cli.py CLI entry
|
|
217
|
+
├── tests/ unit tests (fully offline)
|
|
218
|
+
├── CONTRIBUTING.md contribution guide
|
|
219
|
+
├── CHANGELOG.md changelog
|
|
220
|
+
├── LICENSE MIT
|
|
221
|
+
└── README.md this file
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Security
|
|
225
|
+
|
|
226
|
+
- Never reads-and-prints any key; direct mode resolves once, agent mode never touches keys.
|
|
227
|
+
- Worker subprocess cwd = `--cwd`; file permissions follow DSH sandbox policy.
|
|
228
|
+
- healthcheck consumes real API quota (~a dozen tiny calls per full run).
|
|
229
|
+
- Keys never appear in logs or output.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# router-brain · LLM Routing Brain
|
|
2
|
+
|
|
3
|
+
> **English**: this file · **中文**: [README_CN.md](README_CN.md)
|
|
4
|
+
|
|
5
|
+
> **Separate decision from execution**: a small "brain" model makes decisions (decompose → pick model → dispatch → read feedback); focused workers do one short-context subtask each. Use the right model for the right job, verify across models, fight hallucinations, and save money.
|
|
6
|
+
|
|
7
|
+
> **AI orchestration framework · multi-model router · prompt automation · DeepSeek Harness** — You don't need to know how to write prompts: tell it *what* you want, and the brain decomposes the task, writes the subtask prompts, picks the model, sets the reasoning effort, dispatches workers, reads feedback, and iterates until done.
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://github.com/ZzkAItech/router-brain"><img alt="GitHub stars" src="https://img.shields.io/github/stars/ZzkAItech/router-brain?style=flat-square&label=Stars"></a>
|
|
11
|
+
<a href="https://github.com/ZzkAItech/router-brain"><img alt="GitHub forks" src="https://img.shields.io/github/forks/ZzkAItech/router-brain?style=flat-square&label=Forks"></a>
|
|
12
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square"></a>
|
|
13
|
+
<a href="https://www.python.org/"><img alt="Python" src="https://img.shields.io/badge/python-3.10+-blue?style=flat-square"></a>
|
|
14
|
+
<a href="https://github.com/ZzkAItech/router-brain/actions"><img alt="tests" src="https://github.com/ZzkAItech/router-brain/actions/workflows/test.yml/badge.svg"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Problems it solves
|
|
20
|
+
|
|
21
|
+
### 🎯 Problem 1: LLMs lose focus in long contexts
|
|
22
|
+
|
|
23
|
+
**Problem**: Shove a whole long task into one model and it must hold the goal, background, progress, and current step all at once. The longer the context, the more it drifts ("lost in the middle").
|
|
24
|
+
|
|
25
|
+
**router-brain's approach**: **Separate decision from execution.**
|
|
26
|
+
- The **brain model only decides** — it sees "goal + current state + who to dispatch". It never touches execution details, so its attention stays on judgment.
|
|
27
|
+
- Each **worker handles one short-context subtask** — it only focuses on the one thing in front of it.
|
|
28
|
+
|
|
29
|
+
> The brain doesn't do the work, so the brain doesn't tire; the worker does one thing, so it doesn't get lost.
|
|
30
|
+
|
|
31
|
+
### 🌀 Problem 2: Hallucination
|
|
32
|
+
|
|
33
|
+
**Problem**: A single model generates once, with no one checking. In multi-step tasks one hallucinated step corrupts everything downstream.
|
|
34
|
+
|
|
35
|
+
**router-brain's approach**: **Dispatch → feedback → re-dispatch loop.**
|
|
36
|
+
- Brain dispatches → worker finishes → **result returns to the brain**;
|
|
37
|
+
- Brain checks whether it meets the bar; if not, **re-dispatch / switch model** until done or truly unsolvable;
|
|
38
|
+
- Key conclusions are **cross-validated by different models** — one model may fool you, several rarely fool you together.
|
|
39
|
+
|
|
40
|
+
### 💰 Problem 3: Uncontrollable cost
|
|
41
|
+
|
|
42
|
+
**Problem**: Using an expensive model for everything burns money; using cheap ones for everything can't handle hard tasks.
|
|
43
|
+
|
|
44
|
+
**router-brain's approach**: **Right-size the spend, cheap-first.**
|
|
45
|
+
- The **brain uses a cheap/free model** (it only judges, doesn't need the strongest);
|
|
46
|
+
- **Simple tasks (QA/classify/extract) go direct** — instant reply, no agent boot;
|
|
47
|
+
- **Complex tasks dispatch a worker**, trying cheap → expensive in order;
|
|
48
|
+
- Hooks into **free quotas** automatically.
|
|
49
|
+
|
|
50
|
+
### 🔗 And deeper problems it solves
|
|
51
|
+
|
|
52
|
+
- **No vendor lock-in**: models are a pluggable pool — switch providers by editing config, not code.
|
|
53
|
+
- **One API down doesn't stop the task**: multi-channel failover + circuit breaker + retry.
|
|
54
|
+
- **Long tasks survive mid-way failures**: subtasks are independent; retry only the failed step.
|
|
55
|
+
- **Quotas stay managed**: cheap-first + healthcheck + auto-degrade low-quota channels.
|
|
56
|
+
- **Context stays small**: decision/execution separation keeps each worker's context short.
|
|
57
|
+
- **Model lifecycle handled**: deprecated models auto-fuse; `sync-free-models` pulls the latest.
|
|
58
|
+
- **Multi-model as a team**: pick the best for each job, cross-validate key results.
|
|
59
|
+
|
|
60
|
+
### ✍️ Can't write prompts? The brain writes them
|
|
61
|
+
|
|
62
|
+
You only state the **goal** ("refactor this project", "turn this data into a report"). The brain:
|
|
63
|
+
- decomposes into subtasks and **writes each subtask's prompt** (with guardrails, output path, format);
|
|
64
|
+
- **picks the model** — you don't need to know which model is good at what;
|
|
65
|
+
- **sets reasoning effort** — light for simple steps, heavy for deep reasoning.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Architecture in one line
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
You (goal)
|
|
73
|
+
└─> 🧠 Brain (cheap model, decides only) ──decompose──> 👷 Worker 1 (model A, one task)
|
|
74
|
+
├──> 👷 Worker 2 (model B, another task)
|
|
75
|
+
└──> 🔁 feedback back; re-dispatch / switch if not OK
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **Decision plane** (this process): classify → pick model → dispatch → read feedback → decide next.
|
|
79
|
+
- **Execution plane** (DeepSeek Harness headless agent): a real agent with tools (bash/files/workspace) actually does the work.
|
|
80
|
+
|
|
81
|
+
> Every worker is a **real agent** — not "ask a question, get text back", but "write files, run scripts, read results".
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
### 1. Install
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cd router-brain
|
|
91
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
92
|
+
pip install -e .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Only dependency is PyYAML. Requires **DeepSeek Harness** installed (`dsh` on PATH).
|
|
96
|
+
|
|
97
|
+
### 2. Configure your model pool
|
|
98
|
+
|
|
99
|
+
router-brain ships **no models/channels** — it's a scheduling framework; you add yours.
|
|
100
|
+
|
|
101
|
+
`config/pool.yaml` is a template. Fill in:
|
|
102
|
+
- **providers**: API channels (base_url + credential key name)
|
|
103
|
+
- **models**: the models you can use (id + channel + cost + kind)
|
|
104
|
+
|
|
105
|
+
Credentials go in `~/.dsh/.credentials.yaml` or same-named env vars; the framework reads them and **never prints keys**. Changes take effect immediately — no restart.
|
|
106
|
+
|
|
107
|
+
### 3. Run
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
router-brain list-models # see your pool
|
|
111
|
+
router-brain route "write a python script" # routing decision only (no execution)
|
|
112
|
+
router-brain run "turn /tmp data into a report" # route → dispatch → fallback → summary
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 4. (Optional) Enable the "Router Brain" commander in DeepSeek Harness
|
|
116
|
+
|
|
117
|
+
The repo ships `agent-presets/router-brain/` (commander persona + dispatch skill). Enable it and a new **「路由大脑 / Router Brain」** preset appears in the DSH session picker — it becomes a persistent commander: give it a goal, it decomposes, dispatches, reads feedback, iterates to completion, and gives a final summary.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mkdir -p ~/.dsh/.agent-presets
|
|
121
|
+
cp -r agent-presets/router-brain ~/.dsh/.agent-presets/
|
|
122
|
+
# restart dsh web, pick 「路由大脑」 in the new-session picker
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> The preset ships only the commander tools (bash/fs/jobs/goal/todo/skill) and no models — brain and workers both use models from *your* `config/pool.yaml`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Highlights
|
|
130
|
+
|
|
131
|
+
| Capability | Description |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Decision/execution separation | Brain judges, worker executes → fights attention drift |
|
|
134
|
+
| Dispatch-feedback-re-dispatch | Results return; re-dispatch/switch on failure → fights hallucination |
|
|
135
|
+
| Cross-model validation | Key results validated by different models, never self-proven |
|
|
136
|
+
| No hardcoded division | Brain decides allocation at runtime |
|
|
137
|
+
| Multi-channel failover | One model on many channels; auto-switch on limit/shutdown |
|
|
138
|
+
| Retry + degrade + fuse | 429/timeout/5xx retry; fallback chain; circuit breaker |
|
|
139
|
+
| Permanent-error fuse | Deprecated/not-found models fuse instantly |
|
|
140
|
+
| Cheap-first | Defaults to free/low-cost available models |
|
|
141
|
+
| Direct fast-answer | QA/classify/extract reply instantly, no agent boot |
|
|
142
|
+
| Worker channel switch | `worker:false` marks brain-only channels |
|
|
143
|
+
| Millisecond classifier | Deterministic rules, zero cost, zero latency |
|
|
144
|
+
| Program-readable decision | `route` emits JSON for the brain or other programs |
|
|
145
|
+
| Full-task trace | `task_id` through everything; JSON logs, no secrets |
|
|
146
|
+
| healthcheck | Probe the whole pool to see what actually works |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
### config/routing.yaml
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
execution:
|
|
156
|
+
max_retries: 2 # transient-failure retries
|
|
157
|
+
max_fallbacks: 4 # model-fallback cap
|
|
158
|
+
timeout_seconds: 600 # per-task timeout
|
|
159
|
+
auto_failover: false # false = stop and let the brain choose
|
|
160
|
+
force_channel: "" # "" = all channels; a name = only that channel
|
|
161
|
+
direct_max_tokens: 2048 # direct-mode token budget
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### config/pool.yaml template
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
providers:
|
|
168
|
+
my_channel:
|
|
169
|
+
base_url: https://api.example.com/v1
|
|
170
|
+
credential_key: MY_API_KEY # put the key in ~/.dsh/.credentials.yaml
|
|
171
|
+
|
|
172
|
+
models:
|
|
173
|
+
my-model:
|
|
174
|
+
kind: general
|
|
175
|
+
cost: low
|
|
176
|
+
context: 131072
|
|
177
|
+
providers:
|
|
178
|
+
- {channel: my_channel, dsh_provider: my_channel}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Directory
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
router-brain/
|
|
187
|
+
├── config/
|
|
188
|
+
│ ├── pool.yaml your model pool (template)
|
|
189
|
+
│ └── routing.yaml routing rules + execution params
|
|
190
|
+
├── agent-presets/
|
|
191
|
+
│ └── router-brain/ DSH preset (copy to ~/.dsh/.agent-presets/ to use)
|
|
192
|
+
├── src/router_brain/
|
|
193
|
+
│ ├── config.py config + pool/channel/degrade management
|
|
194
|
+
│ ├── router.py model selection
|
|
195
|
+
│ ├── degrade.py retry + degrade + circuit breaker
|
|
196
|
+
│ ├── executor.py agent/direct execution
|
|
197
|
+
│ ├── llm_api.py direct-mode client (error classification)
|
|
198
|
+
│ └── cli.py CLI entry
|
|
199
|
+
├── tests/ unit tests (fully offline)
|
|
200
|
+
├── CONTRIBUTING.md contribution guide
|
|
201
|
+
├── CHANGELOG.md changelog
|
|
202
|
+
├── LICENSE MIT
|
|
203
|
+
└── README.md this file
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Security
|
|
207
|
+
|
|
208
|
+
- Never reads-and-prints any key; direct mode resolves once, agent mode never touches keys.
|
|
209
|
+
- Worker subprocess cwd = `--cwd`; file permissions follow DSH sandbox policy.
|
|
210
|
+
- healthcheck consumes real API quota (~a dozen tiny calls per full run).
|
|
211
|
+
- Keys never appear in logs or output.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "router-brain"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "模型路由大脑:分析任务 → 从模型池选模型 → 派给 DeepSeek Harness headless agent 执行 → 失败降级重试 → 汇总结果"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "router-brain contributors" }]
|
|
13
|
+
keywords = ["llm", "routing", "agent", "deepseek-harness", "model-router", "multi-model"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
18
|
+
]
|
|
19
|
+
dependencies = ["PyYAML>=6.0"]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest>=7.0"]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
router-brain = "router_brain.cli:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = { "" = "src" }
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
addopts = "-q"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""router-brain: 模型路由大脑。
|
|
2
|
+
|
|
3
|
+
决策面(本进程)负责分类 + 选模型 + 编排;
|
|
4
|
+
执行面(DeepSeek Harness headless agent)负责真正动手干活。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "1.1.0"
|
|
8
|
+
|
|
9
|
+
# 导出核心类
|
|
10
|
+
from .config import Config
|
|
11
|
+
from .degrade import Runner
|
|
12
|
+
from .classifier import Classifier
|
|
13
|
+
from .router import Router
|
|
14
|
+
|
|
15
|
+
__all__ = ["Config", "Runner", "Classifier", "Router", "__version__"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""规则式任务分类器:任务文本 → task_type。
|
|
2
|
+
|
|
3
|
+
确定性、零成本、毫秒级。关键词表来自 routing.yaml 的 classifier 段。
|
|
4
|
+
命中多个类型时按关键词在表里的出现顺序取第一个命中(表顺序即优先级)。
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .config import Config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Classifier:
|
|
12
|
+
def __init__(self, cfg: Config) -> None:
|
|
13
|
+
self._keywords = cfg.keywords
|
|
14
|
+
self._ordered_types = list(self._keywords.keys())
|
|
15
|
+
# 预先将所有关键词转为小写,避免每次循环都调用 lower()
|
|
16
|
+
self._keywords_lower = {
|
|
17
|
+
task_type: [kw.lower() for kw in keywords]
|
|
18
|
+
for task_type, keywords in self._keywords.items()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def classify(self, task: str, hints: str = "") -> str:
|
|
22
|
+
if not task or not task.strip():
|
|
23
|
+
raise ValueError("任务不能为空")
|
|
24
|
+
text = task + " " + (hints or "")
|
|
25
|
+
text_lower = text.lower()
|
|
26
|
+
for task_type in self._ordered_types:
|
|
27
|
+
for kw in self._keywords_lower.get(task_type, []):
|
|
28
|
+
if kw in text_lower:
|
|
29
|
+
return task_type
|
|
30
|
+
# 带图片提示强制视觉
|
|
31
|
+
if any(t in text_lower for t in ("图片", "截图", ".png", ".jpg", ".jpeg", ".webp", "image")):
|
|
32
|
+
return "vision"
|
|
33
|
+
return "fallback"
|