{"record":{"id":"738f778575407a11","repo":"BoundaryML/baml","slug":"failed-to-parse-client-options-for","errorCode":null,"errorMessage":"Failed to parse client options for {}:\n{}","messagePattern":"Failed to parse client options for (.+?):\n(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/baml-runtime/src/client_registry/mod.rs","lineNumber":64,"sourceCode":"            name: format!(\"{provider}/{model}\"),\n            provider: provider.clone(),\n            retry_policy: None,\n            options: vec![(\"model\".to_string(), BamlValue::String(model.to_string()))]\n                .into_iter()\n                .collect(),\n        }\n    }\n\n    pub fn unresolved_options(&self) -> Result<UnresolvedClientProperty<()>> {\n        let property = PropertyHandler::new(\n            self.options\n                .iter()\n                .map(|(k, v)| Ok((k.clone(), ((), v.to_resolvable()?))))\n                .collect::<Result<_>>()?,\n            (),\n        );\n        self.provider.parse_client_property(property).map_err(|e| {\n            anyhow::anyhow!(\n                \"Failed to parse client options for {}:\\n{}\",\n                self.name,\n                e.into_iter()\n                    .map(|e| e.message)\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\")\n            )\n        })\n    }\n}\n\n#[derive(Clone, Deserialize, Debug, PartialEq)]\npub struct ClientRegistry {\n    #[serde(deserialize_with = \"deserialize_clients\")]\n    clients: HashMap<String, ClientProperty>,\n    primary: Option<String>,\n}\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/baml-runtime/src/client_registry/mod.rs#L46-L82","documentation":"Thrown by ClientRegistry when a client definition's options (properties) fail provider-specific deserialization. The runtime resolves each key/value into typed options via the provider's parse_client_property, collecting all per-field validation messages. This means the client's options block in baml.config or the dynamic ClientSpec does not match what the provider expects (wrong types, unknown keys, missing required fields).","triggerScenarios":"Calling any dynamic client constructor (dynamic_new, dynamic_new_generic, dynamic_new_ollama, dynamic_new_azure, dynamic_new_responses, dynamic_new_transcriptions) with an options map whose values cannot be converted with to_resolvable or parsed by the provider's property parser; e.g. passing a string where a number (temperature, max_tokens) is required, or an unrecognized option key.","commonSituations":"Building clients at runtime from JSON/env-driven config where option values are strings instead of typed values; typos in option names for a provider (e.g. Azure deployment settings); upgrading BAML when a provider renames or drops an option; mixing options intended for one provider (OpenAI) into another (Ollama/Azure).","solutions":["Read the joined field messages in the error to find which option key failed, then fix its value type in the options map (e.g. pass temperature as a number, not a string).","Check the provider-specific option names against the BAML docs for that provider (openai/azure/ollama/responses/transcriptions differ).","If building from JSON, parse numeric/boolean fields into real types before calling the dynamic constructor (e.g. serde_json::from_value into a typed struct first).","Upgrade/align the BAML version if the option was renamed in a recent release."],"exampleFix":"// before\nlet options = json!({ \"model\": \"gpt-4o\", \"temperature\": \"0.7\" });\nlet client = runtime.registry().dynamic_new(ctx, \"my-gpt\", options)?;\n// after\nlet options = json!({ \"model\": \"gpt-4o\", \"temperature\": 0.7 });\nlet client = runtime.registry().dynamic_new(ctx, \"my-gpt\", options)?;","handlingStrategy":"validation","validationCode":"fn validate_client_options(options: &serde_json::Map<String, serde_json::Value>) -> Result<(), String> {\n    let numeric = [\"temperature\", \"max_tokens\", \"top_p\", \"seed\"];\n    for k in numeric {\n        if let Some(v) = options.get(*k) {\n            if !v.is_number() {\n                return Err(format!(\"option `{}` must be a number, got: {:?}\", k, v));\n            }\n        }\n    }\n    if !options.contains_key(\"model\") {\n        return Err(\"option `model` is required\".to_string());\n    }\n    Ok(())\n}","typeGuard":"fn is_valid_options(v: &serde_json::Value) -> bool {\n    v.is_object() && v.get(\"model\").map_or(false, |m| m.is_string())\n}","tryCatchPattern":"match registry.dynamic_new(ctx, name, options) {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"Failed to parse client options\") => {\n        eprintln!(\"Bad client options for {}: {e}\", name);\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Parse dynamic options from JSON into a typed provider options struct before constructing the client.","Keep option names per provider in a shared constants module to avoid typos.","Validate JSON/env-driven config at startup, before any client construction.","Pin and review BAML release notes when upgrading for renamed client options."],"tags":["rust","configuration","client-options","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}