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).
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)
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
- dependency name `{name}` is reserved
- all ingress capacities and the response reservation must be
- read capacity must fit inside normal capacity
- one response reservation must fit in the outbound byte capac
- combined normal and reserved capacity overflowed usize
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/738f778575407a11.
Report an issue: GitHub.