Skip to content

chore(libsy): lib and proto clean up - prepare for llm-client PR#76

Merged
messiaen merged 1 commit into
mainfrom
grclark/libsy-lib-proto-cleanup
Jul 16, 2026
Merged

chore(libsy): lib and proto clean up - prepare for llm-client PR#76
messiaen merged 1 commit into
mainfrom
grclark/libsy-lib-proto-cleanup

Conversation

@messiaen

@messiaen messiaen commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Cleans up the libsy / switchyard-protocol boundary on top of the streaming work:
moves the routed-call vocabulary into the protocol crate and threads request context
through the call path.

Base: libsy-streaming-support

Changes

switchyard-protocol

  • New client module owns the Decision and RoutedLlmClient traits — so a crate that
    depends only on the protocol can serve routed calls without pulling in the libsy
    orchestrator.
  • Context moves here (and carries a values map); Metadata gains a wire_format field.

libsy

  • LlmClientRoutedLlmClient, re-exported from the protocol crate along with Decision;
    common.rs removed (its Context now lives in the protocol).
  • Request context is threaded through the call path: RoutedRequest carries ctx, and
    RoutedLlmClient::call takes (ctx, request, decision).

Surrounding

  • libsy-examples (random, classifier, ensemble routers + research_agent) updated for the
    renamed trait and ctx threading.

Validation

Full workspace builds clean, clippy at zero, all tests pass.

  • 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 routing-aware language model client support, including model selection and routing decisions.
    • Added request context values and optional wire-format metadata.
    • Standardized aggregated text responses across routed calls and streaming workflows.
  • Documentation
    • Updated examples and README guidance for the routing-first client interface.
  • Improvements
    • Updated research, ensemble, classification, and randomized examples to use the new routed-call behavior.

@messiaen
messiaen requested a review from a team as a code owner July 16, 2026 16:50
@messiaen
messiaen changed the base branch from main to grclark/libsy-streaming-support July 16, 2026 16:50
@messiaen messiaen self-assigned this Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change replaces the legacy LLM client interface with routed client traits, propagates request context through offloads, normalizes example responses to aggregated text, and updates orchestration tests and documentation.

Changes

Routed LLM migration

Layer / File(s) Summary
Routed client protocol contracts
crates/libsy-protocol/src/*, crates/libsy-protocol/Cargo.toml
Adds Decision and RoutedLlmClient, expands Context and Metadata, and exports the new protocol API.
Context-aware routed orchestration
crates/libsy/src/lib.rs, crates/libsy/README.md
Carries context through RoutedRequest, updates target and driver APIs to use routed clients, and revises the client documentation.
Ensemble routing and validation
crates/libsy-examples/src/ensemble.rs
Passes context through candidate and judge calls, builds text requests, parses aggregate completions, and migrates test clients and assertions.
Routed example clients
crates/libsy-examples/examples/research_agent.rs, crates/libsy-examples/src/llm_class.rs, crates/libsy-examples/src/rand.rs
Migrates example clients and target wiring to RoutedLlmClient and extracts model output from aggregated responses.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Poem

A rabbit hops through routed streams,
With context tucked beside its dreams.
Decisions point where models run,
Aggregate text makes answers one.
“Thump!” says Bun—the migration’s done!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: cleanup across libsy and protocol to prepare the routing/client API work.
Docstring Coverage ✅ Passed Docstring coverage is 96.81% which is sufficient. The required threshold is 80.00%.
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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/switchyard-translation/src/codecs/stream.rs (1)

200-220: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the newly exposed streaming contract.

  • crates/switchyard-translation/src/codecs/stream.rs#L200-L220: add concise docs describing decode/encode direction and error fallback behavior.
  • crates/switchyard-translation/src/lib.rs#L18-L18: document that this re-export is the normalized provider-independent streaming chunk type.

As per coding guidelines, crates/**/*.rs public items must have concise comments/doc comments.

