feat: Morph Model Router as an opt-in complexity classifier (#68) - #78
Merged
Conversation
Adds NADIRCLAW_COMPLEXITY_ANALYZER=morph, delegating the simple/mid/complex routing decision to Morph's hosted Model Router. Implemented per the maintainer triage on #68: - Strictly opt-in: default stays the local binary classifier; morph requires MORPH_API_KEY (missing key degrades to binary at selection time). - Fail-closed to local: on missing key, HTTP error, timeout, or unparseable response, log once and serve that request from BinaryComplexityClassifier. - Latency budget: MORPH_TIMEOUT_MS (default 200ms) + in-memory LRU on prompt hash for repeated CLI prompts. - Tier mapping reuses BinaryComplexityClassifier._score_to_tier so the same NADIRCLAW_TIER_THRESHOLDS apply and `mid` only appears when MID_MODEL is set. - Stdlib-only HTTP (urllib.request) — no new dependency; fully monkeypatched in tests (success, fail-closed, parsing, caching, dispatch). No live API in CI. Cost-tracking honesty (subtracting the per-classification Morph fee from net-saved in `nadirclaw report`) is deferred as a follow-up, noted in the README, pending Morph's published classification price. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #68.
Adds an optional, opt-in
morphclassifier that delegates the simple/mid/complex routing decision to Morph's hosted Model Router, built to the spec from the maintainer triage on #68.What's in it
NADIRCLAW_COMPLEXITY_ANALYZER=morph—nadirclaw/morph_classifier.pywithMorphRouterClassifier, wired intoget_classifier()/warmup()dispatch alongsidebinaryanddistilbert.morphrequiresMORPH_API_KEY; a missing key degrades tobinaryat selection time (never a hard dependency for first-run UX).MORPH_TIMEOUT_MS, default 200ms), or an unparseable response, it logs once and serves that request fromBinaryComplexityClassifier. A Morph outage degrades routing quality, never availability.MORPH_TIMEOUT_MSbounds the call; a small in-memory LRU keyed on prompt hash absorbs the repeated prompts a CLI session generates.difficulty(easy/medium/hard/needs_info) maps to a complexity score, then to a tier viaBinaryComplexityClassifier._score_to_tier, so the sameNADIRCLAW_TIER_THRESHOLDSapply andmidonly appears whenNADIRCLAW_MID_MODELis set.urllib.request, so it's trivially monkeypatched.Config knobs
MORPH_API_KEYmorph; empty → fall back tobinary.MORPH_API_BASEhttps://api.morphllm.com/v1MORPH_TIMEOUT_MS200Tests
tests/test_morph_classifier.py— 15 cases covering the success path, all three fail-closed paths (HTTP error, timeout, bad JSON), response parsing (confidence vs1 − ambiguity, nestedclassification, bad/unknown difficulty), caching, and the dispatch/degrade-without-key behavior. No live API call — the HTTP layer is monkeypatched. Full suite: 812 passed, 1 skipped locally.Deferred (called out in the README)
The maintainer asked for cost-tracking honesty — subtracting the per-classification Morph fee from
net savedinnadirclaw report. Morph's classification price isn't published in the issue, so that wiring is left as a follow-up and noted in the docs rather than guessed at.Notes for review
NADIRCLAW_CLASSIFIER; the actual dispatch var in the codebase isNADIRCLAW_COMPLEXITY_ANALYZER, so I used that for consistency withbinary/distilbert._parse_responseis defensive (flat or nested underclassification;confidenceelse1 − ambiguityelse1.0) and any surprise falls through the fail-closed path.🤖 Generated with Claude Code