@@ -321,6 +321,8 @@ def _build_trimmed_dataset_id(evaluator_id: str) -> str:
321321 if not base :
322322 base = "dataset"
323323 # Ensure first char is a letter
324+ if not base :
325+ base = "dataset"
324326 if not base [0 ].isalpha ():
325327 base = f"eval-{ base } "
326328 if len (base ) > max_base_len :
@@ -449,76 +451,122 @@ def create_rft_command(args) -> int:
449451 # Resolve evaluator resource name to fully-qualified format required by API
450452 evaluator_resource_name = f"accounts/{ account_id } /evaluators/{ evaluator_id } "
451453
454+ # Optional short-circuit: if evaluator already exists and not forcing, skip upload path
455+ skip_upload = False
456+ if not force :
457+ try :
458+ headers = {
459+ "Authorization" : f"Bearer { api_key } " ,
460+ "Content-Type" : "application/json" ,
461+ "User-Agent" : get_user_agent (),
462+ }
463+ resp = requests .get (f"{ api_base } /v1/{ evaluator_resource_name } " , headers = headers , timeout = 10 )
464+ if resp .ok :
465+ state = resp .json ().get ("state" , "STATE_UNSPECIFIED" )
466+ print (f"✓ Evaluator exists (state: { state } ). Skipping upload (use --force to overwrite)." )
467+ # Poll for ACTIVE before proceeding
468+ print (f"Waiting for evaluator '{ evaluator_id } ' to become ACTIVE..." )
469+ if not _poll_evaluator_status (
470+ evaluator_resource_name = evaluator_resource_name ,
471+ api_key = api_key ,
472+ api_base = api_base ,
473+ timeout_minutes = 10 ,
474+ ):
475+ app_base = _map_api_host_to_app_host (api_base )
476+ evaluator_slug = _extract_terminal_segment (evaluator_id )
477+ dashboard_url = f"{ app_base } /dashboard/evaluators/{ evaluator_slug } "
478+ print ("\n ❌ Evaluator is not ready within the timeout period." )
479+ print (f"📊 Please check the evaluator status at: { dashboard_url } " )
480+ print (" Wait for it to become ACTIVE, then run 'eval-protocol create rft' again." )
481+ return 1
482+ _save_last_evaluator (project_root , evaluator_id )
483+ skip_upload = True
484+ except requests .exceptions .RequestException :
485+ pass
486+
452487 # Ensure evaluator exists by invoking the upload flow programmatically
453- try :
454- from .upload import upload_command
488+ if not skip_upload :
489+ try :
490+ from .upload import upload_command
455491
456- tests = _discover_tests (project_root )
457- selected_entry : Optional [str ] = None
458- if len (tests ) == 1 :
459- func_name = tests [0 ].qualname .split ("." )[- 1 ]
460- abs_path = os .path .abspath (tests [0 ].file_path )
461- try :
462- rel = os .path .relpath (abs_path , project_root )
463- except Exception :
464- rel = abs_path
465- selected_entry = f"{ rel } ::{ func_name } "
466- else :
467- # Try to match evaluator_id to a discovered test's normalized ID
468- for t in tests :
469- func_name = t .qualname .split ("." )[- 1 ]
470- source_file_name = os .path .splitext (os .path .basename (t .file_path ))[0 ]
471- candidate = _normalize_evaluator_id (f"{ source_file_name } -{ func_name } " )
472- if candidate == evaluator_id :
473- abs_path = os .path .abspath (t .file_path )
474- try :
475- rel = os .path .relpath (abs_path , project_root )
476- except Exception :
477- rel = abs_path
478- selected_entry = f"{ rel } ::{ func_name } "
479- break
480-
481- upload_args = argparse .Namespace (
482- path = project_root ,
483- entry = selected_entry ,
484- id = evaluator_id ,
485- display_name = None ,
486- description = None ,
487- force = force , # Pass through the --force flag
488- yes = True ,
489- env_file = None , # Add the new env_file parameter
490- )
492+ tests = _discover_tests (project_root )
493+ selected_entry : Optional [str ] = None
494+ if len (tests ) == 1 :
495+ func_name = tests [0 ].qualname .split ("." )[- 1 ]
496+ abs_path = os .path .abspath (tests [0 ].file_path )
497+ try :
498+ rel = os .path .relpath (abs_path , project_root )
499+ except Exception :
500+ rel = abs_path
501+ selected_entry = f"{ rel } ::{ func_name } "
502+ else :
503+ # Try to match evaluator_id to a discovered test's normalized ID
504+ for t in tests :
505+ func_name = t .qualname .split ("." )[- 1 ]
506+ source_file_name = os .path .splitext (os .path .basename (t .file_path ))[0 ]
507+ candidate = _normalize_evaluator_id (f"{ source_file_name } -{ func_name } " )
508+ if candidate == evaluator_id :
509+ abs_path = os .path .abspath (t .file_path )
510+ try :
511+ rel = os .path .relpath (abs_path , project_root )
512+ except Exception :
513+ rel = abs_path
514+ selected_entry = f"{ rel } ::{ func_name } "
515+ break
516+ # If still unresolved and multiple tests exist, fail fast to avoid uploading unintended evaluators
517+ if selected_entry is None :
518+ print (
519+ f"Error: Multiple evaluation tests found, and the selected evaluator_id { evaluator_id } does not match any discovered test.\n "
520+ " Please re-run specifying the evaluator id.\n "
521+ " Hints:\n "
522+ " - eval-protocol create rft --evaluator-id <existing-evaluator-id>\n "
523+ )
524+ return 1
525+
526+ upload_args = argparse .Namespace (
527+ path = project_root ,
528+ entry = selected_entry ,
529+ id = evaluator_id ,
530+ display_name = None ,
531+ description = None ,
532+ force = force , # Pass through the --force flag
533+ yes = True ,
534+ env_file = None , # Add the new env_file parameter
535+ )
491536
492- if force :
493- print (f"🔄 Force flag enabled - will overwrite existing evaluator '{ evaluator_id } '" )
537+ if force :
538+ print (f"🔄 Force flag enabled - will overwrite existing evaluator '{ evaluator_id } '" )
494539
495- rc = upload_command (upload_args )
496- if rc == 0 :
497- print (f"✓ Uploaded/ensured evaluator: { evaluator_id } " )
540+ rc = upload_command (upload_args )
541+ if rc == 0 :
542+ print (f"✓ Uploaded/ensured evaluator: { evaluator_id } " )
498543
499- # Poll for evaluator status
500- print (f"Waiting for evaluator '{ evaluator_id } ' to become ACTIVE..." )
501- is_active = _poll_evaluator_status (
502- evaluator_resource_name = evaluator_resource_name , api_key = api_key , api_base = api_base , timeout_minutes = 10
503- )
544+ # Poll for evaluator status
545+ print (f"Waiting for evaluator '{ evaluator_id } ' to become ACTIVE..." )
546+ is_active = _poll_evaluator_status (
547+ evaluator_resource_name = evaluator_resource_name ,
548+ api_key = api_key ,
549+ api_base = api_base ,
550+ timeout_minutes = 10 ,
551+ )
504552
505- if not is_active :
506- # Print helpful message with dashboard link
507- app_base = _map_api_host_to_app_host (api_base )
508- evaluator_slug = _extract_terminal_segment (evaluator_id )
509- dashboard_url = f"{ app_base } /dashboard/evaluators/{ evaluator_slug } "
553+ if not is_active :
554+ # Print helpful message with dashboard link
555+ app_base = _map_api_host_to_app_host (api_base )
556+ evaluator_slug = _extract_terminal_segment (evaluator_id )
557+ dashboard_url = f"{ app_base } /dashboard/evaluators/{ evaluator_slug } "
510558
511- print ("\n ❌ Evaluator is not ready within the timeout period." )
512- print (f"📊 Please check the evaluator status at: { dashboard_url } " )
513- print (" Wait for it to become ACTIVE, then run 'eval-protocol create rft' again." )
514- return 1
559+ print ("\n ❌ Evaluator is not ready within the timeout period." )
560+ print (f"📊 Please check the evaluator status at: { dashboard_url } " )
561+ print (" Wait for it to become ACTIVE, then run 'eval-protocol create rft' again." )
562+ return 1
563+ else :
564+ # Only persist last-used evaluator after successful ensure + ACTIVE
565+ _save_last_evaluator (project_root , evaluator_id )
515566 else :
516- # Only persist last-used evaluator after successful ensure + ACTIVE
517- _save_last_evaluator (project_root , evaluator_id )
518- else :
519- print ("Warning: Evaluator upload did not complete successfully; proceeding to RFT creation." )
520- except Exception as e :
521- print (f"Warning: Failed to upload evaluator automatically: { e } " )
567+ print ("Warning: Evaluator upload did not complete successfully; proceeding to RFT creation." )
568+ except Exception as e :
569+ print (f"Warning: Failed to upload evaluator automatically: { e } " )
522570
523571 # Determine dataset id and materialization path
524572 dataset_id = getattr (args , "dataset_id" , None )
0 commit comments