feat(python): expose the custom N-target classifier to Python - #365
feat(python): expose the custom N-target classifier to Python#365rbehal wants to merge 1 commit into
Conversation
Binds LlmClassifierConfig::Custom (CustomClassifierConfig + the TargetSelector policy) as libsy.custom_classifier(judge_target, targets, *, default_target, config): schema-driven routing across two or more labeled targets, with the judge's schema-validated verdict selecting a label through a JSON Pointer and unusable verdicts falling open to default_target. The capability already exists in the Rust core and the server's TOML mode; this makes it reachable from Python-embedded hosts that route across more than two tiers. Signed-off-by: rbehal <rahulbehal01@hotmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
WalkthroughThe pull request adds Python bindings for schema-driven custom classifier routing. It introduces configuration and factory APIs, exports them through the Python package, wires labeled targets and fallback behavior, and adds routing and validation tests. ChangesCustom classifier routing
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@switchyard_rust/libsy.py`:
- Around line 68-80: Document the public API symbols CustomClassifierConfig, its
__init__ constructor, and custom_classifier with concise triple-quoted
docstrings. Describe JSON Pointer-based selection, the default_target fallback
behavior, relevant invariants, and error behavior without changing the
implementation.
In `@tests/test_libsy_custom_classifier.py`:
- Around line 77-87: Annotate the build function with the Algorithm return type,
using the existing Algorithm symbol available in the module, while leaving its
custom_classifier construction unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 5c1a8c76-06f4-448a-9c9e-385809728cff
📒 Files selected for processing (5)
crates/switchyard-py/src/libsy_bindings.rsswitchyard/libsy/__init__.pyswitchyard/libsy/algorithms.pyswitchyard_rust/libsy.pytests/test_libsy_custom_classifier.py
| @final | ||
| class CustomClassifierConfig: | ||
| def __init__( | ||
| self, | ||
| prompt: str, | ||
| response_schema: Mapping[str, object], | ||
| selector: str, | ||
| *, | ||
| session_affinity: bool = False, | ||
| message_hash_fallback: bool = False, | ||
| recent_turn_window: int | None = None, | ||
| max_output_tokens: int = 4096, | ||
| ) -> None: ... |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add docstrings for the new public API.
Add concise triple-quoted docstrings to CustomClassifierConfig, its constructor, and
custom_classifier. Document JSON Pointer selection and default_target fallback behavior.
As per coding guidelines: “Add concise triple-quoted docstrings for public functions, classes,
methods, and API entry points; document behavior, important invariants, and relevant error behavior.”
Also applies to: 111-117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@switchyard_rust/libsy.py` around lines 68 - 80, Document the public API
symbols CustomClassifierConfig, its __init__ constructor, and custom_classifier
with concise triple-quoted docstrings. Describe JSON Pointer-based selection,
the default_target fallback behavior, relevant invariants, and error behavior
without changing the implementation.
Source: Coding guidelines
| def build(judge_client: Any, clients: dict[str, EchoClient]): | ||
| return algorithms.custom_classifier( | ||
| LlmTarget("judge", judge_client), | ||
| [(lane, LlmTarget(lane, clients[lane])) for lane in LANES], | ||
| default_target="grok", | ||
| config=CustomClassifierConfig( | ||
| "Pick the best lane.", | ||
| SCHEMA, | ||
| "/model", | ||
| ), | ||
| ) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a return type to build.
Line 77 has no return annotation. Strict mypy can report no-untyped-def for this helper.
Annotate the function with the returned Algorithm type.
As per coding guidelines: “Use Python 3.12+ syntax, including X | Y union types, and maintain
comprehensive type hints; code must pass strict mypy.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_libsy_custom_classifier.py` around lines 77 - 87, Annotate the
build function with the Algorithm return type, using the existing Algorithm
symbol available in the module, while leaving its custom_classifier construction
unchanged.
Source: Coding guidelines
Binds LlmClassifierConfig::Custom (CustomClassifierConfig + TargetSelector) as custom_classifier(judge_target, targets, *, default_target, config): schema-driven routing across two or more labeled targets; unusable verdicts and judge failures fall open to default_target. Filed upstream as NVIDIA-NeMo#365. Co-authored-by: Cursor <cursoragent@cursor.com>
Binds LlmClassifierConfig::Custom (CustomClassifierConfig + TargetSelector) as custom_classifier(judge_target, targets, *, default_target, config): schema-driven routing across two or more labeled targets; unusable verdicts and judge failures fall open to default_target. Filed upstream as NVIDIA-NeMo#365. Co-authored-by: Cursor <cursoragent@cursor.com>
What
Binds the Rust core's
LlmClassifierConfig::Custom(CustomClassifierConfig+ theTargetSelectorpolicy) to Python aslibsy.custom_classifier(judge_target, targets, *, default_target, config), alongside the existingllm_task_classifier/stage_routerfactories. Includes theCustomClassifierConfigpyclass, type stubs, factory re-exports, and binding tests mirroringtest_libsy_minimal_bindings.py.Why
The custom schema-driven classifier already exists in the Rust core and is reachable from the server's TOML
mode = "custom", but not from the Python bindings, which currently expose only two-tier routing. Python-embedded hosts that route across more than two targets (our case: a five-lane model pool behind an existing product feature) currently have to cascade binary classifiers to approximate one N-way decision.Notes
default_target, matching the Rust core's semantics (covered in the tests).target_cores+ClientEntryconventions.We're integrating Switchyard as the routing foundation inside Gumloop's agent platform (decision-only, host-executed model calls) — happy to iterate on API shape if you'd prefer different ergonomics.
Made with Cursor
Summary by CodeRabbit
New Features
Bug Fixes