Repository: tensorzero/tensorzero (language: rust)

Issue:
Don't construct an error inside of `config.get_metric` for comments and demonstrations
Right now when we send demonstrations to the gateway we get 200s on the client but we also log the following (BAD, WRONG, SCARY) warning: 
```tensorzero-gateway-ner-fine-tuning-demonstrations     | {"timestamp":"2025-01-14T17:54:09.538237Z","level":"WARN","message":"Unknown metric: demonstration","spans":[{"metric_name":"demonstration","name":"feedback"}]}```

We should not construct an error in this case so the warning doesn't get logged.


Discussion / hints:

Fix patch:
diff --git a/gateway/src/config_parser.rs b/gateway/src/config_parser.rs
index d3e79b767..9047de4a9 100644
--- a/gateway/src/config_parser.rs
+++ b/gateway/src/config_parser.rs
@@ -219,8 +219,8 @@ impl<'c> Config<'c> {
         })
     }
 
-    /// Get a metric by name
-    pub fn get_metric<'a>(&'a self, metric_name: &str) -> Result<&'a MetricConfig, Error> {
+    /// Get a metric by name, producing an error if it's not found
+    pub fn get_metric_or_err<'a>(&'a self, metric_name: &str) -> Result<&'a MetricConfig, Error> {
         self.metrics.get(metric_name).ok_or_else(|| {
             Error::new(ErrorDetails::UnknownMetric {
                 name: metric_name.to_string(),
@@ -228,6 +228,11 @@ impl<'c> Config<'c> {
         })
     }
 
+    /// Get a metric by name
+    pub fn get_metric<'a>(&'a self, metric_name: &str) -> Option<&'a MetricConfig> {
+        self.metrics.get(metric_name)
+    }
+
     /// Get a tool by name
     pub fn get_tool<'a>(&'a self, tool_name: &str) -> Result<&'a StaticToolConfig, Error> {
         self.tools.get(tool_name).ok_or_else(|| {
diff --git a/gateway/src/endpoints/feedback.rs b/gateway/src/endpoints/feedback.rs
index 222322198..a5b27435b 100644
--- a/gateway/src/endpoints/feedback.rs
+++ b/gateway/src/endpoints/feedback.rs
@@ -170,21 +170,21 @@ fn get_feedback_metadata<'a>(
     }
     let metric = config.get_metric(metric_name);
     let feedback_type = match metric.as_ref() {
-        Ok(metric) => {
+        Some(metric) => {
             let feedback_type: FeedbackType = (&metric.r#type).into();
             Ok(feedback_type)
         }
-        Err(e) => match metric_name {
+        None => match metric_name {
             "comment" => Ok(FeedbackType::Comment),
             "demonstration" => Ok(FeedbackType::Demonstration),
-            _ => Err(Error::new(ErrorDetails::InvalidRequest {
-                message: e.to_string(),
+            _ => Err(Error::new(ErrorDetails::UnknownMetric {
+                name: metric_name.to_string(),
             })),
         },
     }?;
     let feedback_level = match metric {
-        Ok(metric) => Ok(&metric.level),
-        Err(_) => match feedback_type {
+        Some(metric) => Ok(&metric.level),
+        None => match feedback_type {
             FeedbackType::Demonstration => Ok(&MetricConfigLevel::Inference),
             _ => match (inference_id, episode_id) {
                 (Some(_), None) => Ok(&MetricConfigLevel::Inference),
@@ -284,7 +284,8 @@ async fn write_float(
         tags,
         ..
     } = params;
-    let metric_config: &crate::config_parser::MetricConfig = config.get_metric(metric_name)?;
+    let metric_config: &crate::config_parser::MetricConfig =
+        config.get_metric_or_err(metric_name)?;
     // Verify that the function name exists.
     let _ = throttled_get_function_name(&connection_info, &metric_config.level, &target_id).await?;
 
@@ -318,7 +319,7 @@ async fn write_boolean(
         tags,
         ..
     } = params;
-    let metric_config = config.get_metric(metric_name)?;
+    let metric_config = config.get_metric_or_err(metric_name)?;
     // Verify that the function name exists.
     let _ = throttled_get_function_name(&connection_info, &metric_config.level, &target_id).await?;
     let value = value.as_bool().ok_or_else(|| {
@@ -742,6 +743,31 @@ mod tests {
         );
     }
 
+    #[tokio::test]
+    async fn test_feedback_missing_metric() {
+        let metrics = HashMap::new();
+        let config = Config {
+            gateway: GatewayConfig::default(),
+            models: ModelTable::default(),
+            embedding_models: HashMap::new(),
+            metrics,
+            functions: HashMap::new(),
+            tools: HashMap::new(),
+            templates: TemplateConfig::new(),
+        };
+        let inference_id = Uuid::now_v7();
+        let metadata_err =
+            get_feedback_metadata(&config, "missing_metric_name", None, Some(inference_id))
+                .unwrap_err();
+        let details = metadata_err.get_owned_details();
+        assert_eq!(
+            details,
+            ErrorDetails::UnknownMetric {
+                name: "missing_metric_name".to_string(),
+            }
+        );
+    }
+
     #[tokio::test]
     async fn test_feedback_handler() {
         // Test a Comment Feedback


Test patch: