@@ -4,7 +4,7 @@ use datadog_api_client::datadogV2::api_llm_observability::{
44} ;
55use datadog_api_client:: datadogV2:: model:: {
66 LLMObsAnnotationQueueInteractionsRequest , LLMObsAnnotationQueueRequest ,
7- LLMObsAnnotationQueueUpdateRequest , LLMObsDatasetRequest ,
7+ LLMObsAnnotationQueueUpdateRequest , LLMObsCustomEvalConfigUpdateRequest , LLMObsDatasetRequest ,
88 LLMObsDeleteAnnotationQueueInteractionsRequest , LLMObsDeleteExperimentsRequest ,
99 LLMObsExperimentRequest , LLMObsExperimentUpdateRequest , LLMObsProjectRequest ,
1010} ;
@@ -298,6 +298,36 @@ pub async fn annotation_queue_interactions_list(cfg: &Config, queue_id: &str) ->
298298 formatter:: output ( cfg, & resp)
299299}
300300
301+ // ---- Custom Evaluator Configs ----
302+
303+ pub async fn eval_config_get ( cfg : & Config , eval_name : & str ) -> Result < ( ) > {
304+ let api = make_api ( cfg) ;
305+ let resp = api
306+ . get_llm_obs_custom_eval_config ( eval_name. to_string ( ) )
307+ . await
308+ . map_err ( |e| anyhow:: anyhow!( "failed to get LLM obs custom eval config: {e:?}" ) ) ?;
309+ formatter:: output ( cfg, & resp)
310+ }
311+
312+ pub async fn eval_config_update ( cfg : & Config , eval_name : & str , file : & str ) -> Result < ( ) > {
313+ let body: LLMObsCustomEvalConfigUpdateRequest = util:: read_json_file ( file) ?;
314+ let api = make_api ( cfg) ;
315+ api. update_llm_obs_custom_eval_config ( eval_name. to_string ( ) , body)
316+ . await
317+ . map_err ( |e| anyhow:: anyhow!( "failed to update LLM obs custom eval config: {e:?}" ) ) ?;
318+ eprintln ! ( "LLM obs custom eval config '{eval_name}' updated." ) ;
319+ Ok ( ( ) )
320+ }
321+
322+ pub async fn eval_config_delete ( cfg : & Config , eval_name : & str ) -> Result < ( ) > {
323+ let api = make_api ( cfg) ;
324+ api. delete_llm_obs_custom_eval_config ( eval_name. to_string ( ) )
325+ . await
326+ . map_err ( |e| anyhow:: anyhow!( "failed to delete LLM obs custom eval config: {e:?}" ) ) ?;
327+ eprintln ! ( "LLM obs custom eval config '{eval_name}' deleted." ) ;
328+ Ok ( ( ) )
329+ }
330+
301331// ---- Spans (no typed equivalent — unstable MCP endpoint) ----
302332
303333#[ allow( clippy:: too_many_arguments) ]
@@ -1184,6 +1214,129 @@ mod tests {
11841214 cleanup_env ( ) ;
11851215 }
11861216
1217+ #[ tokio:: test]
1218+ async fn test_llm_obs_eval_config_get ( ) {
1219+ let _lock = lock_env ( ) . await ;
1220+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1221+ let mut server = mockito:: Server :: new_async ( ) . await ;
1222+ let cfg = test_config ( & server. url ( ) ) ;
1223+ let body = r#"{"data":{"id":"toxicity","type":"evaluator_config","attributes":{"eval_name":"toxicity","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z"}}}"# ;
1224+ let _mock = mock_any ( & mut server, "GET" , body) . await ;
1225+ let result = super :: eval_config_get ( & cfg, "toxicity" ) . await ;
1226+ assert ! ( result. is_ok( ) , "eval_config_get failed: {:?}" , result. err( ) ) ;
1227+ cleanup_env ( ) ;
1228+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1229+ }
1230+
1231+ #[ tokio:: test]
1232+ async fn test_llm_obs_eval_config_get_404 ( ) {
1233+ let _lock = lock_env ( ) . await ;
1234+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1235+ let mut server = mockito:: Server :: new_async ( ) . await ;
1236+ let cfg = test_config ( & server. url ( ) ) ;
1237+ let _mock = server
1238+ . mock ( "GET" , mockito:: Matcher :: Any )
1239+ . with_status ( 404 )
1240+ . with_header ( "content-type" , "application/json" )
1241+ . with_body ( r#"{"errors":["not found"]}"# )
1242+ . create_async ( )
1243+ . await ;
1244+ let result = super :: eval_config_get ( & cfg, "missing" ) . await ;
1245+ assert ! ( result. is_err( ) , "expected 404 error" ) ;
1246+ cleanup_env ( ) ;
1247+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1248+ }
1249+
1250+ #[ tokio:: test]
1251+ async fn test_llm_obs_eval_config_update ( ) {
1252+ let _lock = lock_env ( ) . await ;
1253+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1254+ let mut server = mockito:: Server :: new_async ( ) . await ;
1255+ let cfg = test_config ( & server. url ( ) ) ;
1256+ let tmp = write_temp_json (
1257+ "pup_test_eval_config_update.json" ,
1258+ r#"{"data":{"type":"evaluator_config","attributes":{"target":{"application_name":"my-app","enabled":true}}}}"# ,
1259+ ) ;
1260+ let _mock = server
1261+ . mock ( "PUT" , mockito:: Matcher :: Any )
1262+ . with_status ( 204 )
1263+ . create_async ( )
1264+ . await ;
1265+ let result = super :: eval_config_update ( & cfg, "toxicity" , tmp. to_str ( ) . unwrap ( ) ) . await ;
1266+ assert ! (
1267+ result. is_ok( ) ,
1268+ "eval_config_update failed: {:?}" ,
1269+ result. err( )
1270+ ) ;
1271+ let _ = std:: fs:: remove_file ( tmp) ;
1272+ cleanup_env ( ) ;
1273+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1274+ }
1275+
1276+ #[ tokio:: test]
1277+ async fn test_llm_obs_eval_config_update_400 ( ) {
1278+ let _lock = lock_env ( ) . await ;
1279+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1280+ let mut server = mockito:: Server :: new_async ( ) . await ;
1281+ let cfg = test_config ( & server. url ( ) ) ;
1282+ let tmp = write_temp_json (
1283+ "pup_test_eval_config_update_400.json" ,
1284+ r#"{"data":{"type":"evaluator_config","attributes":{"target":{"application_name":"my-app","enabled":true}}}}"# ,
1285+ ) ;
1286+ let _mock = server
1287+ . mock ( "PUT" , mockito:: Matcher :: Any )
1288+ . with_status ( 400 )
1289+ . with_header ( "content-type" , "application/json" )
1290+ . with_body ( r#"{"errors":["bad request"]}"# )
1291+ . create_async ( )
1292+ . await ;
1293+ let result = super :: eval_config_update ( & cfg, "toxicity" , tmp. to_str ( ) . unwrap ( ) ) . await ;
1294+ assert ! ( result. is_err( ) , "expected 400 error" ) ;
1295+ let _ = std:: fs:: remove_file ( tmp) ;
1296+ cleanup_env ( ) ;
1297+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1298+ }
1299+
1300+ #[ tokio:: test]
1301+ async fn test_llm_obs_eval_config_delete ( ) {
1302+ let _lock = lock_env ( ) . await ;
1303+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1304+ let mut server = mockito:: Server :: new_async ( ) . await ;
1305+ let cfg = test_config ( & server. url ( ) ) ;
1306+ let _mock = server
1307+ . mock ( "DELETE" , mockito:: Matcher :: Any )
1308+ . with_status ( 204 )
1309+ . create_async ( )
1310+ . await ;
1311+ let result = super :: eval_config_delete ( & cfg, "toxicity" ) . await ;
1312+ assert ! (
1313+ result. is_ok( ) ,
1314+ "eval_config_delete failed: {:?}" ,
1315+ result. err( )
1316+ ) ;
1317+ cleanup_env ( ) ;
1318+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1319+ }
1320+
1321+ #[ tokio:: test]
1322+ async fn test_llm_obs_eval_config_delete_404 ( ) {
1323+ let _lock = lock_env ( ) . await ;
1324+ std:: env:: set_var ( "DD_TOKEN_STORAGE" , "file" ) ;
1325+ let mut server = mockito:: Server :: new_async ( ) . await ;
1326+ let cfg = test_config ( & server. url ( ) ) ;
1327+ let _mock = server
1328+ . mock ( "DELETE" , mockito:: Matcher :: Any )
1329+ . with_status ( 404 )
1330+ . with_header ( "content-type" , "application/json" )
1331+ . with_body ( r#"{"errors":["not found"]}"# )
1332+ . create_async ( )
1333+ . await ;
1334+ let result = super :: eval_config_delete ( & cfg, "missing" ) . await ;
1335+ assert ! ( result. is_err( ) , "expected 404 error" ) ;
1336+ cleanup_env ( ) ;
1337+ std:: env:: remove_var ( "DD_TOKEN_STORAGE" ) ;
1338+ }
1339+
11871340 #[ tokio:: test]
11881341 async fn test_llm_obs_spans_search ( ) {
11891342 let _lock = lock_env ( ) . await ;
0 commit comments