🤖 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-translation/src/codecs/stream.rs` around lines 200 - 220,
Document the public decode_stream_event and encode_stream_event functions in
crates/switchyard-translation/src/codecs/stream.rs, concisely stating their
provider-format-to-neutral and neutral-to-provider directions and that decode
failures produce an error response chunk. Document the LlmResponseChunk
re-export in crates/switchyard-translation/src/lib.rs as the normalized
provider-independent streaming chunk type.

Source: Coding guidelines

crates/libsy-protocol/src/llm.rs (1)

234-249: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new public response types.

StopReason and ResponseOutput are public API additions but currently lack rustdoc. Add concise documentation describing normalized stop reasons and response-output semantics.

Proposed documentation
+/// Provider-neutral reason why generation stopped.
 pub enum StopReason {
     EndTurn,
     MaxTokens,
     ToolUse,
     ContentFilter,
     Error,
     Unknown,
 }

+/// One role-tagged output from a buffered response.
 pub struct ResponseOutput {
     pub role: Role,
     pub content: Vec<ContentBlock>,
     pub stop_reason: Option<StopReason>,
 }

As per coding guidelines, crates/**/*.rs must add concise comments/doc comments for public items and non-obvious 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/libsy-protocol/src/llm.rs` around lines 234 - 249, Add concise rustdoc
comments to the public StopReason enum and ResponseOutput struct. Describe
StopReason as the normalized reason generation stopped, and ResponseOutput as
one normalized assistant response item, including its role, content blocks, and
optional stop reason.

Source: Coding guidelines

🧹 Nitpick comments (2)
crates/libsy-protocol/src/envelope.rs (1)

10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the public Context API.

Add concise docs explaining how values propagates and any conventions for keys. As per coding guidelines, “Add concise comments/doc comments for public items and non-obvious 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/libsy-protocol/src/envelope.rs` around lines 10 - 13, Document the
public Context struct and its values field with concise Rust doc comments,
describing how values propagate and specifying the key-naming conventions
callers should follow.

Source: Coding guidelines

crates/libsy/src/lib.rs (1)

125-140: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid cloning the entire routed request into the promise wrapper.

inner already retains the boxed RoutedRequest, so routed.clone() duplicates normalized messages, metadata, and potentially large raw_request JSON for every outstanding call. Read it from inner on demand or store the payload behind an Arc.

🤖 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 125 - 140, The CallLlmRequest::new
constructor currently clones the entire RoutedRequest into the wrapper,
duplicating potentially large payload data. Remove the owned routed field and
read the existing RoutedRequest from inner on demand through the accessors, or
retain shared ownership with Arc if needed; preserve the current payload
validation behavior without duplicating it.
🤖 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/libsy-examples/examples/streaming_agent.rs`:
- Around line 88-92: Update the chunk handling loop in streaming_agent so
LlmResponseChunk::Error is matched explicitly and propagated instead of being
ignored, while preserving text output handling. Also replace the ignored stdout
flush result with propagation of its failure from the surrounding async
function.

In `@crates/libsy-examples/src/llm_class.rs`:
- Around line 114-120: Clear stale raw_request payloads when constructing the
synthetic classifier request in llm_class.rs around lines 114-120, using None or
regenerating it with the classifier preamble. Apply the same change to the
synthetic judge request in ensemble.rs around lines 273-277, ensuring both
routed requests use their newly generated prompts instead of inherited provider
payloads.
- Around line 137-144: The response-processing algorithms incorrectly use agg(),
which ignores streaming responses. In crates/libsy-examples/src/llm_class.rs
lines 137-144, replace this with aggregate().await? before parsing the
classifier score; in crates/libsy-examples/src/ensemble.rs lines 278-289,
aggregate the judge response before parsing its selection; and in
crates/libsy-examples/src/ensemble.rs lines 334-342, aggregate candidate
responses before constructing the judge prompt and retain each aggregate as the
potential winner response.

In `@crates/libsy-protocol/src/stream.rs`:
- Around line 151-173: Update the response accumulator around the
LlmResponseChunk handling and its emission logic to retain each text, reasoning,
and tool-call content block under its streamed index, rather than merging by
type. Emit the accumulated blocks in ascending index order so interleaved
sequences such as text → tool call → text preserve their original order and can
round-trip through provider encoders.
- Around line 175-177: Update the LlmResponseChunk::MessageStop handling to only
assign stop_reason when the event provides a reason; preserve any previously
stored MaxTokens or ToolUse value when reason is absent, while retaining the
existing EndTurn mapping for explicit reasons.

In `@crates/libsy/README.md`:
- Around line 82-100: The RoutedLlmClient example in README.md is not
self-contained because its required types, helper, and async-trait dependency
are unspecified. Add the necessary imports for Decision, Response, LlmResponse,
and text_response from their actual modules, and document that consumers must
declare async-trait as a direct dependency before using the async_trait
attribute.
- Around line 237-239: Update the score extraction around
classify_response.llm_response.aggregate().await to handle aggregation errors
explicitly instead of silently converting them with .ok(). Match the Err case
according to the intended fail-open behavior, or propagate it with ?; preserve
the existing parsing and no-score handling for successful aggregates.

In `@crates/switchyard-translation/src/codecs/anthropic/stream.rs`:
- Around line 89-109: Update the `message_delta` handling to only store
`stop_reason` in `state.stop_reason` without pushing
`LlmResponseChunk::MessageStop`. Keep the single `MessageStop` emission in the
`message_stop` branch, using the remembered reason so direct consumers receive
completion exactly once.

---

Outside diff comments:
In `@crates/libsy-protocol/src/llm.rs`:
- Around line 234-249: Add concise rustdoc comments to the public StopReason
enum and ResponseOutput struct. Describe StopReason as the normalized reason
generation stopped, and ResponseOutput as one normalized assistant response
item, including its role, content blocks, and optional stop reason.

In `@crates/switchyard-translation/src/codecs/stream.rs`:
- Around line 200-220: Document the public decode_stream_event and
encode_stream_event functions in
crates/switchyard-translation/src/codecs/stream.rs, concisely stating their
provider-format-to-neutral and neutral-to-provider directions and that decode
failures produce an error response chunk. Document the LlmResponseChunk
re-export in crates/switchyard-translation/src/lib.rs as the normalized
provider-independent streaming chunk type.

---

Nitpick comments:
In `@crates/libsy-protocol/src/envelope.rs`:
- Around line 10-13: Document the public Context struct and its values field
with concise Rust doc comments, describing how values propagate and specifying
the key-naming conventions callers should follow.

In `@crates/libsy/src/lib.rs`:
- Around line 125-140: The CallLlmRequest::new constructor currently clones the
entire RoutedRequest into the wrapper, duplicating potentially large payload
data. Remove the owned routed field and read the existing RoutedRequest from
inner on demand through the accessors, or retain shared ownership with Arc if
needed; preserve the current payload validation behavior without duplicating it.
🪄 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: 8c5155af-7655-4302-bef3-1617aa2bf626

📥 Commits

Reviewing files that changed from the base of the PR and between b03b6a6 and 399a407.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (28)
  • crates/libsy-examples/Cargo.toml
  • crates/libsy-examples/examples/research_agent.rs
  • crates/libsy-examples/examples/research_agent_core.rs
  • crates/libsy-examples/examples/streaming_agent.rs
  • crates/libsy-examples/src/ensemble.rs
  • crates/libsy-examples/src/llm_class.rs
  • crates/libsy-examples/src/rand.rs
  • crates/libsy-protocol/Cargo.toml
  • crates/libsy-protocol/src/client.rs
  • crates/libsy-protocol/src/envelope.rs
  • crates/libsy-protocol/src/lib.rs
  • crates/libsy-protocol/src/llm.rs
  • crates/libsy-protocol/src/stream.rs
  • crates/libsy/README.md
  • crates/libsy/src/driver.rs
  • crates/libsy/src/lib.rs
  • crates/switchyard-translation/src/codecs/anthropic/buffered.rs
  • crates/switchyard-translation/src/codecs/anthropic/stream.rs
  • crates/switchyard-translation/src/codecs/mod.rs
  • crates/switchyard-translation/src/codecs/openai_chat/buffered.rs
  • crates/switchyard-translation/src/codecs/openai_chat/stream.rs
  • crates/switchyard-translation/src/codecs/responses/buffered.rs
  • crates/switchyard-translation/src/codecs/responses/stream.rs
  • crates/switchyard-translation/src/codecs/stream.rs
  • crates/switchyard-translation/src/engine.rs
  • crates/switchyard-translation/src/lib.rs
  • crates/switchyard-translation/tests/extension_points.rs
  • crates/switchyard-translation/tests/stream_translation.rs

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/switchyard-translation/src/codecs/stream.rs (1)

200-220: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the newly exposed streaming contract.

  • crates/switchyard-translation/src/codecs/stream.rs#L200-L220: add concise docs describing decode/encode direction and error fallback behavior.
  • crates/switchyard-translation/src/lib.rs#L18-L18: document that this re-export is the normalized provider-independent streaming chunk type.

As per coding guidelines, crates/**/*.rs public items must have concise comments/doc comments.

🤖 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-translation/src/codecs/stream.rs` around lines 200 - 220,
Document the public decode_stream_event and encode_stream_event functions in
crates/switchyard-translation/src/codecs/stream.rs, concisely stating their
provider-format-to-neutral and neutral-to-provider directions and that decode
failures produce an error response chunk. Document the LlmResponseChunk
re-export in crates/switchyard-translation/src/lib.rs as the normalized
provider-independent streaming chunk type.

Source: Coding guidelines

crates/libsy-protocol/src/llm.rs (1)

234-249: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new public response types.

StopReason and ResponseOutput are public API additions but currently lack rustdoc. Add concise documentation describing normalized stop reasons and response-output semantics.

Proposed documentation
+/// Provider-neutral reason why generation stopped.
 pub enum StopReason {
     EndTurn,
     MaxTokens,
     ToolUse,
     ContentFilter,
     Error,
     Unknown,
 }

+/// One role-tagged output from a buffered response.
 pub struct ResponseOutput {
     pub role: Role,
     pub content: Vec<ContentBlock>,
     pub stop_reason: Option<StopReason>,
 }

As per coding guidelines, crates/**/*.rs must add concise comments/doc comments for public items and non-obvious 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/libsy-protocol/src/llm.rs` around lines 234 - 249, Add concise rustdoc
comments to the public StopReason enum and ResponseOutput struct. Describe
StopReason as the normalized reason generation stopped, and ResponseOutput as
one normalized assistant response item, including its role, content blocks, and
optional stop reason.

Source: Coding guidelines

🧹 Nitpick comments (2)
crates/libsy-protocol/src/envelope.rs (1)

10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the public Context API.

Add concise docs explaining how values propagates and any conventions for keys. As per coding guidelines, “Add concise comments/doc comments for public items and non-obvious 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/libsy-protocol/src/envelope.rs` around lines 10 - 13, Document the
public Context struct and its values field with concise Rust doc comments,
describing how values propagate and specifying the key-naming conventions
callers should follow.

Source: Coding guidelines

crates/libsy/src/lib.rs (1)

125-140: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid cloning the entire routed request into the promise wrapper.

inner already retains the boxed RoutedRequest, so routed.clone() duplicates normalized messages, metadata, and potentially large raw_request JSON for every outstanding call. Read it from inner on demand or store the payload behind an Arc.

🤖 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 125 - 140, The CallLlmRequest::new
constructor currently clones the entire RoutedRequest into the wrapper,
duplicating potentially large payload data. Remove the owned routed field and
read the existing RoutedRequest from inner on demand through the accessors, or
retain shared ownership with Arc if needed; preserve the current payload
validation behavior without duplicating it.
🤖 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/libsy-examples/examples/streaming_agent.rs`:
- Around line 88-92: Update the chunk handling loop in streaming_agent so
LlmResponseChunk::Error is matched explicitly and propagated instead of being
ignored, while preserving text output handling. Also replace the ignored stdout
flush result with propagation of its failure from the surrounding async
function.

In `@crates/libsy-examples/src/llm_class.rs`:
- Around line 114-120: Clear stale raw_request payloads when constructing the
synthetic classifier request in llm_class.rs around lines 114-120, using None or
regenerating it with the classifier preamble. Apply the same change to the
synthetic judge request in ensemble.rs around lines 273-277, ensuring both
routed requests use their newly generated prompts instead of inherited provider
payloads.
- Around line 137-144: The response-processing algorithms incorrectly use agg(),
which ignores streaming responses. In crates/libsy-examples/src/llm_class.rs
lines 137-144, replace this with aggregate().await? before parsing the
classifier score; in crates/libsy-examples/src/ensemble.rs lines 278-289,
aggregate the judge response before parsing its selection; and in
crates/libsy-examples/src/ensemble.rs lines 334-342, aggregate candidate
responses before constructing the judge prompt and retain each aggregate as the
potential winner response.

In `@crates/libsy-protocol/src/stream.rs`:
- Around line 151-173: Update the response accumulator around the
LlmResponseChunk handling and its emission logic to retain each text, reasoning,
and tool-call content block under its streamed index, rather than merging by
type. Emit the accumulated blocks in ascending index order so interleaved
sequences such as text → tool call → text preserve their original order and can
round-trip through provider encoders.
- Around line 175-177: Update the LlmResponseChunk::MessageStop handling to only
assign stop_reason when the event provides a reason; preserve any previously
stored MaxTokens or ToolUse value when reason is absent, while retaining the
existing EndTurn mapping for explicit reasons.

In `@crates/libsy/README.md`:
- Around line 82-100: The RoutedLlmClient example in README.md is not
self-contained because its required types, helper, and async-trait dependency
are unspecified. Add the necessary imports for Decision, Response, LlmResponse,
and text_response from their actual modules, and document that consumers must
declare async-trait as a direct dependency before using the async_trait
attribute.
- Around line 237-239: Update the score extraction around
classify_response.llm_response.aggregate().await to handle aggregation errors
explicitly instead of silently converting them with .ok(). Match the Err case
according to the intended fail-open behavior, or propagate it with ?; preserve
the existing parsing and no-score handling for successful aggregates.

In `@crates/switchyard-translation/src/codecs/anthropic/stream.rs`:
- Around line 89-109: Update the `message_delta` handling to only store
`stop_reason` in `state.stop_reason` without pushing
`LlmResponseChunk::MessageStop`. Keep the single `MessageStop` emission in the
`message_stop` branch, using the remembered reason so direct consumers receive
completion exactly once.

