feat: add native Ollama provider support via OLLAMA_HOST env var

- OLLAMA_HOST takes priority over OPENAI_BASE_URL for local Ollama instances
- No API key required; placeholder token used for Authorization header
- Model names like 'qwen3:8b' bypass strict provider/model syntax validation
- detect_provider_kind() checks OLLAMA_HOST first in routing cascade
- ProviderClient dispatch uses from_ollama_env() when OLLAMA_HOST is set
- Updated USAGE.md and docs with OLLAMA_HOST as preferred env var
- Added OLLAMA_CONFIG constant and from_ollama_env() to openai_compat
- Added test_ollama_host_bypasses_model_validation unit test
- Supersedes PR #3213 (which had a duplicate if-let bug in mod.rs)
This commit is contained in:
bellman
2026-06-05 12:12:56 +09:00
parent 503d515f38
commit be8112f5f5
6 changed files with 77 additions and 17 deletions

View File

@@ -2903,6 +2903,14 @@ fn resolve_model_alias_with_config(model: &str) -> String {
/// Rejects: empty, whitespace-only, strings with spaces, or invalid chars.
fn validate_model_syntax(model: &str) -> Result<(), String> {
let trimmed = model.trim();
// Ollama models use names like "qwen3:8b" that don't match provider/model
// syntax. Skip strict validation when OLLAMA_HOST is configured.
if std::env::var_os("OLLAMA_HOST").is_some() {
if trimmed.is_empty() {
return Err("invalid model syntax: model string cannot be empty.\nUsage: --model <model-name> e.g. --model qwen3:8b".to_string());
}
return Ok(());
}
if trimmed.is_empty() {
return Err("invalid model syntax: model string cannot be empty.\nUsage: --model <provider/model> e.g. --model anthropic/claude-opus-4-7".to_string());
}
@@ -19689,4 +19697,16 @@ mod alias_resolution_tests {
assert_eq!(resolve_model_alias_with_config(model), model);
assert!(validate_model_syntax(model).is_ok());
}
#[test]
fn test_ollama_host_bypasses_model_validation() {
// Safety: test sets and clears env var within the test.
std::env::set_var("OLLAMA_HOST", "http://127.0.0.1:11434");
// Ollama model names with colons pass
assert!(validate_model_syntax("qwen3:8b").is_ok());
assert!(validate_model_syntax("gemma4:e2b").is_ok());
assert!(validate_model_syntax("qwen3.6:27b-nvfp4").is_ok());
// Empty model still rejected
assert!(validate_model_syntax("").is_err());
std::env::remove_var("OLLAMA_HOST");
}
}