How Selector Tools Route Multi‑Provider Capabilities in OpenMontage
OpenMontage selector tools route multi-provider capabilities by auto-discovering providers, filtering candidates by capability, scoring them against task context, and executing the best match while preserving user preferences and fallback options.
OpenMontage implements a sophisticated routing layer that decouples capability-level requests from concrete provider implementations. The selector pattern enables dynamic multi-provider routing without hard-coding provider names, allowing new video, image, or text-to-speech providers to be added simply by dropping new tool files into the tools/ directory. This architecture ensures that requests like "create a video" automatically reach the most appropriate backend based on current availability, task requirements, and user preferences.
The Selector Pattern Architecture
The selector architecture in OpenMontage relies on a thin orchestration layer that sits between the application logic and provider-specific implementations. Each capability—such as video generation, image creation, or text-to-speech—has its own selector class (e.g., VideoSelector) that manages a pool of provider tools.
At initialization, selectors invoke ToolRegistry.ensure_discovered() to populate the provider pool. This method walks the tools/ package, imports every module, and registers any concrete subclass of BaseTool. This auto-discovery mechanism means developers can add new providers by creating new tool files without modifying selector code.
Step‑by‑Step Routing Process
The routing logic follows a deterministic pipeline from discovery to execution.
1. Auto‑Discovery of Providers
When a selector is instantiated, it triggers ToolRegistry.ensure_discovered() defined in tools/tool_registry.py. This method scans the filesystem and imports modules to register all available tools, making them available for the selection process.
2. Filtering Capable Candidates
The selector examines incoming payloads and removes providers that cannot satisfy the requested operation. In tools/video/video_selector.py, the _filter_candidates method (lines 84‑132) eliminates providers missing required model hints or lacking support for custom workflows when such capabilities are requested.
3. Building the Scoring Context
Selectors normalize task context through _prepare_task_context, generating a structured representation from the prompt, capability, and operation type. This context feeds into rank_providers (defined in lib/scoring.py), which returns a list of ProviderScore objects ordered by weighted suitability scores.
4. Honoring User Preferences
If the caller specifies a preferred_provider, the selector checks whether that provider's score falls within preferred_provider_gap (default 0.15) of the top score. As implemented in tools/video/video_selector.py (lines 21‑33), if the gap condition is met, the preferred provider wins; otherwise, the highest-scored provider executes.
5. Tool Selection and Schema Adaptation
The selector maps rankings to actual tool instances via _tool_for and selects the best candidate through _select_best_tool. Before delegation, the selector adapts input schemas—rewriting keys like prompt to query for stock tools or uploading reference images when providers require URLs instead of local paths.
6. Execution and Result Enrichment
After the provider's execute method runs, the selector annotates the result with metadata including selected_tool, selected_provider, scoring explanations, and alternative providers considered. This enrichment appears in tools/video/video_selector.py (lines 52‑66).
Provider Preference and Fallback Handling
OpenMontage selectors implement graceful degradation through intelligent fallback mechanisms. The fallback_tools and fallback_tools_for properties supply static fallback lists, while the routing logic automatically drops image-only fallbacks for operations requiring motion.
If a provider becomes unavailable due to missing API keys or network issues, the selector automatically falls back to the next best-scoring option. This ensures robust multi-provider routing that maintains service continuity even when individual providers fail.
Practical Code Examples
Basic Text‑to‑Video Routing
from tools.video.video_selector import VideoSelector
result = VideoSelector().execute({
"prompt": "A sunrise over a misty forest",
"operation": "text_to_video",
"preferred_provider": "auto", # let the selector choose
})
print(result.data["selected_provider"]) # e.g. "fal"
print(result.data["selection_reason"]) # human‑readable scoring explanation
Forcing a Specific Provider
result = VideoSelector().execute({
"prompt": "A futuristic city skyline at night",
"operation": "text_to_video",
"preferred_provider": "heygen",
"preferred_provider_gap": 0.2, # allow a slightly larger gap
})
Routing to Workflow‑Capable Providers
result = VideoSelector().execute({
"operation": "text_to_video",
"workflow_path": "my_workflow.json",
"output_node": "OUTPUT_0",
})
# The selector will pick a provider that advertises `custom_workflow` support.
Inspecting the Provider Matrix
matrix = VideoSelector().provider_matrix
# {'fal': {'tool': 'fal_video', 'strength': 'high‑quality video generation'},
# 'heygen': {'tool': 'heygen_video', 'strength': 'AI avatar video'},
# ...}
Key Source Files
tools/video/video_selector.py– Core selector implementation for the video_generation capability, containing_filter_candidates,_prepare_task_context, and preference handling logic.tools/tool_registry.py– Auto-discovers and registers all provider tools viaensure_discovered(), supplying lookup APIs used by selectors.tools/base_tool.py– Defines theBaseToolcontract, status enums, and common metadata accessed by selectors during the routing process.lib/scoring.py– Implementsrank_providersandProviderScoreclasses used for weighted provider ranking.tests/tools/test_video_selector_routing.py– Verifies routing logic, preference handling, and fallback behavior.docs/ARCHITECTURE.md– High-level overview of the selector/provider architecture (section "Selector Pattern").
Summary
- Dynamic Discovery: Selectors automatically register new providers by scanning the
tools/directory, requiring no code changes to support new backends. - Intelligent Filtering: The
_filter_candidatesmethod removes incapable providers based on operation requirements and model hints. - Preference Awareness: User-specified providers are respected when competitive, using a configurable
preferred_provider_gaptolerance. - Schema Adaptation: Selectors rewrite input keys and handle media uploads to match provider-specific requirements before execution.
- Graceful Degradation: Automatic fallback to next-best providers ensures reliability when primary options fail.
Frequently Asked Questions
How does OpenMontage discover new providers automatically?
OpenMontage uses ToolRegistry.ensure_discovered() in tools/tool_registry.py to walk the tools/ package at runtime. It imports every module and registers any concrete subclass of BaseTool, making new providers immediately available to selectors without code modifications.
What happens if my preferred provider cannot handle the request?
If the preferred provider is filtered out during capability matching, or if its score falls outside the preferred_provider_gap (default 0.15) of the top-scoring provider, the selector automatically chooses the highest-scoring capable provider instead. The selection reason in the result metadata explains this decision.
Can selectors handle custom workflows like ComfyUI?
Yes. When a request includes workflow_path and output_node parameters, the VideoSelector._filter_candidates method restricts candidates to providers advertising custom_workflow support. This ensures custom workflows route only to compatible backends.
Where is the provider scoring logic implemented?
The scoring engine lives in lib/scoring.py, which provides the rank_providers function. This function generates ProviderScore objects based on normalized task context from _prepare_task_context, enabling weighted comparison of provider suitability for specific operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →