{"record":{"id":"dda9bd29c332d60d","repo":"tinyhumansai/openhuman","slug":"chat-factory-no-model-configured-role-reso","errorCode":null,"errorMessage":"[chat-factory] no model configured: role '{}' resolved to an empty model id for slug '{}'. Include a model in the provider string (e.g. '{slug}:<model-id>') or set default_model on the cloud_providers entry for slug '{slug}'.","messagePattern":"\\[chat-factory\\] no model configured: role '(.+?)' resolved to an empty model id for slug '(.+?)'\\. Include a model in the provider string \\(e\\.g\\. '(.+?):<model-id>'\\) or set default_model on the cloud_providers entry for slug '(.+?)'\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/inference/provider/factory.rs","lineNumber":2191,"sourceCode":"    // actionable error. Sending an empty model string to providers like\n    // nvidia-nim causes a 400 \"model field is required\" — a confusing error\n    // that obscures the real cause (missing model in the provider string or\n    // unset default_model on the config entry).\n    // See https://github.com/tinyhumansai/openhuman/issues/2784.\n    //\n    // OpenhumanJwt entries are exempt: they always delegate to\n    // make_openhuman_backend which derives the model from config.default_model,\n    // ignoring whatever effective_model we computed here.\n    if entry.auth_style != AuthStyle::OpenhumanJwt && effective_model.trim().is_empty() {\n        log::warn!(\n            \"[nvidia-nim][chat-factory] role={} slug={} resolved to empty model — \\\n             provider string must include a model id (e.g. '{}:<model-id>') or \\\n             set default_model on the cloud_providers entry\",\n            role,\n            slug,\n            slug,\n        );\n        anyhow::bail!(\n            \"[chat-factory] no model configured: role '{}' resolved to an empty model id for slug '{}'. \\\n             Include a model in the provider string (e.g. '{slug}:<model-id>') or \\\n             set default_model on the cloud_providers entry for slug '{slug}'.\",\n            role,\n            slug,\n        );\n    }\n\n    if entry.auth_style != AuthStyle::OpenhumanJwt && is_abstract_tier_model(&effective_model) {\n        if let Some(default_model) = entry\n            .default_model\n            .as_deref()\n            .map(str::trim)\n            .filter(|m| !m.is_empty() && !is_abstract_tier_model(m))\n        {\n            log::info!(\n                \"[providers][chat-factory] role={} slug={} remapping abstract model {} -> {}\",\n                role,","sourceCodeStart":2173,"sourceCodeEnd":2209,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/inference/provider/factory.rs#L2173-L2209","documentation":"While resolving a cloud provider entry, the effective model id came back empty for a slug whose auth style needs an explicit model (factory.rs:~2185). `OpenhumanJwt` entries are exempt (the managed backend derives the model from `default_model` downstream); for everything else an empty model is fatal. The message names the role and slug and shows the `slug:<model-id>` syntax.","triggerScenarios":"A provider string like `\"deepseek\"` with no `:<model-id>` suffix, combined with a `cloud_providers` entry for that slug that has no `default_model`, resolving to an empty effective model for the role.","commonSituations":"Users add a custom provider slug in Settings but never set a model; a config hand-edit sets `x_provider = \"my-slug\"` without the model suffix; an entry's `default_model` removed during cleanup.","solutions":["Append the model id to the provider string: `my-slug:<model-id>` (e.g. `deepseek:deepseek-chat`).","Or set `default_model` on the matching `cloud_providers[]` entry in config so the bare slug resolves.","Verify with the role named in the message — the fix belongs on that role's provider setting.","After editing config, retry; resolution runs per request."],"exampleFix":"# config.toml — before\nchat_provider = \"deepseek\"\n\n# after (either form works)\nchat_provider = \"deepseek:deepseek-chat\"\n# or on the entry:\n[[cloud_providers]]\nslug = \"deepseek\"\ndefault_model = \"deepseek-chat\"","handlingStrategy":"validation","validationCode":"// Validate provider strings before they reach the factory:\nfn model_id_present(provider: &str, default_model: Option<&str>) -> bool {\n    provider.contains(':') || default_model.map(str::trim).is_some_and(|m| !m.is_empty())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write provider strings as `slug:<model-id>` unless the entry has `default_model`.","Validate provider config in a settings lint before saving.","Remember OpenhumanJwt entries are the only ones allowed to omit the model."],"tags":["config","model-id","provider-string","factory"],"backgroundTag":"missing-model-id","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}