Skip to content

feat(libsy): add minimal Python run API#95

Merged
nachiketb-nvidia merged 4 commits into
mainfrom
nachiketb/minimal-libsy-python-api
Jul 20, 2026
Merged

feat(libsy): add minimal Python run API#95
nachiketb-nvidia merged 4 commits into
mainfrom
nachiketb/minimal-libsy-python-api

Conversation

@nachiketb-nvidia

@nachiketb-nvidia nachiketb-nvidia commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Why

The typed protocol approach in #88 duplicates the Rust IR across thousands of lines of PyO3 wrappers and stubs. Python only needs to configure clients, select a Rust-owned algorithm, and run it. Maintaining a class-for-class mirror is unnecessary for that use case.

This replaces that design with a deliberately small aggregate-only API. It does not expose Step, run_stream, protocol classes, custom Python algorithms, or optional target clients.

What

  • exposes switchyard.libsy.Algorithm with only run(request)
  • exposes required-client LlmTarget(name, client) values
  • defines a structural Python LlmClient protocol with async call(request, *, target)
  • provides algorithms.noop() and algorithms.random(targets) factories
  • accepts requests and returns decisions/responses as dictionaries
  • buffers any internal Rust response stream before returning to Python
  • adds a stable snake_case/tagged Serde dictionary shape for the neutral IR
  • includes a runnable example covering no-op and two-target random routing

The complete implementation, typing surface, example, and tests add 507 lines.

How

pythonize converts dictionaries directly to and from the Serde-owned switchyard-protocol types. A small Rust adapter invokes the configured Python client's async call method, parses its aggregate response dictionary, and lets libsy's existing Algorithm::run drive the algorithm to completion.

Each target requires a client because Python has no host-driven step stream with which to fulfill an unserved call.

What to review

  • whether the two-class public surface is the right minimum
  • the Python client contract: call(request, *, target)
  • the neutral IR's snake_case/tagged Serde dictionary representation
  • GIL and async ownership around the Python client adapter
  • the explicit decision to aggregate responses and omit streaming

Validation

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • uv run ruff check .
  • uv run mypy switchyard switchyard_rust/libsy.py
  • focused binding tests: 7 passed
  • example: Ruff, mypy, and local execution passed
  • hermetic non-integration Python suite: 2001 passed, 12 skipped, 43 deselected

@nachiketb-nvidia
nachiketb-nvidia requested a review from a team as a code owner July 20, 2026 17:06
Signed-off-by: nachiketb <nachiketb@nvidia.com>
@nachiketb-nvidia
nachiketb-nvidia force-pushed the nachiketb/minimal-libsy-python-api branch from ced9e28 to 13590af Compare July 20, 2026 17:09
Signed-off-by: nachiketb <nachiketb@nvidia.com>
@nachiketb-nvidia
nachiketb-nvidia force-pushed the nachiketb/minimal-libsy-python-api branch from 13590af to 8bf53fb Compare July 20, 2026 17:10
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds Rust-backed libsy algorithms to the Python API, adapts Python async LLM clients, introduces typed Python surfaces and error mapping, and updates LLM serde representations for snake_case and tagged dictionary formats.

Changes

Libsy Python bindings

