mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8` tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's scheduler/runtime improvements. **Scope note:** the earlier open-relay/unenforced-admission mode has been removed from this PR at Mic's direction — it is not a product mode we want. Mesh admission remains roster/allowlist driven, exactly as on `main`: on a relay with no NIP-43 membership snapshot the mesh runs self-only. No NIP-11 mode probing, no mode-transition restarts. A future perimeter/admission strategy for open relays will be designed separately. This PR also: - seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above MeshLLM's 600-second backend timeout; - makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg graphs; - runs the feature-enabled desktop test suite in CI; - recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then ladders down through Gemma 4 E4B and Qwen 9B for smaller machines; - keeps stored shared-compute `auto` translated to MeshLLM's supported wire model `mesh`. RC8 verification: - `just ci` passed locally at `92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`. - Pre-push differential gates passed, including both desktop Tauri cfg graphs and package tests. - A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint for a Buzz-shaped system/user/tool/tool-result/final-response loop; all assertions passed and the isolated process was shut down. Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32 s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the first (prefix cache). --------- Signed-off-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Signed-off-by: Alessandro Joabar <sandro@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com> Co-authored-by: Alessandro Joabar <sandro@squareup.com>
This commit is contained in:
4
.github/workflows/_ci-desktop.yml
vendored
4
.github/workflows/_ci-desktop.yml
vendored
@@ -92,6 +92,10 @@ jobs:
|
||||
run: just desktop-tauri-test
|
||||
env:
|
||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
||||
- name: Desktop Mesh feature tests
|
||||
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --features mesh-llm --lib
|
||||
env:
|
||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
||||
- name: Desktop Tauri compiled-flag verification
|
||||
run: just desktop-tauri-test-compiled-flags
|
||||
env:
|
||||
|
||||
479
Cargo.lock
generated
479
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Justfile
11
Justfile
@@ -211,8 +211,13 @@ _ensure-migrations: _ensure-services
|
||||
./scripts/seed-local-community.sh
|
||||
|
||||
# Run clippy on the desktop Tauri Rust crate
|
||||
# Features are additive, so a single invocation lints only one cfg graph.
|
||||
# Both graphs ship (release-windows builds without mesh-llm), so lint both:
|
||||
# the default graph covers the `#[cfg(not(feature = "mesh-llm"))]` arms and
|
||||
# the feature-enabled graph covers the mesh code.
|
||||
desktop-tauri-clippy: _ensure-sidecar-stubs
|
||||
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets -- -D warnings
|
||||
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets --features mesh-llm -- -D warnings
|
||||
|
||||
# Check the desktop Tauri Rust crate compiles
|
||||
desktop-tauri-check: _ensure-sidecar-stubs
|
||||
@@ -682,7 +687,11 @@ desktop-standalone *ARGS: _ensure-sidecar-stubs
|
||||
fi
|
||||
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
||||
echo "Starting standalone desktop on Vite port ${BUZZ_VITE_PORT}; no relay services were started"
|
||||
pnpm exec tauri dev --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
||||
FEATURES=()
|
||||
if [[ -n "{{mesh}}" ]]; then
|
||||
FEATURES=(--features mesh-llm)
|
||||
fi
|
||||
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
||||
|
||||
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
|
||||
staging *ARGS: bootstrap _ensure-sidecar-stubs
|
||||
|
||||
@@ -86,8 +86,8 @@ async-compression = { version = "0.4.42", features = ["tokio", "gzip"] }
|
||||
dev = ["buzz-auth/dev"]
|
||||
|
||||
[dev-dependencies]
|
||||
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] }
|
||||
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] }
|
||||
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] }
|
||||
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] }
|
||||
# Relay-driven mesh lifecycle smoke (examples/mesh_relay_lifecycle_smoke.rs):
|
||||
# the relay client for discovery notes and the exact ed25519 the mesh owner
|
||||
# keys use for binding verification.
|
||||
|
||||
423
desktop/src-tauri/Cargo.lock
generated
423
desktop/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -110,15 +110,15 @@ buzz_voice_pkg = { package = "buzz-voice", path = "../../crates/buzz-voice" }
|
||||
buzz_terminal = { package = "buzz-terminal", path = "crates/buzz-terminal" }
|
||||
buzz_ws_client_pkg = { package = "buzz-ws-client", path = "../../crates/buzz-ws-client" }
|
||||
portable-pty = "0.9"
|
||||
iroh = { version = "1.0.2", optional = true }
|
||||
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
|
||||
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
|
||||
iroh = { version = "1.0.3", optional = true }
|
||||
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
|
||||
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
|
||||
# Model catalog + hardware survey for the Share-compute model picker (same
|
||||
# diagnose pattern as mesh-console). Lib name of mesh-llm-client is mesh_client.
|
||||
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-client", optional = true }
|
||||
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-node", optional = true }
|
||||
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-system", optional = true }
|
||||
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-events", optional = true }
|
||||
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-client", optional = true }
|
||||
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-node", optional = true }
|
||||
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-system", optional = true }
|
||||
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-events", optional = true }
|
||||
base64 = "0.22"
|
||||
sha2 = "0.11"
|
||||
tar = "0.4"
|
||||
|
||||
@@ -353,7 +353,7 @@ pub async fn apply_workspace(
|
||||
}
|
||||
}
|
||||
});
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mesh-llm"))]
|
||||
@@ -372,9 +372,11 @@ pub async fn apply_workspace(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
assert_current_apply_generation(&state.workspace_apply_generation, apply_generation)?;
|
||||
|
||||
Ok(())
|
||||
#[cfg(not(feature = "mesh-llm"))]
|
||||
{
|
||||
assert_current_apply_generation(&state.workspace_apply_generation, apply_generation)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -509,7 +509,7 @@ fn inherited_shared_compute_translates_to_supported_agent_transport() {
|
||||
);
|
||||
assert_eq!(
|
||||
effective.env.get("BUZZ_AGENT_MODEL").map(String::as_str),
|
||||
Some("auto")
|
||||
Some(super::super::RELAY_MESH_VIRTUAL_MODEL_ID)
|
||||
);
|
||||
assert_eq!(
|
||||
effective
|
||||
|
||||
@@ -66,6 +66,21 @@ pub fn apply_relay_mesh_env(
|
||||
// survives (see `insert_default_if_unset`, and the copy-forward list in
|
||||
// `relay_mesh_process_env` that preserves it through the spawn path).
|
||||
insert_default_if_unset(env, "BUZZ_AGENT_REQUIRE_REPLY", "1");
|
||||
// A local mesh prefills a cold multi-ten-thousand-token prompt at a few
|
||||
// hundred tokens/second, so the first turn of a large session legitimately
|
||||
// runs for minutes with no bytes on the wire (`stream: false`). MeshLLM's
|
||||
// own frontend gives such a request 600 s before it aborts
|
||||
// (`OpenAiFrontendConfig::DEFAULT_BACKEND_TIMEOUT`); buzz-agent's 240 s
|
||||
// default abandons it at four minutes, so the client gives up on work the
|
||||
// server is still doing and retries it — adding load to a box that is
|
||||
// already prefilling. Seat the client just above the server's budget so the
|
||||
// mesh's own error is what surfaces, rather than a client-side abort racing
|
||||
// it. Measured: an 88,318-token cold prompt on an M5 Max returns 200 at
|
||||
// 503 s. Remote providers keep the 240 s default; only mesh moves.
|
||||
// A default, not policy — an explicit user value survives via
|
||||
// `insert_default_if_unset` and the copy-forward list in
|
||||
// `relay_mesh_process_env`.
|
||||
insert_default_if_unset(env, "BUZZ_AGENT_LLM_TIMEOUT_SECS", "660");
|
||||
// Deliberately no BUZZ_AGENT_THINKING_EFFORT default: mesh translates
|
||||
// `reasoning_effort` into the chat template's `enable_thinking` flag, so any
|
||||
// value we pick overrides each model's own template default — and the right
|
||||
@@ -102,6 +117,10 @@ pub fn relay_mesh_process_env(
|
||||
for key in [
|
||||
"BUZZ_AGENT_MAX_OUTPUT_TOKENS",
|
||||
"BUZZ_AGENT_THINKING_EFFORT",
|
||||
// Same reason as `BUZZ_AGENT_REQUIRE_REPLY` below: without the
|
||||
// copy-forward, an explicit user timeout is re-defaulted to 660 by
|
||||
// `apply_relay_mesh_env`.
|
||||
"BUZZ_AGENT_LLM_TIMEOUT_SECS",
|
||||
// Must be copied forward for the user's value to survive: this map is
|
||||
// written onto the command *after* the layered user env, so a key absent
|
||||
// here is re-defaulted by `apply_relay_mesh_env` below and an explicit
|
||||
@@ -270,6 +289,74 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// MeshLLM's frontend allows a backend call 600 s
|
||||
/// (`OpenAiFrontendConfig::DEFAULT_BACKEND_TIMEOUT`). buzz-agent's 240 s
|
||||
/// default abandons a healthy cold prefill long before the server does and
|
||||
/// retries it, so the client budget must sit above the server's.
|
||||
#[test]
|
||||
fn native_provider_outlasts_the_mesh_backend_timeout() {
|
||||
let mut env = BTreeMap::new();
|
||||
apply_relay_mesh_env(
|
||||
&mut env,
|
||||
Some(RELAY_MESH_PROVIDER_ID),
|
||||
Some(RELAY_MESH_AUTO_MODEL_ID),
|
||||
);
|
||||
|
||||
let seconds: u64 = env
|
||||
.get("BUZZ_AGENT_LLM_TIMEOUT_SECS")
|
||||
.expect("mesh seeds a client timeout")
|
||||
.parse()
|
||||
.expect("timeout is whole seconds");
|
||||
assert!(
|
||||
seconds > 600,
|
||||
"client budget ({seconds}s) must outlast the mesh frontend's 600s backend timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_provider_preserves_explicit_llm_timeout() {
|
||||
let mut env =
|
||||
BTreeMap::from([("BUZZ_AGENT_LLM_TIMEOUT_SECS".to_string(), "90".to_string())]);
|
||||
apply_relay_mesh_env(
|
||||
&mut env,
|
||||
Some(RELAY_MESH_PROVIDER_ID),
|
||||
Some(RELAY_MESH_AUTO_MODEL_ID),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS").map(String::as_str),
|
||||
Some("90"),
|
||||
"an explicit timeout is a user decision, not a value to re-default"
|
||||
);
|
||||
}
|
||||
|
||||
/// Same spawn-ordering hazard as the reply guard: without the copy-forward
|
||||
/// in `relay_mesh_process_env`, the user's value is re-defaulted here.
|
||||
#[test]
|
||||
fn process_env_preserves_explicit_llm_timeout() {
|
||||
let effective_env =
|
||||
BTreeMap::from([("BUZZ_AGENT_LLM_TIMEOUT_SECS".to_string(), "90".to_string())]);
|
||||
|
||||
let env = relay_mesh_process_env(&effective_env, "Gemma-4");
|
||||
|
||||
assert_eq!(
|
||||
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS").map(String::as_str),
|
||||
Some("90")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_mesh_provider_leaves_llm_timeout_unset() {
|
||||
let mut env = BTreeMap::new();
|
||||
apply_relay_mesh_env(&mut env, Some("anthropic"), Some("claude-haiku-4.5"));
|
||||
|
||||
assert_eq!(
|
||||
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS"),
|
||||
None,
|
||||
"remote providers keep buzz-agent's own default"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_mesh_provider_leaves_reply_guard_unset() {
|
||||
let mut env = BTreeMap::new();
|
||||
|
||||
@@ -545,22 +545,6 @@ pub(crate) fn spawn_pending_profile_reconciliations(app: &tauri::AppHandle, work
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod profile_reconcile_tests {
|
||||
use super::profile_reconcile_completed;
|
||||
use crate::commands::ProfileReconcileOutcome;
|
||||
|
||||
#[test]
|
||||
fn skipped_reconciliation_never_retires_pending_work() {
|
||||
assert!(profile_reconcile_completed(
|
||||
ProfileReconcileOutcome::Reconciled
|
||||
));
|
||||
assert!(!profile_reconcile_completed(
|
||||
ProfileReconcileOutcome::SkippedDisabled
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mesh-llm")]
|
||||
fn persist_restore_error(
|
||||
app: &tauri::AppHandle,
|
||||
@@ -578,3 +562,19 @@ fn persist_restore_error(
|
||||
record.last_error = Some(error);
|
||||
save_managed_agents(app, &records)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod profile_reconcile_tests {
|
||||
use super::profile_reconcile_completed;
|
||||
use crate::commands::ProfileReconcileOutcome;
|
||||
|
||||
#[test]
|
||||
fn skipped_reconciliation_never_retires_pending_work() {
|
||||
assert!(profile_reconcile_completed(
|
||||
ProfileReconcileOutcome::Reconciled
|
||||
));
|
||||
assert!(!profile_reconcile_completed(
|
||||
ProfileReconcileOutcome::SkippedDisabled
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,38 +13,50 @@ use mesh_llm_system::hardware;
|
||||
use mesh_llm_system::vram::{format_rated_capacity, rated_capacity_gb};
|
||||
|
||||
/// Buzz-curated tier picks. These are the models we know survive the agent
|
||||
/// harness on shared compute — deliberately non-reasoning instruction models,
|
||||
/// so agents stay snappy instead of burning hidden reasoning tokens.
|
||||
/// harness on shared compute.
|
||||
///
|
||||
/// The large pick is resolved through mesh-llm's remote catalog
|
||||
/// (huggingface.co/datasets/meshllm/catalog), so it does not need to exist in
|
||||
/// the compiled `MODEL_CATALOG`; the entry is synthesized below.
|
||||
const CURATED_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M";
|
||||
const CURATED_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
|
||||
const CURATED_LARGE_SIZE: &str = "17GB";
|
||||
const CURATED_LARGE_FILE: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf";
|
||||
const CURATED_LARGE_DESCRIPTION: &str =
|
||||
"Gemma 4 26B MoE (4B active) — Buzz default for 64GB+ machines";
|
||||
/// The recommended ladder follows rated unified memory:
|
||||
/// - below 32 GB: Gemma 4 E4B;
|
||||
/// - 32 GB through the rated classes below 64 GB: Qwen3.5 9B;
|
||||
/// - 64 GB and above: Qwen3.8 27B.
|
||||
///
|
||||
/// The Qwen entries are canonicalized from mesh-llm's compiled
|
||||
/// `MODEL_CATALOG` rather than synthesized.
|
||||
const CURATED_LARGE: &str = "unsloth/Qwen3.8-27B-GGUF:Q4_K_M";
|
||||
const CURATED_LARGE_ALIAS: &str = "Qwen3.8-27B-Q4_K_M";
|
||||
const CURATED_MEDIUM: &str = "unsloth/Qwen3.5-9B-GGUF:Q4_K_M";
|
||||
const CURATED_MEDIUM_ALIAS: &str = "Qwen3.5-9B-Vision-Q4_K_M";
|
||||
const CURATED_SMALL: &str = "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M";
|
||||
const CURATED_SMALL_ALIAS: &str = "Gemma-4-E4B-it-Q4_K_M";
|
||||
/// Rated-capacity boundary between the two curated tiers, in GB (marketing
|
||||
/// capacity — a "64GB" Mac rates as 64 even though usable AI memory is less).
|
||||
/// Superseded large alias retained only to preserve the historical string
|
||||
/// canonicalization contract. Availability in a particular Mesh runtime is
|
||||
/// determined by that runtime's compiled catalog.
|
||||
const LEGACY_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M";
|
||||
const LEGACY_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
|
||||
/// Rated-capacity boundary for the balanced Qwen3.5 9B tier.
|
||||
const CURATED_MEDIUM_MIN_RATED_GB: u64 = 32;
|
||||
/// Qwen3.8 27B is recommended for 64 GB-and-larger rated capacity classes,
|
||||
/// leaving substantial headroom beyond its observed working footprint for KV
|
||||
/// cache and runtime use.
|
||||
const CURATED_LARGE_MIN_RATED_GB: u64 = 64;
|
||||
|
||||
/// The Buzz-curated recommendation for a machine's rated memory capacity.
|
||||
fn buzz_recommended_model(rated_gb: Option<u64>) -> &'static str {
|
||||
match rated_gb {
|
||||
Some(gb) if gb >= CURATED_LARGE_MIN_RATED_GB => CURATED_LARGE,
|
||||
_ => CURATED_SMALL,
|
||||
Some(gb) if gb >= CURATED_MEDIUM_MIN_RATED_GB => CURATED_MEDIUM,
|
||||
Some(_) | None => CURATED_SMALL,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Buzz's pre-0.74 curated package aliases into the canonical model
|
||||
/// Convert Buzz's historical curated package aliases into the canonical model
|
||||
/// ids advertised and accepted by Mesh's OpenAI ingress.
|
||||
pub(crate) fn canonical_curated_model_id(model_id: &str) -> &str {
|
||||
match model_id.trim() {
|
||||
CURATED_SMALL_ALIAS => CURATED_SMALL,
|
||||
CURATED_MEDIUM_ALIAS => CURATED_MEDIUM,
|
||||
CURATED_LARGE_ALIAS => CURATED_LARGE,
|
||||
LEGACY_LARGE_ALIAS => LEGACY_LARGE,
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
@@ -172,31 +184,14 @@ fn build_catalog(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The compiled MODEL_CATALOG does not know the Buzz large pick; it
|
||||
// resolves through mesh-llm's remote catalog at download time. Synthesize
|
||||
// its entry so the picker can offer it.
|
||||
if !entries.iter().any(|e| e.name == CURATED_LARGE) {
|
||||
let size_gb = parse_size_gb(CURATED_LARGE_SIZE);
|
||||
entries.push(MeshCatalogEntry {
|
||||
fit: fit_code(size_gb, vram_gb),
|
||||
installed: is_installed(CURATED_LARGE_FILE, CURATED_LARGE)
|
||||
|| is_installed(CURATED_LARGE_FILE, CURATED_LARGE_ALIAS),
|
||||
recommended: false,
|
||||
curated: false,
|
||||
name: CURATED_LARGE.to_string(),
|
||||
size: CURATED_LARGE_SIZE.to_string(),
|
||||
size_gb,
|
||||
description: CURATED_LARGE_DESCRIPTION.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let recommended = Some(buzz_recommended_model(rated_capacity_gb(vram_bytes)).to_string());
|
||||
for entry in &mut entries {
|
||||
entry.recommended = recommended.as_deref() == Some(entry.name.as_str());
|
||||
// Both curated tiers are always offered: the recommended one for this
|
||||
// machine plus the other pick (e.g. the small one as an explicit
|
||||
// lighter choice on big machines).
|
||||
entry.curated = entry.name == CURATED_LARGE || entry.name == CURATED_SMALL;
|
||||
// All curated tiers are always offered: the recommendation plus
|
||||
// lighter and heavier alternatives appropriate to other machine tiers.
|
||||
entry.curated = entry.name == CURATED_LARGE
|
||||
|| entry.name == CURATED_MEDIUM
|
||||
|| entry.name == CURATED_SMALL;
|
||||
}
|
||||
|
||||
entries.sort_by(|a, b| {
|
||||
@@ -269,20 +264,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recommendation_follows_buzz_curated_tiers() {
|
||||
fn recommendation_follows_buzz_curated_ladder() {
|
||||
assert_eq!(CURATED_SMALL, "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M");
|
||||
assert_eq!(CURATED_LARGE, "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M");
|
||||
// 64GB+ rated machines get the large curated pick.
|
||||
assert_eq!(CURATED_MEDIUM, "unsloth/Qwen3.5-9B-GGUF:Q4_K_M");
|
||||
assert_eq!(CURATED_LARGE, "unsloth/Qwen3.8-27B-GGUF:Q4_K_M");
|
||||
|
||||
// Qwen3.8 is recommended for 64 GB-and-larger rated capacity classes.
|
||||
let large = build_catalog(None, 64_000_000_000, 64.0, &[]);
|
||||
assert_eq!(large.recommended.as_deref(), Some(CURATED_LARGE));
|
||||
let large_80 = build_catalog(None, 80_000_000_000, 80.0, &[]);
|
||||
assert_eq!(large_80.recommended.as_deref(), Some(CURATED_LARGE));
|
||||
let big = build_catalog(None, 128_000_000_000, 128.0, &[]);
|
||||
assert_eq!(big.recommended.as_deref(), Some(CURATED_LARGE));
|
||||
// Below the boundary: the small curated pick — never a reasoning
|
||||
// model, never sub-4B guesswork.
|
||||
let small = build_catalog(None, 32_000_000_000, 32.0, &[]);
|
||||
|
||||
// The balanced Qwen3.5 tier covers every rated class from 32 GB up
|
||||
// to (but not including) 64 GB.
|
||||
let medium = build_catalog(None, 32_000_000_000, 32.0, &[]);
|
||||
assert_eq!(medium.recommended.as_deref(), Some(CURATED_MEDIUM));
|
||||
let medium_max = build_catalog(None, 48_000_000_000, 48.0, &[]);
|
||||
assert_eq!(medium_max.recommended.as_deref(), Some(CURATED_MEDIUM));
|
||||
|
||||
// Smaller and unknown machines use the light Gemma tier.
|
||||
let small = build_catalog(None, 24_000_000_000, 24.0, &[]);
|
||||
assert_eq!(small.recommended.as_deref(), Some(CURATED_SMALL));
|
||||
let tiny = build_catalog(None, 16_000_000_000, 16.0, &[]);
|
||||
assert_eq!(tiny.recommended.as_deref(), Some(CURATED_SMALL));
|
||||
let unknown = build_catalog(None, 0, 0.0, &[]);
|
||||
assert_eq!(unknown.recommended.as_deref(), Some(CURATED_SMALL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -291,10 +297,15 @@ mod tests {
|
||||
canonical_curated_model_id(CURATED_SMALL_ALIAS),
|
||||
CURATED_SMALL
|
||||
);
|
||||
assert_eq!(
|
||||
canonical_curated_model_id(CURATED_MEDIUM_ALIAS),
|
||||
CURATED_MEDIUM
|
||||
);
|
||||
assert_eq!(
|
||||
canonical_curated_model_id(CURATED_LARGE_ALIAS),
|
||||
CURATED_LARGE
|
||||
);
|
||||
assert_eq!(canonical_curated_model_id(LEGACY_LARGE_ALIAS), LEGACY_LARGE);
|
||||
assert_eq!(
|
||||
canonical_curated_model_id("other/model:Q4"),
|
||||
"other/model:Q4"
|
||||
@@ -304,14 +315,17 @@ mod tests {
|
||||
#[test]
|
||||
fn curated_picks_lead_the_catalog() {
|
||||
let catalog = build_catalog(None, 96_000_000_000, 96.0, &[]);
|
||||
// Recommended curated entry first, the other curated pick second,
|
||||
// advanced entries after.
|
||||
// Recommended curated entry first, then the other two curated tiers,
|
||||
// with advanced entries after them.
|
||||
assert_eq!(catalog.entries[0].name, CURATED_LARGE);
|
||||
assert!(catalog.entries[0].recommended && catalog.entries[0].curated);
|
||||
assert_eq!(catalog.entries[1].name, CURATED_SMALL);
|
||||
assert_eq!(catalog.entries[1].name, CURATED_MEDIUM);
|
||||
assert!(catalog.entries[1].curated && !catalog.entries[1].recommended);
|
||||
assert!(catalog.entries[2..].iter().all(|e| !e.curated));
|
||||
// The synthesized large pick carries a real size for fit ranking.
|
||||
assert_eq!(catalog.entries[2].name, CURATED_SMALL);
|
||||
assert!(catalog.entries[2].curated && !catalog.entries[2].recommended);
|
||||
assert!(catalog.entries[3..].iter().all(|e| !e.curated));
|
||||
// The large pick comes from the compiled catalog with a real size, so
|
||||
// fit ranking is meaningful rather than a placeholder.
|
||||
assert!(catalog.entries[0].size_gb > 10.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ mod usage;
|
||||
pub use usage::{serving_usage_from_payload, MeshServingUsage};
|
||||
|
||||
mod transport_policy;
|
||||
use transport_policy::{iroh_relay_mode, sdk_iroh_relay_config, validate_advertised_endpoint};
|
||||
#[cfg(test)]
|
||||
use transport_policy::iroh_relay_mode_from;
|
||||
use transport_policy::{iroh_relay_mode, validate_advertised_endpoint, IrohRelayMode};
|
||||
use transport_policy::{iroh_relay_mode_from, IrohRelayMode};
|
||||
|
||||
use mesh_llm_sdk::{client, serve, EmbeddedNodeHandle, MeshDiscoveryMode, TrustPolicy};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -376,13 +376,10 @@ impl DesktopMeshRuntime {
|
||||
if let Some(mesh_name) = request.mesh_name.as_deref() {
|
||||
builder = builder.mesh_name(mesh_name);
|
||||
}
|
||||
builder = match iroh_relay_mode()? {
|
||||
IrohRelayMode::Disabled => builder.disable_iroh_relays(true),
|
||||
IrohRelayMode::Default => builder.disable_iroh_relays(false),
|
||||
IrohRelayMode::Custom(urls) => builder
|
||||
.disable_iroh_relays(false)
|
||||
.iroh_relays(urls.into_iter().map(|url| url.to_string())),
|
||||
};
|
||||
let (disable_iroh_relays, iroh_relays) = sdk_iroh_relay_config(iroh_relay_mode()?);
|
||||
builder = builder
|
||||
.disable_iroh_relays(disable_iroh_relays)
|
||||
.iroh_relays(iroh_relays);
|
||||
if let Some(max_vram_gb) = request.max_vram_gb {
|
||||
builder = builder.max_vram_gb(max_vram_gb as f64);
|
||||
}
|
||||
@@ -416,13 +413,10 @@ impl DesktopMeshRuntime {
|
||||
if let Some(mesh_name) = request.mesh_name.as_deref() {
|
||||
builder = builder.mesh_name(mesh_name);
|
||||
}
|
||||
builder = match iroh_relay_mode()? {
|
||||
IrohRelayMode::Disabled => builder.disable_iroh_relays(true),
|
||||
IrohRelayMode::Default => builder.disable_iroh_relays(false),
|
||||
IrohRelayMode::Custom(urls) => builder
|
||||
.disable_iroh_relays(false)
|
||||
.iroh_relays(urls.into_iter().map(|url| url.to_string())),
|
||||
};
|
||||
let (disable_iroh_relays, iroh_relays) = sdk_iroh_relay_config(iroh_relay_mode()?);
|
||||
builder = builder
|
||||
.disable_iroh_relays(disable_iroh_relays)
|
||||
.iroh_relays(iroh_relays);
|
||||
if let Some(join_token) = request.join_token.as_deref() {
|
||||
builder = builder.join_token(join_token);
|
||||
}
|
||||
|
||||
@@ -443,6 +443,7 @@ mod tests {
|
||||
id: pubkey.to_string(),
|
||||
display_name: pubkey.to_string(),
|
||||
avatar_url: None,
|
||||
description: None,
|
||||
system_prompt: String::new(),
|
||||
runtime: None,
|
||||
model: None,
|
||||
|
||||
@@ -43,6 +43,22 @@ pub(super) fn iroh_relay_mode_from(raw: Option<&str>) -> anyhow::Result<IrohRela
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sdk_iroh_relay_config(mode: IrohRelayMode) -> (bool, Vec<String>) {
|
||||
match mode {
|
||||
IrohRelayMode::Disabled => (true, Vec::new()),
|
||||
IrohRelayMode::Default => (
|
||||
false,
|
||||
MESH_LLM_DEFAULT_RELAYS
|
||||
.iter()
|
||||
.map(|url| (*url).to_string())
|
||||
.collect(),
|
||||
),
|
||||
IrohRelayMode::Custom(urls) => {
|
||||
(false, urls.into_iter().map(|url| url.to_string()).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_configured_relay_url(raw: &str) -> anyhow::Result<RelayUrl> {
|
||||
let parsed = url::Url::parse(raw)
|
||||
.map_err(|error| anyhow::anyhow!("invalid relay URL {raw:?}: {error}"))?;
|
||||
@@ -205,9 +221,11 @@ fn validate_transport(transport: &TransportAddr, mode: &IrohRelayMode) -> anyhow
|
||||
/// mesh-llm serving node (they are not in iroh's own prod relay map).
|
||||
///
|
||||
/// Kept in sync with `effective_relay_urls(RelayPolicy::DefaultPublic, &[])`.
|
||||
const MESH_LLM_DEFAULT_RELAYS: &[&str] = &[
|
||||
pub(super) const MESH_LLM_DEFAULT_RELAYS: &[&str] = &[
|
||||
"https://usw1-2.relay.michaelneale.mesh-llm.iroh.link./",
|
||||
"https://aps1-1.relay.michaelneale.mesh-llm.iroh.link./",
|
||||
"https://euc1-1.relay.michaelneale.mesh-llm.iroh.link./",
|
||||
"https://use1-1.relay.michaelneale.mesh-llm.iroh.link./",
|
||||
];
|
||||
|
||||
/// Whether `relay` is one of mesh-llm's baked-in default public relays.
|
||||
@@ -327,6 +345,27 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sdk_default_relays_are_exactly_the_trusted_default_relays() {
|
||||
let (disabled, configured) = sdk_iroh_relay_config(IrohRelayMode::Default);
|
||||
assert!(!disabled);
|
||||
let configured = configured
|
||||
.into_iter()
|
||||
.map(|url| url.parse::<RelayUrl>().expect("SDK relay URL must parse"))
|
||||
.collect::<Vec<_>>();
|
||||
let trusted = MESH_LLM_DEFAULT_RELAYS
|
||||
.iter()
|
||||
.map(|url| {
|
||||
url.parse::<RelayUrl>()
|
||||
.expect("trusted relay URL must parse")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(configured, trusted);
|
||||
assert!(configured
|
||||
.iter()
|
||||
.all(|relay| relay_allowed(relay, &IrohRelayMode::Default)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_with_one_good_and_one_junk_candidate_is_sanitized() {
|
||||
// A mesh-llm endpoint can advertise a usable relay alongside an
|
||||
|
||||
Reference in New Issue
Block a user