openprism 0.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.
- openprism-0.1.0/LICENSE +21 -0
- openprism-0.1.0/PKG-INFO +240 -0
- openprism-0.1.0/README.md +221 -0
- openprism-0.1.0/openprism/__init__.py +9 -0
- openprism-0.1.0/openprism/__main__.py +6 -0
- openprism-0.1.0/openprism/backends/__init__.py +10 -0
- openprism-0.1.0/openprism/backends/base.py +74 -0
- openprism-0.1.0/openprism/backends/direct.py +77 -0
- openprism-0.1.0/openprism/backends/opencode.py +255 -0
- openprism-0.1.0/openprism/cli.py +106 -0
- openprism-0.1.0/openprism/config.py +224 -0
- openprism-0.1.0/openprism/doctor.py +88 -0
- openprism-0.1.0/openprism/init_cmd.py +154 -0
- openprism-0.1.0/openprism/judge.py +81 -0
- openprism-0.1.0/openprism/mcp_server.py +121 -0
- openprism-0.1.0/openprism/panel.py +48 -0
- openprism-0.1.0/openprism/pipeline.py +133 -0
- openprism-0.1.0/openprism/prompts.py +104 -0
- openprism-0.1.0/openprism/py.typed +0 -0
- openprism-0.1.0/openprism.egg-info/PKG-INFO +240 -0
- openprism-0.1.0/openprism.egg-info/SOURCES.txt +26 -0
- openprism-0.1.0/openprism.egg-info/dependency_links.txt +1 -0
- openprism-0.1.0/openprism.egg-info/entry_points.txt +3 -0
- openprism-0.1.0/openprism.egg-info/requires.txt +5 -0
- openprism-0.1.0/openprism.egg-info/top_level.txt +1 -0
- openprism-0.1.0/pyproject.toml +38 -0
- openprism-0.1.0/setup.cfg +4 -0
- openprism-0.1.0/tests/test_openprism.py +305 -0
openprism-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ba1lly
|
|
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.
|
openprism-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openprism
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Many diverse model voices, split and recombined into one judged answer (Fusion-style panel + judge) — across your own provider keys or any model in opencode.
|
|
5
|
+
Author: ba1lly
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ba1lly/OpenPrism
|
|
8
|
+
Project-URL: Repository, https://github.com/ba1lly/OpenPrism
|
|
9
|
+
Keywords: llm,mcp,opencode,claude-code,ensemble,fusion,ai
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: openai<3,>=1.40
|
|
14
|
+
Requires-Dist: anthropic<1,>=0.40
|
|
15
|
+
Requires-Dist: python-dotenv<2,>=1.0
|
|
16
|
+
Requires-Dist: mcp<2,>=1.2
|
|
17
|
+
Requires-Dist: httpx<1,>=0.27
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# OpenPrism
|
|
21
|
+
|
|
22
|
+
> Many diverse model voices, split and recombined into one judged answer.
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
OpenPrism sends your prompt to a **panel** of different models in parallel, then a
|
|
29
|
+
**judge** model reconciles their answers, an explicit consensus / contradiction /
|
|
30
|
+
gaps / blind-spots pass, then a single response better than any individual model
|
|
31
|
+
produced. It's the [OpenRouter "Fusion"](https://openrouter.ai/blog) pattern as a
|
|
32
|
+
small, self-hostable tool.
|
|
33
|
+
|
|
34
|
+
**Works with:** Claude Code · opencode · Cursor · Windsurf · Gemini CLI · Codex —
|
|
35
|
+
anything that speaks MCP. It runs on models you already have: **any model
|
|
36
|
+
configured in [opencode](https://opencode.ai)** (75+ providers, recommended — zero
|
|
37
|
+
extra keys), or your own OpenAI-compatible provider keys.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Why it works
|
|
42
|
+
|
|
43
|
+
- **Diversity beats raw strength.** Independent models from different houses catch
|
|
44
|
+
different things; a judge that reconciles them outperforms any single model on
|
|
45
|
+
open-ended problems.
|
|
46
|
+
- **Parallel panel.** Wall-clock ≈ the slowest panelist, not the sum — so a 4th
|
|
47
|
+
model is nearly free in latency.
|
|
48
|
+
- **Use it deliberately.** A call costs ~slowest-model + judge. Point it at *hard*
|
|
49
|
+
questions (research, architecture, ambiguous trade-offs), not routine work.
|
|
50
|
+
- **Diversity is measured in model *families*, and never silently faked.** OpenPrism
|
|
51
|
+
picks default panels by family (Anthropic / OpenAI / Google / Qwen / GLM / Kimi /
|
|
52
|
+
MiniMax / …), so two models from the same maker don't count as two voices. Each
|
|
53
|
+
run is graded: **`full`** (all panelists answered, ≥ `OPENPRISM_MIN_FAMILIES`
|
|
54
|
+
distinct families), **`degraded`** (a panelist failed but the family floor still
|
|
55
|
+
holds), or **`stop`** (fewer than the floor answered — effectively a single-family
|
|
56
|
+
monoculture). The grade always prints; it's never silently passed off as
|
|
57
|
+
confident, and `OPENPRISM_STRICT=1` refuses a `stop` outright.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
┌──────────── panel (parallel) ────────────┐
|
|
63
|
+
prompt ───▶ │ model A model B model C model D │ ──▶ judge ──▶ answer
|
|
64
|
+
└───────────────────────────────────────────┘ (reconcile +
|
|
65
|
+
(different providers / houses) synthesize)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Two **backends** decide where panel calls go (set with `OPENPRISM_BACKEND`):
|
|
69
|
+
|
|
70
|
+
| Backend | Models from | Use |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `opencode` **(default, recommended)** | **every provider/model authed in opencode** — discovered live, nothing hardcoded, no keys in OpenPrism | easiest; just `opencode auth login` your providers |
|
|
73
|
+
| `direct` | your own OpenAI-compatible provider keys (`.env` / `providers.json`) | when you don't run opencode |
|
|
74
|
+
|
|
75
|
+
Two **modes**:
|
|
76
|
+
|
|
77
|
+
- **research** (default) — Fusion-style synthesis. This is where the lift is proven.
|
|
78
|
+
- **code** — code is verifiable, so blending prose is wrong: each model gives a
|
|
79
|
+
candidate, the judge does **best-of-N selection + repair** and emits a *Verify by:*
|
|
80
|
+
command. (v1 doesn't auto-run tests yet.)
|
|
81
|
+
|
|
82
|
+
## Install
|
|
83
|
+
|
|
84
|
+
Pick whichever fits — all cross-platform (Linux / macOS / Windows / WSL).
|
|
85
|
+
|
|
86
|
+
**A. Claude Code marketplace** (easiest):
|
|
87
|
+
```
|
|
88
|
+
/plugin marketplace add ba1lly/OpenPrism
|
|
89
|
+
/plugin install openprism@openprism
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**B. Any MCP host via `uvx`** (needs [`uv`](https://docs.astral.sh/uv/); no clone, no venv).
|
|
93
|
+
Generate the config for your host with the built-in wizard:
|
|
94
|
+
```bash
|
|
95
|
+
uvx --from git+https://github.com/ba1lly/OpenPrism openprism init --host opencode
|
|
96
|
+
# ...or --host cursor | windsurf | gemini | codex | claude-code
|
|
97
|
+
```
|
|
98
|
+
It prints (or `--write`s) the exact MCP config block, with the server launching
|
|
99
|
+
via `uvx --from git+…` — works straight from GitHub, nothing to maintain. (After a
|
|
100
|
+
PyPI release this shortens to `uvx openprism …`; pass `--pypi` to emit that form.)
|
|
101
|
+
|
|
102
|
+
**C. From source** (for development):
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/ba1lly/OpenPrism && cd OpenPrism
|
|
105
|
+
./install.sh # Linux / macOS / WSL (Windows: ./install.ps1)
|
|
106
|
+
```
|
|
107
|
+
The installer creates a venv, installs OpenPrism, seeds `.env`, and prints host
|
|
108
|
+
config. Use `openprism init --host <h> --local` to point a host at this checkout.
|
|
109
|
+
|
|
110
|
+
Then add a provider key to `.env` (`direct` backend) **or** set
|
|
111
|
+
`OPENPRISM_BACKEND=opencode` to use opencode's providers with no keys here.
|
|
112
|
+
|
|
113
|
+
### Judge options
|
|
114
|
+
|
|
115
|
+
| `OPENPRISM_JUDGE_BACKEND` | `OPENPRISM_JUDGE_MODEL` | Notes |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `claude-code` (default) | alias, e.g. `opus` | `claude -p` headless via a Claude Max plan |
|
|
118
|
+
| `anthropic-api` | `opus` or a model id | metered Anthropic API key |
|
|
119
|
+
| `opencode` | `provider/model` ref | routes the judge through opencode too |
|
|
120
|
+
|
|
121
|
+
## Usage (CLI)
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
openprism "Best architecture for X, and the trade-offs?" # research
|
|
125
|
+
openprism "Implement a token-bucket rate limiter" --mode code
|
|
126
|
+
openprism "..." --panel "google/gemini-2.5-flash,anthropic/claude-haiku-4-5"
|
|
127
|
+
openprism --bakeoff modelA modelB "a hard prompt" # which model wins?
|
|
128
|
+
openprism --list # presets + known models
|
|
129
|
+
openprism doctor # check your setup
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
With `OPENPRISM_BACKEND=opencode`, panels are `provider/model` refs; with no
|
|
133
|
+
`--panel`, OpenPrism auto-picks a diverse default across your connected providers.
|
|
134
|
+
|
|
135
|
+
## Use as a plugin
|
|
136
|
+
|
|
137
|
+
OpenPrism is one MCP server with four tools — `research`, `code`, `bakeoff`,
|
|
138
|
+
`models` — so every host calls the same core. `openprism init --host <host>`
|
|
139
|
+
generates the right config for each:
|
|
140
|
+
|
|
141
|
+
| Host | Config file | Tools surface as |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| Claude Code | marketplace, or `.mcp.json` | `mcp__openprism__research`, …; commands `/openprism*` |
|
|
144
|
+
| opencode | `~/.config/opencode/opencode.json` | `openprism_research`, …; commands `/openprism*` |
|
|
145
|
+
| Cursor | `~/.cursor/mcp.json` | `openprism` MCP tools |
|
|
146
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `openprism` MCP tools |
|
|
147
|
+
| Gemini CLI | `~/.gemini/settings.json` | `openprism` MCP tools |
|
|
148
|
+
| Codex | `~/.codex/config.toml` | `openprism` MCP tools |
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
openprism init --host cursor # print the block
|
|
152
|
+
openprism init --host opencode --write # merge it into the config file (makes a .bak)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For opencode, also copy the command files for slash commands:
|
|
156
|
+
```bash
|
|
157
|
+
cp integrations/opencode/commands/*.md ~/.config/opencode/commands/
|
|
158
|
+
```
|
|
159
|
+
Use `/openprism-models` (or the `models` tool) to see which `provider/model` refs
|
|
160
|
+
are connected.
|
|
161
|
+
|
|
162
|
+
### Panelist tools (opencode backend)
|
|
163
|
+
|
|
164
|
+
Like Fusion, panelists can use **live web** tools. On the opencode backend the
|
|
165
|
+
panel runs with `OPENPRISM_PANEL_TOOLS`:
|
|
166
|
+
|
|
167
|
+
| Value | Panelists get |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `research` (default, research mode) | `webfetch` + `websearch` + read-only; **all mutation tools denied** (`write`/`edit`/`apply_patch`/`bash`/`task`/`todowrite`) |
|
|
170
|
+
| `none` | pure completions (model knowledge only) — used for `code`/`bakeoff` |
|
|
171
|
+
| `all` | every opencode tool incl. `bash`/`edit`/`write` — **opt-in, runs on your machine** |
|
|
172
|
+
|
|
173
|
+
The `direct` backend is plain completions and ignores tools.
|
|
174
|
+
|
|
175
|
+
## Adding models & providers
|
|
176
|
+
|
|
177
|
+
**opencode backend (recommended) — you don't add anything to OpenPrism.** Add the
|
|
178
|
+
provider *in opencode* and it appears automatically:
|
|
179
|
+
```bash
|
|
180
|
+
opencode auth login # pick a provider, paste its key / OAuth
|
|
181
|
+
```
|
|
182
|
+
Then in any host run `/openprism-models` (or the `models` tool) to see every
|
|
183
|
+
`provider/model` ref now available, and use them:
|
|
184
|
+
```bash
|
|
185
|
+
openprism "…" --panel "anthropic/claude-opus-4-7,google/gemini-3-pro,openai/gpt-5.2"
|
|
186
|
+
```
|
|
187
|
+
No `--panel`? OpenPrism auto-picks a diverse default across your connected providers.
|
|
188
|
+
|
|
189
|
+
**direct backend — add providers to OpenPrism.** The default provider is set in
|
|
190
|
+
`.env` (`ALIBABA_API_KEY` + `ALIBABA_BASE_URL`). Add more OpenAI-compatible
|
|
191
|
+
providers in `providers.json` (copy [`providers.json.example`](providers.json.example)):
|
|
192
|
+
```json
|
|
193
|
+
{ "openrouter": { "base_url": "https://openrouter.ai/api/v1",
|
|
194
|
+
"api_key": "env:OPENROUTER_API_KEY",
|
|
195
|
+
"models": ["anthropic/claude-opus-4", "google/gemini-2.5-pro"] } }
|
|
196
|
+
```
|
|
197
|
+
Reference them as `provider/model` in `--panel`.
|
|
198
|
+
|
|
199
|
+
**Judge model** is independent of the panel — set `OPENPRISM_JUDGE_BACKEND` +
|
|
200
|
+
`OPENPRISM_JUDGE_MODEL` (see the [judge table](#judge-options)).
|
|
201
|
+
|
|
202
|
+
## Health check & uninstall
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
openprism doctor # checks backend, opencode server, judge, keys — prints no secrets
|
|
206
|
+
./uninstall.sh # or ./uninstall.ps1 — removes venv/build; leaves your .env
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Configuration
|
|
210
|
+
|
|
211
|
+
All via env vars (`.env` in the repo root — see [`.env.example`](.env.example)):
|
|
212
|
+
`OPENPRISM_BACKEND`, `OPENPRISM_JUDGE_BACKEND`, `OPENPRISM_JUDGE_MODEL`,
|
|
213
|
+
`ANTHROPIC_API_KEY` (for the `anthropic-api` judge), `OPENPRISM_DEFAULT_PROVIDER`
|
|
214
|
+
(which `direct` provider a bare model id uses), `OPENPRISM_PANEL_TOOLS`
|
|
215
|
+
(research/none/all), `OPENPRISM_MIN_FAMILIES` (diversity floor, default 2),
|
|
216
|
+
`OPENPRISM_STRICT` (refuse below the floor), `OPENPRISM_MAX_PANEL` (size cap),
|
|
217
|
+
`OPENPRISM_PANEL_TIMEOUT`, `OPENPRISM_MAX_TOKENS`, and the opencode-backend
|
|
218
|
+
`OPENPRISM_OPENCODE_*` settings. Extra `direct`-backend providers go in
|
|
219
|
+
`providers.json` (see [`providers.json.example`](providers.json.example)).
|
|
220
|
+
|
|
221
|
+
## Security notes
|
|
222
|
+
|
|
223
|
+
- OpenPrism never logs provider keys. The opencode backend queries only the
|
|
224
|
+
keyless `/provider` endpoint (never `/config/providers`, which would return keys),
|
|
225
|
+
and the judge always runs tool-less. Still, bind the opencode server to localhost
|
|
226
|
+
and set `OPENCODE_SERVER_PASSWORD` if you expose it. See [SECURITY.md](SECURITY.md).
|
|
227
|
+
- Keep `.env` and `providers.json` out of git (already in `.gitignore`).
|
|
228
|
+
|
|
229
|
+
## Roadmap
|
|
230
|
+
|
|
231
|
+
- Publish to PyPI so the launch shortens from `uvx --from git+… openprism-mcp` to
|
|
232
|
+
`uvx --from openprism openprism-mcp` (and the CLI to `uvx openprism …`).
|
|
233
|
+
- `code` mode: optional sandboxed test execution (`--verify "<cmd>"`).
|
|
234
|
+
- Panelist tools on the `direct` backend (a tool-call loop, so non-opencode panels
|
|
235
|
+
can browse too).
|
|
236
|
+
- Smarter default-panel selection (capability-aware, not just provider-diverse).
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# OpenPrism
|
|
2
|
+
|
|
3
|
+
> Many diverse model voices, split and recombined into one judged answer.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
OpenPrism sends your prompt to a **panel** of different models in parallel, then a
|
|
10
|
+
**judge** model reconciles their answers, an explicit consensus / contradiction /
|
|
11
|
+
gaps / blind-spots pass, then a single response better than any individual model
|
|
12
|
+
produced. It's the [OpenRouter "Fusion"](https://openrouter.ai/blog) pattern as a
|
|
13
|
+
small, self-hostable tool.
|
|
14
|
+
|
|
15
|
+
**Works with:** Claude Code · opencode · Cursor · Windsurf · Gemini CLI · Codex —
|
|
16
|
+
anything that speaks MCP. It runs on models you already have: **any model
|
|
17
|
+
configured in [opencode](https://opencode.ai)** (75+ providers, recommended — zero
|
|
18
|
+
extra keys), or your own OpenAI-compatible provider keys.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Why it works
|
|
23
|
+
|
|
24
|
+
- **Diversity beats raw strength.** Independent models from different houses catch
|
|
25
|
+
different things; a judge that reconciles them outperforms any single model on
|
|
26
|
+
open-ended problems.
|
|
27
|
+
- **Parallel panel.** Wall-clock ≈ the slowest panelist, not the sum — so a 4th
|
|
28
|
+
model is nearly free in latency.
|
|
29
|
+
- **Use it deliberately.** A call costs ~slowest-model + judge. Point it at *hard*
|
|
30
|
+
questions (research, architecture, ambiguous trade-offs), not routine work.
|
|
31
|
+
- **Diversity is measured in model *families*, and never silently faked.** OpenPrism
|
|
32
|
+
picks default panels by family (Anthropic / OpenAI / Google / Qwen / GLM / Kimi /
|
|
33
|
+
MiniMax / …), so two models from the same maker don't count as two voices. Each
|
|
34
|
+
run is graded: **`full`** (all panelists answered, ≥ `OPENPRISM_MIN_FAMILIES`
|
|
35
|
+
distinct families), **`degraded`** (a panelist failed but the family floor still
|
|
36
|
+
holds), or **`stop`** (fewer than the floor answered — effectively a single-family
|
|
37
|
+
monoculture). The grade always prints; it's never silently passed off as
|
|
38
|
+
confident, and `OPENPRISM_STRICT=1` refuses a `stop` outright.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌──────────── panel (parallel) ────────────┐
|
|
44
|
+
prompt ───▶ │ model A model B model C model D │ ──▶ judge ──▶ answer
|
|
45
|
+
└───────────────────────────────────────────┘ (reconcile +
|
|
46
|
+
(different providers / houses) synthesize)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Two **backends** decide where panel calls go (set with `OPENPRISM_BACKEND`):
|
|
50
|
+
|
|
51
|
+
| Backend | Models from | Use |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `opencode` **(default, recommended)** | **every provider/model authed in opencode** — discovered live, nothing hardcoded, no keys in OpenPrism | easiest; just `opencode auth login` your providers |
|
|
54
|
+
| `direct` | your own OpenAI-compatible provider keys (`.env` / `providers.json`) | when you don't run opencode |
|
|
55
|
+
|
|
56
|
+
Two **modes**:
|
|
57
|
+
|
|
58
|
+
- **research** (default) — Fusion-style synthesis. This is where the lift is proven.
|
|
59
|
+
- **code** — code is verifiable, so blending prose is wrong: each model gives a
|
|
60
|
+
candidate, the judge does **best-of-N selection + repair** and emits a *Verify by:*
|
|
61
|
+
command. (v1 doesn't auto-run tests yet.)
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
Pick whichever fits — all cross-platform (Linux / macOS / Windows / WSL).
|
|
66
|
+
|
|
67
|
+
**A. Claude Code marketplace** (easiest):
|
|
68
|
+
```
|
|
69
|
+
/plugin marketplace add ba1lly/OpenPrism
|
|
70
|
+
/plugin install openprism@openprism
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**B. Any MCP host via `uvx`** (needs [`uv`](https://docs.astral.sh/uv/); no clone, no venv).
|
|
74
|
+
Generate the config for your host with the built-in wizard:
|
|
75
|
+
```bash
|
|
76
|
+
uvx --from git+https://github.com/ba1lly/OpenPrism openprism init --host opencode
|
|
77
|
+
# ...or --host cursor | windsurf | gemini | codex | claude-code
|
|
78
|
+
```
|
|
79
|
+
It prints (or `--write`s) the exact MCP config block, with the server launching
|
|
80
|
+
via `uvx --from git+…` — works straight from GitHub, nothing to maintain. (After a
|
|
81
|
+
PyPI release this shortens to `uvx openprism …`; pass `--pypi` to emit that form.)
|
|
82
|
+
|
|
83
|
+
**C. From source** (for development):
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/ba1lly/OpenPrism && cd OpenPrism
|
|
86
|
+
./install.sh # Linux / macOS / WSL (Windows: ./install.ps1)
|
|
87
|
+
```
|
|
88
|
+
The installer creates a venv, installs OpenPrism, seeds `.env`, and prints host
|
|
89
|
+
config. Use `openprism init --host <h> --local` to point a host at this checkout.
|
|
90
|
+
|
|
91
|
+
Then add a provider key to `.env` (`direct` backend) **or** set
|
|
92
|
+
`OPENPRISM_BACKEND=opencode` to use opencode's providers with no keys here.
|
|
93
|
+
|
|
94
|
+
### Judge options
|
|
95
|
+
|
|
96
|
+
| `OPENPRISM_JUDGE_BACKEND` | `OPENPRISM_JUDGE_MODEL` | Notes |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `claude-code` (default) | alias, e.g. `opus` | `claude -p` headless via a Claude Max plan |
|
|
99
|
+
| `anthropic-api` | `opus` or a model id | metered Anthropic API key |
|
|
100
|
+
| `opencode` | `provider/model` ref | routes the judge through opencode too |
|
|
101
|
+
|
|
102
|
+
## Usage (CLI)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
openprism "Best architecture for X, and the trade-offs?" # research
|
|
106
|
+
openprism "Implement a token-bucket rate limiter" --mode code
|
|
107
|
+
openprism "..." --panel "google/gemini-2.5-flash,anthropic/claude-haiku-4-5"
|
|
108
|
+
openprism --bakeoff modelA modelB "a hard prompt" # which model wins?
|
|
109
|
+
openprism --list # presets + known models
|
|
110
|
+
openprism doctor # check your setup
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
With `OPENPRISM_BACKEND=opencode`, panels are `provider/model` refs; with no
|
|
114
|
+
`--panel`, OpenPrism auto-picks a diverse default across your connected providers.
|
|
115
|
+
|
|
116
|
+
## Use as a plugin
|
|
117
|
+
|
|
118
|
+
OpenPrism is one MCP server with four tools — `research`, `code`, `bakeoff`,
|
|
119
|
+
`models` — so every host calls the same core. `openprism init --host <host>`
|
|
120
|
+
generates the right config for each:
|
|
121
|
+
|
|
122
|
+
| Host | Config file | Tools surface as |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| Claude Code | marketplace, or `.mcp.json` | `mcp__openprism__research`, …; commands `/openprism*` |
|
|
125
|
+
| opencode | `~/.config/opencode/opencode.json` | `openprism_research`, …; commands `/openprism*` |
|
|
126
|
+
| Cursor | `~/.cursor/mcp.json` | `openprism` MCP tools |
|
|
127
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `openprism` MCP tools |
|
|
128
|
+
| Gemini CLI | `~/.gemini/settings.json` | `openprism` MCP tools |
|
|
129
|
+
| Codex | `~/.codex/config.toml` | `openprism` MCP tools |
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
openprism init --host cursor # print the block
|
|
133
|
+
openprism init --host opencode --write # merge it into the config file (makes a .bak)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For opencode, also copy the command files for slash commands:
|
|
137
|
+
```bash
|
|
138
|
+
cp integrations/opencode/commands/*.md ~/.config/opencode/commands/
|
|
139
|
+
```
|
|
140
|
+
Use `/openprism-models` (or the `models` tool) to see which `provider/model` refs
|
|
141
|
+
are connected.
|
|
142
|
+
|
|
143
|
+
### Panelist tools (opencode backend)
|
|
144
|
+
|
|
145
|
+
Like Fusion, panelists can use **live web** tools. On the opencode backend the
|
|
146
|
+
panel runs with `OPENPRISM_PANEL_TOOLS`:
|
|
147
|
+
|
|
148
|
+
| Value | Panelists get |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `research` (default, research mode) | `webfetch` + `websearch` + read-only; **all mutation tools denied** (`write`/`edit`/`apply_patch`/`bash`/`task`/`todowrite`) |
|
|
151
|
+
| `none` | pure completions (model knowledge only) — used for `code`/`bakeoff` |
|
|
152
|
+
| `all` | every opencode tool incl. `bash`/`edit`/`write` — **opt-in, runs on your machine** |
|
|
153
|
+
|
|
154
|
+
The `direct` backend is plain completions and ignores tools.
|
|
155
|
+
|
|
156
|
+
## Adding models & providers
|
|
157
|
+
|
|
158
|
+
**opencode backend (recommended) — you don't add anything to OpenPrism.** Add the
|
|
159
|
+
provider *in opencode* and it appears automatically:
|
|
160
|
+
```bash
|
|
161
|
+
opencode auth login # pick a provider, paste its key / OAuth
|
|
162
|
+
```
|
|
163
|
+
Then in any host run `/openprism-models` (or the `models` tool) to see every
|
|
164
|
+
`provider/model` ref now available, and use them:
|
|
165
|
+
```bash
|
|
166
|
+
openprism "…" --panel "anthropic/claude-opus-4-7,google/gemini-3-pro,openai/gpt-5.2"
|
|
167
|
+
```
|
|
168
|
+
No `--panel`? OpenPrism auto-picks a diverse default across your connected providers.
|
|
169
|
+
|
|
170
|
+
**direct backend — add providers to OpenPrism.** The default provider is set in
|
|
171
|
+
`.env` (`ALIBABA_API_KEY` + `ALIBABA_BASE_URL`). Add more OpenAI-compatible
|
|
172
|
+
providers in `providers.json` (copy [`providers.json.example`](providers.json.example)):
|
|
173
|
+
```json
|
|
174
|
+
{ "openrouter": { "base_url": "https://openrouter.ai/api/v1",
|
|
175
|
+
"api_key": "env:OPENROUTER_API_KEY",
|
|
176
|
+
"models": ["anthropic/claude-opus-4", "google/gemini-2.5-pro"] } }
|
|
177
|
+
```
|
|
178
|
+
Reference them as `provider/model` in `--panel`.
|
|
179
|
+
|
|
180
|
+
**Judge model** is independent of the panel — set `OPENPRISM_JUDGE_BACKEND` +
|
|
181
|
+
`OPENPRISM_JUDGE_MODEL` (see the [judge table](#judge-options)).
|
|
182
|
+
|
|
183
|
+
## Health check & uninstall
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
openprism doctor # checks backend, opencode server, judge, keys — prints no secrets
|
|
187
|
+
./uninstall.sh # or ./uninstall.ps1 — removes venv/build; leaves your .env
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
All via env vars (`.env` in the repo root — see [`.env.example`](.env.example)):
|
|
193
|
+
`OPENPRISM_BACKEND`, `OPENPRISM_JUDGE_BACKEND`, `OPENPRISM_JUDGE_MODEL`,
|
|
194
|
+
`ANTHROPIC_API_KEY` (for the `anthropic-api` judge), `OPENPRISM_DEFAULT_PROVIDER`
|
|
195
|
+
(which `direct` provider a bare model id uses), `OPENPRISM_PANEL_TOOLS`
|
|
196
|
+
(research/none/all), `OPENPRISM_MIN_FAMILIES` (diversity floor, default 2),
|
|
197
|
+
`OPENPRISM_STRICT` (refuse below the floor), `OPENPRISM_MAX_PANEL` (size cap),
|
|
198
|
+
`OPENPRISM_PANEL_TIMEOUT`, `OPENPRISM_MAX_TOKENS`, and the opencode-backend
|
|
199
|
+
`OPENPRISM_OPENCODE_*` settings. Extra `direct`-backend providers go in
|
|
200
|
+
`providers.json` (see [`providers.json.example`](providers.json.example)).
|
|
201
|
+
|
|
202
|
+
## Security notes
|
|
203
|
+
|
|
204
|
+
- OpenPrism never logs provider keys. The opencode backend queries only the
|
|
205
|
+
keyless `/provider` endpoint (never `/config/providers`, which would return keys),
|
|
206
|
+
and the judge always runs tool-less. Still, bind the opencode server to localhost
|
|
207
|
+
and set `OPENCODE_SERVER_PASSWORD` if you expose it. See [SECURITY.md](SECURITY.md).
|
|
208
|
+
- Keep `.env` and `providers.json` out of git (already in `.gitignore`).
|
|
209
|
+
|
|
210
|
+
## Roadmap
|
|
211
|
+
|
|
212
|
+
- Publish to PyPI so the launch shortens from `uvx --from git+… openprism-mcp` to
|
|
213
|
+
`uvx --from openprism openprism-mcp` (and the CLI to `uvx openprism …`).
|
|
214
|
+
- `code` mode: optional sandboxed test execution (`--verify "<cmd>"`).
|
|
215
|
+
- Panelist tools on the `direct` backend (a tool-call loop, so non-opencode panels
|
|
216
|
+
can browse too).
|
|
217
|
+
- Smarter default-panel selection (capability-aware, not just provider-diverse).
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""OpenPrism — many diverse model voices, split and recombined into one judged answer.
|
|
2
|
+
|
|
3
|
+
A panel of models (your own provider keys, or any model in opencode) runs in
|
|
4
|
+
parallel; a judge model reconciles them. Two modes:
|
|
5
|
+
- research: Fusion-style synthesis (consensus / contradictions / gaps -> grounded answer)
|
|
6
|
+
- code: best-of-N selection + repair into one final solution
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Model backends — where Prism's panel calls actually go.
|
|
2
|
+
|
|
3
|
+
- DirectBackend: Prism's own OpenAI-compatible providers (keys in .env / providers.json).
|
|
4
|
+
Used by Claude Code and the standalone CLI, which have no provider registry to borrow.
|
|
5
|
+
- OpencodeBackend: piggybacks on a running opencode server — every provider/model the
|
|
6
|
+
user has configured/authed in opencode, with zero hardcoding. Used in opencode.
|
|
7
|
+
"""
|
|
8
|
+
from .base import Backend, ModelInfo, get_backend
|
|
9
|
+
|
|
10
|
+
__all__ = ["Backend", "ModelInfo", "get_backend"]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Backend interface + factory.
|
|
2
|
+
|
|
3
|
+
A backend turns a model reference into a completion. Model references are always
|
|
4
|
+
`provider/model`; because some model ids themselves contain slashes
|
|
5
|
+
(e.g. opencode's `requesty/xai/grok-4`), we split on the FIRST slash only.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ModelInfo:
|
|
15
|
+
ref: str # full reference, e.g. "google/gemini-2.5-flash"
|
|
16
|
+
provider: str # provider id
|
|
17
|
+
model: str # model id (may contain slashes)
|
|
18
|
+
name: str = "" # human display name, if known
|
|
19
|
+
connected: bool = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def split_ref(ref: str, default_provider: str = "") -> tuple[str, str]:
|
|
23
|
+
"""`provider/model` -> (provider, model). Bare ref -> (default_provider, ref)."""
|
|
24
|
+
if "/" in ref:
|
|
25
|
+
provider, model = ref.split("/", 1)
|
|
26
|
+
return provider, model
|
|
27
|
+
return default_provider, ref
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Backend(ABC):
|
|
31
|
+
"""Async backend. Implementations must be safe to call concurrently."""
|
|
32
|
+
|
|
33
|
+
name = "base"
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
async def list_models(self) -> list[ModelInfo]:
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def complete(
|
|
41
|
+
self, model_ref: str, prompt: str, system: str | None, max_tokens: int,
|
|
42
|
+
tools: dict | None = None,
|
|
43
|
+
) -> tuple[str, int]:
|
|
44
|
+
"""Return (text, total_tokens). Raise on failure — the panel layer
|
|
45
|
+
catches per-model so one failure never sinks the run. `tools` is a
|
|
46
|
+
name->bool map for backends that support tool use (opencode); backends
|
|
47
|
+
that can't (direct raw completions) ignore it."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
async def default_panel(self, mode: str) -> list[str]:
|
|
51
|
+
"""Models to use when the caller gives no explicit panel. Default: the
|
|
52
|
+
configured preset for `mode`. opencode overrides this dynamically."""
|
|
53
|
+
from .. import config
|
|
54
|
+
|
|
55
|
+
return config.resolve_panel(mode if mode in config.PANELS else None)
|
|
56
|
+
|
|
57
|
+
async def aclose(self) -> None:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_backend(name: str | None = None) -> Backend:
|
|
62
|
+
"""Factory. `name` overrides config.BACKEND."""
|
|
63
|
+
from .. import config
|
|
64
|
+
|
|
65
|
+
backend = (name or config.BACKEND).lower()
|
|
66
|
+
if backend == "opencode":
|
|
67
|
+
from .opencode import OpencodeBackend
|
|
68
|
+
|
|
69
|
+
return OpencodeBackend()
|
|
70
|
+
if backend == "direct":
|
|
71
|
+
from .direct import DirectBackend
|
|
72
|
+
|
|
73
|
+
return DirectBackend()
|
|
74
|
+
raise config.PrismError(f"Unknown OPENPRISM_BACKEND: {backend!r} (use 'direct' or 'opencode').")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""DirectBackend — Prism's own OpenAI-compatible providers.
|
|
2
|
+
|
|
3
|
+
Providers come from config.load_providers() (the Alibaba Coding Plan from .env by
|
|
4
|
+
default, plus anything in providers.json). Used by Claude Code and the CLI, which
|
|
5
|
+
have no host provider registry to borrow. A bare model id (no slash) resolves to
|
|
6
|
+
the default provider, preserving the original single-provider behaviour.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from openai import AsyncOpenAI
|
|
11
|
+
|
|
12
|
+
from .. import config
|
|
13
|
+
from .base import Backend, ModelInfo, split_ref
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DirectBackend(Backend):
|
|
17
|
+
name = "direct"
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.providers = config.load_providers()
|
|
21
|
+
if not self.providers:
|
|
22
|
+
raise config.PrismError(
|
|
23
|
+
"No providers configured. Set ALIBABA_API_KEY in .env (or add "
|
|
24
|
+
"providers.json), or use OPENPRISM_BACKEND=opencode."
|
|
25
|
+
)
|
|
26
|
+
self.default_provider = config.DEFAULT_PROVIDER or next(iter(self.providers))
|
|
27
|
+
if self.default_provider not in self.providers:
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
fallback = next(iter(self.providers))
|
|
31
|
+
print(f"openprism: OPENPRISM_DEFAULT_PROVIDER={self.default_provider!r} is not "
|
|
32
|
+
f"configured; using {fallback!r} for bare model ids", file=sys.stderr)
|
|
33
|
+
self.default_provider = fallback
|
|
34
|
+
self._clients: dict[str, AsyncOpenAI] = {}
|
|
35
|
+
|
|
36
|
+
def _client(self, provider_id: str) -> AsyncOpenAI:
|
|
37
|
+
if provider_id not in self.providers:
|
|
38
|
+
raise config.PrismError(
|
|
39
|
+
f"Provider {provider_id!r} not configured. Known: {list(self.providers)}"
|
|
40
|
+
)
|
|
41
|
+
if provider_id not in self._clients:
|
|
42
|
+
p = self.providers[provider_id]
|
|
43
|
+
self._clients[provider_id] = AsyncOpenAI(api_key=p.api_key, base_url=p.base_url)
|
|
44
|
+
return self._clients[provider_id]
|
|
45
|
+
|
|
46
|
+
async def list_models(self) -> list[ModelInfo]:
|
|
47
|
+
out: list[ModelInfo] = []
|
|
48
|
+
for pid, p in self.providers.items():
|
|
49
|
+
for m in p.models:
|
|
50
|
+
ref = m if pid == self.default_provider else f"{pid}/{m}"
|
|
51
|
+
out.append(ModelInfo(ref=ref, provider=pid, model=m))
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
async def complete(
|
|
55
|
+
self, model_ref: str, prompt: str, system: str | None, max_tokens: int,
|
|
56
|
+
tools: dict | None = None,
|
|
57
|
+
) -> tuple[str, int]:
|
|
58
|
+
# `tools` is ignored: direct providers are plain completions. Panelist tool
|
|
59
|
+
# use (web/fetch) requires the opencode backend.
|
|
60
|
+
provider_id, model = split_ref(model_ref, self.default_provider)
|
|
61
|
+
client = self._client(provider_id)
|
|
62
|
+
messages = ([{"role": "system", "content": system}] if system else []) + [
|
|
63
|
+
{"role": "user", "content": prompt}
|
|
64
|
+
]
|
|
65
|
+
resp = await client.chat.completions.create(
|
|
66
|
+
model=model, messages=messages, max_tokens=max_tokens
|
|
67
|
+
)
|
|
68
|
+
if not getattr(resp, "choices", None):
|
|
69
|
+
raise RuntimeError(f"{model}: provider returned no choices")
|
|
70
|
+
text = resp.choices[0].message.content or ""
|
|
71
|
+
usage = getattr(resp, "usage", None)
|
|
72
|
+
tokens = getattr(usage, "total_tokens", 0) if usage else 0
|
|
73
|
+
return text, tokens
|
|
74
|
+
|
|
75
|
+
async def aclose(self) -> None:
|
|
76
|
+
for client in self._clients.values():
|
|
77
|
+
await client.close()
|