Skip to content

refactor(worker): extract HashRing into its own module (Finding 5) - #1129

Merged
slin1237 merged 2 commits into
mainfrom
refactor/extract-hash-ring
Apr 14, 2026
Merged

slin1237 merged 2 commits into
mainfrom
refactor/extract-hash-ring

Conversation

@slin1237

@slin1237 slin1237 commented Apr 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Finding 5 from .claude/plans/2026-04-14-worker-module-followup-cleanup.md. Pulls the 118-line consistent-hash-ring data structure out of the 2134-line registry.rs into a focused worker/hash_ring.rs, and narrows its public API so the type no longer depends on the Worker trait.

This is a pure organizational refactor — zero behavior changes, no hot-path allocation regression, and the new file ships with six direct unit tests that fill a previously-empty test coverage gap for the ring.

What changed

New file model_gateway/src/worker/hash_ring.rs (~190 lines incl. tests):

  • VIRTUAL_NODES_PER_WORKER const
  • HashRing struct + impl (new, find_healthy_url, is_empty, len, worker_count, private hash_position)
  • #[cfg(test)] mod tests — six new unit tests: empty ring, length-scales-with-virtual-nodes, deterministic lookup, unhealthy-worker skip, all-unhealthy fallback, owned-String iterator input

model_gateway/src/worker/registry.rs:

  • Delete the 118-line HashRing block (struct + impl + const)
  • Update the file-header doc comment to reference the new module
  • Import hash_ring::HashRing from the sibling module
  • Update rebuild_hash_ring to construct the ring from a URL iterator: HashRing::new(workers.value().iter().map(|w| w.url())) instead of HashRing::new(&workers)

model_gateway/src/worker/mod.rs:

  • Register pub mod hash_ring;
  • Split the re-export so downstream callers still write use crate::worker::HashRing without touching their imports:
    pub use hash_ring::HashRing;
    pub use registry::WorkerRegistry;

Caller updates (7 test sites total):

  • model_gateway/src/policies/prefix_hash.rs — 5 test sites
  • model_gateway/src/policies/consistent_hashing.rs — 2 test sites

All changed from HashRing::new(&workers) to HashRing::new(workers.iter().map(|w| w.url())).

API change: HashRing::new

// Before
pub fn new(workers: &[Arc<dyn Worker>]) -> Self

// After
pub fn new<I>(urls: I) -> Self
where
    I: IntoIterator,
    I::Item: AsRef<str>,

Accepts &[&str], &[String], owned Vec<String>, or any iterator of string-like items. The rebuild path in registry.rs feeds URLs directly from the model index without allocating an intermediate Vec, matching the previous zero-alloc hot path.

Why

  • Decoupling. HashRing's only dependency on the Worker trait was worker.url() in its constructor. Threading the full trait object through the signature coupled a pure consistent-hash data structure to the gateway's worker abstraction. Narrowing the constructor to a URL iterator removes that coupling so HashRing could, in a later PR, move to a shared common/ crate if the gossip/mesh layers ever need the same ring.
  • File weight. registry.rs had grown to 2134 lines mixing the ring implementation with worker-registry wiring, model index management, and the state subscriber. Extracting the ring drops registry.rs by ~115 net lines and gives reviewers a focused 190-line file to touch when tuning the ring.
  • Test coverage. The inline test module on hash_ring.rs fills a real coverage gap — the ring previously had zero direct tests (the policy tests exercised it transitively, but a broken ring would surface in policy-level assertions, not as a localized failure).

