Repository: tensorzero/tensorzero (language: rust)

Issue:
Short-hand model configuration doesn't work if there aren't any models explicitly defined
The following `tensorzero.toml`...

```toml
[functions.chatbot]
type = "chat"

[functions.chatbot.variants.openai]
type = "chat_completion"
model = "openai::gpt-4o-mini-2024-07-18"
```

...leads to:

```
gateway-1  | {"timestamp":"2024-12-26T04:46:24.615350Z","level":"ERROR","message":"missing field `models`\n","spans":[{"name":"load"}]}
gateway-1  | {"timestamp":"2024-12-26T04:46:24.615395Z","level":"ERROR","message":"Failed to load config: missing field `models`\n"}
```

The following `tensorzero.toml` works.

```toml
[models.not_used]
routing = ["openai"]

[models.not_used.providers.openai]
type = "openai"
model_name = "not_used"

[functions.chatbot]
type = "chat"

[functions.chatbot.variants.openai]
type = "chat_completion"
model = "openai::gpt-4o-mini-2024-07-18"
```



Discussion / hints:

Fix patch:
diff --git a/gateway/src/config_parser.rs b/gateway/src/config_parser.rs
index b3a65233b..4b29fad65 100644
--- a/gateway/src/config_parser.rs
+++ b/gateway/src/config_parser.rs
@@ -277,6 +277,7 @@ impl<'c> Config<'c> {
 #[serde(deny_unknown_fields)]
 struct UninitializedConfig {
     pub gateway: Option<GatewayConfig>,
+    #[serde(default)]
     pub models: ModelTable, // model name => model config
     #[serde(default)]
     pub embedding_models: HashMap<String, EmbeddingModelConfig>, // embedding model name => embedding model config
@@ -664,10 +665,16 @@ mod tests {
             .remove("models")
             .expect("Failed to remove `[models]` section");
 
+        // Remove all functions except generate_draft so we are sure what error will be thrown
+        config["functions"]
+            .as_table_mut()
+            .unwrap()
+            .retain(|k, _| k == "generate_draft");
+
         assert_eq!(
             Config::load_from_toml(config, base_path).unwrap_err(),
             Error::new(ErrorDetails::Config {
-                message: "missing field `models`\n".to_string()
+                message: "Model name 'gpt-3.5-turbo' not found in model table".to_string()
             })
         );
     }
@@ -1332,6 +1339,40 @@ mod tests {
         assert_eq!(templates.len(), 10);
     }
 
+    #[test]
+    fn test_config_load_shorthand_models_only() {
+        let config_str = r#"
+        # ┌────────────────────────────────────────────────────────────────────────────┐
+        # │                                  GENERAL                                   │
+        # └────────────────────────────────────────────────────────────────────────────┘
+
+        [gateway]
+        bind_address = "0.0.0.0:3000"
+
+
+        # ┌────────────────────────────────────────────────────────────────────────────┐
+        # │                                 FUNCTIONS                                  │
+        # └────────────────────────────────────────────────────────────────────────────┘
+
+        [functions.generate_draft]
+        type = "chat"
+        system_schema = "fixtures/config/functions/generate_draft/system_schema.json"
+
+        [functions.generate_draft.variants.openai_promptA]
+        type = "chat_completion"
+        weight = 0.9
+        model = "openai::gpt-3.5-turbo"
+        system_template = "fixtures/config/functions/generate_draft/promptA/system_template.minijinja"
+        "#;
+        env::set_var("OPENAI_API_KEY", "sk-something");
+        env::set_var("ANTHROPIC_API_KEY", "sk-something");
+        env::set_var("AZURE_OPENAI_API_KEY", "sk-something");
+
+        let config = toml::from_str(config_str).expect("Failed to parse sample config");
+        let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+        Config::load_from_toml(config, base_path.clone()).expect("Failed to load config");
+    }
+
     /// Get a sample valid config for testing
     fn get_sample_valid_config() -> toml::Table {
         let config_str = r#"


Test patch: