BoundaryML/baml · error
Failed to parse client options for
Error message
Failed to parse client options for {}:
{} What it means
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).
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.
Example fix
// before
let options = json!({ "model": "gpt-4o", "temperature": "0.7" });
let client = runtime.registry().dynamic_new(ctx, "my-gpt", options)?;
// after
let options = json!({ "model": "gpt-4o", "temperature": 0.7 });
let client = runtime.registry().dynamic_new(ctx, "my-gpt", options)?; Defensive patterns
Strategy: validation
Validate before calling
fn validate_client_options(options: &serde_json::Map<String, serde_json::Value>) -> Result<(), String> {
let numeric = ["temperature", "max_tokens", "top_p", "seed"];
for k in numeric {
if let Some(v) = options.get(*k) {
if !v.is_number() {
return Err(format!("option `{}` must be a number, got: {:?}", k, v));
}
}
}
if !options.contains_key("model") {
return Err("option `model` is required".to_string());
}
Ok(())
} Type guard
fn is_valid_options(v: &serde_json::Value) -> bool {
v.is_object() && v.get("model").map_or(false, |m| m.is_string())
} Try / catch
match registry.dynamic_new(ctx, name, options) {
Ok(c) => c,
Err(e) if e.to_string().contains("Failed to parse client options") => {
eprintln!("Bad client options for {}: {e}", name);
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- {0}
- all ingress capacities and the response reservation must be…
- BamlError: BamlInvalidArgumentError
- combined normal and reserved capacity overflowed usize
- Configuration error
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/738f778575407a11.
Report an issue: GitHub.
Appendix: source
Thrown at engine/baml-runtime/src/client_registry/mod.rs:64
name: format!("{provider}/{model}"),
provider: provider.clone(),
retry_policy: None,
options: vec![("model".to_string(), BamlValue::String(model.to_string()))]
.into_iter()
.collect(),
}
}
pub fn unresolved_options(&self) -> Result<UnresolvedClientProperty<()>> {
let property = PropertyHandler::new(
self.options
.iter()
.map(|(k, v)| Ok((k.clone(), ((), v.to_resolvable()?))))
.collect::<Result<_>>()?,
(),
);
self.provider.parse_client_property(property).map_err(|e| {
anyhow::anyhow!(
"Failed to parse client options for {}:\n{}",
self.name,
e.into_iter()
.map(|e| e.message)
.collect::<Vec<_>>()
.join("\n")
)
})
}
}
#[derive(Clone, Deserialize, Debug, PartialEq)]
pub struct ClientRegistry {
#[serde(deserialize_with = "deserialize_clients")]
clients: HashMap<String, ClientProperty>,
primary: Option<String>,
}
View on GitHub (pinned to bd85ce9dee)