fusefable 0.1.1__tar.gz → 0.1.2__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.
- fusefable-0.1.2/PKG-INFO +221 -0
- fusefable-0.1.2/README.md +193 -0
- fusefable-0.1.2/fusefable/__init__.py +1 -0
- fusefable-0.1.2/fusefable.egg-info/PKG-INFO +221 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/pyproject.toml +1 -1
- fusefable-0.1.1/PKG-INFO +0 -156
- fusefable-0.1.1/README.md +0 -128
- fusefable-0.1.1/fusefable/__init__.py +0 -1
- fusefable-0.1.1/fusefable.egg-info/PKG-INFO +0 -156
- {fusefable-0.1.1 → fusefable-0.1.2}/LICENSE +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/cli.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/client.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/config.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/core.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/cost.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/fanout.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/fusion.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/judge.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/mcp_server.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/models.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/__init__.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/anthropic.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/base.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/factory.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/google.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/providers/openai_compat.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/routing.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable/wizard.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable.egg-info/SOURCES.txt +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable.egg-info/dependency_links.txt +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable.egg-info/entry_points.txt +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable.egg-info/requires.txt +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/fusefable.egg-info/top_level.txt +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/setup.cfg +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_cli.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_client.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_config.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_core.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_cost.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_fanout.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_fusion.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_judge.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_mcp_server.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_models.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_native_providers.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_openai_compat.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_routing.py +0 -0
- {fusefable-0.1.1 → fusefable-0.1.2}/tests/test_wizard.py +0 -0
fusefable-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fusefable
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Fuse multiple AI models and judge the best answer for coding
|
|
5
|
+
Author: proultrax9
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/proultrax9/fusefable
|
|
8
|
+
Project-URL: Repository, https://github.com/proultrax9/fusefable
|
|
9
|
+
Keywords: llm,ai,openrouter,cli,mcp,fusion,coding
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Fuse Fable
|
|
30
|
+
|
|
31
|
+
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
32
|
+
|
|
33
|
+
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
34
|
+
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
35
|
+
|
|
36
|
+
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
37
|
+
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
```bash
|
|
41
|
+
pip install fusefable # core
|
|
42
|
+
pip install "fusefable[mcp]" # if you want the MCP server
|
|
43
|
+
```
|
|
44
|
+
From source:
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/proultrax9/fusefable.git
|
|
47
|
+
cd fusefable
|
|
48
|
+
pip install -e ".[mcp]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Setup (first run)
|
|
52
|
+
```bash
|
|
53
|
+
fusefable config
|
|
54
|
+
```
|
|
55
|
+
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
56
|
+
prompts for each one.
|
|
57
|
+
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
58
|
+
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
59
|
+
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
60
|
+
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
61
|
+
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
62
|
+
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
63
|
+
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
64
|
+
|
|
65
|
+
Set your API key as an environment variable named as the wizard asks:
|
|
66
|
+
```bash
|
|
67
|
+
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
68
|
+
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Config is stored at `~/.fusefable/config.yaml`.
|
|
72
|
+
|
|
73
|
+
## 1) Use as a CLI
|
|
74
|
+
```bash
|
|
75
|
+
fusefable ask "Write a quicksort function in Python"
|
|
76
|
+
fusefable ask --show-all "..." # show every answer + judge reason
|
|
77
|
+
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
78
|
+
fusefable ask --cheap "..." # use cheap_models from config
|
|
79
|
+
ff ask "..." # short alias
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 2) Use as a subagent / in a pipe
|
|
83
|
+
```bash
|
|
84
|
+
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
85
|
+
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
86
|
+
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
87
|
+
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
88
|
+
```
|
|
89
|
+
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
90
|
+
|
|
91
|
+
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
92
|
+
Run as an MCP server over stdio:
|
|
93
|
+
```bash
|
|
94
|
+
fusefable mcp
|
|
95
|
+
```
|
|
96
|
+
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
97
|
+
|
|
98
|
+
### Cursor
|
|
99
|
+
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"fusefable": {
|
|
104
|
+
"command": "fusefable",
|
|
105
|
+
"args": ["mcp"],
|
|
106
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### VS Code (Copilot / MCP-compatible extension)
|
|
113
|
+
`.vscode/mcp.json` in your project:
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"servers": {
|
|
117
|
+
"fusefable": {
|
|
118
|
+
"command": "fusefable",
|
|
119
|
+
"args": ["mcp"],
|
|
120
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Claude Desktop
|
|
127
|
+
`claude_desktop_config.json`:
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": {
|
|
131
|
+
"fusefable": {
|
|
132
|
+
"command": "fusefable",
|
|
133
|
+
"args": ["mcp"],
|
|
134
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
141
|
+
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
ENTRYPOINTS (one shared core)
|
|
147
|
+
┌─────────────┬──────────────────┬─────────────────────┐
|
|
148
|
+
│ CLI │ pipe / subagent │ MCP server │
|
|
149
|
+
│ fusefable │ stdin · --quiet │ fusefable mcp │
|
|
150
|
+
│ ask "..." │ · --json │ tool: fuse_ask() │
|
|
151
|
+
└──────┬──────┴────────┬─────────┴──────────┬──────────┘
|
|
152
|
+
└───────────────┴────────────────────┘
|
|
153
|
+
│
|
|
154
|
+
▼
|
|
155
|
+
┌───────────────────┐ ~/.fusefable/config.yaml
|
|
156
|
+
│ core.fuse() │◀──── (gateway | single providers,
|
|
157
|
+
└─────────┬─────────┘ models, judge, timeout)
|
|
158
|
+
│
|
|
159
|
+
▼
|
|
160
|
+
┌───────────────────┐
|
|
161
|
+
│ routing │ build (provider, model) routes
|
|
162
|
+
│ + provider │ via factory → pick adapter by kind
|
|
163
|
+
│ factory │
|
|
164
|
+
└─────────┬─────────┘
|
|
165
|
+
▼
|
|
166
|
+
┌────────────── FAN-OUT (asyncio.gather) ──────────────┐
|
|
167
|
+
│ ทุกตัวยิงพร้อมกัน · per-model timeout · ตัวพัง = ตัดทิ้ง │
|
|
168
|
+
│ │
|
|
169
|
+
▼ ▼ ▼ ▼ ▼
|
|
170
|
+
┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────┐
|
|
171
|
+
│ openai_ │ │ anthropic│ │ google │ │ openai_ │ │ ... │
|
|
172
|
+
│ compat │ │ native │ │ native │ │ compat │ │ │
|
|
173
|
+
│(gateway)│ │/v1/msgs │ │generateCont│ │ │ │ │
|
|
174
|
+
└────┬────┘ └────┬─────┘ └─────┬──────┘ └────┬─────┘ └───┬────┘
|
|
175
|
+
└───────────┴──────────────┴───────────────┴───────────┘
|
|
176
|
+
│ Completion[] (สำเร็จเท่านั้น)
|
|
177
|
+
▼
|
|
178
|
+
┌───────────────────┐
|
|
179
|
+
│ judge │ ปกปิดชื่อ → Answer A/B/C...
|
|
180
|
+
│ (anonymized) │ ให้ judge model เลือกตัวดีสุด
|
|
181
|
+
└─────────┬─────────┘ (พัง → fallback ตัวแรก)
|
|
182
|
+
▼
|
|
183
|
+
┌───────────────────┐
|
|
184
|
+
│ FinalAnswer │ text · chosen_model · reason
|
|
185
|
+
│ (+ cost estimate)│ · cost_usd · candidates
|
|
186
|
+
└───────────────────┘
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Request lifecycle**
|
|
190
|
+
1. **Entrypoint** — CLI, a pipe/subagent call, or the MCP tool `fuse_ask()` — all funnel into one core function `core.fuse()`.
|
|
191
|
+
2. **Routing** — config (gateway or single providers) is turned into `(provider, model)` routes; a provider **factory** picks the right adapter per `kind` (`openai_compat` / `anthropic` / `google`).
|
|
192
|
+
3. **Fan-out** — every model is called concurrently via `asyncio.gather` (total time = slowest model). Each call has its own timeout; any model that times out or errors is dropped and never slows the run.
|
|
193
|
+
4. **Judge** — model names are anonymized (Answer A/B/C...) so the judge picks on quality alone, not by brand; if the judge fails it falls back to the first answer.
|
|
194
|
+
5. **Result** — returns the best answer with the chosen model, the judge's reason, an estimated cost, and all candidates.
|
|
195
|
+
|
|
196
|
+
**Components** (`fusefable/`)
|
|
197
|
+
|
|
198
|
+
| File | Responsibility |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `cli.py` | Typer CLI (`ask` / `config` / `mcp`), output modes |
|
|
201
|
+
| `mcp_server.py` | MCP server exposing the `fuse_ask` tool |
|
|
202
|
+
| `core.py` | shared `fuse()` entrypoint + model selection |
|
|
203
|
+
| `config.py` | load/save `config.yaml` |
|
|
204
|
+
| `wizard.py` | interactive setup (gateway vs single, API kind) |
|
|
205
|
+
| `routing.py` | config → `(provider, model)` routes |
|
|
206
|
+
| `providers/factory.py` | pick adapter by `kind` |
|
|
207
|
+
| `providers/openai_compat.py` · `anthropic.py` · `google.py` | provider adapters |
|
|
208
|
+
| `fanout.py` | parallel fan-out (drops failures) |
|
|
209
|
+
| `judge.py` | anonymized judging |
|
|
210
|
+
| `fusion.py` | orchestrator: fan-out → judge → `FinalAnswer` |
|
|
211
|
+
| `cost.py` | token-based cost estimate |
|
|
212
|
+
| `models.py` | dataclasses: `Completion`, `FinalAnswer`, `ProviderConfig` |
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
```bash
|
|
216
|
+
pip install -e ".[dev,mcp]"
|
|
217
|
+
pytest -q
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
MIT
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Fuse Fable
|
|
2
|
+
|
|
3
|
+
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
4
|
+
|
|
5
|
+
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
6
|
+
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
7
|
+
|
|
8
|
+
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
9
|
+
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
```bash
|
|
13
|
+
pip install fusefable # core
|
|
14
|
+
pip install "fusefable[mcp]" # if you want the MCP server
|
|
15
|
+
```
|
|
16
|
+
From source:
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/proultrax9/fusefable.git
|
|
19
|
+
cd fusefable
|
|
20
|
+
pip install -e ".[mcp]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup (first run)
|
|
24
|
+
```bash
|
|
25
|
+
fusefable config
|
|
26
|
+
```
|
|
27
|
+
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
28
|
+
prompts for each one.
|
|
29
|
+
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
30
|
+
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
31
|
+
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
32
|
+
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
33
|
+
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
34
|
+
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
35
|
+
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
36
|
+
|
|
37
|
+
Set your API key as an environment variable named as the wizard asks:
|
|
38
|
+
```bash
|
|
39
|
+
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
40
|
+
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Config is stored at `~/.fusefable/config.yaml`.
|
|
44
|
+
|
|
45
|
+
## 1) Use as a CLI
|
|
46
|
+
```bash
|
|
47
|
+
fusefable ask "Write a quicksort function in Python"
|
|
48
|
+
fusefable ask --show-all "..." # show every answer + judge reason
|
|
49
|
+
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
50
|
+
fusefable ask --cheap "..." # use cheap_models from config
|
|
51
|
+
ff ask "..." # short alias
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 2) Use as a subagent / in a pipe
|
|
55
|
+
```bash
|
|
56
|
+
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
57
|
+
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
58
|
+
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
59
|
+
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
60
|
+
```
|
|
61
|
+
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
62
|
+
|
|
63
|
+
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
64
|
+
Run as an MCP server over stdio:
|
|
65
|
+
```bash
|
|
66
|
+
fusefable mcp
|
|
67
|
+
```
|
|
68
|
+
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
69
|
+
|
|
70
|
+
### Cursor
|
|
71
|
+
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"fusefable": {
|
|
76
|
+
"command": "fusefable",
|
|
77
|
+
"args": ["mcp"],
|
|
78
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### VS Code (Copilot / MCP-compatible extension)
|
|
85
|
+
`.vscode/mcp.json` in your project:
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"servers": {
|
|
89
|
+
"fusefable": {
|
|
90
|
+
"command": "fusefable",
|
|
91
|
+
"args": ["mcp"],
|
|
92
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Claude Desktop
|
|
99
|
+
`claude_desktop_config.json`:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"fusefable": {
|
|
104
|
+
"command": "fusefable",
|
|
105
|
+
"args": ["mcp"],
|
|
106
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
113
|
+
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
114
|
+
|
|
115
|
+
## Architecture
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
ENTRYPOINTS (one shared core)
|
|
119
|
+
┌─────────────┬──────────────────┬─────────────────────┐
|
|
120
|
+
│ CLI │ pipe / subagent │ MCP server │
|
|
121
|
+
│ fusefable │ stdin · --quiet │ fusefable mcp │
|
|
122
|
+
│ ask "..." │ · --json │ tool: fuse_ask() │
|
|
123
|
+
└──────┬──────┴────────┬─────────┴──────────┬──────────┘
|
|
124
|
+
└───────────────┴────────────────────┘
|
|
125
|
+
│
|
|
126
|
+
▼
|
|
127
|
+
┌───────────────────┐ ~/.fusefable/config.yaml
|
|
128
|
+
│ core.fuse() │◀──── (gateway | single providers,
|
|
129
|
+
└─────────┬─────────┘ models, judge, timeout)
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
┌───────────────────┐
|
|
133
|
+
│ routing │ build (provider, model) routes
|
|
134
|
+
│ + provider │ via factory → pick adapter by kind
|
|
135
|
+
│ factory │
|
|
136
|
+
└─────────┬─────────┘
|
|
137
|
+
▼
|
|
138
|
+
┌────────────── FAN-OUT (asyncio.gather) ──────────────┐
|
|
139
|
+
│ ทุกตัวยิงพร้อมกัน · per-model timeout · ตัวพัง = ตัดทิ้ง │
|
|
140
|
+
│ │
|
|
141
|
+
▼ ▼ ▼ ▼ ▼
|
|
142
|
+
┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────┐
|
|
143
|
+
│ openai_ │ │ anthropic│ │ google │ │ openai_ │ │ ... │
|
|
144
|
+
│ compat │ │ native │ │ native │ │ compat │ │ │
|
|
145
|
+
│(gateway)│ │/v1/msgs │ │generateCont│ │ │ │ │
|
|
146
|
+
└────┬────┘ └────┬─────┘ └─────┬──────┘ └────┬─────┘ └───┬────┘
|
|
147
|
+
└───────────┴──────────────┴───────────────┴───────────┘
|
|
148
|
+
│ Completion[] (สำเร็จเท่านั้น)
|
|
149
|
+
▼
|
|
150
|
+
┌───────────────────┐
|
|
151
|
+
│ judge │ ปกปิดชื่อ → Answer A/B/C...
|
|
152
|
+
│ (anonymized) │ ให้ judge model เลือกตัวดีสุด
|
|
153
|
+
└─────────┬─────────┘ (พัง → fallback ตัวแรก)
|
|
154
|
+
▼
|
|
155
|
+
┌───────────────────┐
|
|
156
|
+
│ FinalAnswer │ text · chosen_model · reason
|
|
157
|
+
│ (+ cost estimate)│ · cost_usd · candidates
|
|
158
|
+
└───────────────────┘
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Request lifecycle**
|
|
162
|
+
1. **Entrypoint** — CLI, a pipe/subagent call, or the MCP tool `fuse_ask()` — all funnel into one core function `core.fuse()`.
|
|
163
|
+
2. **Routing** — config (gateway or single providers) is turned into `(provider, model)` routes; a provider **factory** picks the right adapter per `kind` (`openai_compat` / `anthropic` / `google`).
|
|
164
|
+
3. **Fan-out** — every model is called concurrently via `asyncio.gather` (total time = slowest model). Each call has its own timeout; any model that times out or errors is dropped and never slows the run.
|
|
165
|
+
4. **Judge** — model names are anonymized (Answer A/B/C...) so the judge picks on quality alone, not by brand; if the judge fails it falls back to the first answer.
|
|
166
|
+
5. **Result** — returns the best answer with the chosen model, the judge's reason, an estimated cost, and all candidates.
|
|
167
|
+
|
|
168
|
+
**Components** (`fusefable/`)
|
|
169
|
+
|
|
170
|
+
| File | Responsibility |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `cli.py` | Typer CLI (`ask` / `config` / `mcp`), output modes |
|
|
173
|
+
| `mcp_server.py` | MCP server exposing the `fuse_ask` tool |
|
|
174
|
+
| `core.py` | shared `fuse()` entrypoint + model selection |
|
|
175
|
+
| `config.py` | load/save `config.yaml` |
|
|
176
|
+
| `wizard.py` | interactive setup (gateway vs single, API kind) |
|
|
177
|
+
| `routing.py` | config → `(provider, model)` routes |
|
|
178
|
+
| `providers/factory.py` | pick adapter by `kind` |
|
|
179
|
+
| `providers/openai_compat.py` · `anthropic.py` · `google.py` | provider adapters |
|
|
180
|
+
| `fanout.py` | parallel fan-out (drops failures) |
|
|
181
|
+
| `judge.py` | anonymized judging |
|
|
182
|
+
| `fusion.py` | orchestrator: fan-out → judge → `FinalAnswer` |
|
|
183
|
+
| `cost.py` | token-based cost estimate |
|
|
184
|
+
| `models.py` | dataclasses: `Completion`, `FinalAnswer`, `ProviderConfig` |
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
```bash
|
|
188
|
+
pip install -e ".[dev,mcp]"
|
|
189
|
+
pytest -q
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fusefable
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Fuse multiple AI models and judge the best answer for coding
|
|
5
|
+
Author: proultrax9
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/proultrax9/fusefable
|
|
8
|
+
Project-URL: Repository, https://github.com/proultrax9/fusefable
|
|
9
|
+
Keywords: llm,ai,openrouter,cli,mcp,fusion,coding
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Fuse Fable
|
|
30
|
+
|
|
31
|
+
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
32
|
+
|
|
33
|
+
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
34
|
+
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
35
|
+
|
|
36
|
+
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
37
|
+
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
```bash
|
|
41
|
+
pip install fusefable # core
|
|
42
|
+
pip install "fusefable[mcp]" # if you want the MCP server
|
|
43
|
+
```
|
|
44
|
+
From source:
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/proultrax9/fusefable.git
|
|
47
|
+
cd fusefable
|
|
48
|
+
pip install -e ".[mcp]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Setup (first run)
|
|
52
|
+
```bash
|
|
53
|
+
fusefable config
|
|
54
|
+
```
|
|
55
|
+
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
56
|
+
prompts for each one.
|
|
57
|
+
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
58
|
+
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
59
|
+
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
60
|
+
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
61
|
+
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
62
|
+
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
63
|
+
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
64
|
+
|
|
65
|
+
Set your API key as an environment variable named as the wizard asks:
|
|
66
|
+
```bash
|
|
67
|
+
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
68
|
+
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Config is stored at `~/.fusefable/config.yaml`.
|
|
72
|
+
|
|
73
|
+
## 1) Use as a CLI
|
|
74
|
+
```bash
|
|
75
|
+
fusefable ask "Write a quicksort function in Python"
|
|
76
|
+
fusefable ask --show-all "..." # show every answer + judge reason
|
|
77
|
+
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
78
|
+
fusefable ask --cheap "..." # use cheap_models from config
|
|
79
|
+
ff ask "..." # short alias
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 2) Use as a subagent / in a pipe
|
|
83
|
+
```bash
|
|
84
|
+
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
85
|
+
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
86
|
+
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
87
|
+
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
88
|
+
```
|
|
89
|
+
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
90
|
+
|
|
91
|
+
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
92
|
+
Run as an MCP server over stdio:
|
|
93
|
+
```bash
|
|
94
|
+
fusefable mcp
|
|
95
|
+
```
|
|
96
|
+
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
97
|
+
|
|
98
|
+
### Cursor
|
|
99
|
+
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"fusefable": {
|
|
104
|
+
"command": "fusefable",
|
|
105
|
+
"args": ["mcp"],
|
|
106
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### VS Code (Copilot / MCP-compatible extension)
|
|
113
|
+
`.vscode/mcp.json` in your project:
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"servers": {
|
|
117
|
+
"fusefable": {
|
|
118
|
+
"command": "fusefable",
|
|
119
|
+
"args": ["mcp"],
|
|
120
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Claude Desktop
|
|
127
|
+
`claude_desktop_config.json`:
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": {
|
|
131
|
+
"fusefable": {
|
|
132
|
+
"command": "fusefable",
|
|
133
|
+
"args": ["mcp"],
|
|
134
|
+
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
141
|
+
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
ENTRYPOINTS (one shared core)
|
|
147
|
+
┌─────────────┬──────────────────┬─────────────────────┐
|
|
148
|
+
│ CLI │ pipe / subagent │ MCP server │
|
|
149
|
+
│ fusefable │ stdin · --quiet │ fusefable mcp │
|
|
150
|
+
│ ask "..." │ · --json │ tool: fuse_ask() │
|
|
151
|
+
└──────┬──────┴────────┬─────────┴──────────┬──────────┘
|
|
152
|
+
└───────────────┴────────────────────┘
|
|
153
|
+
│
|
|
154
|
+
▼
|
|
155
|
+
┌───────────────────┐ ~/.fusefable/config.yaml
|
|
156
|
+
│ core.fuse() │◀──── (gateway | single providers,
|
|
157
|
+
└─────────┬─────────┘ models, judge, timeout)
|
|
158
|
+
│
|
|
159
|
+
▼
|
|
160
|
+
┌───────────────────┐
|
|
161
|
+
│ routing │ build (provider, model) routes
|
|
162
|
+
│ + provider │ via factory → pick adapter by kind
|
|
163
|
+
│ factory │
|
|
164
|
+
└─────────┬─────────┘
|
|
165
|
+
▼
|
|
166
|
+
┌────────────── FAN-OUT (asyncio.gather) ──────────────┐
|
|
167
|
+
│ ทุกตัวยิงพร้อมกัน · per-model timeout · ตัวพัง = ตัดทิ้ง │
|
|
168
|
+
│ │
|
|
169
|
+
▼ ▼ ▼ ▼ ▼
|
|
170
|
+
┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────┐
|
|
171
|
+
│ openai_ │ │ anthropic│ │ google │ │ openai_ │ │ ... │
|
|
172
|
+
│ compat │ │ native │ │ native │ │ compat │ │ │
|
|
173
|
+
│(gateway)│ │/v1/msgs │ │generateCont│ │ │ │ │
|
|
174
|
+
└────┬────┘ └────┬─────┘ └─────┬──────┘ └────┬─────┘ └───┬────┘
|
|
175
|
+
└───────────┴──────────────┴───────────────┴───────────┘
|
|
176
|
+
│ Completion[] (สำเร็จเท่านั้น)
|
|
177
|
+
▼
|
|
178
|
+
┌───────────────────┐
|
|
179
|
+
│ judge │ ปกปิดชื่อ → Answer A/B/C...
|
|
180
|
+
│ (anonymized) │ ให้ judge model เลือกตัวดีสุด
|
|
181
|
+
└─────────┬─────────┘ (พัง → fallback ตัวแรก)
|
|
182
|
+
▼
|
|
183
|
+
┌───────────────────┐
|
|
184
|
+
│ FinalAnswer │ text · chosen_model · reason
|
|
185
|
+
│ (+ cost estimate)│ · cost_usd · candidates
|
|
186
|
+
└───────────────────┘
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Request lifecycle**
|
|
190
|
+
1. **Entrypoint** — CLI, a pipe/subagent call, or the MCP tool `fuse_ask()` — all funnel into one core function `core.fuse()`.
|
|
191
|
+
2. **Routing** — config (gateway or single providers) is turned into `(provider, model)` routes; a provider **factory** picks the right adapter per `kind` (`openai_compat` / `anthropic` / `google`).
|
|
192
|
+
3. **Fan-out** — every model is called concurrently via `asyncio.gather` (total time = slowest model). Each call has its own timeout; any model that times out or errors is dropped and never slows the run.
|
|
193
|
+
4. **Judge** — model names are anonymized (Answer A/B/C...) so the judge picks on quality alone, not by brand; if the judge fails it falls back to the first answer.
|
|
194
|
+
5. **Result** — returns the best answer with the chosen model, the judge's reason, an estimated cost, and all candidates.
|
|
195
|
+
|
|
196
|
+
**Components** (`fusefable/`)
|
|
197
|
+
|
|
198
|
+
| File | Responsibility |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `cli.py` | Typer CLI (`ask` / `config` / `mcp`), output modes |
|
|
201
|
+
| `mcp_server.py` | MCP server exposing the `fuse_ask` tool |
|
|
202
|
+
| `core.py` | shared `fuse()` entrypoint + model selection |
|
|
203
|
+
| `config.py` | load/save `config.yaml` |
|
|
204
|
+
| `wizard.py` | interactive setup (gateway vs single, API kind) |
|
|
205
|
+
| `routing.py` | config → `(provider, model)` routes |
|
|
206
|
+
| `providers/factory.py` | pick adapter by `kind` |
|
|
207
|
+
| `providers/openai_compat.py` · `anthropic.py` · `google.py` | provider adapters |
|
|
208
|
+
| `fanout.py` | parallel fan-out (drops failures) |
|
|
209
|
+
| `judge.py` | anonymized judging |
|
|
210
|
+
| `fusion.py` | orchestrator: fan-out → judge → `FinalAnswer` |
|
|
211
|
+
| `cost.py` | token-based cost estimate |
|
|
212
|
+
| `models.py` | dataclasses: `Completion`, `FinalAnswer`, `ProviderConfig` |
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
```bash
|
|
216
|
+
pip install -e ".[dev,mcp]"
|
|
217
|
+
pytest -q
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
MIT
|
fusefable-0.1.1/PKG-INFO
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: fusefable
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Fuse multiple AI models and judge the best answer for coding
|
|
5
|
-
Author: proultrax9
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/proultrax9/fusefable
|
|
8
|
-
Project-URL: Repository, https://github.com/proultrax9/fusefable
|
|
9
|
-
Keywords: llm,ai,openrouter,cli,mcp,fusion,coding
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
|
-
Classifier: Topic :: Software Development :: Code Generators
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: httpx>=0.27
|
|
19
|
-
Requires-Dist: typer>=0.12
|
|
20
|
-
Requires-Dist: pyyaml>=6.0
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
23
|
-
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
-
Requires-Dist: respx>=0.21; extra == "dev"
|
|
25
|
-
Provides-Extra: mcp
|
|
26
|
-
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
27
|
-
Dynamic: license-file
|
|
28
|
-
|
|
29
|
-
# Fuse Fable
|
|
30
|
-
|
|
31
|
-
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
32
|
-
|
|
33
|
-
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
34
|
-
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
35
|
-
|
|
36
|
-
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
37
|
-
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
38
|
-
|
|
39
|
-
## Install
|
|
40
|
-
```bash
|
|
41
|
-
pip install fusefable # core
|
|
42
|
-
pip install "fusefable[mcp]" # if you want the MCP server
|
|
43
|
-
```
|
|
44
|
-
From source:
|
|
45
|
-
```bash
|
|
46
|
-
git clone https://github.com/proultrax9/fusefable.git
|
|
47
|
-
cd fusefable
|
|
48
|
-
pip install -e ".[mcp]"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Setup (first run)
|
|
52
|
-
```bash
|
|
53
|
-
fusefable config
|
|
54
|
-
```
|
|
55
|
-
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
56
|
-
prompts for each one.
|
|
57
|
-
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
58
|
-
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
59
|
-
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
60
|
-
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
61
|
-
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
62
|
-
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
63
|
-
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
64
|
-
|
|
65
|
-
Set your API key as an environment variable named as the wizard asks:
|
|
66
|
-
```bash
|
|
67
|
-
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
68
|
-
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Config is stored at `~/.fusefable/config.yaml`.
|
|
72
|
-
|
|
73
|
-
## 1) Use as a CLI
|
|
74
|
-
```bash
|
|
75
|
-
fusefable ask "Write a quicksort function in Python"
|
|
76
|
-
fusefable ask --show-all "..." # show every answer + judge reason
|
|
77
|
-
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
78
|
-
fusefable ask --cheap "..." # use cheap_models from config
|
|
79
|
-
ff ask "..." # short alias
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## 2) Use as a subagent / in a pipe
|
|
83
|
-
```bash
|
|
84
|
-
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
85
|
-
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
86
|
-
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
87
|
-
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
88
|
-
```
|
|
89
|
-
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
90
|
-
|
|
91
|
-
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
92
|
-
Run as an MCP server over stdio:
|
|
93
|
-
```bash
|
|
94
|
-
fusefable mcp
|
|
95
|
-
```
|
|
96
|
-
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
97
|
-
|
|
98
|
-
### Cursor
|
|
99
|
-
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"mcpServers": {
|
|
103
|
-
"fusefable": {
|
|
104
|
-
"command": "fusefable",
|
|
105
|
-
"args": ["mcp"],
|
|
106
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### VS Code (Copilot / MCP-compatible extension)
|
|
113
|
-
`.vscode/mcp.json` in your project:
|
|
114
|
-
```json
|
|
115
|
-
{
|
|
116
|
-
"servers": {
|
|
117
|
-
"fusefable": {
|
|
118
|
-
"command": "fusefable",
|
|
119
|
-
"args": ["mcp"],
|
|
120
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Claude Desktop
|
|
127
|
-
`claude_desktop_config.json`:
|
|
128
|
-
```json
|
|
129
|
-
{
|
|
130
|
-
"mcpServers": {
|
|
131
|
-
"fusefable": {
|
|
132
|
-
"command": "fusefable",
|
|
133
|
-
"args": ["mcp"],
|
|
134
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
141
|
-
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
142
|
-
|
|
143
|
-
## How it works
|
|
144
|
-
1. **Fan-out** — every model is called concurrently via `asyncio` (total time = slowest model).
|
|
145
|
-
2. Any model that times out or fails is dropped — it never slows the whole run.
|
|
146
|
-
3. **Judge** — model names are anonymized (Answer A/B/C...) and a judge model picks the best.
|
|
147
|
-
4. Returns the best answer plus an estimated cost.
|
|
148
|
-
|
|
149
|
-
## Development
|
|
150
|
-
```bash
|
|
151
|
-
pip install -e ".[dev,mcp]"
|
|
152
|
-
pytest -q
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## License
|
|
156
|
-
MIT
|
fusefable-0.1.1/README.md
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# Fuse Fable
|
|
2
|
-
|
|
3
|
-
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
4
|
-
|
|
5
|
-
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
6
|
-
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
7
|
-
|
|
8
|
-
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
9
|
-
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
10
|
-
|
|
11
|
-
## Install
|
|
12
|
-
```bash
|
|
13
|
-
pip install fusefable # core
|
|
14
|
-
pip install "fusefable[mcp]" # if you want the MCP server
|
|
15
|
-
```
|
|
16
|
-
From source:
|
|
17
|
-
```bash
|
|
18
|
-
git clone https://github.com/proultrax9/fusefable.git
|
|
19
|
-
cd fusefable
|
|
20
|
-
pip install -e ".[mcp]"
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Setup (first run)
|
|
24
|
-
```bash
|
|
25
|
-
fusefable config
|
|
26
|
-
```
|
|
27
|
-
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
28
|
-
prompts for each one.
|
|
29
|
-
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
30
|
-
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
31
|
-
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
32
|
-
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
33
|
-
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
34
|
-
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
35
|
-
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
36
|
-
|
|
37
|
-
Set your API key as an environment variable named as the wizard asks:
|
|
38
|
-
```bash
|
|
39
|
-
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
40
|
-
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Config is stored at `~/.fusefable/config.yaml`.
|
|
44
|
-
|
|
45
|
-
## 1) Use as a CLI
|
|
46
|
-
```bash
|
|
47
|
-
fusefable ask "Write a quicksort function in Python"
|
|
48
|
-
fusefable ask --show-all "..." # show every answer + judge reason
|
|
49
|
-
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
50
|
-
fusefable ask --cheap "..." # use cheap_models from config
|
|
51
|
-
ff ask "..." # short alias
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## 2) Use as a subagent / in a pipe
|
|
55
|
-
```bash
|
|
56
|
-
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
57
|
-
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
58
|
-
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
59
|
-
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
60
|
-
```
|
|
61
|
-
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
62
|
-
|
|
63
|
-
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
64
|
-
Run as an MCP server over stdio:
|
|
65
|
-
```bash
|
|
66
|
-
fusefable mcp
|
|
67
|
-
```
|
|
68
|
-
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
69
|
-
|
|
70
|
-
### Cursor
|
|
71
|
-
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
72
|
-
```json
|
|
73
|
-
{
|
|
74
|
-
"mcpServers": {
|
|
75
|
-
"fusefable": {
|
|
76
|
-
"command": "fusefable",
|
|
77
|
-
"args": ["mcp"],
|
|
78
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### VS Code (Copilot / MCP-compatible extension)
|
|
85
|
-
`.vscode/mcp.json` in your project:
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"servers": {
|
|
89
|
-
"fusefable": {
|
|
90
|
-
"command": "fusefable",
|
|
91
|
-
"args": ["mcp"],
|
|
92
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Claude Desktop
|
|
99
|
-
`claude_desktop_config.json`:
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"mcpServers": {
|
|
103
|
-
"fusefable": {
|
|
104
|
-
"command": "fusefable",
|
|
105
|
-
"args": ["mcp"],
|
|
106
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
113
|
-
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
114
|
-
|
|
115
|
-
## How it works
|
|
116
|
-
1. **Fan-out** — every model is called concurrently via `asyncio` (total time = slowest model).
|
|
117
|
-
2. Any model that times out or fails is dropped — it never slows the whole run.
|
|
118
|
-
3. **Judge** — model names are anonymized (Answer A/B/C...) and a judge model picks the best.
|
|
119
|
-
4. Returns the best answer plus an estimated cost.
|
|
120
|
-
|
|
121
|
-
## Development
|
|
122
|
-
```bash
|
|
123
|
-
pip install -e ".[dev,mcp]"
|
|
124
|
-
pytest -q
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## License
|
|
128
|
-
MIT
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: fusefable
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Fuse multiple AI models and judge the best answer for coding
|
|
5
|
-
Author: proultrax9
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/proultrax9/fusefable
|
|
8
|
-
Project-URL: Repository, https://github.com/proultrax9/fusefable
|
|
9
|
-
Keywords: llm,ai,openrouter,cli,mcp,fusion,coding
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
|
-
Classifier: Topic :: Software Development :: Code Generators
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: httpx>=0.27
|
|
19
|
-
Requires-Dist: typer>=0.12
|
|
20
|
-
Requires-Dist: pyyaml>=6.0
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
23
|
-
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
-
Requires-Dist: respx>=0.21; extra == "dev"
|
|
25
|
-
Provides-Extra: mcp
|
|
26
|
-
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
27
|
-
Dynamic: license-file
|
|
28
|
-
|
|
29
|
-
# Fuse Fable
|
|
30
|
-
|
|
31
|
-
🌐 **Languages:** English · [ไทย (Thai)](https://github.com/proultrax9/fusefable/blob/main/README.th.md)
|
|
32
|
-
|
|
33
|
-
Fan out a coding prompt to many AI models **in parallel**, then let a judge pick the
|
|
34
|
-
single best answer — total latency ≈ **x2** (slowest model + one judge pass), not x7–x15.
|
|
35
|
-
|
|
36
|
-
Works three ways: as a **CLI**, as an **MCP server** (connect Cursor / VS Code / Claude),
|
|
37
|
-
and as a **subagent / pipe** (callable by other tools and scripts).
|
|
38
|
-
|
|
39
|
-
## Install
|
|
40
|
-
```bash
|
|
41
|
-
pip install fusefable # core
|
|
42
|
-
pip install "fusefable[mcp]" # if you want the MCP server
|
|
43
|
-
```
|
|
44
|
-
From source:
|
|
45
|
-
```bash
|
|
46
|
-
git clone https://github.com/proultrax9/fusefable.git
|
|
47
|
-
cd fusefable
|
|
48
|
-
pip install -e ".[mcp]"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Setup (first run)
|
|
52
|
-
```bash
|
|
53
|
-
fusefable config
|
|
54
|
-
```
|
|
55
|
-
- Choose **AI Gateway** → one key for everything, then it asks "how many models?" and
|
|
56
|
-
prompts for each one.
|
|
57
|
-
- Known gateways (base URL auto-filled): `openrouter`, `groq`, `together`,
|
|
58
|
-
`fireworks`, `deepinfra`, `novita`, `hyperbolic`, `aimlapi`, `portkey`,
|
|
59
|
-
`deepseek`, `openai` — any other works too, just type its base URL.
|
|
60
|
-
- Or **Single providers** → it asks how many, then the **API kind** of each:
|
|
61
|
-
- `openai_compat` — any OpenAI-compatible endpoint (you provide the base URL)
|
|
62
|
-
- `anthropic` — Anthropic native (`/v1/messages`, base URL auto-filled)
|
|
63
|
-
- `google` — Google Gemini native (`generateContent`, base URL auto-filled)
|
|
64
|
-
|
|
65
|
-
Set your API key as an environment variable named as the wizard asks:
|
|
66
|
-
```bash
|
|
67
|
-
export OPENROUTER_API_KEY=sk-... # macOS/Linux
|
|
68
|
-
setx OPENROUTER_API_KEY "sk-..." # Windows (open a new terminal afterwards)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Config is stored at `~/.fusefable/config.yaml`.
|
|
72
|
-
|
|
73
|
-
## 1) Use as a CLI
|
|
74
|
-
```bash
|
|
75
|
-
fusefable ask "Write a quicksort function in Python"
|
|
76
|
-
fusefable ask --show-all "..." # show every answer + judge reason
|
|
77
|
-
fusefable ask --models gpt-5,qwen3-coder "..." # restrict to specific models
|
|
78
|
-
fusefable ask --cheap "..." # use cheap_models from config
|
|
79
|
-
ff ask "..." # short alias
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## 2) Use as a subagent / in a pipe
|
|
83
|
-
```bash
|
|
84
|
-
fusefable ask --quiet "..." # print only the answer (no headers)
|
|
85
|
-
echo "Explain this code" | fusefable ask --quiet # read the prompt from stdin
|
|
86
|
-
cat bug.py | fusefable ask -q "Find the bug in this code"
|
|
87
|
-
fusefable ask --json "..." # JSON output: answer, chosen_model, reason, cost, candidates
|
|
88
|
-
```
|
|
89
|
-
`--json` is ideal for scripts/agents that parse the result; `--quiet` is ideal for piping.
|
|
90
|
-
|
|
91
|
-
## 3) Use as an MCP server (Cursor / VS Code / Claude / other agents)
|
|
92
|
-
Run as an MCP server over stdio:
|
|
93
|
-
```bash
|
|
94
|
-
fusefable mcp
|
|
95
|
-
```
|
|
96
|
-
Exposes a tool `fuse_ask(question, models?, cheap?)` for any MCP client.
|
|
97
|
-
|
|
98
|
-
### Cursor
|
|
99
|
-
`~/.cursor/mcp.json` (or Settings → MCP):
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"mcpServers": {
|
|
103
|
-
"fusefable": {
|
|
104
|
-
"command": "fusefable",
|
|
105
|
-
"args": ["mcp"],
|
|
106
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### VS Code (Copilot / MCP-compatible extension)
|
|
113
|
-
`.vscode/mcp.json` in your project:
|
|
114
|
-
```json
|
|
115
|
-
{
|
|
116
|
-
"servers": {
|
|
117
|
-
"fusefable": {
|
|
118
|
-
"command": "fusefable",
|
|
119
|
-
"args": ["mcp"],
|
|
120
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Claude Desktop
|
|
127
|
-
`claude_desktop_config.json`:
|
|
128
|
-
```json
|
|
129
|
-
{
|
|
130
|
-
"mcpServers": {
|
|
131
|
-
"fusefable": {
|
|
132
|
-
"command": "fusefable",
|
|
133
|
-
"args": ["mcp"],
|
|
134
|
-
"env": { "OPENROUTER_API_KEY": "sk-..." }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
> Requires `pip install "fusefable[mcp]"` and a completed `fusefable config`.
|
|
141
|
-
> If `fusefable` isn't on the app's PATH, use a full path such as `python -m fusefable.cli`.
|
|
142
|
-
|
|
143
|
-
## How it works
|
|
144
|
-
1. **Fan-out** — every model is called concurrently via `asyncio` (total time = slowest model).
|
|
145
|
-
2. Any model that times out or fails is dropped — it never slows the whole run.
|
|
146
|
-
3. **Judge** — model names are anonymized (Answer A/B/C...) and a judge model picks the best.
|
|
147
|
-
4. Returns the best answer plus an estimated cost.
|
|
148
|
-
|
|
149
|
-
## Development
|
|
150
|
-
```bash
|
|
151
|
-
pip install -e ".[dev,mcp]"
|
|
152
|
-
pytest -q
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## License
|
|
156
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|