fix(discovery): bound OpenRouter free-model endpoint fan-out to one deadline - #939
fix(discovery): bound OpenRouter free-model endpoint fan-out to one deadline#939seonghobae wants to merge 11 commits into
Conversation
…eadline _openrouter_free_model_endpoints() used a per-request timeout but no overall deadline: with a fixed 8-worker thread pool, total wall time still grew with the free-model count (ceil(n/8) sequential timeout waves). At today's ~94-model free catalog that's up to 180s on its own -- enough on its own to exhaust the contextual-orchestrator review sidecar's 180s startup watchdog in ContextualWisdomLab/.github, flagged by Devin Review on .github#1463. Bounded it to one shared timeout via concurrent.futures.wait(), reporting any model not done by the deadline as unmapped rather than waiting on further batches; this data is best-effort provider-privacy enrichment, not required for discovery. Regression test proves the bound holds regardless of catalog size. Also adds opt-in verbose/debug logging for provider discovery (contextual_orchestrator.model_discovery: per-provider and per-fetch timing/counts, never api_key or provider payload/content) via --log-level or CONTEXTUAL_ORCHESTRATOR_LOG_LEVEL, off by default and a true no-op when unset so it can't leak state across this repo's many in-process main() calls in tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj32ABZLZ2a6TPTyvYrRkg
|
Warning Review limit reachedNext included review available in 41 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
일반화: 앞 코멘트의 NVIDIA는 예시일 뿐입니다. 모든 KV credential은 독립 provider-account/catalog boundary입니다. 같은 vendor/endpoint/model ID라도 서로 다른 key의 모델·권한·가격·privacy·가용성·실패 상태가 같다고 추정하거나 dedupe하지 마십시오. 개념은 제거하며, 논리적 모델 동등성과 latency/quality evidence 공유는 오직 operator가 명시한 으로만 성립합니다. |
|
Reviewed against this PR's diff — it doesn't touch dedup, credential-equivalence, or endpoint-racing logic (it's scoped to bounding Checked the existing code against the principle raised: Generated by Claude Code |
|
Generated by Claude Code |
…ability gap Documents PR #939's fix and the separate, larger orchestrator/free gap found while root-causing .github#1463's live Strix failure (tracked as issue #940) -- a single model that rejects multi-tool- call requests hard-fails the whole request with no failover, and there's no capability signal to exclude it from candidate selection. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj32ABZLZ2a6TPTyvYrRkg
|
최종 일반 계약: 모든 KV credential은 독립 provider-account/catalog boundary입니다. 같은 vendor, endpoint, model ID라도 서로 다른 key의 모델, 권한, 가격, privacy, 가용성, 실패 상태를 같다고 추정하거나 dedupe하지 않습니다. provider family는 없으며, 논리적 모델 동등성과 latency/quality evidence 공유는 오직 operator가 명시한 model_group으로만 성립합니다. |
|
Understood — acting on this now as its own fix, separate from this PR's scope. Found the actual violation: I'll remove the heuristic (treat every KV credential/provider_name as its own independent family — no collapsing) at all three call sites, update the tests that currently assert the collapsing behavior, and where removing it changes real operational tolerance ( Generated by Claude Code |
|
Propagation update: PR #941 is now merged to main at 42da1d5. This PR branch conflicts with automatic base update, so its implementation must preserve the main contract: every KV credential account is discovered independently; vendor or endpoint identity does not imply model equivalence; only explicit model_group membership shares routing evidence; peak observed RPM and TPM remain measured per account-model route. The protected base already enforces this contract even before this branch resolves its conflicts. |
…removal #941 removed _provider_family() (nvidia_nim/nvidia_nim_sub collapsing) but didn't touch tests/test_discovery_bootstrap_selection.py, leaving test_bootstrap_selector_treats_nim_primary_and_sub_as_one_outage_domain asserting the now-removed collapsing behavior. Updated to assert the correct independent-provider behavior. Also folds this PR's logging additions to match #941's "account="-prefixed message convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj32ABZLZ2a6TPTyvYrRkg
…iscovery-deadline # Conflicts: # tests/test_discovery_bootstrap_selection.py
…iscovery-deadline # Conflicts: # contextual_orchestrator/model_discovery.py
…iscovery-deadline # Conflicts: # contextual_orchestrator/model_discovery.py
|
Generated by Claude Code |
|
Root cause: a hardcoded Generated by Claude Code |
|
Cross-PR integration contract: routing identity is provider-neutral |
|
Superseded by #971 and the organization no-wall-clock-deadline policy. This branch adds a shared elapsed-time deadline to OpenRouter free/ZDR discovery; #971 already discovers concrete zero-priced model IDs while excluding the aggregate |
Why
_openrouter_free_model_endpoints()(incontextual_orchestrator/model_discovery.py) fetches per-model/endpointsdata for every zero-priced OpenRouter model through an 8-workerThreadPoolExecutor, with a per-requesttimeout. That per-request bound alone still lets total wall time scale with the free-catalog size:ceil(len(model_ids) / 8)sequential timeout waves. At today's ~94-model free catalog, that's up to12 * 15s = 180sfor this one enrichment step alone.This was flagged by Devin Review on
ContextualWisdomLab/.github#1463(thread here):.github'scontextual-orchestratorreview sidecar has a 180s startup watchdog covering discovery + catalog build + preflight combined (scripts/ci/contextual_orchestrator_review_sidecar.sh's own comments already track this as a known, tight budget — seeContextualWisdomLab/.github#1455). This single OpenRouter enrichment call could exhaust that entire budget on its own in the worst case, on top of the ~6 other sequential discovery calls.Confirmed the function is new relative to the sidecar's old vendored pin (
git log -S "_openrouter_free_model_endpoints"→ one commit,6376d85), so this is a genuine regression risk introduced since that pin was last bumped — not a pre-existing characteristic Devin misattributed.Live evidence:
.github#1463's own Strix run (33348306414) shows the sidecar provisioning step completed successfully (~5m41s including clone+install, well under the watchdog) — so this is hardening against a worst-case tail risk, not a fix for an observed outage.What changed
_openrouter_free_model_endpoints(): bounded to one overalltimeout-second deadline viaconcurrent.futures.wait(futures, timeout=timeout)instead ofexecutor.map()(which has no total-time bound and blocks thewith-block exit on every submitted task regardless). Any model whose fetch hasn't completed by the shared deadline is reported as unmapped (None) — this is best-effort provider-privacy enrichment, not required for a model to be discovered.executor.shutdown(wait=False, cancel_futures=True)avoids blocking on stragglers.test_openrouter_free_model_endpoint_fetch_bounded_by_one_overall_deadline) simulates 200 permanently-hanging models and asserts total wall time stays under 2s (vs. an unbounded ~10s+ observed on the pre-fix code in a RED check).--log-level/CONTEXTUAL_ORCHESTRATOR_LOG_LEVEL, off by default): per-provider and per-fetch timing/counts inmodel_discovery.py, wired through__main__.py. Never logs anapi_keyor provider payload/content — only identifiers, counts, and elapsed time. This gives operators visibility into exactly the kind of startup-budget question this PR investigates. Caught and fixed a real bug in my first pass: an unconditionalsetLevelcall leaked global logger state across this repo's many in-processmain()calls in tests, breakingtest_telemetry.py'scaplog.at_level("DEBUG")— fixed so_configure_logging()is a true no-op unless verbosity is actually requested.Verification
python -m pytest tests -q(deselecting one pre-existing, unrelated environment gap —test_psychometric_routing.py'sfast_mlsirmimport isn't installed in this sandbox venv):2803 passed, 1 skipped.interrogate contextual_orchestrator/model_discovery.py contextual_orchestrator/__main__.py: 100% docstring coverage on both changed files.Generated by Claude Code