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,16 +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 (eval_set_path , eval_ids )
278-
279321 eval_context .report_coverage = report_coverage
280322 eval_context .input_overrides = input_overrides
281323 eval_context .resume = resume
@@ -309,69 +351,103 @@ async def execute_eval():
309351 eval_context .job_id = ctx .job_id
310352
311353 runtime_factory = UiPathRuntimeFactoryRegistry .get (context = ctx )
312- factory_settings = await runtime_factory .get_settings ()
313- trace_settings = (
314- factory_settings .trace_settings if factory_settings else None
315- )
316-
317- if (
318- ctx .job_id or should_register_progress_reporter
319- ) and UiPathConfig .is_tracing_enabled :
320- # Live tracking for Orchestrator or Studio Web
321- # Uses UIPATH_TRACE_ID from environment for trace correlation
322- trace_manager .add_span_processor (
323- LiveTrackingSpanProcessor (
324- LlmOpsHttpExporter (),
325- settings = trace_settings ,
326- )
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 to resolve the path
382+ _ , resolved_eval_set_path = EvalHelpers .load_eval_set (
383+ eval_set_path , eval_ids
327384 )
328385
329- if trace_file :
386+ factory_settings = await runtime_factory . get_settings ()
330387 trace_settings = (
331388 factory_settings .trace_settings
332389 if factory_settings
333390 else None
334391 )
335- trace_manager .add_span_exporter (
336- JsonLinesFileExporter (trace_file ), settings = trace_settings
337- )
338392
339- project_id = UiPathConfig .project_id
393+ if (
394+ ctx .job_id or should_register_progress_reporter
395+ ) and UiPathConfig .is_tracing_enabled :
396+ # Live tracking for Orchestrator or Studio Web
397+ # Uses UIPATH_TRACE_ID from environment for trace correlation
398+ trace_manager .add_span_processor (
399+ LiveTrackingSpanProcessor (
400+ LlmOpsHttpExporter (),
401+ settings = trace_settings ,
402+ )
403+ )
340404
341- eval_context .execution_id = (
342- eval_context .job_id
343- or eval_context .eval_set_run_id
344- or str (uuid .uuid4 ())
345- )
405+ if trace_file :
406+ trace_settings = (
407+ factory_settings .trace_settings
408+ if factory_settings
409+ else None
410+ )
411+ trace_manager .add_span_exporter (
412+ JsonLinesFileExporter (trace_file ),
413+ settings = trace_settings ,
414+ )
346415
347- # Load eval set (path is already resolved in cli_eval.py)
348- eval_context .evaluation_set , _ = EvalHelpers .load_eval_set (
349- resolved_eval_set_path , eval_ids
350- )
416+ project_id = UiPathConfig .project_id
351417
352- # Resolve model settings override from eval set
353- settings_override = _resolve_model_settings_override (
354- model_settings_id , eval_context .evaluation_set
355- )
418+ eval_context .execution_id = (
419+ eval_context .job_id
420+ or eval_context .eval_set_run_id
421+ or str (uuid .uuid4 ())
422+ )
356423
357- runtime = await runtime_factory .new_runtime (
358- entrypoint = eval_context .entrypoint or "" ,
359- runtime_id = eval_context .execution_id ,
360- settings = settings_override ,
361- )
424+ # Load eval set (path is already resolved in cli_eval.py)
425+ eval_context .evaluation_set , _ = EvalHelpers .load_eval_set (
426+ resolved_eval_set_path , eval_ids
427+ )
362428
363- eval_context .runtime_schema = await runtime .get_schema ()
429+ # Resolve model settings override from eval set
430+ settings_override = _resolve_model_settings_override (
431+ model_settings_id , eval_context .evaluation_set
432+ )
364433
365- eval_context . evaluators = await EvalHelpers . load_evaluators (
366- resolved_eval_set_path ,
367- eval_context .evaluation_set ,
368- _get_agent_model ( eval_context . runtime_schema ) ,
369- )
434+ runtime = await runtime_factory . new_runtime (
435+ entrypoint = eval_context . entrypoint or "" ,
436+ runtime_id = eval_context .execution_id ,
437+ settings = settings_override ,
438+ )
370439
371- # Runtime is not required anymore.
372- await runtime .dispose ()
440+ eval_context .runtime_schema = await runtime .get_schema ()
441+
442+ eval_context .evaluators = await EvalHelpers .load_evaluators (
443+ resolved_eval_set_path ,
444+ eval_context .evaluation_set ,
445+ _get_agent_model (eval_context .runtime_schema ),
446+ )
447+
448+ # Runtime is not required anymore.
449+ await runtime .dispose ()
373450
374- try :
375451 if project_id :
376452 studio_client = StudioClient (project_id )
377453
@@ -395,11 +471,12 @@ async def execute_eval():
395471 event_bus ,
396472 )
397473 finally :
398- if runtime_factory :
399- await runtime_factory .dispose ()
474+ await runtime_factory .dispose ()
400475
401476 asyncio .run (execute_eval ())
402477
478+ except _EvalDiscoveryError as e :
479+ _show_eval_usage_help (e .entrypoints , e .eval_sets )
403480 except Exception as e :
404481 console .error (
405482 f"Error occurred: { e or 'Execution failed' } " , include_traceback = True
0 commit comments