Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
11 KiB
multi-provider-openrouter learnings
Task 1: Extend DispatchProvider config model for OpenRouter
DispatchProviderinsrc/config.pynow carrieshas_energy_telemetry: bool = Falseandenabled: bool = True.- Config is strict (
extra="forbid") — adding new keys todispatch_providersentries without matching model fields would fail at load. config/config.yamlkeepsneuralwattenabled with energy telemetry on, and adds anopenrouterprovider entry without telemetry..env.examplenow lists bothNEURALWATT_API_KEY=andOPENROUTER_API_KEY=.- Full suite passes (1251 tests). Verification script exits 0.
Task 5: Multi-provider main loop in poller
- Refactored
poller.main()to iterate overcfg.dispatch_providers.items()instead of always pollingdispatch_settings.default_provider. main()now skips disabled providers, dispatchesneuralwattrows viafetch_neuralwatt()andopenrouterrows viafetch_openrouter(), and logs/skips unknown provider keys.- Per-provider fetch failures (including
CatalogTooSmalland zero-row catalogs) are caught and logged; other enabled providers continue unaffected. mark_stale()gained an optionalprovider=kwarg that scopes the staleness update withAND provider = ?; calling withoutproviderretains the historical global behavior, preserving any external callers/tests.- After all providers are processed,
main()callstier.apply_tiering(conn, cfg)so freshly polled rows are immediately routable without a separatetierrun. - Existing freshness tests in
tests/test_poller_freshness.pywere updated to isolate theneuralwattprovider under test and to expect provider-skip semantics rather than process abort on fetch error/zero rows. - Added
tests/test_multi_provider_poller.pywith tests for: two providers both upserting, failure isolation (one fails, other succeeds), disabled provider skip, zero-row provider skip log, per-providermark_stalescoping, unknown provider key skip, and backward-compatible globalmark_stale. - Verification:
python -m py_compile src/poller.py tests/test_multi_provider_poller.pypasses; targeted pytest suite passes (64 tests).
Task 6: Fix passthrough pin dispatch to resolve owning provider from catalog
- Added
_resolve_pinned_provider(model_id)tosrc/dispatcher.pythat queriesmodelsfor active rows across configured providers, preferringcfg.dispatch_settings.default_providerwhen the samemodel_idexists on multiple providers. - Updated alias stripping to handle opencode's
llm-router/<vendor>/<model>prefix before falling back torsplit("/", 1)[-1]; this preserves full OpenRouter ids likeopenai/gpt-6-astrainstead of mangling them togpt-6-astra. - Passthrough branch now uses
_resolve_pinned_provider(requested) or cfg.dispatch_settings.default_providerand passes the resolved provider to_check_pinned_capabilities(...). - Added three tests in
tests/test_chat_completions.pycovering OpenRouter-only pin dispatch,llm-router/openai/gpt-6-astraprefix handling, and Neuralwatt-only pins still using the default provider. - Full suite remains green; changed path verified by
tests/test_chat_completions.py.
Task 8: Admin provider CRUD tests
-
Expanded
tests/test_admin_providers.pyfrom 3 to 8 tests using isolated temporary config directories. -
DELETE
/admin/api/providers/neuralwatt(the currentdispatch_settings.default_provider) returns 422 and leaves bothconfig.yamlandconfig.local.yamluntouched. -
DELETE
/admin/api/providers/openrouter(base-configured, non-default provider) returns 403 with a detail namingconfig/config.yamland read-only provenance. -
DELETE
/admin/api/providers/nonexistentreturns 404 and does not modifyconfig.yaml. -
POST
/admin/api/providers/testprovwrites only toconfig.local.yaml, leavesconfig.yamlunchanged, and creates aconfig.local.yaml.bak.<ts>backup. -
GET
/admin/api/providersreturns the merged view, including both base providers (neuralwatt,openrouter) and an overlay provider (overlayprov). -
Verification:
PYTHONPATH=src python -m pytest tests/test_admin_providers.py -x --tb=shortpasses (8 tests, ~2 s). No real provider APIs are called because the tests target the admin router's in-memory config handling only. -
Added
fetch_openrouter(provider)andparse_openrouter_model(raw_model)tosrc/poller.py. -
Virtual routers (
openrouter/auto,openrouter/auto-beta,openrouter/free,openrouter/fusion,openrouter/pareto-code,openrouter/bodybuilder) are dropped before becoming rows. -
Variant suffixes are preserved;
:batchmaps tolatency_class='flex', other variants and base rows to'standard'. -
Catalog
idis used asmodel_id;canonical_slugis used asbase_model_id. -
Per-token USD strings from
pricing.promptandpricing.completionare converted to cost per 1M tokens. -
pricing.web_searchandpricing.cache_readdifferentials are intentionally omitted from catalog cost estimates. -
Added
tests/test_openrouter_poller.pywith 15 mocked tests covering row construction, variant handling, virtual-router exclusion, malformed prices, and request-exception propagation. -
Full suite passes (1266 tests).
Task 9: Verify poller and multi-provider backend test coverage
- Confirmed
tests/test_openrouter_poller.pyalready covers:fetch_openrouter()row construction, virtual-router exclusion,:batch->latency_class='flex',:free/base ->'standard', malformed pricing handling, and request exception propagation. - Confirmed
tests/test_multi_provider_poller.pyalready covers: two mocked providers both upsert, failure isolation, disabled provider skip, zero-row provider skip, per-providermark_stale()scoping, unknown provider key skip, and backward-compatible globalmark_stale(). - Added
test_in_process_tiering_runs_after_upsertto explicitly verify thatmain()calls in-process tiering so OpenRouter rows receive a non-nulltierimmediately after the poll. - Targeted verification passes:
PYTHONPATH=src python -m pytest tests/test_openrouter_poller.py tests/test_multi_provider_poller.py tests/test_poller_freshness.py -x --tb=short-> 30 passed. - poller.py coverage on these files: 199 stmts, 12 miss, 94%.
- Evidence recorded in
.omo/evidence/task-9-multi-provider-openrouter.json.
Task 7: Per-provider telemetry and refusal isolation
extract_telemetry({})now safely returns aTelemetry()instance with allNonefields; added an explicit non-dict guard so a malformed payload does not crash downstream logging._sniff_telemetry_line(": OPENROUTER PROCESSING")returnsNonebecause only comments whose first word is exactly"energy"or"cost"are treated as telemetry.- Replaced module-level
_last_account_refusal: floatwith_provider_refusal_since: dict[str, float]. - Renamed
_record_account_refusal()to_record_provider_refusal(provider)and keyed the timestamp by provider. - Updated the classifier cascade refusal check (around line 654) to consult
_provider_refusal_since.get(cfg.dispatch_settings.default_provider)usingcfg.classifier.cooldown_seconds. - Updated non-streaming and streaming dispatch refusal triggers to call
_record_provider_refusal(provider). - SSE
: energy/: costsniffing is now gated bycfg.dispatch_providers[provider].has_energy_telemetry; when false, telemetry comments are dropped rather than proxied. - Added a module
_self_check()that runs at import time to lockextract_telemetry({})and_sniff_telemetry_linebehavior. - Targeted tests
tests/test_classifier_cascade.pyandtests/test_local_dispatch_fallback.pyfail as expected because they still reference the removed_last_account_refusal/_record_account_refusalsymbols; task 10 will migrate those fixtures. - Verification:
python -m py_compile src/dispatcher.pypasses;PYTHONPATH=src python -c "from dispatcher import extract_telemetry; t = extract_telemetry({}); assert t.energy_kwh is None"passes. - Evidence recorded in
.omo/evidence/task-7-multi-provider-openrouter.json.
Task 10: migrate classifier cascade tests to per-provider refusal symbols
- Updated
tests/test_classifier_cascade.py_clean_statefixture: replaced the removed_last_account_refusalmonkeypatch withdispatcher._provider_refusal_since.clear()(reset both before and after each test). - Renamed
test_account_refusal_skips_the_cloud_steptotest_provider_refusal_skips_the_cloud_stepand replaced_record_account_refusal()with_record_provider_refusal("neuralwatt"). - Verified
tests/test_local_dispatch_fallback.pycontains no references to_last_account_refusal/_record_account_refusal; the existing tests continue to pass because_provider_refusal_sinceis scoped per provider and the helper unit tests exercise_account_level_refusal()semantics unchanged. - Added
tests/test_multi_provider_dispatch.pywith two focused tests:test_openrouter_streaming_ignores_keepalive_comments_and_logs_null_telemetry: a streamed OpenRouter response containing: OPENROUTER PROCESSINGkeeps the comment in the proxied body, does not crash, and writes anenergy_observationsrow withenergy_kwhandcost_usdNULL.test_provider_refusal_is_isolated_between_providers: recording a refusal onneuralwattskips the cloud classifier whendefault_provider=neuralwatt, but does not skip it whendefault_provider=openrouter.
- Targeted run:
PYTHONPATH=src python -m pytest tests/test_classifier_cascade.py tests/test_local_dispatch_fallback.py tests/test_multi_provider_dispatch.py -x --tb=short→ 39 passed. - Full suite:
PYTHONPATH=src python -m pytest --tb=short→ 1288 passed, zero failures. - Evidence recorded in
.omo/evidence/task-10-multi-provider-openrouter.json.
Task 11: Final verification — Playwright smoke and full test suite
- Threw away uvicorn on port 8081 (never touched port 8080 / systemd). Verified
/admin/providersand/admin/api/providersreturn 200 before driving the UI. - Playwright (Chromium, headless) smoke executed at 1400px and 800px.
- Passed: page renders, Providers navbar link visible, base providers
neuralwattandopenrouterdisplayed, create providersmoketestappears in the list, XSS payload<script>alert('xss')</script>is escaped in the DOM, and 30s idle produced zero console errors. - Failed / defect found: newly-created overlay providers render with source badge
unknownand no edit/delete buttons becauseGET /admin/api/providersomitsin_base/in_overlayprovenance fields. The UI (providers.html) keys editability offin_overlay, so the toggle and delete flow cannot be exercised via the real page. Root cause:src/admin.py:admin_providers_list()returns onlyname,base_url,api_key_env,has_energy_telemetry,enabled; it does not include the provenance flags returned for profiles.
- Passed: page renders, Providers navbar link visible, base providers
- Full pytest suite passes: 1288 passed, 0 failed. Command:
PYTHONPATH=src python -m pytest --tb=short. - Overlay cleanup: restored
config/config.local.yamlto its pre-test state (local_energyblock only); backup files created by_persist_toremain inconfig/but are gitignored;git statusis clean. - Evidence, screenshots (1400/800), and console log captured in
.omo/evidence/task-11-multi-provider-openrouter.json.