|
3 | 3 | import logging |
4 | 4 | import os |
5 | 5 | import uuid |
| 6 | +from pathlib import Path |
6 | 7 | from typing import Any |
7 | 8 |
|
8 | 9 | import click |
|
15 | 16 | from uipath._cli.middlewares import Middlewares |
16 | 17 | from uipath.core.events import EventBus |
17 | 18 | from 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 |
20 | 20 | from uipath.eval.models.evaluation_set import EvaluationSet |
21 | 21 | from uipath.eval.runtime import UiPathEvalContext, evaluate |
22 | 22 | from uipath.platform.chat import set_llm_concurrency |
@@ -135,6 +135,57 @@ def _resolve_model_settings_override( |
135 | 135 | return override if override else None |
136 | 136 |
|
137 | 137 |
|
| 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( |
| 155 | + entrypoints: list[str], eval_set_files: list[Path] |
| 156 | +) -> None: |
| 157 | + """Show available entrypoints and eval sets with usage examples.""" |
| 158 | + lines: list[str] = [] |
| 159 | + |
| 160 | + if entrypoints: |
| 161 | + lines.append("Available entrypoints:") |
| 162 | + for name in entrypoints: |
| 163 | + lines.append(f" - {name}") |
| 164 | + else: |
| 165 | + lines.append( |
| 166 | + "No entrypoints found. " |
| 167 | + "Add a 'functions' or 'agents' section to your config file " |
| 168 | + "(e.g. uipath.json, langgraph.json)." |
| 169 | + ) |
| 170 | + |
| 171 | + if eval_set_files: |
| 172 | + lines.append("\nAvailable eval sets:") |
| 173 | + for f in eval_set_files: |
| 174 | + lines.append(f" - {f}") |
| 175 | + else: |
| 176 | + lines.append( |
| 177 | + f"\nNo eval sets found in '{EVAL_SETS_DIRECTORY_NAME}/' directory." |
| 178 | + ) |
| 179 | + |
| 180 | + lines.append("\nUsage: uipath eval <entrypoint> <eval_set>") |
| 181 | + if entrypoints and eval_set_files: |
| 182 | + ep_name = entrypoints[0] |
| 183 | + es_path = eval_set_files[0] |
| 184 | + lines.append(f"Example: uipath eval {ep_name} {es_path}") |
| 185 | + |
| 186 | + click.echo("\n".join(lines)) |
| 187 | + |
| 188 | + |
138 | 189 | @click.command() |
139 | 190 | @click.argument("entrypoint", required=False) |
140 | 191 | @click.argument("eval_set", required=False) |
@@ -266,16 +317,9 @@ def eval( |
266 | 317 |
|
267 | 318 | if result.should_continue: |
268 | 319 | eval_context = UiPathEvalContext() |
269 | | - |
270 | | - eval_context.entrypoint = entrypoint or auto_discover_entrypoint() |
271 | 320 | eval_context.workers = workers |
272 | 321 | eval_context.eval_set_run_id = eval_set_run_id |
273 | 322 | 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 | | - |
279 | 323 | eval_context.report_coverage = report_coverage |
280 | 324 | eval_context.input_overrides = input_overrides |
281 | 325 | eval_context.resume = resume |
@@ -309,6 +353,37 @@ async def execute_eval(): |
309 | 353 | eval_context.job_id = ctx.job_id |
310 | 354 |
|
311 | 355 | runtime_factory = UiPathRuntimeFactoryRegistry.get(context=ctx) |
| 356 | + |
| 357 | + # Auto-discover entrypoint and eval set using the runtime factory |
| 358 | + resolved_entrypoint = entrypoint |
| 359 | + eval_set_path = eval_set |
| 360 | + |
| 361 | + available_entrypoints = runtime_factory.discover_entrypoints() |
| 362 | + available_eval_sets = _discover_eval_sets() |
| 363 | + |
| 364 | + if not resolved_entrypoint: |
| 365 | + if len(available_entrypoints) == 1: |
| 366 | + resolved_entrypoint = available_entrypoints[0] |
| 367 | + else: |
| 368 | + raise _EvalDiscoveryError( |
| 369 | + available_entrypoints, available_eval_sets |
| 370 | + ) |
| 371 | + |
| 372 | + if not eval_set_path: |
| 373 | + if len(available_eval_sets) == 1: |
| 374 | + eval_set_path = str(available_eval_sets[0]) |
| 375 | + else: |
| 376 | + raise _EvalDiscoveryError( |
| 377 | + available_entrypoints, available_eval_sets |
| 378 | + ) |
| 379 | + |
| 380 | + eval_context.entrypoint = resolved_entrypoint |
| 381 | + |
| 382 | + # Load eval set to resolve the path |
| 383 | + _, resolved_eval_set_path = EvalHelpers.load_eval_set( |
| 384 | + eval_set_path, eval_ids |
| 385 | + ) |
| 386 | + |
312 | 387 | factory_settings = await runtime_factory.get_settings() |
313 | 388 | trace_settings = ( |
314 | 389 | factory_settings.trace_settings if factory_settings else None |
@@ -400,6 +475,8 @@ async def execute_eval(): |
400 | 475 |
|
401 | 476 | asyncio.run(execute_eval()) |
402 | 477 |
|
| 478 | + except _EvalDiscoveryError as e: |
| 479 | + _show_eval_usage_help(e.entrypoints, e.eval_sets) |
403 | 480 | except Exception as e: |
404 | 481 | console.error( |
405 | 482 | f"Error occurred: {e or 'Execution failed'}", include_traceback=True |
|
0 commit comments