Test plan

  • cargo check -p smg — clean.
  • cargo clippy -p smg --all-targets -- -D warnings — clean.
  • cargo clippy -p smg-golang --all-targets -- -D warnings — clean.
  • cargo check -p smg-python — clean.
  • cargo test -p smg --lib — 544 passed; 0 failed; 4 ignored (+6 new HashRing unit tests compared to main's 538).

Refs: Finding 5 in .claude/plans/2026-04-14-worker-module-followup-cleanup.md

Summary by CodeRabbit

  • Refactor
    • Moved and reorganized the consistent hashing implementation into a dedicated internal module to improve maintainability and reliability of worker selection.
  • Tests
    • Updated test setups to align with the new hashing interface, preserving existing routing and health-check behavior.

… Worker trait

Finding 5 from .claude/plans/2026-04-14-worker-module-followup-cleanup.md.
Pulls the 118-line consistent-hash-ring data structure out of the
2134-line registry.rs into a focused worker/hash_ring.rs, and narrows
its public API so the type no longer depends on the Worker trait.

What changed
- Add model_gateway/src/worker/hash_ring.rs (new file, ~190 lines incl.
  tests). Contains VIRTUAL_NODES_PER_WORKER, `HashRing`, `hash_position`,
  and six unit tests that cover: empty ring, ring length with virtual
  nodes, deterministic lookup, unhealthy-worker skip, all-unhealthy
  fallback, and owned-String iterator input.
- model_gateway/src/worker/registry.rs:
  * Delete the 118-line HashRing block (struct + impl + const).
  * Update the file-header doc comment to reference the new module.
  * Drop `use std::collections::HashSet` from the HashRing usage (kept
    for other uses in the file).
  * Import `hash_ring::HashRing` from the sibling module.
  * Update `rebuild_hash_ring` to construct the ring from a URL
    iterator: `HashRing::new(workers.value().iter().map(|w| w.url()))`
    instead of `HashRing::new(&workers)`.
- model_gateway/src/worker/mod.rs:
  * Register `pub mod hash_ring;`.
  * Split the re-export — `pub use hash_ring::HashRing;` + `pub use
    registry::WorkerRegistry;` — so downstream callers still write
    `use crate::worker::HashRing` without touching imports.
- model_gateway/src/policies/prefix_hash.rs (5 test sites) and
  model_gateway/src/policies/consistent_hashing.rs (2 test sites):
  * Adapt callers to the new iterator-based signature:
    `HashRing::new(workers.iter().map(|w| w.url()))`.

API change: `HashRing::new`
- Before: `pub fn new(workers: &[Arc<dyn Worker>]) -> Self`
- After:  `pub fn new<I>(urls: I) -> Self where I: IntoIterator, I::Item: AsRef<str>`

  Accepts `&[&str]`, `&[String]`, owned `Vec<String>`, or any iterator
  of string-like items. The rebuild path in `registry.rs` feeds the
  URLs directly from the model index without allocating an intermediate
  Vec, matching the previous zero-alloc hot path.

Why
- HashRing's only dependency on the `Worker` trait was `worker.url()`
  in its constructor. Threading the full trait object through the
  signature coupled a pure consistent-hash data structure to the
  gateway's worker abstraction. Narrowing the constructor to a URL
  iterator removes that coupling so `HashRing` could, in a later PR,
  move to a shared `common/` crate if the gossip/mesh layers ever
  need the same ring.
- `registry.rs` had grown to 2134 lines mixing the ring implementation
  with worker-registry wiring, model index management, and the state
  subscriber. Extracting the ring drops registry.rs by ~115 net lines
  and gives reviewers a focused 190-line file to touch when tuning
  the ring.
- The inline test module on `hash_ring.rs` fills a real coverage gap
  — the ring previously had zero direct tests (the policy tests
  exercised it transitively, but a broken ring would surface in
  policy-level assertions, not as a localized failure).

Verification
- cargo check -p smg — clean.
- cargo clippy -p smg --all-targets -- -D warnings — clean.
- cargo clippy -p smg-golang --all-targets -- -D warnings — clean.
- cargo check -p smg-python — clean.
- cargo test -p smg --lib — 544 passed; 0 failed; 4 ignored
  (+6 new unit tests for HashRing).

Refs: Finding 5 in .claude/plans/2026-04-14-worker-module-followup-cleanup.md
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@slin1237
slin1237 requested a review from CatherineSue as a code owner April 14, 2026 15:39
@github-actions github-actions Bot added the model-gateway Model gateway crate changes label Apr 14, 2026
@coderabbitai

coderabbitai Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f03b5320-cdba-44ab-a650-c453f859198f

📥 Commits

Reviewing files that changed from the base of the PR and between 09b5116 and c0c4437.

📒 Files selected for processing (2)
  • model_gateway/src/worker/hash_ring.rs
  • model_gateway/src/worker/mod.rs

📝 Walkthrough

Walkthrough

The PR extracts the HashRing implementation into a new model_gateway::worker::hash_ring module, updates worker::mod exports, changes WorkerRegistry to build rings from worker URL iterators, and updates tests to call HashRing::new(workers.iter().map(|w| w.url())) instead of passing worker slices.

Changes

Cohort / File(s) Summary
HashRing Module
model_gateway/src/worker/hash_ring.rs
Added new HashRing implementation: blake3-based virtual nodes, ring construction from AsRef<str> iterators, lookup find_healthy_url, and unit tests.
Worker Module Exports
model_gateway/src/worker/mod.rs
Added pub mod hash_ring; and re-exported HashRing from the new module; adjusted re-exports for WorkerRegistry.
Registry
model_gateway/src/worker/registry.rs
Removed in-file HashRing; now imports crate::worker::hash_ring::HashRing. rebuild_hash_ring now constructs rings from `workers.value().iter().map(
Policy Tests
model_gateway/src/policies/consistent_hashing.rs, model_gateway/src/policies/prefix_hash.rs
Updated tests to construct HashRing using an iterator of worker URLs (`workers.iter().map(

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

tests

Suggested reviewers

  • CatherineSue
  • key4ng

Poem

🐰 I nibbled through code, made a ring anew,
URLs in a circle, hashed straight and true.
Virtual hops bounce from node to node,
Cleanly separated — a lighter load.
Hop, clap, deploy — the rabbit’s pleased too!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: extracting the HashRing implementation from registry.rs into its own dedicated module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/extract-hash-ring

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the HashRing implementation into a dedicated module and decouples it from the Worker trait by using worker URLs for construction. Feedback indicates that find_healthy_url performs a HashSet allocation on the hot path, which conflicts with the "allocation-free" claim in the documentation. Further improvements were suggested to optimize string formatting during ring initialization using a reusable buffer and to add an early exit in the lookup loop to avoid redundant iterations when all workers are unhealthy.

Comment thread model_gateway/src/worker/hash_ring.rs Outdated
//!
//! The ring maps a routing key to a worker URL using consistent hashing over
//! virtual nodes. The registry rebuilds one ring per model when workers are
//! added or removed, so lookups stay allocation-free on the request path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The documentation states that lookups stay "allocation-free on the request path", but find_healthy_url (line 109) allocates a HashSet on every call. This allocation occurs on the hot path of every request using consistent hashing. Consider updating the documentation to reflect this or exploring ways to avoid the allocation (e.g., using a stack-allocated structure for small worker counts).

References
  1. To prevent vulnerabilities from duplicate entries, use data structures that inherently enforce uniqueness, such as HashSet, instead of manually deduplicating collections like Vec.

Comment on lines +52 to +61
for url in iter {
// Create Arc<str> once per worker, share across all virtual nodes.
let url: Arc<str> = Arc::from(url.as_ref());

for vnode in 0..VIRTUAL_NODES_PER_WORKER {
let vnode_key = format!("{url}#{vnode}");
let pos = Self::hash_position(&vnode_key);
entries.push((pos, Arc::clone(&url)));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This loop performs VIRTUAL_NODES_PER_WORKER (150) string allocations per worker due to format!. While this happens during registry rebuilds rather than on the request path, it can be optimized by reusing a single String buffer and using write! to reduce the number of allocations to one per worker. Additionally, using Arc<str> for the URL improves performance by making clones cheap.

Suggested change
for url in iter {
// Create Arc<str> once per worker, share across all virtual nodes.
let url: Arc<str> = Arc::from(url.as_ref());
for vnode in 0..VIRTUAL_NODES_PER_WORKER {
let vnode_key = format!("{url}#{vnode}");
let pos = Self::hash_position(&vnode_key);
entries.push((pos, Arc::clone(&url)));
}
}
let mut vnode_key = String.new();
for url in iter {
let url: Arc<str> = Arc::from(url.as_ref());
for vnode in 0..VIRTUAL_NODES_PER_WORKER {
vnode_key.clear();
use std::fmt::Write as _;
let _ = write!(&mut vnode_key, "{url}#{vnode}");
let pos = Self::hash_position(&vnode_key);
entries.push((pos, Arc::clone(&url)));
}
}
References
  1. For types that are frequently cloned on hot paths and represent a small, repeated set of values (e.g., worker IDs or tenant IDs), use an interned string type like Arc to improve performance by making clones cheap (atomic reference count increments).

Comment on lines +111 to +122
for i in 0..self.entries.len() {
let (_, url) = &self.entries[(start + i) % self.entries.len()];
let url_str: &str = url;

if !checked_urls.insert(url_str) {
continue;
}

if is_healthy(url_str) {
return Some(url_str);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In the worst-case scenario where all workers are unhealthy, this loop iterates through all virtual nodes (worker_count * 150). Since checked_urls already tracks unique workers using a HashSet, the loop can be terminated early once all unique workers have been checked. This significantly improves performance when many workers are down.

        for i in 0..self.entries.len() {
            let (_, url) = &self.entries[(start + i) % self.entries.len()];
            let url_str: &str = url;

            if !checked_urls.insert(url_str) {
                continue;
            }

            if is_healthy(url_str) {
                return Some(url_str);
            }

            if checked_urls.len() >= self.worker_count() {
                break;
            }
        }
References
  1. To prevent vulnerabilities from duplicate entries, use data structures that inherently enforce uniqueness, such as HashSet, instead of manually deduplicating collections like Vec.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/worker/hash_ring.rs`:
- Around line 3-6: The doc claim about "allocation-free" lookups is incorrect
because find_healthy_url allocates a HashSet each call; either relax the comment
or eliminate the per-call allocation by changing find_healthy_url to avoid
building a HashSet (e.g., iterate ring entries directly and check health via the
existing healthy_urls map/structure, returning the first healthy URL found) or
accept a preallocated/borrowed set as an argument; update the file comment or
the function find_healthy_url accordingly and remove the HashSet allocation
around lines where the HashSet is created.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d6f404c-6d28-473f-97d3-18b77d93e11a

📥 Commits

Reviewing files that changed from the base of the PR and between 9e9c0c3 and 09b5116.

📒 Files selected for processing (5)
  • model_gateway/src/policies/consistent_hashing.rs
  • model_gateway/src/policies/prefix_hash.rs
  • model_gateway/src/worker/hash_ring.rs
  • model_gateway/src/worker/mod.rs
  • model_gateway/src/worker/registry.rs

Comment on lines +3 to +6
//! The ring maps a routing key to a worker URL using consistent hashing over
//! virtual nodes. The registry rebuilds one ring per model when workers are
//! added or removed, so lookups stay allocation-free on the request path.
//!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Correct the “allocation-free lookup” claim.

Line 5 says lookups are allocation-free, but Line 109 allocates a HashSet on every lookup. Please either relax the wording or remove per-call allocation in find_healthy_url.

🛠️ Proposed doc fix
-//! added or removed, so lookups stay allocation-free on the request path.
+//! added or removed, so lookups avoid per-request ring reconstruction on the request path.

Also applies to: 109-110

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/worker/hash_ring.rs` around lines 3 - 6, The doc claim
about "allocation-free" lookups is incorrect because find_healthy_url allocates
a HashSet each call; either relax the comment or eliminate the per-call
allocation by changing find_healthy_url to avoid building a HashSet (e.g.,
iterate ring entries directly and check health via the existing healthy_urls
map/structure, returning the first healthy URL found) or accept a
preallocated/borrowed set as an argument; update the file comment or the
function find_healthy_url accordingly and remove the HashSet allocation around
lines where the HashSet is created.

Addresses an AI review comment on PR #1129. The module-level doc
comment claimed "lookups stay allocation-free on the request path",
but `HashRing::find_healthy_url` allocates a small bounded `HashSet`
on every call to dedupe virtual-node hits while walking the ring.

The `HashSet` allocation itself is *pre-existing*: it was copied
verbatim from the original `registry.rs:117` implementation. What
was new is the "allocation-free" claim — I fabricated it in the
module header when moving the code to its own file. The old
`registry.rs` docs only said "the ring is rebuilt when workers are
added/removed, not per-request" (a true statement about *build*
cadence). I over-rewrote that into a false *lookup* claim.

Fix
- model_gateway/src/worker/hash_ring.rs:
  * Rewrite the module doc to describe the actual cost model:
    amortized build, `O(log n)` binary search per lookup, plus a
    small bounded dedupe set.
  * Expand the `find_healthy_url` doc with an explicit "Cost per call"
    section so the allocation is discoverable from the public API.
  * Inline comment at the `HashSet::with_capacity` site explains why
    the dedupe set exists and that the capacity is bounded by
    `worker_count().min(16)`, typically a handful of slots.
- model_gateway/src/worker/mod.rs: rustfmt-driven reorder of
  `pub use hash_ring::HashRing;` into alphabetical position between
  `error` and `http_client` (this is what was failing the format
  check on the parent PR).

Why not also remove the allocation?
- It pre-dates this PR and is not the issue the review flagged. The
  cost is bounded (~200 bytes for ≤16 slots) and dominates neither
  the `O(log n)` search nor the blake3 hashing. Optimizing it
  without profiling data would be premature, and mixing an
  algorithmic change into a doc-accuracy fix muddies the review.
  Tracked as a possible follow-up if profiling shows it matters.

Verification
- make fmt — clean.
- cargo fmt --all --check — exit 0.
- cargo clippy -p smg --all-targets -- -D warnings — clean.
- cargo test -p smg --lib — 544 passed; 0 failed; 4 ignored.

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@slin1237
slin1237 merged commit f2c6d31 into main Apr 14, 2026
47 checks passed
@slin1237
slin1237 deleted the refactor/extract-hash-ring branch April 14, 2026 16:45
slin1237 added a commit that referenced this pull request Apr 15, 2026
Audit found ~30 inline comments in worker/ that restated what the
next line of code already says. None of them encoded a non-obvious
invariant, workaround, or design rationale — they were just
narration of the form "// Increment counter" above `counter += 1`.

Following the project rule "default to writing no comments — only
add one when the WHY is non-obvious", this PR removes them so the
remaining comments in worker/ are uniformly load-bearing.

What was removed (per file)

- worker/circuit_breaker.rs (1)
  * "// Update last failure time atomically" above an atomic store.

- worker/hash_ring.rs (4)
  * "// Create Arc<str> once per worker, share across all virtual
    nodes." (the extract-and-clone pattern speaks for itself)
  * "// Sort by ring position for binary search."
    (sort_unstable_by_key on the position is self-evident)
  * "// Take first 8 bytes as u64." (u64::from_le_bytes(hash[..8]))
  * "// Binary search to find first entry at or after key_pos."
    (partition_point IS binary search)
  Kept: the expanded HashSet rationale block at find_healthy_url —
  that one explains WHY the dedupe set exists and that its capacity
  is bounded by worker_count, which is load-bearing context for
  reviewers per the recent allocation-claim review on #1129.

- worker/kv_event_monitor.rs (4)
  * "// Remove subscription from handles under lock." in
    on_worker_removed (the lock-scoped block already shows it)
  * Three "// Store first" / "// Remove" narrations in the
    apply_event dispatch tests (variable names already say it).

- worker/metrics_aggregator.rs (1, shortened)
  * Tighten "openmetrics_parser doesn't handle colons in metric
    names; replace with underscores" → "openmetrics_parser rejects
    colons in metric names." (drops the "and replace with
    underscores" tail since the .replace(":", "_") right below is
    self-evident).

- worker/registry.rs (~16)
  * Filter method narration in `WorkerRegistry::list` — four
    "// Check X if specified" comments above their `if let
    Some(...)` filters.
  * "// Get total workers count efficiently from DashMap" above a
    plain `.len()` call (the "efficiently" claim wasn't even
    actionable — it just decorated the call).
  * "// Update type/connection mode index if changed" above their
    `if old != new` blocks.
  * "// Remove from URL/type/connection mode mapping" narrations
    in the worker removal path.
  * "// Store worker" above `self.workers.insert(...)`. Kept the
    nearby copy-on-write rationale + DashMap-key-ownership notes
    because those encode WHY the clones are needed.
  * Test setup narration in five tests: "// Create a worker with
    labels", "// Remove worker", "// Create workers for different
    models", "// Set config for a model", "// Set retry config for
    the model", "// Create and register a worker", "// Remove the
    worker". Kept the "Last write wins" / "Disable retries" /
    "Remove first|last worker — config should still exist|be
    cleaned up" comments because those state the test's expected
    behavior, not its mechanics.

- worker/token_bucket.rs (3, one rewritten)
  * "// Wait for notify signal from return_tokens()" above
    `notify.notified().await` — the function-level doc above
    already explains the refill_rate=0 path.
  * "// Return a token - now we should be able to acquire" test
    narration above `bucket.return_tokens(1.0)`.
  * "// Wait - should NOT refill automatically" rewritten to
    "// refill_rate=0 should NOT refill automatically even after
    waiting" — keeps the assertion intent without narrating the
    sleep call.

What was deliberately KEPT
- All `///` doc comments and `//!` module docs (out of scope).
- Section-header comments in `worker.rs` that group trait methods
  (`// ── Health check counter accessors ──` etc.) — they add
  semantic grouping to a 2000-line file.
- Comments explaining lock ordering, atomic ordering rationale,
  borrow-checker workarounds, mesh-sync ordering invariants, and
  test logic premises ("Last write wins", "Should receive Removed
  event").
- The 7 `//` comments in `registry.rs` that document non-obvious
  invariants (mesh sync ordering, replace lock lifecycle, model
  retry config cleanup intent, etc.).

Net change: 6 files changed, 4 insertions(+), 42 deletions(-).

Verification
- cargo clippy -p smg --all-targets -- -D warnings — clean.
- cargo clippy -p smg-golang --all-targets -- -D warnings — clean.
- cargo check -p smg-python — clean.
- cargo test -p smg --lib — 546 passed; 0 failed; 4 ignored.
- make fmt — clean.
- rustup run nightly cargo fmt -- --check — EXIT=0 (matches CI).

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-gateway Model gateway crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant