Skip to content

[OMEGA-376] Introduce a simple LLM ROUTER that will allow choice of "economy" or "premium" LLMs when openrouter is selected. - #373

Draft
jazzbox35 wants to merge 6 commits into
singnet:mainfrom
jazzbox35:LLM-router-with-PR358
Draft

jazzbox35 wants to merge 6 commits into
singnet:mainfrom
jazzbox35:LLM-router-with-PR358

Conversation

@jazzbox35

@jazzbox35 jazzbox35 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Change the docker startup script so that if the user selects openrouter as provider and enters a specific model, the user is then prompted to input an optional premium model. The first model entered is the default LLM (typically the "economy" model for simpler calls). If the user elects to input a premium model too, it must be a valid openrouter model, and this is used for more complicated LLM calls.

There is a new "default.metta" program in a new ./router directory which does some basic searching over the input message from the user chat and also tool call results.

Important note: the new API tool call format is included here -- the starting point is python objects not the old prompt.
Secondly: this is intended just to be the start of possible routers, and this is just a simple start at the outset.

How Has This Been Tested?

It does appear to work -- however this is a draft only PR for now.

Checklist

  • PR contains autogenerated code
  • Self-review completed
  • Test scenarios above are passed with the version of the code from PR

@alyona-snet
alyona-snet requested a review from kabirkbr October 1, 2026 10:56
@alyona-snet alyona-snet changed the title Introduce a simple LLM ROUTER that will allow choice of "economy" or "premium" LLMs when openrouter is selected. [OMEGA-376] Introduce a simple LLM ROUTER that will allow choice of "economy" or "premium" LLMs when openrouter is selected. Oct 1, 2026
@kabirkbr

kabirkbr commented Oct 12, 2026 •

Copy link
Copy Markdown

Hi @jazzbox35 .

I was reading this PR against the design we agreed on
OMEGA-282 (09-18), and against your
own description of it on Mattermost (09-21) and elsewhere. My main focus is to see if it aligns with the architectural direction, establish common interfaces etc so that we could move between providers freely.

What works well

  • Routing is off unless configured, so existing setups are unaffected.
  • The router is selected through configuration rather than registered as a provider, as agreed.
  • The router is pure (no calls, no credentials), and it decides before any provider runs.
  • llmRequestContentsNormalized is a useful, reusable piece.

Where it diverges from the design. The router returns a label (premium_model), and openrouter.py interprets it. In the design, the router
returns the provider and model itself, the "complete envelope" as you put it. This has two
practical effects:

  • Routing takes effect only with OpenRouter. The other four providers ignore the label. I am not sure how this solutions as it is would evolve from open router to other providers with the same interfaces.
  • CI can't observe it. The tests run on the mock provider, which ignores the label, so the green build doesn't cover the router.

Smaller points, but still important in my view:

  • As I understand, adding a second router currently requires changes in core: the
    import and the case.
  • A mistyped router name silently disables routing. Your 09-22 note suggested halting
    instead.
  • There are no tests yet in the PR. I tried to add them to the suggested code (link below);
  • Keywords: the code matches premium, so premium_model (the example from your 09-28
    note) wouldn't escalate. error also matches harmless text. The < 2 user-message rule can't trip, because the loop clears messages on every turn.

Suggested changes. In my opinion, these are the changes that would bring the PR in line
with the architecture, roughly in this order:

  1. The router returns (provider, model), and core writes it into the request: optional
    provider / model / router / metadata fields on LLMRequest in place of tier. Vitaly agreed these fields on OMEGA-282 (09-18). Your keyword policy stays as the router's logic. We still have to check how this relates to the OMEGA-427, as it is a dependency.
  2. Providers use request.model when it's set, through one helper on AIProvider used in
    convert_request and openai.py's override. The branch and the second client in openrouter.py are then no longer needed.
  3. Routers load as plugins through the existing loader
    (plugins.yaml with loader: metta, loadMettaPlugin), with a small router registry along the lines of registerLLMProvider. That replaces the import and the case.
  4. "Off" becomes a configured null router, and an unknown router name stops startup with a
    message naming it.
  5. Tests: a MeTTa unit test for the router, plus the
    mock reporting the model it received, so an autotest (as sell as telemetry, etc.) can check which model a request went to. No API key is needed.
  6. Small fixes:
    • match premium_model;
    • drop the < 2 rule;
    • include the routing fields in __repr__;
    • remove the unused imports;
    • document router and premium_model in config/config.yaml.

Separately, the stop() guard fixes a real crash, but
only for OpenRouter. In AIProvider.stop(), the same
guard would cover every provider. It's probably best as a small PR to main of its own.

On gatway and the wider architecture. As defined in OMEGA-282 (09-18), the router only decides; the credentials and the per-agent usage and cost accounting belong to a separate layer, LiteLLM (or some other in the meantime), which every LLM call goes through (nginx keeps the keys in the meantime, without counting). This PR neither resolves that nor conflicts with it: the router holds no keys and makes no calls, which is right. But routing makes escalation to a premium model possible, and until the gateway (like litellm) is in place nothing counts what that costs per agent. So I'd see the gateway layer as the next step after routing rather than part of this PR, and it is the same separation of infrastructure from the agent core that OMEGA-360 asks for. The suggested changes below keep the router's contract (it returns the provider and model, nothing more), so the router can later move behind that layer, or into its own container, without changing core again.

In a separate comment below to be posted soon, I suggest concrete code-level changes along these lines. I suggest to wait for that comment and then read all together, as they will hopefully will make more sense and then discuss.

[I have just learned at the last moment when publishing this comment that there is OMEGA-427 ticket which deals with the format of the calls and LLM router depends on it. cc @vsbogd , given that you are doing that ticket, please comment how you see we can align, if needed.]

-- thanks!


Tracking: OMEGA-376.

AI-assisted. This review was prepared with AI assistance. I'm posting it, and I'm responsible for its content. The team, by the names used in commits and mail:

  • snet-omega-software-architect: review and design. Claude Code on Claude Opus 5 (claude-opus-5) for the original assessment (2026-10-09), and on Claude Opus 5.5 (claude-opus-5-5) for this review.
  • snet-omega-software-engineer: the code behind the suggested changes (see the next comment). Models not confirmed at time of writing.
  • snet-omega-qa-engineer: tests and grades; checked the assessment and caught two errors in it. Models not confirmed at time of writing.
  • `snet-omega-ai-co-tech-lead': coordination. Claude Code on Claude Opus 5.5 (claude-opus-5-5) today; earlier sessions of this avatar ran on OpenAI GPT-6.1 Sol (openai/gpt-6.1-sol).

@kabirkbr

kabirkbr commented Oct 12, 2026 •

Copy link
Copy Markdown

Hi @jazzbox35, here is the code-level follow-up to my review above: its suggestions, implemented and tested on top of your branch (193e9f58), so we can discuss them on concrete code.

What's on our fork

  1. The design, written down in one place. ADR-0001 records the OMEGA-282 design (09-18) as a single current decision, since it was spread over several Jira comments that partly contradict each other. In short: the router decides and returns one (provider, model) pair from a catalogue of configured models; the provider makes the call and decides nothing; the gateway holds the keys outside the agent (nginx today, LiteLLM later). Status: proposed (it may diverge from some of decisions in the design, but this is what i came up when looking at the code level; but we have a clearer close-to the code architectural decisions that we could hopefully discuss more efficently).
  2. Tickets. Each suggestion is an issue (R1–R7) stating what it changes, what is tested, where the tests are, and the expected results. Issue #9 is the index, with the full routing diagram and how it differs from OMEGA-282. I decided to brake them in this way, also to be able to discuss separately.
  3. PRs, one per ticket, stacked on the head of your branck in review order. Each shows only its own change and has its own CI run.
Review suggestion PR
1. The router returns (provider, model); core writes it into the request #12 (request fields), #16 (router)
2. Providers use request.model, through one helper #12
3. Routers load as plugins; a registry replaces the import and the case #15
4. "Off" is a null router; an unknown name stops startup #15
5. Tests #16 (router tests); the mock and end-to-end tests are still to do
6. Small fixes (premium_model, the < 2 rule, __repr__, imports, docs) #16, #12, #15
The stop() guard for all providers #11; also to go to main separately
Beyond the review: the catalogue as an allow-list, and two architecture checks (providers decide nothing; routers hold no key and make no call) #14, #13

Your keyword policy and llmRequestContentsNormalized are kept as the default router.

What changes in the call path

flowchart LR
  subgraph now["#373 now"]
    direction LR
    R1["router"] -- "label: premium_model" --> O1["openrouter.py<br/>picks the model"]
    R1 -. "label ignored" .-> X1["other providers"]
  end
  subgraph prop["Proposed"]
    direction LR
    R2["router plugin"] -- "(provider, model)<br/>from the catalogue" --> C2["core fills the<br/>request, checks<br/>the catalogue"]
    C2 --> P2["any provider<br/>sends request.model"]
    P2 --> G2["gateway adds the key<br/>(nginx now, LiteLLM later)"]
  end
Loading

Because the chosen model is now in the request, the test mock can report it, and CI can check routing for every provider.

Status

  • All six PRs: CI green, QA pass. The top of the stack is the same code that was reviewed as one branch.
  • Still to do: the mock reporting the model it received, and the end-to-end routing tests on it.
  • Open: alignment with OMEGA-427 / #358, the call format this builds on (cc @vsbogd); calling a provider other than the configured one; the gateway itself, which belongs with OMEGA-360.
  • I have invited you to the organization https://github.com/snet-omega-factory where I was proparing and testing code, so that I prepare all without disturbing team process; if this style works, i am happy to do that directly or adjust; @jazzbox35 .

Read in order, #11 → #16. Comments are welcome on the PRs or here, and I'm happy to go through it together in a call.


AI-generated code, proposed for review; I'm responsible for it. Agents: snet-omega-software-engineer (code), snet-omega-qa-engineer (tests and grades), snet-omega-software-architect (design and review), snet-omega-ai-co-tech-lead (coordination).

@aquashade aquashade added new-feature New feature or request and removed paused labels Oct 16, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants