mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 20:46:24 +00:00
feat(inference): register burst-v1 tier; SuperContext scout uses it (#4260)
This commit is contained in:
@@ -39,6 +39,7 @@ The router wraps several pre-created providers (Anthropic, OpenAI, Google, Groq,
|
|||||||
| `hint:vision` | A vision-capable model | Screenshots, image attachments, OCR |
|
| `hint:vision` | A vision-capable model | Screenshots, image attachments, OCR |
|
||||||
| `hint:summarize` | A model good at compression | Memory tree summary builders |
|
| `hint:summarize` | A model good at compression | Memory tree summary builders |
|
||||||
| `hint:code` | A code-tuned model | Native coder turns |
|
| `hint:code` | A code-tuned model | Native coder turns |
|
||||||
|
| `hint:burst` | A high-throughput, low-cost model | Cheap, latency-tolerant pre-flight sweeps — e.g. the SuperContext scout |
|
||||||
|
|
||||||
The exact mappings are configurable; the defaults ship sensible per-provider routes.
|
The exact mappings are configurable; the defaults ship sensible per-provider routes.
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ New thread, first message
|
|||||||
|
|
||||||
Because the scout is **read-only**, it can never take an action on a fresh thread — it only reads and summarizes. And because it runs in the harness rather than as an optional tool, the redundant `agent_prepare_context` tool is suppressed for that turn, so the agent doesn't do the work twice.
|
Because the scout is **read-only**, it can never take an action on a fresh thread — it only reads and summarizes. And because it runs in the harness rather than as an optional tool, the redundant `agent_prepare_context` tool is suppressed for that turn, so the agent doesn't do the work twice.
|
||||||
|
|
||||||
|
The scout runs on the **`burst` tier** (`hint = "burst"` → `burst-v1` on the managed backend) — a cheap, high-throughput, non-reasoning model. The sweep is a latency-tolerant pre-flight pass, so raw throughput on a fast model is a better fit than the pricier agentic/reasoning tiers. See [Automatic Model Routing](model-routing/README.md).
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## Safety and robustness
|
## Safety and robustness
|
||||||
|
|||||||
@@ -101,6 +101,15 @@ pub const PRICING_TABLE: &[ModelPricing] = &[
|
|||||||
cached_input_per_mtok_usd: 0.003625,
|
cached_input_per_mtok_usd: 0.003625,
|
||||||
output_per_mtok_usd: 0.87,
|
output_per_mtok_usd: 0.87,
|
||||||
},
|
},
|
||||||
|
// Burst tier — high-throughput, low-cost model; flat rate both directions,
|
||||||
|
// no prompt cache (so cached rate mirrors the input rate). Used by the
|
||||||
|
// SuperContext scout.
|
||||||
|
ModelPricing {
|
||||||
|
model: "burst-v1",
|
||||||
|
input_per_mtok_usd: 0.208,
|
||||||
|
cached_input_per_mtok_usd: 0.208,
|
||||||
|
output_per_mtok_usd: 0.208,
|
||||||
|
},
|
||||||
// Vision tier — multimodal; estimate only. The backend's echoed
|
// Vision tier — multimodal; estimate only. The backend's echoed
|
||||||
// `charged_amount_usd` is authoritative when present.
|
// `charged_amount_usd` is authoritative when present.
|
||||||
ModelPricing {
|
ModelPricing {
|
||||||
@@ -252,6 +261,17 @@ mod tests {
|
|||||||
assert_eq!(p.output_per_mtok_usd, 15.0);
|
assert_eq!(p.output_per_mtok_usd, 15.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lookup_pricing_has_a_burst_row() {
|
||||||
|
// The burst tier (SuperContext scout) must price from its own row —
|
||||||
|
// NOT via the $3/$15 fallback, which would inflate first-turn scout cost
|
||||||
|
// and could trip budget gates.
|
||||||
|
let p = lookup_pricing("burst-v1");
|
||||||
|
assert_eq!(p.model, "burst-v1");
|
||||||
|
assert_eq!(p.input_per_mtok_usd, 0.208);
|
||||||
|
assert_eq!(p.output_per_mtok_usd, 0.208);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lookup_pricing_falls_back_for_unknown_model() {
|
fn lookup_pricing_falls_back_for_unknown_model() {
|
||||||
let p = lookup_pricing("totally-unknown-model");
|
let p = lookup_pricing("totally-unknown-model");
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ omit_profile = false
|
|||||||
omit_memory_md = false
|
omit_memory_md = false
|
||||||
|
|
||||||
[model]
|
[model]
|
||||||
# Multi-step gathering loop (recall → maybe fetch → assess), so the agentic
|
# Multi-step gathering loop (recall → maybe fetch → assess). Rides the
|
||||||
# tier fits — not the slow reasoning tier (the scout is meant to be cheap).
|
# high-throughput `burst` tier: the scout is a cheap, latency-tolerant,
|
||||||
hint = "agentic"
|
# non-reasoning pre-flight pass, so raw throughput on a fast model beats the
|
||||||
|
# pricier agentic/reasoning tiers. Resolves to `burst-v1` on the managed backend.
|
||||||
|
hint = "burst"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
# Curated read-only context-gathering surface. No writes, no shell, no
|
# Curated read-only context-gathering surface. No writes, no shell, no
|
||||||
|
|||||||
@@ -988,6 +988,14 @@ mod tests {
|
|||||||
let def = find("context_scout");
|
let def = find("context_scout");
|
||||||
assert_eq!(def.agent_tier, AgentTier::Worker);
|
assert_eq!(def.agent_tier, AgentTier::Worker);
|
||||||
assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly);
|
assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly);
|
||||||
|
// Super-context scout rides the cheap, high-throughput `burst` tier
|
||||||
|
// (resolves to `burst-v1` on the managed backend) — not the pricier
|
||||||
|
// agentic/reasoning tiers.
|
||||||
|
assert!(
|
||||||
|
matches!(&def.model, ModelSpec::Hint(h) if h == "burst"),
|
||||||
|
"context_scout must spawn on the burst tier, got {:?}",
|
||||||
|
def.model
|
||||||
|
);
|
||||||
// Bundle cap — load-bearing for the parent's context budget. Leaves
|
// Bundle cap — load-bearing for the parent's context budget. Leaves
|
||||||
// room for the `recommended_skills` block alongside summary + plan.
|
// room for the `recommended_skills` block alongside summary + plan.
|
||||||
assert_eq!(def.max_result_chars, Some(5000));
|
assert_eq!(def.max_result_chars, Some(5000));
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ pub use schema::{
|
|||||||
StorageProviderSection, StreamMode, TeamModelConfig, TelegramConfig, TokenjuiceConfig,
|
StorageProviderSection, StreamMode, TeamModelConfig, TelegramConfig, TokenjuiceConfig,
|
||||||
UpdateConfig, UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, WebSearchConfig,
|
UpdateConfig, UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, WebSearchConfig,
|
||||||
WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MEMORY_SYNC_INTERVAL_SECS, DEFAULT_MODEL,
|
WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MEMORY_SYNC_INTERVAL_SECS, DEFAULT_MODEL,
|
||||||
MEMORY_SYNC_INTERVAL_PRESETS_SECS, MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1,
|
MEMORY_SYNC_INTERVAL_PRESETS_SECS, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1,
|
||||||
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
|
||||||
SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL,
|
MODEL_VISION_V1, SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED,
|
||||||
SEARCH_ENGINE_QUERIT,
|
SEARCH_ENGINE_PARALLEL, SEARCH_ENGINE_QUERIT,
|
||||||
};
|
};
|
||||||
pub use schemas::{
|
pub use schemas::{
|
||||||
all_controller_schemas as all_config_controller_schemas,
|
all_controller_schemas as all_config_controller_schemas,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ impl Default for CostConfig {
|
|||||||
/// Default pricing for popular models (USD per 1M tokens)
|
/// Default pricing for popular models (USD per 1M tokens)
|
||||||
fn get_default_pricing() -> HashMap<String, ModelPricing> {
|
fn get_default_pricing() -> HashMap<String, ModelPricing> {
|
||||||
use super::types::{
|
use super::types::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
||||||
MODEL_REASONING_V1,
|
MODEL_REASONING_V1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -192,6 +192,14 @@ fn get_default_pricing() -> HashMap<String, ModelPricing> {
|
|||||||
output: 3.30,
|
output: 3.30,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// Burst tier — high-throughput, low-cost model; flat rate both directions.
|
||||||
|
prices.insert(
|
||||||
|
MODEL_BURST_V1.into(),
|
||||||
|
ModelPricing {
|
||||||
|
input: 0.208,
|
||||||
|
output: 0.208,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
prices
|
prices
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ pub const MODEL_CHAT_V1: &str = "chat-v1";
|
|||||||
/// Legacy low-latency chat tier slug retained for older persisted configs.
|
/// Legacy low-latency chat tier slug retained for older persisted configs.
|
||||||
pub const MODEL_REASONING_QUICK_V1: &str = "reasoning-quick-v1";
|
pub const MODEL_REASONING_QUICK_V1: &str = "reasoning-quick-v1";
|
||||||
pub const MODEL_CODING_V1: &str = "coding-v1";
|
pub const MODEL_CODING_V1: &str = "coding-v1";
|
||||||
|
/// High-throughput "burst" tier served by the managed backend. Cheap, fast,
|
||||||
|
/// non-reasoning, text-only, 128k context, no prompt cache; used by the
|
||||||
|
/// super-context scout. Managed-backend only (no BYOK knob).
|
||||||
|
pub const MODEL_BURST_V1: &str = "burst-v1";
|
||||||
pub const MODEL_SUMMARIZATION_V1: &str = "summarization-v1";
|
pub const MODEL_SUMMARIZATION_V1: &str = "summarization-v1";
|
||||||
/// Multimodal (image-input) tier. Managed backend serves this with the vision
|
/// Multimodal (image-input) tier. Managed backend serves this with the vision
|
||||||
/// flag enabled; the vision sub-agent rides this tier via `hint:vision`.
|
/// flag enabled; the vision sub-agent rides this tier via `hint:vision`.
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
//! metadata is not yet available.
|
//! metadata is not yet available.
|
||||||
|
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
||||||
|
MODEL_REASONING_V1,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Conservative default for OpenHuman abstract tier models (tokens).
|
/// Conservative default for OpenHuman abstract tier models (tokens).
|
||||||
@@ -126,6 +127,9 @@ fn tier_context_window(model: &str) -> Option<u64> {
|
|||||||
MODEL_REASONING_V1 => Some(TIER_REASONING_CONTEXT),
|
MODEL_REASONING_V1 => Some(TIER_REASONING_CONTEXT),
|
||||||
MODEL_AGENTIC_V1 | MODEL_CODING_V1 => Some(TIER_LARGE_CONTEXT),
|
MODEL_AGENTIC_V1 | MODEL_CODING_V1 => Some(TIER_LARGE_CONTEXT),
|
||||||
"summarization-v1" => Some(TIER_SUMMARIZATION_CONTEXT),
|
"summarization-v1" => Some(TIER_SUMMARIZATION_CONTEXT),
|
||||||
|
// Burst tier advertises a 128k window on the managed backend. Matched on
|
||||||
|
// the `burst-v1` alias before any substring fallbacks below.
|
||||||
|
MODEL_BURST_V1 => Some(TIER_STANDARD_CONTEXT),
|
||||||
MODEL_CHAT_V1 | MODEL_REASONING_QUICK_V1 | "chat" => Some(TIER_STANDARD_CONTEXT),
|
MODEL_CHAT_V1 | MODEL_REASONING_QUICK_V1 | "chat" => Some(TIER_STANDARD_CONTEXT),
|
||||||
m if m.starts_with("gemma") || m.contains(":1b") || m.contains("270m") => {
|
m if m.starts_with("gemma") || m.contains(":1b") || m.contains("270m") => {
|
||||||
Some(TIER_LOCAL_CONTEXT)
|
Some(TIER_LOCAL_CONTEXT)
|
||||||
@@ -277,6 +281,9 @@ mod tests {
|
|||||||
assert_eq!(context_window_for_model("reasoning-v1"), Some(1_000_000));
|
assert_eq!(context_window_for_model("reasoning-v1"), Some(1_000_000));
|
||||||
assert_eq!(context_window_for_model("agentic-v1"), Some(200_000));
|
assert_eq!(context_window_for_model("agentic-v1"), Some(200_000));
|
||||||
assert_eq!(context_window_for_model("chat-v1"), Some(128_000));
|
assert_eq!(context_window_for_model("chat-v1"), Some(128_000));
|
||||||
|
// Burst tier — 128k on the managed backend. Matched on the alias, not
|
||||||
|
// the local-gemma 8k substring arm.
|
||||||
|
assert_eq!(context_window_for_model("burst-v1"), Some(128_000));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
context_window_for_model("reasoning-quick-v1"),
|
context_window_for_model("reasoning-quick-v1"),
|
||||||
Some(128_000)
|
Some(128_000)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub(crate) const NO_MODEL_CONFIGURED_ANCHOR: &str = "resolved to an empty model
|
|||||||
|
|
||||||
fn is_abstract_tier_model(model: &str) -> bool {
|
fn is_abstract_tier_model(model: &str) -> bool {
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
||||||
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
||||||
};
|
};
|
||||||
let trimmed = model.trim();
|
let trimmed = model.trim();
|
||||||
@@ -86,6 +86,7 @@ fn is_abstract_tier_model(model: &str) -> bool {
|
|||||||
|| trimmed == MODEL_CHAT_V1
|
|| trimmed == MODEL_CHAT_V1
|
||||||
|| trimmed == MODEL_AGENTIC_V1
|
|| trimmed == MODEL_AGENTIC_V1
|
||||||
|| trimmed == MODEL_CODING_V1
|
|| trimmed == MODEL_CODING_V1
|
||||||
|
|| trimmed == MODEL_BURST_V1
|
||||||
|| trimmed == MODEL_VISION_V1
|
|| trimmed == MODEL_VISION_V1
|
||||||
|| trimmed == MODEL_SUMMARIZATION_V1
|
|| trimmed == MODEL_SUMMARIZATION_V1
|
||||||
}
|
}
|
||||||
@@ -109,6 +110,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
|
|||||||
("chat", crate::openhuman::config::MODEL_CHAT_V1),
|
("chat", crate::openhuman::config::MODEL_CHAT_V1),
|
||||||
("agentic", crate::openhuman::config::MODEL_AGENTIC_V1),
|
("agentic", crate::openhuman::config::MODEL_AGENTIC_V1),
|
||||||
("coding", crate::openhuman::config::MODEL_CODING_V1),
|
("coding", crate::openhuman::config::MODEL_CODING_V1),
|
||||||
|
("burst", crate::openhuman::config::MODEL_BURST_V1),
|
||||||
("vision", crate::openhuman::config::MODEL_VISION_V1),
|
("vision", crate::openhuman::config::MODEL_VISION_V1),
|
||||||
(
|
(
|
||||||
"summarization",
|
"summarization",
|
||||||
@@ -125,6 +127,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
|
|||||||
(crate::openhuman::config::MODEL_REASONING_QUICK_V1, "chat"),
|
(crate::openhuman::config::MODEL_REASONING_QUICK_V1, "chat"),
|
||||||
(crate::openhuman::config::MODEL_AGENTIC_V1, "agentic"),
|
(crate::openhuman::config::MODEL_AGENTIC_V1, "agentic"),
|
||||||
(crate::openhuman::config::MODEL_CODING_V1, "coding"),
|
(crate::openhuman::config::MODEL_CODING_V1, "coding"),
|
||||||
|
(crate::openhuman::config::MODEL_BURST_V1, "burst"),
|
||||||
(crate::openhuman::config::MODEL_VISION_V1, "vision"),
|
(crate::openhuman::config::MODEL_VISION_V1, "vision"),
|
||||||
(
|
(
|
||||||
crate::openhuman::config::MODEL_SUMMARIZATION_V1,
|
crate::openhuman::config::MODEL_SUMMARIZATION_V1,
|
||||||
@@ -184,7 +187,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
|
|||||||
/// to the backend.
|
/// to the backend.
|
||||||
pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
|
pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
||||||
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
||||||
};
|
};
|
||||||
matches!(
|
matches!(
|
||||||
@@ -193,6 +196,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
|
|||||||
| MODEL_CHAT_V1
|
| MODEL_CHAT_V1
|
||||||
| MODEL_AGENTIC_V1
|
| MODEL_AGENTIC_V1
|
||||||
| MODEL_CODING_V1
|
| MODEL_CODING_V1
|
||||||
|
| MODEL_BURST_V1
|
||||||
| MODEL_REASONING_QUICK_V1
|
| MODEL_REASONING_QUICK_V1
|
||||||
| MODEL_SUMMARIZATION_V1
|
| MODEL_SUMMARIZATION_V1
|
||||||
| MODEL_VISION_V1
|
| MODEL_VISION_V1
|
||||||
@@ -200,6 +204,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
|
|||||||
| "hint:chat"
|
| "hint:chat"
|
||||||
| "hint:agentic"
|
| "hint:agentic"
|
||||||
| "hint:coding"
|
| "hint:coding"
|
||||||
|
| "hint:burst"
|
||||||
| "hint:summarization"
|
| "hint:summarization"
|
||||||
| "hint:vision"
|
| "hint:vision"
|
||||||
)
|
)
|
||||||
@@ -219,7 +224,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
|
|||||||
/// ([`crate::openhuman::inference::model_context::model_vision_enabled`]).
|
/// ([`crate::openhuman::inference::model_context::model_vision_enabled`]).
|
||||||
pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
|
pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
|
||||||
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
|
||||||
};
|
};
|
||||||
match model {
|
match model {
|
||||||
@@ -231,6 +236,8 @@ pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
|
|||||||
MODEL_REASONING_QUICK_V1 => false,
|
MODEL_REASONING_QUICK_V1 => false,
|
||||||
MODEL_AGENTIC_V1 | "hint:agentic" => false,
|
MODEL_AGENTIC_V1 | "hint:agentic" => false,
|
||||||
MODEL_CODING_V1 | "hint:coding" => false,
|
MODEL_CODING_V1 | "hint:coding" => false,
|
||||||
|
// Burst is a text-only tier.
|
||||||
|
MODEL_BURST_V1 | "hint:burst" => false,
|
||||||
MODEL_SUMMARIZATION_V1 | "hint:summarization" => false,
|
MODEL_SUMMARIZATION_V1 | "hint:summarization" => false,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@@ -259,6 +266,11 @@ pub fn provider_for_role(role: &str, config: &Config) -> String {
|
|||||||
"reasoning" => config.reasoning_provider.as_deref(),
|
"reasoning" => config.reasoning_provider.as_deref(),
|
||||||
"agentic" => config.agentic_provider.as_deref(),
|
"agentic" => config.agentic_provider.as_deref(),
|
||||||
"coding" => config.coding_provider.as_deref(),
|
"coding" => config.coding_provider.as_deref(),
|
||||||
|
// Burst is managed-backend only (no per-workload provider knob): it rides
|
||||||
|
// the hosted high-throughput tier. Unset → falls through to
|
||||||
|
// `primary_cloud` (→ managed `burst-v1`) like the other tier-specific
|
||||||
|
// background workloads.
|
||||||
|
"burst" => None,
|
||||||
// Tier-specific multimodal model; like `agentic` it is NOT part of the
|
// Tier-specific multimodal model; like `agentic` it is NOT part of the
|
||||||
// chat-tier BYOK inheritance below — when unset it falls through to
|
// chat-tier BYOK inheritance below — when unset it falls through to
|
||||||
// `primary_cloud` (→ managed `vision-v1`).
|
// `primary_cloud` (→ managed `vision-v1`).
|
||||||
@@ -299,13 +311,19 @@ pub fn provider_for_role(role: &str, config: &Config) -> String {
|
|||||||
if !matches!(role, "chat" | "reasoning" | "coding") {
|
if !matches!(role, "chat" | "reasoning" | "coding") {
|
||||||
if let Some(chat) = config.chat_provider.as_deref() {
|
if let Some(chat) = config.chat_provider.as_deref() {
|
||||||
if crate::openhuman::inference::local::profile::is_local_provider_string(chat) {
|
if crate::openhuman::inference::local::profile::is_local_provider_string(chat) {
|
||||||
|
// burst is managed-backend only — there is no `burst_provider`
|
||||||
|
// knob, so don't suggest setting one.
|
||||||
|
let override_hint = if role == "burst" {
|
||||||
|
"managed-backend only; no per-workload override".to_string()
|
||||||
|
} else {
|
||||||
|
format!("set {role}_provider explicitly to override")
|
||||||
|
};
|
||||||
log::info!(
|
log::info!(
|
||||||
"[providers][local-fallback] role={} using managed backend (chat is \
|
"[providers][local-fallback] role={} using managed backend (chat is \
|
||||||
local '{}' but background workloads require cloud — set \
|
local '{}' but background workloads require cloud — {})",
|
||||||
{}_provider explicitly to override)",
|
|
||||||
role,
|
role,
|
||||||
chat,
|
chat,
|
||||||
role
|
override_hint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,12 +889,18 @@ pub(crate) fn create_local_chat_provider_from_string(
|
|||||||
/// every attached image — leaving the managed vision sub-agent blind.
|
/// every attached image — leaving the managed vision sub-agent blind.
|
||||||
fn managed_tier_for_role(role: &str) -> Option<&'static str> {
|
fn managed_tier_for_role(role: &str) -> Option<&'static str> {
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
|
||||||
|
MODEL_VISION_V1,
|
||||||
};
|
};
|
||||||
match role {
|
match role {
|
||||||
"reasoning" => Some(MODEL_REASONING_V1),
|
"reasoning" => Some(MODEL_REASONING_V1),
|
||||||
"agentic" => Some(MODEL_AGENTIC_V1),
|
"agentic" => Some(MODEL_AGENTIC_V1),
|
||||||
"coding" => Some(MODEL_CODING_V1),
|
"coding" => Some(MODEL_CODING_V1),
|
||||||
|
// Burst rides the managed backend's high-throughput tier. Pinned here
|
||||||
|
// (rather than collapsing to `default_model`) so the `hint = "burst"`
|
||||||
|
// sub-agent — the super-context scout — actually reaches `burst-v1`.
|
||||||
|
// There is no `burst_provider` knob: burst is managed-only.
|
||||||
|
"burst" => Some(MODEL_BURST_V1),
|
||||||
"vision" => Some(MODEL_VISION_V1),
|
"vision" => Some(MODEL_VISION_V1),
|
||||||
// Background subconscious tick/triage: pinned to the lightweight chat
|
// Background subconscious tick/triage: pinned to the lightweight chat
|
||||||
// tier (see the doc above for why it is pinned despite being background).
|
// tier (see the doc above for why it is pinned despite being background).
|
||||||
@@ -977,6 +1001,7 @@ fn make_openhuman_backend(
|
|||||||
Some("chat") => crate::openhuman::config::MODEL_CHAT_V1.to_string(),
|
Some("chat") => crate::openhuman::config::MODEL_CHAT_V1.to_string(),
|
||||||
Some("agentic") => crate::openhuman::config::MODEL_AGENTIC_V1.to_string(),
|
Some("agentic") => crate::openhuman::config::MODEL_AGENTIC_V1.to_string(),
|
||||||
Some("coding") => crate::openhuman::config::MODEL_CODING_V1.to_string(),
|
Some("coding") => crate::openhuman::config::MODEL_CODING_V1.to_string(),
|
||||||
|
Some("burst") => crate::openhuman::config::MODEL_BURST_V1.to_string(),
|
||||||
Some("summarization") => crate::openhuman::config::MODEL_SUMMARIZATION_V1.to_string(),
|
Some("summarization") => crate::openhuman::config::MODEL_SUMMARIZATION_V1.to_string(),
|
||||||
Some("vision") => crate::openhuman::config::MODEL_VISION_V1.to_string(),
|
Some("vision") => crate::openhuman::config::MODEL_VISION_V1.to_string(),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ fn create_chat_provider_uses_role() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn managed_backend_pins_specialised_role_to_tier() {
|
fn managed_backend_pins_specialised_role_to_tier() {
|
||||||
use crate::openhuman::config::{
|
use crate::openhuman::config::{
|
||||||
MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1,
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1,
|
||||||
};
|
};
|
||||||
// default_model is chat-v1 — the value the buggy path would have leaked.
|
// default_model is chat-v1 — the value the buggy path would have leaked.
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
@@ -414,6 +414,7 @@ fn managed_backend_pins_specialised_role_to_tier() {
|
|||||||
("reasoning", MODEL_REASONING_V1),
|
("reasoning", MODEL_REASONING_V1),
|
||||||
("agentic", MODEL_AGENTIC_V1),
|
("agentic", MODEL_AGENTIC_V1),
|
||||||
("coding", MODEL_CODING_V1),
|
("coding", MODEL_CODING_V1),
|
||||||
|
("burst", MODEL_BURST_V1),
|
||||||
("vision", MODEL_VISION_V1),
|
("vision", MODEL_VISION_V1),
|
||||||
] {
|
] {
|
||||||
let (_, model) = create_chat_provider_from_string(role, "openhuman", &config)
|
let (_, model) = create_chat_provider_from_string(role, "openhuman", &config)
|
||||||
@@ -481,12 +482,16 @@ fn managed_backend_summarization_ignores_cloud_llm_model_override() {
|
|||||||
// `code_executor` agent (`hint = "coding"`) makes when it spawns.
|
// `code_executor` agent (`hint = "coding"`) makes when it spawns.
|
||||||
#[test]
|
#[test]
|
||||||
fn subagent_hint_resolves_to_tier_on_managed_backend() {
|
fn subagent_hint_resolves_to_tier_on_managed_backend() {
|
||||||
use crate::openhuman::config::{MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1};
|
use crate::openhuman::config::{
|
||||||
|
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
|
||||||
|
};
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
for (hint, expected_tier) in &[
|
for (hint, expected_tier) in &[
|
||||||
("coding", MODEL_CODING_V1),
|
("coding", MODEL_CODING_V1),
|
||||||
("agentic", MODEL_AGENTIC_V1),
|
("agentic", MODEL_AGENTIC_V1),
|
||||||
("reasoning", MODEL_REASONING_V1),
|
("reasoning", MODEL_REASONING_V1),
|
||||||
|
// The super-context scout (`context_scout`) spawns with `hint = "burst"`.
|
||||||
|
("burst", MODEL_BURST_V1),
|
||||||
] {
|
] {
|
||||||
let (_, model) =
|
let (_, model) =
|
||||||
create_chat_provider(hint, &config).expect("create_chat_provider must succeed");
|
create_chat_provider(hint, &config).expect("create_chat_provider must succeed");
|
||||||
@@ -1010,10 +1015,23 @@ fn known_hints_pass() {
|
|||||||
assert!(is_known_openhuman_tier("hint:chat"));
|
assert!(is_known_openhuman_tier("hint:chat"));
|
||||||
assert!(is_known_openhuman_tier("hint:agentic"));
|
assert!(is_known_openhuman_tier("hint:agentic"));
|
||||||
assert!(is_known_openhuman_tier("hint:coding"));
|
assert!(is_known_openhuman_tier("hint:coding"));
|
||||||
|
assert!(is_known_openhuman_tier("hint:burst"));
|
||||||
assert!(is_known_openhuman_tier("hint:summarization"));
|
assert!(is_known_openhuman_tier("hint:summarization"));
|
||||||
assert!(is_known_openhuman_tier("hint:vision"));
|
assert!(is_known_openhuman_tier("hint:vision"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `hint:burst` is accepted by `is_known_openhuman_tier`, so it must also be
|
||||||
|
// translated to `burst-v1` by the managed backend — otherwise a saved
|
||||||
|
// `default_model = "hint:burst"` would be forwarded literally and 400.
|
||||||
|
#[test]
|
||||||
|
fn managed_backend_translates_hint_burst_to_burst_tier() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.default_model = Some("hint:burst".to_string());
|
||||||
|
let (_, model) = create_chat_provider_from_string("chat", "openhuman", &config)
|
||||||
|
.expect("managed backend must build");
|
||||||
|
assert_eq!(model, crate::openhuman::config::MODEL_BURST_V1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_models_fail() {
|
fn invalid_models_fail() {
|
||||||
assert!(!is_known_openhuman_tier("deepseek-v4-pro"));
|
assert!(!is_known_openhuman_tier("deepseek-v4-pro"));
|
||||||
|
|||||||
Reference in New Issue
Block a user