Skip to content

Commit e1eac9c

Browse files
Chibionosclaude
andauthored
fix(eval): stop stripping -community-agents model suffix before calling LLM Gateway (#1803)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 22f5687 commit e1eac9c

9 files changed

Lines changed: 224 additions & 35 deletions

packages/uipath/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.13.9"
3+
version = "2.13.10"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath/src/uipath/eval/evaluators/legacy_context_precision_evaluator.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
LegacyEvaluatorConfig,
1919
track_evaluation_metrics,
2020
)
21-
from .legacy_evaluator_utils import clean_model_name, serialize_object
21+
from .legacy_evaluator_utils import serialize_object
2222

2323

2424
class LegacyContextPrecisionEvaluatorConfig(LegacyEvaluatorConfig):
@@ -326,8 +326,7 @@ async def _get_structured_llm_response(
326326
ToolParametersDefinition,
327327
)
328328

329-
# Remove community-agents suffix from llm model name
330-
model = clean_model_name(self.model)
329+
model = self.model
331330

332331
# Create tool definition for context precision evaluation
333332
# Note: We pass the array schema as a raw dict because ToolPropertyDefinition

packages/uipath/src/uipath/eval/evaluators/legacy_evaluator_utils.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,6 @@
33
import json
44
from typing import Any, Optional
55

6-
from uipath.platform.constants import COMMUNITY_agents_SUFFIX
7-
8-
9-
def clean_model_name(model: str) -> str:
10-
"""Remove community-agents suffix from model name.
11-
12-
Args:
13-
model: Model name that may have the community suffix
14-
15-
Returns:
16-
Model name without the community suffix
17-
"""
18-
if model.endswith(COMMUNITY_agents_SUFFIX):
19-
return model.replace(COMMUNITY_agents_SUFFIX, "")
20-
return model
21-
226

237
def serialize_object(
248
content: Any,

packages/uipath/src/uipath/eval/evaluators/legacy_faithfulness_evaluator.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
track_evaluation_metrics,
1919
)
2020
from .legacy_evaluator_utils import (
21-
clean_model_name,
2221
serialize_object,
2322
)
2423

@@ -513,8 +512,7 @@ async def _get_structured_llm_response(
513512
ToolParametersDefinition,
514513
)
515514

516-
# Remove community-agents suffix from llm model name
517-
model = clean_model_name(self.model)
515+
model = self.model
518516

519517
# Create a dynamic tool definition based on the schema
520518
tool = ToolDefinition(

packages/uipath/src/uipath/eval/evaluators/legacy_llm_as_judge_evaluator.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from uipath.platform import UiPath
99
from uipath.platform.chat import UiPathLlmChatService
1010
from uipath.platform.chat.llm_gateway import RequiredToolChoice
11-
from uipath.platform.constants import COMMUNITY_agents_SUFFIX
1211

1312
from .._execution_context import eval_set_run_id_context
1413
from .._helpers.helpers import is_empty_value
@@ -193,10 +192,7 @@ async def _get_llm_response(self, evaluation_prompt: str) -> LLMResponse:
193192
Returns:
194193
LLMResponse with score and justification
195194
"""
196-
# remove community-agents suffix from llm model name
197195
model = self.model
198-
if model.endswith(COMMUNITY_agents_SUFFIX):
199-
model = model.replace(COMMUNITY_agents_SUFFIX, "")
200196

201197
# Create evaluation tool for function calling (works across all models)
202198
evaluation_tool = create_evaluation_tool()

packages/uipath/src/uipath/eval/evaluators/legacy_trajectory_evaluator.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from uipath.platform import UiPath
1010
from uipath.platform.chat import UiPathLlmChatService
1111
from uipath.platform.chat.llm_gateway import RequiredToolChoice
12-
from uipath.platform.constants import COMMUNITY_agents_SUFFIX
1312

1413
from .._execution_context import eval_set_run_id_context
1514
from .._helpers.evaluators_helpers import trace_to_str
@@ -163,8 +162,6 @@ async def _get_llm_response(self, evaluation_prompt: str) -> LLMResponse:
163162
assert self.llm, "LLM should be initialized before calling this method."
164163

165164
model = self.model
166-
if model.endswith(COMMUNITY_agents_SUFFIX):
167-
model = model.replace(COMMUNITY_agents_SUFFIX, "")
168165

169166
# Create evaluation tool for function calling (works across all models)
170167
evaluation_tool = create_evaluation_tool()

packages/uipath/src/uipath/eval/evaluators/llm_as_judge_evaluator.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from uipath.platform import UiPath
1313
from uipath.platform.chat import UiPathLlmChatService
14-
from uipath.platform.constants import COMMUNITY_agents_SUFFIX
1514

1615
from .._execution_context import eval_set_run_id_context
1716
from ..models import (
@@ -211,10 +210,7 @@ async def _get_llm_response(self, evaluation_prompt: str) -> LLMResponse:
211210
ToolPropertyDefinition,
212211
)
213212

214-
# Remove community-agents suffix from llm model name
215213
model = self.evaluator_config.model
216-
if model.endswith(COMMUNITY_agents_SUFFIX):
217-
model = model.replace(COMMUNITY_agents_SUFFIX, "")
218214

219215
# Define function/tool for structured output (works for ALL models via Normalized API)
220216
evaluation_tool = ToolDefinition(
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""Regression tests: LLM-judge evaluators must send the model name to the LLM
2+
Gateway exactly as configured, including a "-community-agents" suffix.
3+
4+
Community/EU tenants' LLM Gateway routing rules are keyed on the suffixed
5+
model id -- the same id AgentHub sends when it runs the agent itself.
6+
Stripping the suffix before calling the Gateway causes a 417 "No llm routing
7+
rule found for product agentsplaygroundfallback in EU using model ..." for
8+
every Community/EU evaluation run, even though the identical model id works
9+
fine for the agent.
10+
"""
11+
12+
import uuid
13+
from types import SimpleNamespace
14+
from unittest.mock import AsyncMock, patch
15+
16+
import pytest
17+
18+
from uipath.eval.evaluators.base_legacy_evaluator import LegacyEvaluationCriteria
19+
from uipath.eval.evaluators.legacy_context_precision_evaluator import (
20+
LegacyContextPrecisionEvaluator,
21+
LegacyContextPrecisionEvaluatorConfig,
22+
)
23+
from uipath.eval.evaluators.legacy_faithfulness_evaluator import (
24+
LegacyFaithfulnessEvaluator,
25+
LegacyFaithfulnessEvaluatorConfig,
26+
)
27+
from uipath.eval.evaluators.legacy_llm_as_judge_evaluator import (
28+
LegacyLlmAsAJudgeEvaluator,
29+
LegacyLlmAsAJudgeEvaluatorConfig,
30+
)
31+
from uipath.eval.evaluators.legacy_trajectory_evaluator import (
32+
LegacyTrajectoryEvaluator,
33+
LegacyTrajectoryEvaluatorConfig,
34+
)
35+
from uipath.eval.evaluators.llm_judge_output_evaluator import LLMJudgeOutputEvaluator
36+
from uipath.eval.evaluators.llm_judge_trajectory_evaluator import (
37+
LLMJudgeTrajectoryEvaluator,
38+
)
39+
from uipath.eval.models.models import LegacyEvaluatorCategory, LegacyEvaluatorType
40+
41+
COMMUNITY_MODEL = "gpt-5.4-2026-03-05-community-agents"
42+
43+
44+
def _fake_tool_call_response(score: float = 90, justification: str = "ok"):
45+
tool_call = SimpleNamespace(
46+
arguments={"score": score, "justification": justification}
47+
)
48+
message = SimpleNamespace(tool_calls=[tool_call])
49+
choice = SimpleNamespace(message=message)
50+
return SimpleNamespace(choices=[choice])
51+
52+
53+
def _legacy_context_precision_evaluator() -> LegacyContextPrecisionEvaluator:
54+
return LegacyContextPrecisionEvaluator(
55+
id="context-precision",
56+
config_type=LegacyContextPrecisionEvaluatorConfig,
57+
evaluation_criteria_type=LegacyEvaluationCriteria,
58+
justification_type=str,
59+
category=LegacyEvaluatorCategory.LlmAsAJudge,
60+
type=LegacyEvaluatorType.ContextPrecision,
61+
name="Context Precision",
62+
description="Evaluates context chunk relevance",
63+
createdAt="2025-01-01T00:00:00Z",
64+
updatedAt="2025-01-01T00:00:00Z",
65+
targetOutputKey="*",
66+
model=COMMUNITY_MODEL,
67+
)
68+
69+
70+
class TestLegacyContextPrecisionEvaluatorSendsConfiguredModel:
71+
@pytest.mark.asyncio
72+
async def test_get_structured_llm_response_sends_full_model_name(self):
73+
evaluator = _legacy_context_precision_evaluator()
74+
mock_chat_completions = AsyncMock(return_value=_fake_tool_call_response())
75+
evaluator.llm = AsyncMock(chat_completions=mock_chat_completions)
76+
77+
await evaluator._get_structured_llm_response("some evaluation prompt")
78+
79+
sent_model = mock_chat_completions.call_args.kwargs["model"]
80+
assert sent_model == COMMUNITY_MODEL
81+
82+
83+
def _legacy_faithfulness_evaluator() -> LegacyFaithfulnessEvaluator:
84+
return LegacyFaithfulnessEvaluator(
85+
id="faithfulness",
86+
config_type=LegacyFaithfulnessEvaluatorConfig,
87+
evaluation_criteria_type=LegacyEvaluationCriteria,
88+
justification_type=str,
89+
category=LegacyEvaluatorCategory.LlmAsAJudge,
90+
type=LegacyEvaluatorType.Faithfulness,
91+
name="Faithfulness",
92+
description="Evaluates faithfulness of claims against context",
93+
createdAt="2025-01-01T00:00:00Z",
94+
updatedAt="2025-01-01T00:00:00Z",
95+
targetOutputKey="*",
96+
model=COMMUNITY_MODEL,
97+
)
98+
99+
100+
class TestLegacyFaithfulnessEvaluatorSendsConfiguredModel:
101+
@pytest.mark.asyncio
102+
async def test_get_structured_llm_response_sends_full_model_name(self):
103+
evaluator = _legacy_faithfulness_evaluator()
104+
mock_chat_completions = AsyncMock(return_value=_fake_tool_call_response())
105+
evaluator.llm = AsyncMock(chat_completions=mock_chat_completions)
106+
107+
await evaluator._get_structured_llm_response(
108+
"some evaluation prompt", "submit_result", {"type": "object"}
109+
)
110+
111+
sent_model = mock_chat_completions.call_args.kwargs["model"]
112+
assert sent_model == COMMUNITY_MODEL
113+
114+
115+
class TestLLMJudgeOutputEvaluatorSendsConfiguredModel:
116+
"""Covers LLMJudgeMixin._get_llm_response -- the code path hit by
117+
'uipath-llm-judge-output-semantic-similarity' in production."""
118+
119+
@pytest.mark.asyncio
120+
async def test_get_llm_response_sends_full_model_name_to_gateway(self):
121+
config = {
122+
"name": "TestEvaluator",
123+
"prompt": "Evaluate {{ActualOutput}} against {{ExpectedOutput}}",
124+
"model": COMMUNITY_MODEL,
125+
}
126+
with patch("uipath.platform.UiPath"):
127+
evaluator = LLMJudgeOutputEvaluator.model_validate(
128+
{"evaluatorConfig": config, "id": str(uuid.uuid4())}
129+
)
130+
mock_llm_service = AsyncMock(return_value=_fake_tool_call_response())
131+
evaluator.llm_service = mock_llm_service
132+
133+
await evaluator._get_llm_response("some evaluation prompt")
134+
135+
sent_model = mock_llm_service.call_args.kwargs["model"]
136+
assert sent_model == COMMUNITY_MODEL
137+
138+
139+
class TestLLMJudgeTrajectoryEvaluatorSendsConfiguredModel:
140+
"""Covers the same LLMJudgeMixin._get_llm_response via the trajectory
141+
evaluator -- this is the exact evaluator type from the reported
142+
production trace ('uipath-llm-judge-trajectory-similarity')."""
143+
144+
@pytest.mark.asyncio
145+
async def test_get_llm_response_sends_full_model_name_to_gateway(self):
146+
config = {
147+
"name": "TestEvaluator",
148+
"prompt": "Judge {{AgentRunHistory}} against {{ExpectedAgentBehavior}}",
149+
"model": COMMUNITY_MODEL,
150+
}
151+
with patch("uipath.platform.UiPath"):
152+
evaluator = LLMJudgeTrajectoryEvaluator.model_validate(
153+
{"evaluatorConfig": config, "id": str(uuid.uuid4())}
154+
)
155+
mock_llm_service = AsyncMock(return_value=_fake_tool_call_response())
156+
evaluator.llm_service = mock_llm_service
157+
158+
await evaluator._get_llm_response("some evaluation prompt")
159+
160+
sent_model = mock_llm_service.call_args.kwargs["model"]
161+
assert sent_model == COMMUNITY_MODEL
162+
163+
164+
def _legacy_trajectory_evaluator() -> LegacyTrajectoryEvaluator:
165+
return LegacyTrajectoryEvaluator(
166+
id=str(uuid.uuid4()),
167+
name="Legacy trajectory",
168+
config_type=LegacyTrajectoryEvaluatorConfig,
169+
evaluation_criteria_type=LegacyEvaluationCriteria,
170+
justification_type=str,
171+
category=LegacyEvaluatorCategory.Trajectory,
172+
type=LegacyEvaluatorType.Trajectory,
173+
prompt="History:\n{{AgentRunHistory}}\nExpected:\n{{ExpectedAgentBehavior}}",
174+
model=COMMUNITY_MODEL,
175+
createdAt="2026-05-14T00:00:00Z",
176+
updatedAt="2026-05-14T00:00:00Z",
177+
)
178+
179+
180+
class TestLegacyTrajectoryEvaluatorSendsConfiguredModel:
181+
@pytest.mark.asyncio
182+
async def test_get_llm_response_sends_full_model_name_to_gateway(self):
183+
evaluator = _legacy_trajectory_evaluator()
184+
mock_chat_completions = AsyncMock(return_value=_fake_tool_call_response())
185+
evaluator.llm = AsyncMock(chat_completions=mock_chat_completions)
186+
187+
await evaluator._get_llm_response("some evaluation prompt")
188+
189+
sent_model = mock_chat_completions.call_args.kwargs["model"]
190+
assert sent_model == COMMUNITY_MODEL
191+
192+
193+
def _legacy_llm_as_judge_evaluator() -> LegacyLlmAsAJudgeEvaluator:
194+
return LegacyLlmAsAJudgeEvaluator(
195+
id=str(uuid.uuid4()),
196+
name="Legacy LLM judge",
197+
config_type=LegacyLlmAsAJudgeEvaluatorConfig,
198+
evaluation_criteria_type=LegacyEvaluationCriteria,
199+
justification_type=str,
200+
category=LegacyEvaluatorCategory.LlmAsAJudge,
201+
type=LegacyEvaluatorType.Factuality,
202+
prompt="Compare {{ActualOutput}} to {{ExpectedOutput}}",
203+
model=COMMUNITY_MODEL,
204+
createdAt="2026-05-14T00:00:00Z",
205+
updatedAt="2026-05-14T00:00:00Z",
206+
)
207+
208+
209+
class TestLegacyLlmAsAJudgeEvaluatorSendsConfiguredModel:
210+
@pytest.mark.asyncio
211+
async def test_get_llm_response_sends_full_model_name_to_gateway(self):
212+
evaluator = _legacy_llm_as_judge_evaluator()
213+
mock_chat_completions = AsyncMock(return_value=_fake_tool_call_response())
214+
evaluator.llm = AsyncMock(chat_completions=mock_chat_completions)
215+
216+
await evaluator._get_llm_response("some evaluation prompt")
217+
218+
sent_model = mock_chat_completions.call_args.kwargs["model"]
219+
assert sent_model == COMMUNITY_MODEL

packages/uipath/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)