|
8 | 8 | Awaitable, |
9 | 9 | Iterable, |
10 | 10 | Iterator, |
11 | | - Sequence, |
12 | 11 | Tuple, |
13 | 12 | ) |
14 | 13 |
|
15 | 14 | import coverage |
16 | | -from opentelemetry import context as context_api |
17 | | -from opentelemetry.sdk.trace import ReadableSpan, Span |
18 | | -from opentelemetry.sdk.trace.export import ( |
19 | | - SpanExporter, |
20 | | - SpanExportResult, |
21 | | -) |
| 15 | +from opentelemetry.sdk.trace import ReadableSpan |
22 | 16 | from opentelemetry.trace import ( |
23 | 17 | NonRecordingSpan, |
24 | 18 | SpanContext, |
|
29 | 23 | ) |
30 | 24 | from pydantic import BaseModel |
31 | 25 | from uipath.core.tracing import UiPathTraceManager |
32 | | -from uipath.core.tracing.processors import UiPathExecutionBatchTraceProcessor |
33 | 26 | from uipath.runtime import ( |
34 | 27 | UiPathExecuteOptions, |
35 | 28 | UiPathExecutionRuntime, |
|
45 | 38 | from uipath.runtime.logging import UiPathRuntimeExecutionLogHandler |
46 | 39 | from uipath.runtime.schema import UiPathRuntimeSchema |
47 | 40 |
|
| 41 | +from uipath._cli._evals._context import UiPathEvalContext |
| 42 | +from uipath._cli._evals._exporters import ( |
| 43 | + ExecutionLogsExporter, |
| 44 | + ExecutionSpanExporter, |
| 45 | + ExecutionSpanProcessor, |
| 46 | +) |
48 | 47 | from uipath._cli._evals._span_utils import ( |
49 | 48 | configure_eval_set_run_span, |
50 | 49 | configure_evaluation_span, |
|
54 | 53 | from uipath._cli._evals.mocks.input_mocker import ( |
55 | 54 | generate_llm_input, |
56 | 55 | ) |
| 56 | +from uipath.eval.mocks.types import MockingContext |
| 57 | +from uipath.eval.models.evaluation_set import ( |
| 58 | + EvaluationItem, |
| 59 | + EvaluationSet, |
| 60 | +) |
57 | 61 |
|
58 | 62 | from ..._events._event_bus import EventBus |
59 | 63 | from ..._events._events import ( |
|
69 | 73 | from ...eval.models.models import AgentExecution, EvalItemResult |
70 | 74 | from .._utils._parallelization import execute_parallel |
71 | 75 | from ._eval_util import apply_input_overrides |
72 | | -from ._models._evaluation_set import ( |
73 | | - EvaluationItem, |
74 | | - EvaluationSet, |
75 | | -) |
76 | 76 | from ._models._exceptions import EvaluationRuntimeException |
77 | 77 | from ._models._output import ( |
78 | 78 | EvaluationResultDto, |
|
90 | 90 | from .mocks.mocks import ( |
91 | 91 | cache_manager_context, |
92 | 92 | clear_execution_context, |
93 | | - execution_id_context, |
94 | 93 | set_execution_context, |
95 | 94 | ) |
96 | | -from .mocks.types import MockingContext |
97 | 95 |
|
98 | 96 | logger = logging.getLogger(__name__) |
99 | 97 |
|
100 | 98 |
|
101 | | -class ExecutionSpanExporter(SpanExporter): |
102 | | - """Custom exporter that stores spans grouped by execution ids.""" |
103 | | - |
104 | | - def __init__(self): |
105 | | - # { execution_id -> list of spans } |
106 | | - self._spans: dict[str, list[ReadableSpan]] = defaultdict(list) |
107 | | - |
108 | | - def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: |
109 | | - for span in spans: |
110 | | - if span.attributes is not None: |
111 | | - exec_id = span.attributes.get("execution.id") |
112 | | - if exec_id is not None and isinstance(exec_id, str): |
113 | | - self._spans[exec_id].append(span) |
114 | | - |
115 | | - return SpanExportResult.SUCCESS |
116 | | - |
117 | | - def get_spans(self, execution_id: str) -> list[ReadableSpan]: |
118 | | - """Retrieve spans for a given execution id.""" |
119 | | - return self._spans.get(execution_id, []) |
120 | | - |
121 | | - def clear(self, execution_id: str | None = None) -> None: |
122 | | - """Clear stored spans for one or all executions.""" |
123 | | - if execution_id: |
124 | | - self._spans.pop(execution_id, None) |
125 | | - else: |
126 | | - self._spans.clear() |
127 | | - |
128 | | - def shutdown(self) -> None: |
129 | | - self.clear() |
130 | | - |
131 | | - |
132 | | -class ExecutionSpanProcessor(UiPathExecutionBatchTraceProcessor): |
133 | | - """Span processor that adds spans to ExecutionSpanCollector when they start.""" |
134 | | - |
135 | | - def __init__(self, span_exporter: SpanExporter, collector: ExecutionSpanCollector): |
136 | | - super().__init__(span_exporter) |
137 | | - self.collector = collector |
138 | | - |
139 | | - def on_start( |
140 | | - self, span: Span, parent_context: context_api.Context | None = None |
141 | | - ) -> None: |
142 | | - super().on_start(span, parent_context) |
143 | | - |
144 | | - exec_id = span.attributes.get("execution.id") if span.attributes else None |
145 | | - |
146 | | - # Fallback: if execution.id wasn't propagated (e.g., NonRecordingSpan |
147 | | - # parent on resume), get it from the execution context variable. |
148 | | - if exec_id is None: |
149 | | - ctx_exec_id = execution_id_context.get() |
150 | | - if ctx_exec_id: |
151 | | - span.set_attribute("execution.id", ctx_exec_id) |
152 | | - exec_id = ctx_exec_id |
153 | | - |
154 | | - if span.attributes and "execution.id" in span.attributes: |
155 | | - exec_id = span.attributes["execution.id"] |
156 | | - if isinstance(exec_id, str): |
157 | | - self.collector.add_span(span, exec_id) |
158 | | - |
159 | | - |
160 | | -class ExecutionLogsExporter: |
161 | | - """Custom exporter that stores multiple execution log handlers.""" |
162 | | - |
163 | | - def __init__(self): |
164 | | - self._log_handlers: dict[str, UiPathRuntimeExecutionLogHandler] = {} |
165 | | - |
166 | | - def register( |
167 | | - self, execution_id: str, handler: UiPathRuntimeExecutionLogHandler |
168 | | - ) -> None: |
169 | | - self._log_handlers[execution_id] = handler |
170 | | - |
171 | | - def get_logs(self, execution_id: str) -> list[logging.LogRecord]: |
172 | | - """Clear stored spans for one or all executions.""" |
173 | | - log_handler = self._log_handlers.get(execution_id) |
174 | | - return log_handler.buffer if log_handler else [] |
175 | | - |
176 | | - def clear(self, execution_id: str | None = None) -> None: |
177 | | - """Clear stored spans for one or all executions.""" |
178 | | - if execution_id: |
179 | | - self._log_handlers.pop(execution_id, None) |
180 | | - else: |
181 | | - self._log_handlers.clear() |
182 | | - |
183 | | - |
184 | | -class UiPathEvalContext: |
185 | | - """Context used for evaluation runs.""" |
186 | | - |
187 | | - # Required Fields |
188 | | - runtime_schema: UiPathRuntimeSchema |
189 | | - evaluation_set: EvaluationSet |
190 | | - evaluators: list[GenericBaseEvaluator[Any, Any, Any]] |
191 | | - execution_id: str |
192 | | - |
193 | | - # Optional Fields |
194 | | - entrypoint: str | None = None |
195 | | - workers: int | None = 1 |
196 | | - eval_set_run_id: str | None = None |
197 | | - verbose: bool = False |
198 | | - enable_mocker_cache: bool = False |
199 | | - report_coverage: bool = False |
200 | | - input_overrides: dict[str, Any] | None = None |
201 | | - resume: bool = False |
202 | | - job_id: str | None = None |
203 | | - |
204 | | - |
205 | 99 | class UiPathEvalRuntime: |
206 | 100 | """Specialized runtime for evaluation runs, with access to the factory.""" |
207 | 101 |
|
|
0 commit comments