mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
This commit is contained in:
1
changelog.d/maintenance/10779-combo-invocation-docs.md
Normal file
1
changelog.d/maintenance/10779-combo-invocation-docs.md
Normal file
@@ -0,0 +1 @@
|
||||
- **docs:** Custom combos are only invoked by their exact name in the `model` field — `auto` remains a separate zero-config router, and `openrouter/auto` is a paid OpenRouter product, not an alias ([#10779](https://github.com/diegosouzapw/OmniRoute/pull/10779)) — thanks @maxmad64bis
|
||||
@@ -189,7 +189,7 @@ Use `auto/smart` when you want the best quality and are okay with occasional exp
|
||||
|
||||
### "Can I force a specific provider?"
|
||||
|
||||
Yes! Use a combo with `priority` strategy instead of `auto`. See the [Technical Reference](../routing/AUTO-COMBO.md) for details.
|
||||
Yes! Use a combo with `priority` strategy instead of `auto`, then send the combo's **exact name** as the `model` field (e.g. `model: "my-combo"` — not `auto`). See the [Technical Reference](../routing/AUTO-COMBO.md) for details.
|
||||
|
||||
### "How is this different from round-robin?"
|
||||
|
||||
|
||||
@@ -159,6 +159,28 @@ enumerating every existing combo that shadows a model id, so operators who
|
||||
hit this by accident (rather than intentionally, per #6940) have a signal.
|
||||
The detection helper lives in `src/lib/combos/modelNameCollision.ts`.
|
||||
|
||||
## Calling a Custom Combo From a Client
|
||||
|
||||
Persisted combos (Settings → Combos) are only used when the client sends the combo's **exact name** in the `model` field — there is no fuzzy or partial matching of the combo name, and no `auto/` prefix involved. Resolution order (`getComboForModel()` in `src/sse/services/model.ts`):
|
||||
|
||||
1. exact combo-name match (`model: "my-combo"`),
|
||||
2. `combo/<name>` prefix (`model: "combo/my-combo"`),
|
||||
3. model→combo glob mappings (`/api/model-combo-mappings`).
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/v1/chat/completions \
|
||||
-H "Authorization: Bearer <key>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"my-combo","messages":[{"role":"user","content":"Hello"}]}'
|
||||
```
|
||||
|
||||
Two common pitfalls:
|
||||
|
||||
- **`auto` does not use your combos.** `auto`/`auto/*` builds its own zero-config candidate pool and only consults persisted combos if a combo is literally named `auto` (not recommended). To route through a combo, send its exact name — not `auto`.
|
||||
- **`openrouter/auto` is a real paid OpenRouter product** ("Auto Best Available"), not an OmniRoute alias. It is the single static model entry of the OpenRouter registry (`open-sse/config/providers/registry/openrouter/index.ts`) and is billed separately. Use Settings → Routing → Hide paid models to exclude it from `auto` pools.
|
||||
|
||||
See [#7992](https://github.com/diegosouzapw/OmniRoute/issues/7992) and [#7111](https://github.com/diegosouzapw/OmniRoute/issues/7111) for the original confusion this documents.
|
||||
|
||||
## How It Works (Persisted Auto-Combos)
|
||||
|
||||
The Auto-Combo Engine dynamically selects the best provider/model for each request using a **14-factor scoring function** (defined in `open-sse/services/autoCombo/scoring.ts` → `DEFAULT_WEIGHTS`). Weights form a normalized distribution (custom weights are renormalized by `normalizeScoringWeights()`).
|
||||
|
||||
@@ -1440,6 +1440,33 @@ function ComboUsageGuide({ onHide, onHideForever, onCreateCombo }) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 rounded-lg border border-primary/20 bg-primary/5 px-3 py-2">
|
||||
<p className="text-[11px] font-semibold text-primary">
|
||||
{getI18nOrFallback(t, "usageGuideInvokeTitle", "How to call this combo")}
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] leading-4 text-text-muted">
|
||||
{getI18nOrFallback(
|
||||
t,
|
||||
"usageGuideInvokeDesc",
|
||||
'Send the combo\'s exact name as the model, e.g. model: "my-combo" (or combo/my-combo).'
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] leading-4 text-text-muted">
|
||||
{getI18nOrFallback(
|
||||
t,
|
||||
"usageGuideInvokeAutoNote",
|
||||
"auto and auto/* are a separate zero-config router that does not use your combos (unless a combo is literally named auto)."
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] leading-4 text-text-muted">
|
||||
{getI18nOrFallback(
|
||||
t,
|
||||
"usageGuideInvokeOpenrouterNote",
|
||||
"openrouter/auto is a real paid OpenRouter product (Auto Best Available), not an OmniRoute alias — exclude it via Settings → Routing → Hide paid models."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<Button size="sm" icon="add" onClick={onCreateCombo}>
|
||||
{getI18nOrFallback(t, "createFirstCombo", "Create Your First Combo")}
|
||||
|
||||
@@ -3606,6 +3606,10 @@
|
||||
"wizardStep3Desc": "Pick how requests are distributed across your models - 14 strategies available",
|
||||
"wizardStep4Title": "Review & Save",
|
||||
"wizardStep4Desc": "Review your configuration and activate the combo",
|
||||
"usageGuideInvokeTitle": "How to call this combo",
|
||||
"usageGuideInvokeDesc": "Send the combo's exact name as the model, e.g. model: \"my-combo\" (or combo/my-combo).",
|
||||
"usageGuideInvokeAutoNote": "auto and auto/* are a separate zero-config router that does not use your combos (unless a combo is literally named auto).",
|
||||
"usageGuideInvokeOpenrouterNote": "openrouter/auto is a real paid OpenRouter product (Auto Best Available), not an OmniRoute alias — exclude it via Settings → Routing → Hide paid models.",
|
||||
"emailVisibilityStateOn": "On",
|
||||
"emailVisibilityStateOff": "Off",
|
||||
"reorderHandle": "Drag to reorder",
|
||||
|
||||
37
tests/unit/combo-guide-invocation-keys.test.ts
Normal file
37
tests/unit/combo-guide-invocation-keys.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const en = require("../../src/i18n/messages/en.json");
|
||||
|
||||
const invokeKeys = [
|
||||
"usageGuideInvokeTitle",
|
||||
"usageGuideInvokeDesc",
|
||||
"usageGuideInvokeAutoNote",
|
||||
"usageGuideInvokeOpenrouterNote",
|
||||
];
|
||||
|
||||
test("combos: usage guide invocation keys exist in en.json with expected copy", () => {
|
||||
for (const key of invokeKeys) {
|
||||
assert.ok(
|
||||
typeof en.combos[key] === "string" && en.combos[key].length > 0,
|
||||
`combos.${key} missing`
|
||||
);
|
||||
}
|
||||
assert.match(en.combos.usageGuideInvokeDesc, /exact name/);
|
||||
assert.match(en.combos.usageGuideInvokeAutoNote, /does not use your combos/);
|
||||
assert.match(en.combos.usageGuideInvokeOpenrouterNote, /paid OpenRouter product/);
|
||||
});
|
||||
|
||||
test("combos: ComboUsageGuide renders the invocation keys via getI18nOrFallback", () => {
|
||||
const page = readFileSync(
|
||||
path.join(import.meta.dirname, "../../src/app/(dashboard)/dashboard/combos/page.tsx"),
|
||||
"utf8"
|
||||
);
|
||||
for (const key of invokeKeys) {
|
||||
assert.ok(page.includes(`"${key}"`), `page.tsx references combos.${key}`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user