---

Outside diff comments:
In `@crates/libsy-protocol/src/llm.rs`:
- Around line 234-249: Add concise rustdoc comments to the public StopReason
enum and ResponseOutput struct. Describe StopReason as the normalized reason
generation stopped, and ResponseOutput as one normalized assistant response
item, including its role, content blocks, and optional stop reason.

In `@crates/switchyard-translation/src/codecs/stream.rs`:
- Around line 200-220: Document the public decode_stream_event and
encode_stream_event functions in
crates/switchyard-translation/src/codecs/stream.rs, concisely stating their
provider-format-to-neutral and neutral-to-provider directions and that decode
failures produce an error response chunk. Document the LlmResponseChunk
re-export in crates/switchyard-translation/src/lib.rs as the normalized
provider-independent streaming chunk type.

---

Nitpick comments:
In `@crates/libsy-protocol/src/envelope.rs`:
- Around line 10-13: Document the public Context struct and its values field
with concise Rust doc comments, describing how values propagate and specifying
the key-naming conventions callers should follow.

In `@crates/libsy/src/lib.rs`:
- Around line 125-140: The CallLlmRequest::new constructor currently clones the
entire RoutedRequest into the wrapper, duplicating potentially large payload
data. Remove the owned routed field and read the existing RoutedRequest from
inner on demand through the accessors, or retain shared ownership with Arc if
needed; preserve the current payload validation behavior without duplicating it.
🪄 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: 8c5155af-7655-4302-bef3-1617aa2bf626

📥 Commits

Reviewing files that changed from the base of the PR and between b03b6a6 and 399a407.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (28)
  • crates/libsy-examples/Cargo.toml
  • crates/libsy-examples/examples/research_agent.rs
  • crates/libsy-examples/examples/research_agent_core.rs
  • crates/libsy-examples/examples/streaming_agent.rs
  • crates/libsy-examples/src/ensemble.rs
  • crates/libsy-examples/src/llm_class.rs
  • crates/libsy-examples/src/rand.rs
  • crates/libsy-protocol/Cargo.toml
  • crates/libsy-protocol/src/client.rs
  • crates/libsy-protocol/src/envelope.rs
  • crates/libsy-protocol/src/lib.rs
  • crates/libsy-protocol/src/llm.rs
  • crates/libsy-protocol/src/stream.rs
  • crates/libsy/README.md
  • crates/libsy/src/driver.rs
  • crates/libsy/src/lib.rs
  • crates/switchyard-translation/src/codecs/anthropic/buffered.rs
  • crates/switchyard-translation/src/codecs/anthropic/stream.rs
  • crates/switchyard-translation/src/codecs/mod.rs
  • crates/switchyard-translation/src/codecs/openai_chat/buffered.rs
  • crates/switchyard-translation/src/codecs/openai_chat/stream.rs
  • crates/switchyard-translation/src/codecs/responses/buffered.rs
  • crates/switchyard-translation/src/codecs/responses/stream.rs
  • crates/switchyard-translation/src/codecs/stream.rs
  • crates/switchyard-translation/src/engine.rs
  • crates/switchyard-translation/src/lib.rs
  • crates/switchyard-translation/tests/extension_points.rs
  • crates/switchyard-translation/tests/stream_translation.rs
🛑 Comments failed to post (8)
crates/libsy-examples/examples/streaming_agent.rs (1)

88-92: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Propagate in-band stream errors instead of silently ignoring them.

LlmResponseChunk::Error currently falls through the if let, so the example reports successful output after a provider error. Match it explicitly and propagate the error; also propagate flush failures.

Proposed fix
-                    while let Some(chunk) = chunks.next().await {
-                        if let LlmResponseChunk::TextDelta { text, .. } = chunk? {
-                            print!("{text}");
-                            std::io::stdout().flush().ok();
-                        }
+                    while let Some(chunk) = chunks.next().await {
+                        match chunk? {
+                            LlmResponseChunk::TextDelta { text, .. } => {
+                                print!("{text}");
+                                std::io::stdout().flush()?;
+                            }
+                            LlmResponseChunk::Error { message } => return Err(message.into()),
+                            _ => {}
+                        }
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                    while let Some(chunk) = chunks.next().await {
                        match chunk? {
                            LlmResponseChunk::TextDelta { text, .. } => {
                                print!("{text}");
                                std::io::stdout().flush()?;
                            }
                            LlmResponseChunk::Error { message } => return Err(message.into()),
                            _ => {}
                        }
                    }
🤖 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-examples/examples/streaming_agent.rs` around lines 88 - 92,
Update the chunk handling loop in streaming_agent so LlmResponseChunk::Error is
matched explicitly and propagated instead of being ignored, while preserving
text output handling. Also replace the ignored stdout flush result with
propagation of its failure from the surrounding async function.
crates/libsy-examples/src/llm_class.rs (2)

114-120: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Synthetic requests retain stale provider payloads. If a routed client honors raw_request, it bypasses the newly generated normalized prompt.

  • crates/libsy-examples/src/llm_class.rs#L114-L120: set raw_request: None or regenerate it with the classifier preamble.
  • crates/libsy-examples/src/ensemble.rs#L273-L277: set raw_request: None or regenerate it with the judge prompt.
📍 Affects 2 files
  • crates/libsy-examples/src/llm_class.rs#L114-L120 (this comment)
  • crates/libsy-examples/src/ensemble.rs#L273-L277
🤖 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-examples/src/llm_class.rs` around lines 114 - 120, Clear stale
raw_request payloads when constructing the synthetic classifier request in
llm_class.rs around lines 114-120, using None or regenerating it with the
classifier preamble. Apply the same change to the synthetic judge request in
ensemble.rs around lines 273-277, ensuring both routed requests use their newly
generated prompts instead of inherited provider payloads.

137-144: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The algorithms only inspect buffered responses despite accepting streaming routed clients. Calling agg() silently maps streams to None, changing routing decisions.

  • crates/libsy-examples/src/llm_class.rs#L137-L144: call aggregate().await? before parsing the classifier score.
  • crates/libsy-examples/src/ensemble.rs#L278-L289: aggregate the judge response before parsing its selection.
  • crates/libsy-examples/src/ensemble.rs#L334-L342: aggregate candidate responses before building the judge prompt, retaining the resulting aggregate as the potential winner response.
📍 Affects 2 files
  • crates/libsy-examples/src/llm_class.rs#L137-L144 (this comment)
  • crates/libsy-examples/src/ensemble.rs#L278-L289
  • crates/libsy-examples/src/ensemble.rs#L334-L342
🤖 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-examples/src/llm_class.rs` around lines 137 - 144, The
response-processing algorithms incorrectly use agg(), which ignores streaming
responses. In crates/libsy-examples/src/llm_class.rs lines 137-144, replace this
with aggregate().await? before parsing the classifier score; in
crates/libsy-examples/src/ensemble.rs lines 278-289, aggregate the judge
response before parsing its selection; and in
crates/libsy-examples/src/ensemble.rs lines 334-342, aggregate candidate
responses before constructing the judge prompt and retain each aggregate as the
potential winner response.
crates/libsy-protocol/src/stream.rs (2)

151-173: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve streamed content-block index and order.

The accumulator discards text/reasoning indices and always emits reasoning, then merged text, then tool calls. A stream such as text → tool call → text is therefore reordered and cannot round-trip through provider encoders. Store partial content by index and emit it in index order.

Also applies to: 182-200

🤖 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-protocol/src/stream.rs` around lines 151 - 173, Update the
response accumulator around the LlmResponseChunk handling and its emission logic
to retain each text, reasoning, and tool-call content block under its streamed
index, rather than merging by type. Emit the accumulated blocks in ascending
index order so interleaved sequences such as text → tool call → text preserve
their original order and can round-trip through provider encoders.

175-177: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve an earlier stop reason when MessageStop has none.

A reasonless terminal event currently overwrites MaxTokens or ToolUse with EndTurn. This contradicts crates/switchyard-translation/tests/stream_translation.rs’s Anthropic stop-reason contract.

Proposed fix
 LlmResponseChunk::MessageStop { reason } => {
-    self.stop_reason = Some(stop_reason_from_str(reason.as_deref()));
+    if reason.is_some() || self.stop_reason.is_none() {
+        self.stop_reason = Some(stop_reason_from_str(reason.as_deref()));
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            LlmResponseChunk::MessageStop { reason } => {
                if reason.is_some() || self.stop_reason.is_none() {
                    self.stop_reason = Some(stop_reason_from_str(reason.as_deref()));
                }
            }
🤖 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-protocol/src/stream.rs` around lines 175 - 177, Update the
LlmResponseChunk::MessageStop handling to only assign stop_reason when the event
provides a reason; preserve any previously stored MaxTokens or ToolUse value
when reason is absent, while retaining the existing EndTurn mapping for explicit
reasons.
crates/libsy/README.md (2)

82-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the routed-client example self-contained.

The snippet uses Decision, Response, LlmResponse, and text_response, but the documented imports do not provide them. It also uses async_trait::async_trait without stating that the consumer must add async-trait as a direct dependency. As written, copying this example will not compile.

🤖 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/README.md` around lines 82 - 100, The RoutedLlmClient example in
README.md is not self-contained because its required types, helper, and
async-trait dependency are unspecified. Add the necessary imports for Decision,
Response, LlmResponse, and text_response from their actual modules, and document
that consumers must declare async-trait as a direct dependency before using the
async_trait attribute.

237-239: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant README section
git ls-files crates/libsy/README.md
nl -ba crates/libsy/README.md | sed -n '210,260p'

# Find the aggregate API and any docs/comments around its error behavior
rg -n "aggregate\(\)|mid-stream error|surfaces errors|completion_text\(" crates -g '!**/target/**'

# Map relevant source files before reading them
fd -a ".*" crates | rg "llm_response|aggregate|completion_text|response|README.md"

Repository: NVIDIA-NeMo/Switchyard

Length of output: 223


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- README excerpt ---'
sed -n '210,260p' crates/libsy/README.md | cat -n

echo
echo '--- aggregate / completion_text references ---'
rg -n "aggregate\(\)|mid-stream error|surfaces errors|completion_text\(" crates

echo
echo '--- candidate files ---'
fd -a "aggregate|completion_text|llm_response|README.md" crates

Repository: NVIDIA-NeMo/Switchyard

Length of output: 5596


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- libsy aggregate docs/implementation ---'
sed -n '150,240p' crates/libsy/src/lib.rs | cat -n

echo
echo '--- aggregate call sites in libsy source ---'
sed -n '580,640p' crates/libsy/src/lib.rs | cat -n

echo
echo '--- protocol completion_text / response types ---'
sed -n '1,180p' crates/libsy-protocol/src/lib.rs | cat -n

Repository: NVIDIA-NeMo/Switchyard

Length of output: 12395


Handle aggregate() errors explicitly. .ok() turns a mid-stream failure into None, so this example treats upstream stream errors the same as “no score” and routes to the strong model. If fail-open is intended, match on Err explicitly; otherwise propagate the error with ?.

🤖 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/README.md` around lines 237 - 239, Update the score extraction
around classify_response.llm_response.aggregate().await to handle aggregation
errors explicitly instead of silently converting them with .ok(). Match the Err
case according to the intended fail-open behavior, or propagate it with ?;
preserve the existing parsing and no-score handling for successful aggregates.
crates/switchyard-translation/src/codecs/anthropic/stream.rs (1)

89-109: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Emit MessageStop only for terminal message_stop.

Line 98 emits a terminal chunk during message_delta, then Lines 107-109 emit it again. Remember the reason at message_delta, but emit the sole MessageStop when the terminal provider event arrives; otherwise direct stream consumers can process completion twice.

Proposed fix
             {
                 // Remember the provider stop reason: Anthropic delivers it here, on
                 // `message_delta`, while the terminal `message_stop` carries none of its own.
                 state.stop_reason = Some(stop_reason.to_string());
-                out.push(LlmResponseChunk::MessageStop {
-                    reason: Some(stop_reason.to_string()),
-                });
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            if let Some(stop_reason) = object
                .get("delta")
                .and_then(Value::as_object)
                .and_then(|delta| delta.get("stop_reason"))
                .and_then(Value::as_str)
            {
                // Remember the provider stop reason: Anthropic delivers it here, on
                // `message_delta`, while the terminal `message_stop` carries none of its own.
                state.stop_reason = Some(stop_reason.to_string());
            }
            out
        }
        // Anthropic's terminal `message_stop` carries no reason of its own; replay the one
        // remembered from `message_delta` so a chunk accumulator keeps the real stop reason
        // (e.g. `max_tokens`) instead of overwriting it with a reasonless `EndTurn`.
        Some("message_stop") => vec![LlmResponseChunk::MessageStop {
            reason: state.stop_reason.clone(),
        }],
🤖 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-translation/src/codecs/anthropic/stream.rs` around lines 89
- 109, Update the `message_delta` handling to only store `stop_reason` in
`state.stop_reason` without pushing `LlmResponseChunk::MessageStop`. Keep the
single `MessageStop` emission in the `message_stop` branch, using the remembered
reason so direct consumers receive completion exactly once.

@messiaen
messiaen force-pushed the grclark/libsy-streaming-support branch from 6547267 to 1a0b14c Compare July 16, 2026 17:58
Base automatically changed from grclark/libsy-streaming-support to main July 16, 2026 18:10
@messiaen
messiaen force-pushed the grclark/libsy-lib-proto-cleanup branch from 399a407 to 9e31bda Compare July 16, 2026 18:30
@messiaen

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/libsy-examples/src/ensemble.rs (2)

188-195: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the full inbound LlmRequest.

text_request(prompt_text(...)) drops message roles, instructions, tools, sampling/output settings, and provider extensions. Forward or clone the original request for routed candidate/committed calls.

  • crates/libsy-examples/src/ensemble.rs#L188-L195: send the original committed request unchanged.
  • crates/libsy-examples/src/ensemble.rs#L228-L232: clone the original request per candidate rather than rebuilding text-only requests.
🤖 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-examples/src/ensemble.rs` around lines 188 - 195, Preserve the
complete inbound LlmRequest instead of rebuilding text-only requests with
text_request and prompt_text. In crates/libsy-examples/src/ensemble.rs lines
188-195, make the routed committed call send the original request unchanged; in
lines 228-232, clone the original request for each candidate while applying only
the candidate-specific routing changes required by the existing flow.

282-287: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Aggregate streamed responses before judging.

RoutedLlmClient may return LlmResponse::Stream, but as_agg() yields None for streams. Candidate prompts become empty and streamed judge output silently falls back to candidate zero. Aggregate responses with into_agg().await before constructing/parsing the judge exchange.

  • crates/libsy-examples/src/ensemble.rs#L282-L287: aggregate the judge response before parse_choice.
  • crates/libsy-examples/src/ensemble.rs#L337-L341: aggregate candidate responses before embedding them in the judge prompt.
🤖 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-examples/src/ensemble.rs` around lines 282 - 287, The judge
response at crates/libsy-examples/src/ensemble.rs lines 282-287 must be
aggregated before parse_choice; await into_agg() and parse the resulting
aggregate text. Apply the same aggregation before embedding candidate responses
in the judge prompt at crates/libsy-examples/src/ensemble.rs lines 337-341,
preserving the existing prompt and choice-selection 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 `@crates/libsy-protocol/src/envelope.rs`:
- Around line 10-16: Update the doc comment for the public Context type to
remove the inaccurate “empty today” description and document the intended
contract of Context::values, including that it stores per-request cross-cutting
state as string key/value entries. Keep the existing derives and field
unchanged.

---

Outside diff comments:
In `@crates/libsy-examples/src/ensemble.rs`:
- Around line 188-195: Preserve the complete inbound LlmRequest instead of
rebuilding text-only requests with text_request and prompt_text. In
crates/libsy-examples/src/ensemble.rs lines 188-195, make the routed committed
call send the original request unchanged; in lines 228-232, clone the original
request for each candidate while applying only the candidate-specific routing
changes required by the existing flow.
- Around line 282-287: The judge response at
crates/libsy-examples/src/ensemble.rs lines 282-287 must be aggregated before
parse_choice; await into_agg() and parse the resulting aggregate text. Apply the
same aggregation before embedding candidate responses in the judge prompt at
crates/libsy-examples/src/ensemble.rs lines 337-341, preserving the existing
prompt and choice-selection behavior.
🪄 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: 804fc475-2e8b-4b59-b5eb-e4fc3584c4fc

📥 Commits

Reviewing files that changed from the base of the PR and between c7a6475 and 9e31bda.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (10)
  • crates/libsy-examples/examples/research_agent.rs
  • crates/libsy-examples/src/ensemble.rs
  • crates/libsy-examples/src/llm_class.rs
  • crates/libsy-examples/src/rand.rs
  • crates/libsy-protocol/Cargo.toml
  • crates/libsy-protocol/src/client.rs
  • crates/libsy-protocol/src/envelope.rs
  • crates/libsy-protocol/src/lib.rs
  • crates/libsy/README.md
  • crates/libsy/src/lib.rs

Comment thread crates/libsy-protocol/src/envelope.rs
Comment thread crates/libsy-examples/examples/research_agent.rs
Comment thread crates/libsy-protocol/src/client.rs
@messiaen
messiaen force-pushed the grclark/libsy-lib-proto-cleanup branch from 9e31bda to f2fc719 Compare July 16, 2026 23:11
Signed-off-by: Greg Clark <grclark@nvidia.com>
@messiaen
messiaen force-pushed the grclark/libsy-lib-proto-cleanup branch from f2fc719 to 57c015d Compare July 16, 2026 23:13
@messiaen
messiaen enabled auto-merge (squash) July 16, 2026 23:23
@messiaen
messiaen merged commit 694aa27 into main Jul 16, 2026
22 of 32 checks passed
@messiaen
messiaen deleted the grclark/libsy-lib-proto-cleanup branch July 16, 2026 23:27
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.

3 participants