Skip to content

Commit d2dea9c

Browse files
authored
feat: add support for input overrides for attachments in conversational evals (#1383)
1 parent 2f7b418 commit d2dea9c

5 files changed

Lines changed: 693 additions & 17 deletions

File tree

src/uipath/_cli/cli_eval.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ def eval(
274274

275275
# Load eval set to resolve the path
276276
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)
277+
_, resolved_eval_set_path = EvalHelpers.load_eval_set(
278+
eval_set_path, eval_ids, input_overrides=input_overrides
279+
)
278280

279281
eval_context.report_coverage = report_coverage
280282
eval_context.input_overrides = input_overrides
@@ -346,7 +348,9 @@ async def execute_eval():
346348

347349
# Load eval set (path is already resolved in cli_eval.py)
348350
eval_context.evaluation_set, _ = EvalHelpers.load_eval_set(
349-
resolved_eval_set_path, eval_ids
351+
resolved_eval_set_path,
352+
eval_ids,
353+
input_overrides=input_overrides,
350354
)
351355

352356
# Resolve model settings override from eval set

src/uipath/eval/helpers.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,51 @@
2424
EVAL_SETS_DIRECTORY_NAME = "evaluations/eval-sets"
2525

2626

27+
def _apply_file_overrides_to_conversational_inputs(
28+
conversational_inputs: Any,
29+
overrides: dict[str, Any],
30+
) -> None:
31+
"""Apply file overrides to conversational input attachments before mapper conversion.
32+
33+
Extracts file objects from override values (single dict or array), matches them
34+
to existing attachments by FullName, and replaces attachment fields in-place.
35+
No-op if there are no file overrides or no matching attachments.
36+
"""
37+
if not overrides:
38+
return
39+
40+
file_overrides: list[dict[str, Any]] = []
41+
for value in overrides.values():
42+
if isinstance(value, list):
43+
file_overrides.extend(f for f in value if isinstance(f, dict) and "ID" in f)
44+
elif isinstance(value, dict) and "ID" in value:
45+
file_overrides.append(value)
46+
47+
if not file_overrides:
48+
return
49+
50+
override_by_name = {f["FullName"]: f for f in file_overrides if "FullName" in f}
51+
52+
def _override_attachments(attachments: list[Any] | None) -> None:
53+
if not attachments:
54+
return
55+
for attachment in attachments:
56+
override = override_by_name.get(attachment.full_name)
57+
if override:
58+
attachment.id = override["ID"]
59+
if "FullName" in override:
60+
attachment.full_name = override["FullName"]
61+
if "MimeType" in override:
62+
attachment.mime_type = override["MimeType"]
63+
64+
_override_attachments(conversational_inputs.current_user_prompt.attachments)
65+
66+
for exchange in conversational_inputs.conversation_history:
67+
for message in exchange:
68+
if hasattr(message, "attachments"):
69+
_override_attachments(message.attachments)
70+
71+
2772
def discriminate_eval_set(data: dict[str, Any]) -> EvaluationSet | LegacyEvaluationSet:
2873
"""Discriminate and parse evaluation set based on version field.
2974
@@ -90,13 +135,19 @@ def auto_discover_eval_set() -> str:
90135

91136
@staticmethod
92137
def load_eval_set(
93-
eval_set_path: str, eval_ids: list[str] | None = None
138+
eval_set_path: str,
139+
eval_ids: list[str] | None = None,
140+
input_overrides: dict[str, Any] | None = None,
94141
) -> tuple[EvaluationSet, str]:
95142
"""Load the evaluation set from file.
96143
97144
Args:
98145
eval_set_path: Path to the evaluation set file
99146
eval_ids: Optional list of evaluation IDs to filter
147+
input_overrides: Optional input field overrides per evaluation ID.
148+
For conversational agents, file overrides are applied to attachments
149+
before the legacy-to-messages conversion so that overridden IDs
150+
are baked into the messages before mapping.
100151
101152
Returns:
102153
Tuple of (EvaluationSet, resolved_path)
@@ -147,6 +198,16 @@ def migrate_evaluation_item(
147198
)
148199

149200
if evaluation.conversational_inputs:
201+
overrides_for_eval = (
202+
input_overrides.get(evaluation.id, {})
203+
if input_overrides
204+
else {}
205+
)
206+
_apply_file_overrides_to_conversational_inputs(
207+
evaluation.conversational_inputs,
208+
overrides_for_eval,
209+
)
210+
150211
conversational_messages_input = UiPathLegacyEvalChatMessagesMapper.legacy_conversational_eval_input_to_uipath_message_list(
151212
evaluation.conversational_inputs
152213
)

src/uipath/eval/models/_conversational_utils.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
UiPathConversationToolCall,
1313
UiPathConversationToolCallData,
1414
UiPathConversationToolCallResult,
15+
UiPathExternalValue,
1516
UiPathInlineValue,
1617
)
1718

@@ -146,13 +147,21 @@ def legacy_conversational_eval_input_to_uipath_message_list(
146147
else []
147148
)
148149

149-
# TODO: Add attachments if present
150-
# if message.attachments:
151-
# for attachment in message.attachments:
152-
# content_parts.append(
153-
# UiPathConversationContentPart(...)
154-
# )
155-
150+
if eval_message.attachments:
151+
for attachment in eval_message.attachments:
152+
content_parts.append(
153+
UiPathConversationContentPart(
154+
content_part_id=str(uuid.uuid4()),
155+
mime_type=attachment.mime_type,
156+
data=UiPathExternalValue(
157+
uri=f"urn:uipath:cas:file:orchestrator:{attachment.id}"
158+
),
159+
name=attachment.full_name,
160+
citations=[],
161+
created_at=timestamp,
162+
updated_at=timestamp,
163+
)
164+
)
156165
messages.append(
157166
UiPathConversationMessage(
158167
message_id=str(uuid.uuid4()),
@@ -228,12 +237,21 @@ def legacy_conversational_eval_input_to_uipath_message_list(
228237
else []
229238
)
230239

231-
# TODO Add attachments if present
232-
# if eval_input.current_user_prompt.attachments:
233-
# for attachment in eval_input.current_user_prompt.attachments:
234-
# content_parts.append(
235-
# UiPathConversationContentPart(...)
236-
# )
240+
if eval_input.current_user_prompt.attachments:
241+
for attachment in eval_input.current_user_prompt.attachments:
242+
content_parts.append(
243+
UiPathConversationContentPart(
244+
content_part_id=str(uuid.uuid4()),
245+
mime_type=attachment.mime_type,
246+
data=UiPathExternalValue(
247+
uri=f"urn:uipath:cas:file:orchestrator:{attachment.id}"
248+
),
249+
name=attachment.full_name,
250+
citations=[],
251+
created_at=timestamp,
252+
updated_at=timestamp,
253+
)
254+
)
237255

238256
messages.append(
239257
UiPathConversationMessage(

0 commit comments

Comments
 (0)