Layer / File(s) Summary
LLM serde contracts
crates/libsy-protocol/src/llm.rs
LLM enums now use snake_case and tagged JSON shapes, with defaults for selected request and response fields and serde coverage for Python dictionary representations.
Binding foundation and error registration
crates/switchyard-py/Cargo.toml, crates/switchyard-py/src/{errors.rs,lib.rs,py_serde.rs}
The Python extension adds the libsy crate, generic Python↔Serde conversion helpers, LibsyError, and libsy module registration.
Rust-backed algorithm API
crates/switchyard-py/src/libsy_bindings.rs
Python LLM clients, targets, algorithms, noop, and random are connected to Rust execution and response conversion.
Python package surface
switchyard/libsy/*, switchyard_rust/libsy.*
Python package modules, lazy native exports, protocols, and type stubs define the public libsy API.
Python binding validation
tests/test_libsy_minimal_bindings.py
Tests cover routing, no-op execution, API shape, target and request validation, empty target rejection, and error propagation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Poem

A rabbit hops through Rusty code,
With tagged-up JSON on the road.
Python clients call, targets dance,
Algorithms run with a whisker’s chance.
Errors wear a Libsy cloak—
Tests cheer softly: “It works!” 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a minimal Python API for running libsy algorithms.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/switchyard-py/src/libsy_bindings.rs (1)

175-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document why Python errors are erased here.

This helper intentionally discards the original PyErr type and traceback to cross the BoxError boundary; document that behavior and its later conversion to LibsyError.

As per coding guidelines, add concise Rust comments or /// documentation for non-obvious private helpers.

🤖 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 `@crates/switchyard-py/src/libsy_bindings.rs` around lines 175 - 177, Document
the private helper boxed_python_error with a concise Rust comment explaining
that it erases the original PyErr type and traceback when crossing the BoxError
boundary, and that the resulting error is later converted to LibsyError.

Source: Coding guidelines

🤖 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 `@crates/switchyard-py/src/libsy_bindings.rs`:
- Around line 82-90: Update LlmTarget::new in
crates/switchyard-py/src/libsy_bindings.rs (lines 82-90) to validate that
client.call is asynchronous and accepts the async call(request, *, target)
contract, rejecting incompatible implementations during construction. Add
regression tests in tests/test_libsy_minimal_bindings.py (lines 77-82) covering
both a synchronous callable and a callable with an incompatible signature.

In `@switchyard_rust/libsy.py`:
- Around line 15-18: Preserve LibsyError’s SwitchyardRuntimeError type across
both typing surfaces: in switchyard_rust/libsy.py lines 15-18, type-import
SwitchyardRuntimeError and annotate LibsyError as type[SwitchyardRuntimeError];
in switchyard_rust/libsy.pyi line 17, import SwitchyardRuntimeError and declare
LibsyError as inheriting from it.

---

Nitpick comments:
In `@crates/switchyard-py/src/libsy_bindings.rs`:
- Around line 175-177: Document the private helper boxed_python_error with a
concise Rust comment explaining that it erases the original PyErr type and
traceback when crossing the BoxError boundary, and that the resulting error is
later converted to LibsyError.
🪄 Autofix (Beta)

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: 142efe86-56f1-4c3e-89e1-42b225cd0d6f

📥 Commits

Reviewing files that changed from the base of the PR and between 88cbc24 and ced9e28.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (11)
  • crates/libsy-protocol/src/llm.rs
  • crates/switchyard-py/Cargo.toml
  • crates/switchyard-py/src/errors.rs
  • crates/switchyard-py/src/lib.rs
  • crates/switchyard-py/src/libsy_bindings.rs
  • crates/switchyard-py/src/py_serde.rs
  • switchyard/libsy/__init__.py
  • switchyard/libsy/algorithms.py
  • switchyard_rust/libsy.py
  • switchyard_rust/libsy.pyi
  • tests/test_libsy_minimal_bindings.py

Comment thread crates/switchyard-py/src/libsy_bindings.rs
Comment thread switchyard_rust/libsy.py Outdated
@grahamking

Copy link
Copy Markdown
Contributor

I go this from Claude. Could you check it?

switchyard_rust/libsy.py duplicates the LlmClient Protocol that's already in libsy.pyi. The runtime definition is unused (Protocols are typing-only unless @runtime_checkable). Drop the runtime LlmClient class and keep only the .pyi stub; adjust all accordingly. Saves ~15 lines.

Comment thread crates/switchyard-py/src/py_serde.rs
@nachiketb-nvidia
nachiketb-nvidia force-pushed the nachiketb/minimal-libsy-python-api branch from 469a13b to 5438255 Compare July 20, 2026 19:41
Signed-off-by: nachiketb <nachiketb@nvidia.com>
Signed-off-by: nachiketb <nachiketb@nvidia.com>
@nachiketb-nvidia
nachiketb-nvidia force-pushed the nachiketb/minimal-libsy-python-api branch from 5438255 to 7df1473 Compare July 20, 2026 19:42
@nachiketb-nvidia
nachiketb-nvidia merged commit 0d494f4 into main Jul 20, 2026
16 checks passed
@nachiketb-nvidia
nachiketb-nvidia deleted the nachiketb/minimal-libsy-python-api branch July 20, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants