Skip to content

feat(libsy): core interfaces and examples#17

Merged
messiaen merged 1 commit into
mainfrom
grclark/feat/libsy-routers-poc
Jul 14, 2026
Merged

feat(libsy): core interfaces and examples#17
messiaen merged 1 commit into
mainfrom
grclark/feat/libsy-routers-poc

Conversation

@messiaen

@messiaen messiaen commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds libsy — a lightweight, embeddable Rust SDK for multi-LLM agent
optimization, with routing as the first use case. An algorithm decides —
statefully, using more than the request — which model(s) to call and how, and
never performs the call itself ("ask, don't call"), keeping the core provider-
and transport-agnostic. Ships with reference routers, runnable examples, and a
demo HTTP proxy that wires libsy routing into Switchyard's existing server /
translation crates.

What's included

  • Core model (crates/libsy/src/lib.rs)
    • OrchAlgo (the algorithm) + MultiLlmOrchestrator (the driver).
    • LlmTarget targets that either serve a call (carry an
      LlmClient) or offload it as a promise the host fulfills.
    • DecisionTrace trait-object decisions (uniform to read, downcast when the
      algorithm is known).
    • Two run modes: orchestrate_direct (all targets self-serve → (trace, response)) and streaming orchestrate (offloaded calls surface as
      CallLlm promises; set_response(Ok/Err) propagates results and errors).
  • Concurrency — one shared Arc<dyn OrchAlgo> with no lock; process_request(&self)
    runs requests in parallel; each orchestrate run gets its own promise channel
    via a task-local, so concurrent requests' offloaded calls never cross.
  • Reference algorithms
    • RandomOrchAlgo (rand.rs) — uniform random, single call.
    • LlmClassifierOrchAlgo (llm_class.rs) — classify → strong/weak, fail-open.
    • EnsembleOrchAlgo (ensemble.rs) — stateful fan-out → judge → commit.
  • Examplesresearch_agent.rs (self-serving / orchestrate_direct) and
    research_agent_core.rs (offloaded / streaming).
  • DocsREADME.md (code-first scope) and DESIGN.md (narrative design).
  • Demo proxy (demo/libsy-proxy) — a real HTTP proxy: Switchyard's server +
    translation crates handle the OpenAI / Anthropic / Responses APIs and format
    translation, while all routing is libsy's classifier algorithm. Shows
    libsy embedded in a production-shaped I/O stack without depending on
    Switchyard's own routing.

Architecture

request → OrchAlgo.process_request (N × target.call) → (DecisionTrace, response)

serve (has client)│ or offload → CallLlm promise → host fulfills

The core owns no HTTP; the single piece of I/O it doesn't own is the LlmClient
trait the host implements. The demo proxy supplies one backed by Switchyard's
OpenAI-compatible backend.

Testing

  • cargo test -p libsy — unit tests covering the reference algorithms, parallel
    request processing (multi-thread barrier), and offload error propagation.
  • demo/libsy-proxy verified end-to-end: /health, /v1/models, and routed
    POST /v1/chat/completions + /v1/messages.

Scope note

This branch is far ahead of the current public main (05a4e979), so the raw
diff also includes a large amount of already-landed internal work unrelated to
libsy (stage_router rename, skill-distillation, latency-service, release infra,
etc.). Recommend retargeting the PR base to an up-to-date main (or splitting
the libsy POC into its own PR) so review focuses on the ~10 new libsy/demo files.
Closes #

How tested

  • uv run ruff check . clean
  • uv run mypy switchyard clean
  • uv run pytest tests/ green
  • Manual smoke (describe what was run)

Checklist

  • One class per file; filename = snake_case of the primary class.
  • New public symbols exported from switchyard/__init__.py.__all__ if intended for downstream use.
  • Unit tests added for new components / bug fixes.
  • README / --help updated if customer-facing surface changed.
  • Commits signed off (Signed-off-by: Your Name <email>) per the DCO.

Notes for reviewers

Anything reviewers should pay extra attention to — risky paths, follow-up tickets, intentional trade-offs.

Summary by CodeRabbit

  • New Features

    • Added a new routing framework for choosing between multiple model backends.
    • Introduced a classifier-based router, a random router, and an ensemble router for model selection.
    • Added a demo proxy that accepts common request formats and forwards them through the router.
    • Included example apps showing both direct and streamed orchestration flows.
  • Documentation

    • Added overview and design docs for the new routing library and usage patterns.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://NVIDIA-NeMo.github.io/Switchyard/pr-preview/pr-17/

Built to branch gh-pages at 2026-07-09 16:58 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Comment thread crates/libsy/DESIGN.md Outdated
@messiaen messiaen requested a review from sabhatinas July 9, 2026 17:58
@messiaen messiaen changed the title Grclark/feat/libsy routers poc feat(libsy): multi-LLM routing SDK (OrchAlgo / MultiLlmOrchestrator) + demo proxy Jul 9, 2026
@messiaen messiaen force-pushed the grclark/feat/libsy-routers-poc branch from 81c5d5c to 9261a09 Compare July 9, 2026 19:18
@messiaen messiaen marked this pull request as ready for review July 9, 2026 20:48
@messiaen messiaen requested a review from a team as a code owner July 9, 2026 20:48
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a new libsy Rust crate implementing multi-LLM orchestration with pluggable routing algorithms (random, classifier-threshold, ensemble-judge), promise-based offload channels, examples, and design docs. Adds a demo/libsy-proxy binary that wires classifier-based routing into switchyard's OpenAI-compatible passthrough backend.

Changes

libsy orchestration crate and proxy demo

Layer / File(s) Summary
Workspace registration
Cargo.toml
Adds crates/libsy and demo/libsy-proxy to workspace members.
Core orchestrator types and manifest
crates/libsy/Cargo.toml, crates/libsy/src/lib.rs
Defines request/response types, DecisionTrace, promise-based offload channels, LlmClient/LlmTarget/LlmTargetSet, OrchAlgo/OrchAlgoBuilder, and MultiLlmOrchestrator with streaming and direct execution, plus tests.
Random routing algorithm
crates/libsy/src/rand.rs
Implements RandomOrchAlgo selecting targets uniformly at random with RandomDecision trace, builder, and tests.
Classifier routing algorithm
crates/libsy/src/llm_class.rs
Implements LlmClassifierOrchAlgo scoring requests via a classifier model then routing strong/weak by threshold, with builder and tests.
Ensemble routing algorithm
crates/libsy/src/ensemble.rs
Implements EnsembleOrchAlgo with concurrent candidate fan-out, judge-based selection, mutex-protected per-session commit state, builder, and concurrency tests.
Example research agents
crates/libsy/examples/research_agent.rs, crates/libsy/examples/research_agent_core.rs
Demonstrates client-backed direct orchestration and client-less streaming orchestration with promise fulfillment.
Crate design documentation
crates/libsy/DESIGN.md, crates/libsy/README.md
Documents orchestration contracts, target model, decision trace, algorithms, and concurrency semantics.
libsy-proxy demo service
demo/libsy-proxy/Cargo.toml, demo/libsy-proxy/src/main.rs
Implements an HTTP proxy translating OpenAI/Anthropic/Responses requests, routing via libsy's classifier, calling switchyard's passthrough backend, and returning responses with routing metadata headers.

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

Poem

A rabbit hops through crates anew,
Routing prompts strong and weak, on cue 🐇
Judges pick, ensembles commit,
Proxies forward, bit by bit,
Through streams and promises, hop-hop we go —
Switchyard tracks lit up in a row! 🚦

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main addition of libsy core interfaces and accompanying examples.

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.

❤️ Share

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: 1

🧹 Nitpick comments (5)
demo/libsy-proxy/src/main.rs (2)

184-193: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

completion_text doesn't handle array-type content.

Value::as_str returns None when content is an array of content blocks (e.g. [{"type":"text","text":"..."}]), yielding an empty completion via unwrap_or_default(). For a demo this is likely fine, but if the upstream ever returns array-type content the proxy will silently produce blank responses. Consider reusing content_to_text as a fallback.

Proposed fix
 fn completion_text(body: &Value) -> Option<String> {
     body.get("choices")
         .and_then(Value::as_array)
         .and_then(|choices| choices.first())
         .and_then(|choice| choice.get("message"))
         .and_then(|message| message.get("content"))
-        .and_then(Value::as_str)
-        .map(str::to_string)
+        .and_then(|content| {
+            content.as_str().map(str::to_string).or_else(|| content_to_text(content))
+        })
 }
🤖 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 `@demo/libsy-proxy/src/main.rs` around lines 184 - 193, The completion_text
helper only reads string content, so it returns nothing when the assistant
message content is an array of blocks. Update completion_text to fall back to
content_to_text when message.content is not a string, using the existing
content_to_text helper and keeping the current choices/message traversal intact
so array-based responses still produce text.

150-166: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

extract_prompt ignores system prompts and prior conversation turns.

Only the last role: "user" message is extracted; role: "system" messages and earlier user turns are discarded. For a demo this is acceptable, but routing quality may degrade for multi-turn conversations where the system prompt or earlier context determines complexity. Consider concatenating system + user messages into the prompt passed to the classifier, or document this as a known demo limitation.

🤖 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 `@demo/libsy-proxy/src/main.rs` around lines 150 - 166, `extract_prompt`
currently only returns the last user message and drops system or earlier
conversation context, which can reduce classifier quality for multi-turn chats.
Update the prompt extraction logic in `extract_prompt` to either combine
relevant `system` plus prior `user` turns into the returned prompt, or
explicitly preserve the current behavior but document it as a known demo
limitation near `extract_prompt` and the inbound body handling.
crates/libsy/Cargo.toml (2)

20-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant tokio dev-dependency entry.

The main dependency already enables features = ["full"], which is a superset of ["macros", "rt"]. Cargo unifies features across dependencies and dev-dependencies, so this entry adds nothing.

♻️ Proposed fix
 [dev-dependencies]
-tokio = { version = "1", features = ["macros", "rt"] }
🤖 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/libsy/Cargo.toml` around lines 20 - 24, Remove the redundant tokio
entry from the dev-dependencies section in Cargo.toml because the main tokio
dependency already enables a superset of the needed features. Keep the existing
tokio and tokio-stream setup, but delete the extra dev-dependency specification
so Cargo only resolves tokio once.

14-21: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Narrow the Tokio feature set. The main tokio dependency can drop full here; the library/examples only need macros, rt, sync, and time, and the #[tokio::test(flavor = "multi_thread")] cases can keep rt-multi-thread in dev-dependencies.

🤖 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/libsy/Cargo.toml` around lines 14 - 21, The tokio dependency is using
an overly broad feature set, so narrow it in the Cargo.toml dependency list for
libsy. Update the main tokio entry to use only the features needed by the
library and examples (macros, rt, sync, time), and keep rt-multi-thread only
where the #[tokio::test(flavor = "multi_thread")] cases need it under
dev-dependencies.
crates/libsy/src/lib.rs (1)

485-490: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider making channel capacity configurable.

Both the stream channel (line 485) and promise channel (line 490) use a hardcoded capacity of 10. For algorithms that fan out to many models concurrently (e.g., ensemble with >10 candidates), this creates backpressure. While not a deadlock (the consumer drains CallLlm steps), a configurable capacity or a builder pattern would give hosts control over buffering.

🤖 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/libsy/src/lib.rs` around lines 485 - 490, The `orchestrate` channel
setup currently hardcodes capacity 10 for both `stream_tx/stream_rx` and
`promise_tx/promise_rx`, which limits buffering under high fan-out workloads.
Update the `orchestrate` flow in `libsy::lib` to take a configurable channel
capacity (or expose it through the existing builder/config path), then use that
value when creating both `tokio::sync::mpsc::channel` instances so hosts can
tune backpressure behavior.
🤖 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 `@demo/libsy-proxy/src/main.rs`:
- Around line 210-217: `build_orchestrator` currently uses
`std::env::var("ANTHROPIC_API_KEY").ok()`, which hides a missing key and lets
the proxy start in a broken state. Update `build_orchestrator` to validate
`ANTHROPIC_API_KEY` at startup by requiring the env var before constructing
`EndpointConfig`, and return an explicit error with a clear message if it is
absent or empty. Keep the fix localized around `build_orchestrator` and the
`EndpointConfig` initialization so startup fails fast instead of deferring the
auth failure to request time.

---

Nitpick comments:
In `@crates/libsy/Cargo.toml`:
- Around line 20-24: Remove the redundant tokio entry from the dev-dependencies
section in Cargo.toml because the main tokio dependency already enables a
superset of the needed features. Keep the existing tokio and tokio-stream setup,
but delete the extra dev-dependency specification so Cargo only resolves tokio
once.
- Around line 14-21: The tokio dependency is using an overly broad feature set,
so narrow it in the Cargo.toml dependency list for libsy. Update the main tokio
entry to use only the features needed by the library and examples (macros, rt,
sync, time), and keep rt-multi-thread only where the #[tokio::test(flavor =
"multi_thread")] cases need it under dev-dependencies.

In `@crates/libsy/src/lib.rs`:
- Around line 485-490: The `orchestrate` channel setup currently hardcodes
capacity 10 for both `stream_tx/stream_rx` and `promise_tx/promise_rx`, which
limits buffering under high fan-out workloads. Update the `orchestrate` flow in
`libsy::lib` to take a configurable channel capacity (or expose it through the
existing builder/config path), then use that value when creating both
`tokio::sync::mpsc::channel` instances so hosts can tune backpressure behavior.

In `@demo/libsy-proxy/src/main.rs`:
- Around line 184-193: The completion_text helper only reads string content, so
it returns nothing when the assistant message content is an array of blocks.
Update completion_text to fall back to content_to_text when message.content is
not a string, using the existing content_to_text helper and keeping the current
choices/message traversal intact so array-based responses still produce text.
- Around line 150-166: `extract_prompt` currently only returns the last user
message and drops system or earlier conversation context, which can reduce
classifier quality for multi-turn chats. Update the prompt extraction logic in
`extract_prompt` to either combine relevant `system` plus prior `user` turns
into the returned prompt, or explicitly preserve the current behavior but
document it as a known demo limitation near `extract_prompt` and the inbound
body handling.
🪄 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: eb1842e9-fae5-42b4-81b4-8b9d0ea849d5

📥 Commits

Reviewing files that changed from the base of the PR and between 072ae3e and 7427288.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • crates/libsy/Cargo.toml
  • crates/libsy/DESIGN.md
  • crates/libsy/README.md
  • crates/libsy/examples/research_agent.rs
  • crates/libsy/examples/research_agent_core.rs
  • crates/libsy/src/ensemble.rs
  • crates/libsy/src/lib.rs
  • crates/libsy/src/llm_class.rs
  • crates/libsy/src/rand.rs
  • demo/libsy-proxy/Cargo.toml
  • demo/libsy-proxy/src/main.rs

Comment thread demo/libsy-proxy/src/main.rs Outdated
@messiaen messiaen force-pushed the grclark/feat/libsy-routers-poc branch from 1df67dc to aa72aa0 Compare July 10, 2026 17:06
Comment thread crates/libsy-examples/src/llm_class.rs
Comment thread crates/libsy/src/lib.rs Outdated
Comment thread crates/libsy/src/lib.rs
Signed-off-by: Greg Clark <grclark@nvidia.com>
@messiaen messiaen force-pushed the grclark/feat/libsy-routers-poc branch from fcdc0ad to eab29f6 Compare July 14, 2026 14:20
@messiaen messiaen changed the title feat(libsy): multi-LLM routing SDK (OrchAlgo / MultiLlmOrchestrator) + demo proxy feat(libsy): core interfaces and examples Jul 14, 2026
@messiaen messiaen merged commit bfabc3b into main Jul 14, 2026
15 checks passed
@messiaen messiaen deleted the grclark/feat/libsy-routers-poc branch July 14, 2026 14:28
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.

4 participants