mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(inference): demote OpenRouter no-tool-use-endpoint 404 + surface it in Subconscious (#3996) (#4003)
This commit is contained in:
@@ -251,6 +251,18 @@ pub fn is_provider_config_rejection_message(body: &str) -> bool {
|
||||
// harmless (`.any()` short-circuits) and kept so each Sentry
|
||||
// family stays self-documenting.
|
||||
"does not support tools",
|
||||
// TAURI-RUST-ADC (~5.9k events / 10 users) — OpenRouter's
|
||||
// *router-level* phrasing of the same tool-capability user-state.
|
||||
// When the picked model has no provider endpoint that supports tool
|
||||
// calling, OpenRouter returns a 404 with `{"error":{"message":"No
|
||||
// endpoints found that support tool use. Try disabling \"<tool>\".
|
||||
// ..."}}`. The wording differs from the direct-provider `does not
|
||||
// support tools` body above ("tool use" vs "tools", prefixed with
|
||||
// "No endpoints found"), so it needs its own anchor. Same user-state
|
||||
// class: pick a tool-capable model. Surfaced most often by the
|
||||
// autonomous Subconscious loop, which additionally halts on this via
|
||||
// its own capability breaker (`subconscious/engine.rs`).
|
||||
"no endpoints found that support tool use",
|
||||
// TAURI-RUST-4P6 (~36.6k events / 2 users) — user picked an
|
||||
// *embedding* model (Ollama `bge-m3:latest`, OpenHuman's default
|
||||
// memory-tree embed model) as their chat model. Ollama rejects every
|
||||
@@ -367,6 +379,13 @@ mod tests {
|
||||
"J4",
|
||||
r#"custom_openai streaming API error (404 Not Found): {"error":{"message":"model 'llama3.3' not found","type":"not_found_error","param":null,"code":null}}"#,
|
||||
),
|
||||
// TAURI-RUST-ADC — OpenRouter router-level "no tool-use endpoint"
|
||||
// 404, surfaced by the autonomous Subconscious loop on a
|
||||
// content-safety model that supports no tools.
|
||||
(
|
||||
"ADC",
|
||||
r#"openrouter API error (404 Not Found): {"error":{"message":"No endpoints found that support tool use. Try disabling \"spawn_async_subagent\". To learn more about provider routing, visit: https://openrouter.ai/docs/guides/routing/provider-selection"}}"#,
|
||||
),
|
||||
// TAURI-RUST-4NM — nvidia-nim (and compatible providers) return
|
||||
// this body when the request body has an empty `"model":""`.
|
||||
// This is user-configuration state: the provider string had no
|
||||
|
||||
@@ -34,6 +34,13 @@ const TICK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30 * 60
|
||||
/// Per-tool-call timeout injected into the agent config.
|
||||
const TOOL_CALL_TIMEOUT_SECS: u64 = 5 * 60;
|
||||
|
||||
/// Actionable reason surfaced (via `SubconsciousStatus.provider_unavailable_reason`)
|
||||
/// when a subconscious tick fails because the configured chat model has no
|
||||
/// tool-use endpoint. The subconscious turn is inherently tool-bearing (it
|
||||
/// maintains its scratchpad through tools), so a tool-incapable model can never
|
||||
/// satisfy a tick — this tells the user how to recover. See TAURI-RUST-ADC.
|
||||
const TOOL_UNSUPPORTED_REASON: &str = "The selected chat model has no tool-use endpoint, so Subconscious can't run. Pick a tool-capable model in Settings > AI.";
|
||||
|
||||
/// Pick the `TrustedAutomationSource` variant for a subconscious tick.
|
||||
///
|
||||
/// Extracted from the engine's `run_agent` body so the
|
||||
@@ -285,6 +292,21 @@ impl SubconsciousEngine {
|
||||
state.total_ticks += 1;
|
||||
if agent_failed {
|
||||
state.consecutive_failures += 1;
|
||||
// Surface an actionable reason when the failure is a permanent
|
||||
// tool-capability error: the subconscious turn is inherently
|
||||
// tool-bearing, so the configured chat model can never satisfy it
|
||||
// and the user must pick a tool-capable model. The hard Sentry
|
||||
// flood from this error is suppressed at the provider classifier
|
||||
// (is_provider_config_rejection_message, TAURI-RUST-ADC); here we
|
||||
// only make the cause visible in Subconscious status.
|
||||
if let Err(e) = &agent_result {
|
||||
if is_tool_capability_error(e) {
|
||||
info!(
|
||||
"[subconscious] configured chat model has no tool-use endpoint — Subconscious can't run until the model changes (TAURI-RUST-ADC)"
|
||||
);
|
||||
state.provider_unavailable_reason = Some(TOOL_UNSUPPORTED_REASON.to_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.consecutive_failures = 0;
|
||||
state.last_tick_at = tick_at;
|
||||
@@ -464,6 +486,18 @@ fn resolve_subconscious_route(config: &Config) -> SubconsciousProviderRoute {
|
||||
}
|
||||
}
|
||||
|
||||
/// True when an agent-run error means the configured chat model can't do tool
|
||||
/// calls at all — a permanent, user-actionable condition (pick a tool-capable
|
||||
/// model). Matches both the direct-provider body (`<model> does not support
|
||||
/// tools`) and OpenRouter's router-level phrasing (`No endpoints found that
|
||||
/// support tool use`, TAURI-RUST-ADC). Kept narrow to tool capability so an
|
||||
/// unrelated provider error (auth, billing, rate-limit) is not misread as one.
|
||||
fn is_tool_capability_error(msg: &str) -> bool {
|
||||
let lower = msg.to_ascii_lowercase();
|
||||
lower.contains("no endpoints found that support tool use")
|
||||
|| lower.contains("does not support tools")
|
||||
}
|
||||
|
||||
// ── Pre-LLM memory retrieval ────────────────────────────────────────────────
|
||||
|
||||
/// Query the memory tree using scratchpad entries as context, returning
|
||||
|
||||
@@ -18,3 +18,33 @@ fn tick_origin_with_external_sync_chunk_uses_tainted_source() {
|
||||
TrustedAutomationSource::SubconsciousTainted
|
||||
));
|
||||
}
|
||||
|
||||
// ── Tool-capability error detection (TAURI-RUST-ADC) ────────────────────
|
||||
|
||||
#[test]
|
||||
fn tool_capability_error_matches_openrouter_and_direct_bodies() {
|
||||
// OpenRouter router-level 404 (the reported ADC body).
|
||||
assert!(is_tool_capability_error(
|
||||
r#"agent run: openrouter API error (404 Not Found): {"error":{"message":"No endpoints found that support tool use. Try disabling \"spawn_async_subagent\"."}}"#
|
||||
));
|
||||
// Direct-provider "does not support tools" phrasing (TAURI-RUST-35 family).
|
||||
assert!(is_tool_capability_error(
|
||||
r#"agent run: cloud API error: {"error":{"message":"qwen2:0.5b does not support tools"}}"#
|
||||
));
|
||||
// Case-insensitive.
|
||||
assert!(is_tool_capability_error(
|
||||
"NO ENDPOINTS FOUND THAT SUPPORT TOOL USE"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_capability_error_ignores_unrelated_failures() {
|
||||
// A different 404, an auth wall, and a generic timeout must NOT match.
|
||||
assert!(!is_tool_capability_error(
|
||||
r#"agent run: openrouter API error (404 Not Found): {"error":{"message":"model 'llama3.3' not found"}}"#
|
||||
));
|
||||
assert!(!is_tool_capability_error(
|
||||
"agent run: Backend returned 401 Unauthorized: Invalid token"
|
||||
));
|
||||
assert!(!is_tool_capability_error("agent run: request timed out"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user