33import logging
44import os
55import uuid
6+ from pathlib import Path
67from typing import Any
78
89import click
1516from uipath ._cli .middlewares import Middlewares
1617from uipath .core .events import EventBus
1718from uipath .core .tracing import UiPathTraceManager
18- from uipath .eval ._helpers import auto_discover_entrypoint
19- from uipath .eval .helpers import EvalHelpers
19+ from uipath .eval .helpers import EVAL_SETS_DIRECTORY_NAME , EvalHelpers
2020from uipath .eval .models .evaluation_set import EvaluationSet
2121from uipath .eval .runtime import UiPathEvalContext , evaluate
2222from uipath .platform .chat import set_llm_concurrency
@@ -135,6 +135,55 @@ def _resolve_model_settings_override(
135135 return override if override else None
136136
137137
138+ class _EvalDiscoveryError (Exception ):
139+ """Raised when auto-discovery of entrypoint or eval set fails."""
140+
141+ def __init__ (self , entrypoints : list [str ], eval_sets : list [Path ]):
142+ self .entrypoints = entrypoints
143+ self .eval_sets = eval_sets
144+
145+
146+ def _discover_eval_sets () -> list [Path ]:
147+ """Discover available eval set files."""
148+ eval_sets_dir = Path (EVAL_SETS_DIRECTORY_NAME )
149+ if eval_sets_dir .exists ():
150+ return sorted (eval_sets_dir .glob ("*.json" ))
151+ return []
152+
153+
154+ def _show_eval_usage_help (entrypoints : list [str ], eval_set_files : list [Path ]) -> None :
155+ """Show available entrypoints and eval sets with usage examples."""
156+ lines : list [str ] = []
157+
158+ if entrypoints :
159+ lines .append ("Available entrypoints:" )
160+ for name in entrypoints :
161+ lines .append (f" - { name } " )
162+ else :
163+ lines .append (
164+ "No entrypoints found. "
165+ "Add a 'functions' or 'agents' section to your config file "
166+ "(e.g. uipath.json, langgraph.json)."
167+ )
168+
169+ if eval_set_files :
170+ lines .append ("\n Available eval sets:" )
171+ for f in eval_set_files :
172+ lines .append (f" - { f } " )
173+ else :
174+ lines .append (
175+ f"\n No eval sets found in '{ EVAL_SETS_DIRECTORY_NAME } /' directory."
176+ )
177+
178+ lines .append ("\n Usage: uipath eval <entrypoint> <eval_set>" )
179+ if entrypoints and eval_set_files :
180+ ep_name = entrypoints [0 ]
181+ es_path = eval_set_files [0 ]
182+ lines .append (f"Example: uipath eval { ep_name } { es_path } " )
183+
184+ click .echo ("\n " .join (lines ))
185+
186+
138187@click .command ()
139188@click .argument ("entrypoint" , required = False )
140189@click .argument ("eval_set" , required = False )
@@ -266,18 +315,9 @@ def eval(
266315
267316 if result .should_continue :
268317 eval_context = UiPathEvalContext ()
269-
270- eval_context .entrypoint = entrypoint or auto_discover_entrypoint ()
271318 eval_context .workers = workers
272319 eval_context .eval_set_run_id = eval_set_run_id
273320 eval_context .enable_mocker_cache = enable_mocker_cache
274-
275- # Load eval set to resolve the path
276- eval_set_path = eval_set or EvalHelpers .auto_discover_eval_set ()
277- _ , resolved_eval_set_path = EvalHelpers .load_eval_set (
278- eval_set_path , eval_ids , input_overrides = input_overrides
279- )
280-
281321 eval_context .report_coverage = report_coverage
282322 eval_context .input_overrides = input_overrides
283323 eval_context .resume = resume
@@ -311,71 +351,102 @@ async def execute_eval():
311351 eval_context .job_id = ctx .job_id
312352
313353 runtime_factory = UiPathRuntimeFactoryRegistry .get (context = ctx )
314- factory_settings = await runtime_factory .get_settings ()
315- trace_settings = (
316- factory_settings .trace_settings if factory_settings else None
317- )
318-
319- if (
320- ctx .job_id or should_register_progress_reporter
321- ) and UiPathConfig .is_tracing_enabled :
322- # Live tracking for Orchestrator or Studio Web
323- # Uses UIPATH_TRACE_ID from environment for trace correlation
324- trace_manager .add_span_processor (
325- LiveTrackingSpanProcessor (
326- LlmOpsHttpExporter (),
327- settings = trace_settings ,
354+
355+ try :
356+ # Auto-discover entrypoint and eval set using the runtime factory
357+ resolved_entrypoint = entrypoint
358+ eval_set_path = eval_set
359+
360+ available_entrypoints = runtime_factory .discover_entrypoints ()
361+ available_eval_sets = _discover_eval_sets ()
362+
363+ if not resolved_entrypoint :
364+ if len (available_entrypoints ) == 1 :
365+ resolved_entrypoint = available_entrypoints [0 ]
366+ else :
367+ raise _EvalDiscoveryError (
368+ available_entrypoints , available_eval_sets
369+ )
370+
371+ if not eval_set_path :
372+ if len (available_eval_sets ) == 1 :
373+ eval_set_path = str (available_eval_sets [0 ])
374+ else :
375+ raise _EvalDiscoveryError (
376+ available_entrypoints , available_eval_sets
377+ )
378+
379+ eval_context .entrypoint = resolved_entrypoint
380+
381+ # Load eval set and resolve the path
382+ loaded_eval_set , resolved_eval_set_path = (
383+ EvalHelpers .load_eval_set (
384+ eval_set_path , eval_ids , input_overrides = input_overrides
328385 )
329386 )
330387
331- if trace_file :
388+ factory_settings = await runtime_factory . get_settings ()
332389 trace_settings = (
333390 factory_settings .trace_settings
334391 if factory_settings
335392 else None
336393 )
337- trace_manager .add_span_exporter (
338- JsonLinesFileExporter (trace_file ), settings = trace_settings
339- )
340394
341- project_id = UiPathConfig .project_id
395+ if (
396+ ctx .job_id or should_register_progress_reporter
397+ ) and UiPathConfig .is_tracing_enabled :
398+ # Live tracking for Orchestrator or Studio Web
399+ # Uses UIPATH_TRACE_ID from environment for trace correlation
400+ trace_manager .add_span_processor (
401+ LiveTrackingSpanProcessor (
402+ LlmOpsHttpExporter (),
403+ settings = trace_settings ,
404+ )
405+ )
342406
343- eval_context .execution_id = (
344- eval_context .job_id
345- or eval_context .eval_set_run_id
346- or str (uuid .uuid4 ())
347- )
407+ if trace_file :
408+ trace_settings = (
409+ factory_settings .trace_settings
410+ if factory_settings
411+ else None
412+ )
413+ trace_manager .add_span_exporter (
414+ JsonLinesFileExporter (trace_file ),
415+ settings = trace_settings ,
416+ )
348417
349- # Load eval set (path is already resolved in cli_eval.py)
350- eval_context .evaluation_set , _ = EvalHelpers .load_eval_set (
351- resolved_eval_set_path ,
352- eval_ids ,
353- input_overrides = input_overrides ,
354- )
418+ project_id = UiPathConfig .project_id
355419
356- # Resolve model settings override from eval set
357- settings_override = _resolve_model_settings_override (
358- model_settings_id , eval_context .evaluation_set
359- )
420+ eval_context .execution_id = (
421+ eval_context .job_id
422+ or eval_context .eval_set_run_id
423+ or str (uuid .uuid4 ())
424+ )
360425
361- runtime = await runtime_factory .new_runtime (
362- entrypoint = eval_context .entrypoint or "" ,
363- runtime_id = eval_context .execution_id ,
364- settings = settings_override ,
365- )
426+ eval_context .evaluation_set = loaded_eval_set
366427
367- eval_context .runtime_schema = await runtime .get_schema ()
428+ # Resolve model settings override from eval set
429+ settings_override = _resolve_model_settings_override (
430+ model_settings_id , eval_context .evaluation_set
431+ )
368432
369- eval_context . evaluators = await EvalHelpers . load_evaluators (
370- resolved_eval_set_path ,
371- eval_context .evaluation_set ,
372- _get_agent_model ( eval_context . runtime_schema ) ,
373- )
433+ runtime = await runtime_factory . new_runtime (
434+ entrypoint = eval_context . entrypoint or "" ,
435+ runtime_id = eval_context .execution_id ,
436+ settings = settings_override ,
437+ )
374438
375- # Runtime is not required anymore.
376- await runtime .dispose ()
439+ eval_context .runtime_schema = await runtime .get_schema ()
440+
441+ eval_context .evaluators = await EvalHelpers .load_evaluators (
442+ resolved_eval_set_path ,
443+ eval_context .evaluation_set ,
444+ _get_agent_model (eval_context .runtime_schema ),
445+ )
446+
447+ # Runtime is not required anymore.
448+ await runtime .dispose ()
377449
378- try :
379450 if project_id :
380451 studio_client = StudioClient (project_id )
381452
@@ -399,11 +470,14 @@ async def execute_eval():
399470 event_bus ,
400471 )
401472 finally :
402- if runtime_factory :
403- await runtime_factory .dispose ()
473+ await runtime_factory .dispose ()
404474
405475 asyncio .run (execute_eval ())
406476
477+ except _EvalDiscoveryError as e :
478+ _show_eval_usage_help (e .entrypoints , e .eval_sets )
479+ except ValueError as e :
480+ console .error (str (e ))
407481 except Exception as e :
408482 console .error (
409483 f"Error occurred: { e or 'Execution failed' } " , include_traceback = True
0 commit comments