Enable bare messages= input on Relevance/Similarity/Fluency/Retrieval/ResponseCompleteness + all RAI evaluators - #48629
Conversation
Both evaluators previously required either a conversation dict or scalar
query/
esponse inputs. When the SDK batch engine invokes them from a
customer's data_mapping: {"messages": "${data.messages}"} (produced by
ACA on unified-messages evaluation groups), it sends a bare messages=[...]
kwarg alongside optional scalar context, ground_truth, and
ool_definitions kwargs. The old code paths rejected this combination
with:
RelevanceEvaluator: "Either 'conversation' or individual inputs must be
provided." ("No data to process" family)
SimilarityEvaluator: "'query' is missing" / "Cannot provide both
'conversation' and individual inputs at the same time."
Fix:
- Swap ConversationValidator for MessagesOrQueryResponseInputValidator
in each evaluator's __init__ so validation accepts either shape.
- Add a _convert_kwargs_to_eval_input override in each evaluator that
hoists a bare messages=[...] kwarg into conversation={"messages": ...}
and merges any top-level context / ool_definitions scalars into the
same conversation dict. A top-level ground_truth scalar is stamped
onto each assistant turn that doesn't already carry one, so the base
_derive_conversation_converter picks it up per-response.
Regression paths preserved:
- Passing conversation={"messages": [...]} still works (unchanged).
- Passing scalar query/
esponse(/ground_truth) still works.
- SimilarityEvaluator with query=None still rejects with the pre-existing
error message.
Verified end-to-end against real Azure OpenAI (gpt-4o-mini):
- Relevance with bare messages -> score 5.0, pass.
- Similarity with bare messages + per-turn ground_truth -> score 5.0, pass.
- Similarity with bare messages + top-level context + ground_truth +
tool_definitions -> score 5.0, pass.
- Relevance/Similarity with q/r scalars -> score 5.0, pass (regression).
- Similarity with query=None -> correctly rejected (regression).
This is a partial migration; the same pattern applies to the other
prompty/metric/RAI/tool evaluators that today reject messages-format input
at turn/conversation level.
The _convert_kwargs_to_eval_input normalization added to RelevanceEvaluator
and SimilarityEvaluator was identical logic in two places. Extracted it into
_evaluators/_common/_conversation_input.py as hoist_messages_to_conversation
so:
- The other ~21 bucket-3/4 evaluators (fluency, intent_resolution,
response_completeness, retrieval, tool_call_*, RAI safety family, metric
family) can adopt the same pattern with a ~3-line override each, instead
of copying the same 20-line block.
- The normalization has a single documented contract about how bare
messages= + scalar context/ground_truth/ ool_definitions kwargs
get folded into conversation={...}.
Net line count in this commit: -39 (removed inline blocks, added helper +
imports).
Re-verified end-to-end with real gpt-4o-mini scoring — all 6 scenarios
(bare messages, bare messages + adjuncts on both evaluators, q/r baselines,
query=None regression) behave identically to the pre-extraction state.
Apply the shared hoist_messages_to_conversation helper pattern to the
remaining prompty-family evaluators with a conversation overload:
- FluencyEvaluator: validator swap ConversationValidator ->
MessagesOrQueryResponseInputValidator + convert override
- RetrievalEvaluator: convert override (no explicit validator to swap)
- ResponseCompletenessEvaluator: convert override (no explicit validator)
Not included in this commit: IntentResolutionEvaluator. Its __call__
signature only exposes one overload (query: Union[str, List[dict]],
response: Union[str, List[dict]], tool_definitions) with NO
conversation overload, so hoisting messages=[...] into
conversation={...} does not help — the base singleton matcher never
routes into a conversation converter. That evaluator needs a different
pattern (either a new conversation overload OR a custom transform that
splits the messages list into a leading query context + trailing response
turns per the intent-resolution semantic). Deferred to a follow-up.
Verified end-to-end against real gpt-4o-mini:
- Fluency messages input -> score 2.0 (baseline response scalar -> 3.0);
no more "No data to process" error, evaluator runs to completion.
- Retrieval messages+context input -> reaches the model; both messages
and baseline paths return NaN, so any NaN behavior is not tied to the
migration (baseline had it too before the change).
- ResponseCompleteness messages+ground_truth input -> score 3.0 pass,
identical to baseline.
Hoist a bare `messages=[...]` kwarg (plus optional scalar `context` /
`ground_truth` / `tool_definitions`) into `conversation={...}` inside
`RaiServiceEvaluatorBase._convert_kwargs_to_eval_input` so every RAI safety
evaluator inherits messages-input support in one change instead of eight:
- ViolenceEvaluator
- HateUnfairnessEvaluator
- SelfHarmEvaluator
- SexualEvaluator
- ProtectedMaterialEvaluator
- IndirectAttackEvaluator (XPIA)
- CodeVulnerabilityEvaluator
- ECIEvaluator
All 7 publicly-exported RAI evaluators verified to inherit the hoist via
MRO inspection. The base class already routes `conversation` into
`_evaluate_conversation` which per-turn-splits and calls the RAI service,
so no service-side change is required — the hoist just lets the customer's
`messages=` kwarg reach that existing path instead of failing at the
kwarg matcher.
Regression: pre-existing `query`/`response` scalar calls are untouched
(hoist is a no-op when `messages` is absent). Legacy-endpoint path
(`_use_legacy_endpoint`) still short-circuits with the raw conversation
dict when supplied directly.
Not exercised end-to-end here: firing a live RAI service call requires an
Azure AI project scope with RAI credentials, which is a separate infra
concern from the SDK plumbing. The kwarg-level fix (verified above) is
what the customer-facing "No data to process" error was tied to.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
openai released v3.0 which migrated its transport layer from httpx to
httpx2. The eval SDK test proxy (tests/__openai_patcher.py) still
constructs httpx.URL objects, so with openai 3.x the tests fail with:
TypeError: Invalid type for url. Expected str or httpx.URL,
got <class 'httpx2.URL'>
Verified empirically: the passing PR CI run (build 6717764, queued
01:15 UTC) resolved openai==2.54.0 with httpx; the failing run (build
6717844, queued 01:50 UTC) resolved openai==3.0.0 with httpx2. The
only relevant change between the two runs was that openai 3.0 was
published to PyPI in that ~35 min window, and pip picked it because
setup.py had no upper bound.
Pinning openai<3.0 restores the CI to the passing configuration. When
the test proxy is updated to use httpx2 (or a compatibility shim), the
pin should be lifted.
This does not affect the messages= input feature under review; it is
strictly a CI unblocker.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Enables evaluators to accept a bare messages=[...] kwarg (plus optional context, ground_truth, tool_definitions) by normalizing it into the existing conversation={...} input shape, fixing batch-engine routing failures without changing evaluation semantics.
Changes:
- Added shared
hoist_messages_to_conversationhelper to convertmessages-shaped kwargs intoconversationkwargs. - Applied the helper to multiple Prompty-based evaluators and the RAI service evaluator base to unblock
messages=input across the targeted evaluator set. - Tightened the
openaidependency spec with an upper bound.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/evaluation/azure-ai-evaluation/setup.py | Adds an upper bound to the openai dependency range. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_conversation_input.py | Introduces shared normalization helper to hoist messages into conversation. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/init.py | Exports the new helper from the common evaluator package. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py | Uses the helper so all RAI evaluators accept messages= input. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py | Switches validator + hoists messages= into conversation before base conversion. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py | Adds validator and hoists messages= while preserving single-turn q/r validation behavior. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py | Switches validator + hoists messages= into conversation before base conversion. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py | Hoists messages= into conversation before base conversion. |
| sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.py | Hoists messages= into conversation before base conversion. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Addresses three of the four Copilot review findings on the
hoist_messages_to_conversation helper:
Finding 2 (real bug): the helper mutated caller-provided message dicts
in place when stamping a top-level ground_truth. Two evaluations reusing
the same messages list with different ground_truth values would see the
first call's value leak into subsequent calls. Fix: shallow-copy the
messages list and shallow-copy each affected assistant-turn dict before
injecting ground_truth. Zero runtime cost, purely allocation.
Finding 1 (edge case): explicitly document behavior when messages entries
are not plain dicts (typed Message objects, dataclasses). The helper
skips ground_truth stamping for such items but still hoists the overall
input into conversation. The base `_derive_conversation_converter`
requires plain dicts and would fail on non-dicts regardless, so this
matches existing SDK convention.
Finding 4 (test coverage): add tests/unittests/test_conversation_input.py
with seven table-driven tests covering:
- no messages, no conversation (noop)
- conversation already provided (short-circuit)
- bare messages only
- bare messages + all adjuncts (context, ground_truth, tool_definitions)
- non-dict messages entries skipped for stamping
- immutability: same messages list reused across two calls with
different ground_truth scalars (regression guard for Finding 2)
- per-turn ground_truth is not overwritten by top-level scalar
Verified end-to-end with live gpt-4o-mini scoring on all 5 direct-file-edit
evaluators (Relevance, Similarity, Fluency, Retrieval,
ResponseCompleteness). All previously scored scenarios still score
identically; the new immutability guarantee is validated at runtime by
reusing the same messages list across two consecutive Similarity calls
with different ground_truth values and asserting the caller's assistant
dict is unchanged after both.
Finding 3 (clearer error for mixed messages+q/r inputs) is not addressed
here: the base class already rejects the mixed combination correctly,
just with a generic error message. Cosmetic; deferred.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- The replacement validator's
messagesbranch checks only dictionary shape and roles, unlike the previousConversationValidator, which also requires and validates each message'scontent. For example,[{'role': 'user'}, {'role': 'assistant'}]now passes validation and is converted into empty query/response strings that are sent to the judge. Preserve full conversation validation for the bare-messages path, either by hoisting before the original validator runs or by strengthening the shared messages validator.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- The replacement validator's
messagesbranch checks only dictionary shape and roles, unlike the previousConversationValidator, which also requires and validates each message'scontent. Thus role-only messages pass and are converted into an empty response that is scored. Preserve full conversation validation for the bare-messages path, either by hoisting before the original validator runs or by strengthening the shared messages validator.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- This validator is never invoked by
SimilarityEvaluator: neither this class norPromptyEvaluatorBasecalls_validator.validate_eval_input, and the conversion method immediately hoistsmessagesaway. Consequently malformed bare-message inputs bypass the validator that this change initializes. Validate the rawmessagespath before hoisting (without changing the existing scalar error ordering), or remove the unused validator and perform equivalent validation in the converter.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/tests/unittests/test_conversation_input.py:24
- These tests exercise only the helper in isolation, so they do not verify that the five evaluator overrides and the RAI base actually route
messagesthrough their converters while preserving scalar behavior. Add automated integration-level unit tests with mocked model/service calls for the affected direct evaluators and at least representative legacy and non-legacy RAI paths; this would also catch wiring errors such as an initialized validator never being called.
class TestHoistMessagesToConversation:
Addresses reviewer feedback: the previous `openai<3.0` upper bound on
install_requires punished end-user installs by making them incompatible
with the current openai 3.x line (3.0 released 2026-08-12, latest 3.3
released 2026-08-18) even though the SDK's runtime is fine with openai
3.x. The incompatibility is confined to the CI test proxy:
tests/__openai_patcher.py:74
request.url = httpx.URL(config.proxy_url).join(request_path)
openai 3.x switched its transport layer to httpx2 and rejects httpx.URL
objects with:
TypeError: Invalid type for url. Expected str or httpx.URL,
got <class 'httpx2.URL'>
Production code paths that use `AsyncAzureOpenAI` directly never
reach that patcher, so end users installing this SDK alongside openai
3.x are not affected.
Revert the setup.py upper bound and move the constraint to
dev_requirements.txt so only the test environment is pinned. Removes
this pin once tests/__openai_patcher.py is updated for httpx2
compatibility (out of scope for this PR).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (5)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- This swap weakens validation for the new bare-message path:
MessagesOrQueryResponseInputValidatorchecks only each item's type and role, whereas the previousConversationValidatoralso requires non-empty, correctly typedcontent. A user/assistant pair with missingcontentnow passes validation and is converted to empty query/response strings instead of raising an input error. Preserve the conversation-level structural validation when acceptingmessages=.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py:123
- This unconditional base-class hoist also enables
messages=forUngroundedAttributesEvaluator, which inherits this method but explicitly supports single-turnquery/response/contextonly (_ungrounded_attributes.py:13-16,64-82). The base converter will silently project every message pair into calls for that evaluator, despite the PR explicitly leaving evaluators without conversation overloads out of scope. Please opt the intended RAI evaluators in rather than applying the normalization to every subclass.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:20
ConversationValidatoris no longer referenced after the validator swap, so this import triggers the package's unused-import lint check. Remove it from the import list.
This issue also appears on line 99 of the same file.
ConversationValidator,
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- The new validator's
messagesbranch validates roles but not the required, non-emptycontentfield thatConversationValidatorpreviously enforced. Consequently malformed assistant messages pass_real_callvalidation and reach the fluency prompt with an empty or invalid response. Please retain the existing conversation message validation for this new input shape.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- The newly initialized
_validatoris never invoked anywhere inSimilarityEvaluator, so this bare-message path bypasses the validation the assignment appears intended to add. Validate the original kwargs before hoisting so invalidmessagesvalues produce the expectedEvaluationExceptionrather than failing later in the conversation converter.
hoist_messages_to_conversation(kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (5)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- The newly initialized validator is never invoked by
SimilarityEvaluator(the inherited_real_calldoes not use_validator). Consequently malformed bare input bypasses the intended checks; for example,messages=[]is hoisted, converted to zero turns, and returns{}instead of raisingEvaluationException. Validate the messages/conversation path before hoisting while retaining the existing scalar-specific error checks.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py:123
- Applying this normalization in the shared RAI base changes more evaluators than the eight listed in the PR:
GroundednessProEvaluatorandUngroundedAttributesEvaluatoralso inherit this method. In particular,UngroundedAttributesEvaluatoris documented as single-turn and requirescontext, butmessages=[...]without context is now converted by_derive_conversation_converterinto a call with the synthetic string"{}", bypassing that required input. Restrict the hoist to the intended evaluators, or explicitly define and validate the semantics for every affected subclass.
# Normalize a bare ``messages=[...]`` kwarg (plus optional scalar ``context``
# / ``ground_truth`` / ``tool_definitions``) into ``conversation={...}`` so
# RAI safety evaluators route messages-shape input through the
# ``_evaluate_conversation`` path instead of failing kwarg matching.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/tests/unittests/test_conversation_input.py:24
- These tests exercise only the helper in isolation; none invokes an affected evaluator with
messages=. They therefore cannot detect missing validator wiring, interaction with singleton extraction, or the extra subclasses affected throughRaiServiceEvaluatorBase. Add representative/parameterized evaluator-level tests for the five Prompty evaluators and the RAI path, including adjuncts and invalid empty messages.
class TestHoistMessagesToConversation:
sdk/evaluation/azure-ai-evaluation/dev_requirements.txt:24
- This prevents CI from exercising OpenAI 3.x even though
setup.pystill permitsopenai>=1.108.0with no upper bound. A production incompatibility with the new major could therefore ship undetected. Please migrate the test proxy tohttpx2so the supported major remains covered, or temporarily cap the runtime dependency as well until compatibility is verified.
openai<3.0
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:17
ConversationValidatoris no longer referenced after the validator swap, so this import triggers the package's unused-import lint check. Remove it from the import list.
ConversationValidator,
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:20
ConversationValidatoris no longer referenced after the validator swap. Leaving this import causes the package's pylint validation to report an unused import; remove it from the import list.
ConversationValidator,
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- The newly initialized validator is never invoked in this class. Consequently,
SimilarityEvaluator(messages=[])is hoisted to an empty conversation and the inherited_real_callreturns{}instead of raising the validator'sEvaluationException; malformed message entries likewise bypass the new structural checks. Validatekwargsbefore hoisting (as Relevance and Fluency do in_real_call).
hoist_messages_to_conversation(kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- The new validator's
messages=branch does not perform the content validation that the previousConversationValidatorapplies: entries with missing or emptycontentpass and are converted to empty responses. That makes bare messages behave differently from the equivalentconversation={"messages": ...}input despite this being a plumbing-only change. Please make the shared messages validator reuse the full conversation message validation before adopting it here.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- The replacement validator makes the new
messages=path less strict than the existingconversation=path.MessagesOrQueryResponseInputValidatoronly checks that each entry is a dict with a valid role, so messages with missing/empty/invalidcontentpass here;ConversationValidatorrejects those shapes, and the converter can otherwise score"". Since this is intended as routing-only normalization, please preserve the full conversation message validation (for example, by delegating the messages branch to the conversation validator after wrapping it).
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- This validator swap weakens the existing conversation validation for the new path.
MessagesOrQueryResponseInputValidatorvalidates item type and role but does not require non-emptycontentor validate structured content asConversationValidatordoes. A bare user/assistant pair with missing content therefore passes and is scored as an empty response, whereas the equivalentconversation=payload is rejected. Normalize before validation and reuseConversationValidator, or make the shared messages validator delegate to the same conversation validation.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:96
- This validator is never invoked:
SimilarityEvaluatorhas no_real_callvalidation hook, and the base conversion path does not consult_validator. As a result, the new initialization does not validate bare messages at all despite the comment, so malformed payloads bypass the intended checks. Wire validation into the execution path (while preserving conversation-equivalent validation) or remove the dead field/imports.
# Initialize input validator — accepts messages OR query/response(/ground_truth).
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.SIMILARITY_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_conversation_input.py:22
- The new input shape is accepted only dynamically. The affected evaluators' public
__call__overloads still expose only scalar inputs andconversation=, so mypy/Pyright reportsevaluator(messages=[...])as having no matching overload and generated API documentation omits the newly supported shape. Add typedmessagesoverloads, including the supported adjuncts, to the direct evaluators and inherited RAI evaluator APIs.
def hoist_messages_to_conversation(kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""Promote a bare ``messages=[...]`` kwarg into a
``conversation={"messages": [...], ...}`` dict so the base
``_derive_conversation_converter`` extracts per-turn ``query``/``response``
for the judge.
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- This validator swap weakens the existing conversation validation for the new path.
MessagesOrQueryResponseInputValidatorchecks only that each item is a dict with a valid role and that user/assistant roles exist; unlikeConversationValidator._validate_input_messages_list, it never requires non-emptycontentor validates structured content. Consequently, bare messages such as[{'role': 'user'}, {'role': 'assistant'}]now pass validation and are converted to empty query/response strings for scoring, while the equivalentconversation=input is rejected. Normalize before validation and reuseConversationValidator, or make the shared messages validator delegate to the same conversation validation.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- The new validator is never invoked in this class. Unlike Relevance and Fluency, Similarity has no
_real_calloverride, somessages=[]is hoisted, the base converter returns an empty list, andEvaluatorBasereturns{}instead of rejecting the invalid input; malformed message entries can likewise escape the intended validation. Validate the baremessagesshape before hoisting while preserving the existing scalar required-field checks.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:174
- Runtime support for
messages=is not reflected in the public__call__overloads or keyword documentation. Since type checkers use the overload signatures, a valid call such asevaluator(messages=[...])is still reported as an unexpected argument, and generated API documentation will not expose this feature. Add amessagesoverload (including the supported adjuncts) to each affected direct and RAI evaluator.
def _convert_kwargs_to_eval_input(self, **kwargs):
"""Normalize a bare ``messages=[...]`` kwarg (plus optional scalar
``context`` / ``ground_truth`` / ``tool_definitions``) into
``conversation={...}`` so the base ``_derive_conversation_converter``
can extract per-turn q/r for the judge. Shared with other evaluators
via ``hoist_messages_to_conversation``."""
hoist_messages_to_conversation(kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (3)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:96
- The new validator is never invoked: this class has no
_real_callvalidation override, and neitherEvaluatorBasenorPromptyEvaluatorBasecallsself._validator. Thus malformed bare messages (including turns with missing content) still reach conversion/the model, so this initialization does not provide the stated validation. Either wire validation into the call path as Relevance/Fluency do or remove the dead validator and validate during conversion.
# Initialize input validator — accepts messages OR query/response(/ground_truth).
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.SIMILARITY_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- This validator's
messagesbranch only checks that entries are dicts with valid roles; unlikeConversationValidator._validate_conversation, it never requires non-emptycontent. Consequently,RelevanceEvaluator(messages=[{"role": "user"}, {"role": "assistant"}])now passes validation and the converter evaluates empty strings, while the equivalentconversation=input is rejected. Please make bare messages go through the same message-content validation as the conversation path.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- This changes bare-message validation semantics:
MessagesOrQueryResponseInputValidatorvalidates roles but not the required, non-emptycontentfield thatConversationValidatorchecks. Fluency therefore sends empty content to the judge for malformedmessages=input even though the equivalentconversation=input raises anEvaluationException. Please route bare messages through the existing full conversation-message validation.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (7)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:160
- Bare
messagestakes this early conversation branch before the existing required-field checks. If neither the scalar adjunct nor assistant turns provideground_truth,_derive_conversation_convertersuppliesground_truth="", so Similarity evaluates without the reference that the scalar path rejects as missing; the newly assigned validator is not invoked anywhere in this class either. Validate the messages shape and require per-turn/top-level ground truth before returning through this branch.
hoist_messages_to_conversation(kwargs)
if kwargs.get("conversation") is not None:
return super()._convert_kwargs_to_eval_input(**kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- This validator is weaker than the replaced
ConversationValidator: its messages branch checks only dictionary type and roles, not thatcontentexists, is valid, or is non-empty. Consequently bare messages such as[{"role":"user"}, {"role":"assistant"}]now pass validation and are scored as empty strings, whereas the equivalentconversationinput is rejected. Preserve the conversation-level structural validation for the new bare-messages path.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- The replacement validator does not validate message
content, unlikeConversationValidator; it accepts missing or empty content as long as user and assistant roles exist. The converter then turns those messages into an empty response and Fluency sends it to the judge. The bare-messages path should retain the same structural/content validation as the existing conversation path.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py:158
- This also accepts bare messages with no top-level or per-turn context, even though Retrieval's scalar overload requires
context. In that case the conversation converter produces the literal string"{}"as context and the model returns a score without any retrieved material to assess. Reject this shape unless global or query-turn context is available.
hoist_messages_to_conversation(kwargs)
return super()._convert_kwargs_to_eval_input(**kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.py:151
- Bare messages without scalar or per-assistant
ground_truthare now routed through the conversation converter, which insertsground_truth=""._do_evalonly checks that the key exists, so this bypasses the established missing-ground-truth error and asks the model to assess completeness without a reference. Require ground truth before accepting this new input shape.
hoist_messages_to_conversation(kwargs)
return super()._convert_kwargs_to_eval_input(**kwargs)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_conversation_input.py:22
- The runtime normalization enables a new public call shape, but none of the affected evaluators'
__call__overloads or docstrings declaremessages(or its adjuncts). Static type checkers therefore reportevaluator(messages=...)as having no matching overload, and generated API documentation does not expose the feature. Add the messages overload/documentation to each affected public evaluator while accounting for overload introspection inEvaluatorBase.
def hoist_messages_to_conversation(kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""Promote a bare ``messages=[...]`` kwarg into a
``conversation={"messages": [...], ...}`` dict so the base
``_derive_conversation_converter`` extracts per-turn ``query``/``response``
for the judge.
sdk/evaluation/azure-ai-evaluation/dev_requirements.txt:24
setup.pyallowsopenai>=1.108.0with no upper bound, but this global development pin forces every package test environment onto 2.x. Thus the stated OpenAI 3 runtime compatibility is never exercised in CI, and v3-only production regressions can ship undetected. Update the test proxy for v3 or retain a dedicated v3 test job rather than excluding a supported major version from all tests.
openai<3.0
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (5)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py:127
- The legacy short-circuit bypasses the base class's mutual-exclusion check. After hoisting, a call containing
messagesplusquery/responsereturns all fields here;_do_evalseesresponseand silently evaluates the scalar pair while ignoring the messages. An explicitconversationplus baremessagesis likewise silently accepted. Reject these mixed shapes before returning[kwargs], matching the non-legacy path and the evaluator contract.
if self._use_legacy_endpoint and "conversation" in kwargs and kwargs["conversation"] is not None:
# Legacy endpoint: pass conversation through intact so _evaluate_conversation
# can send all messages in a single API call (pre-sync-migration behavior).
return [kwargs]
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.py:99
- Replacing
ConversationValidatorhere weakens validation for the new bare-messagespath.MessagesOrQueryResponseInputValidatorchecks only that each item is a dict with a valid role; it does not enforce the required/non-emptycontentfield thatConversationValidator._validate_input_messages_listenforces. Because_real_callvalidates before this class hoists, inputs such asmessages=[{"role": "user"}, {"role": "assistant", "content": "a"}]now pass validation and are scored with an empty query, while the equivalentconversation={"messages": ...}is rejected. Please make the shared messages validator apply the full conversation message validation, or hoist before retaining the existing validator.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(error_target=ErrorTarget.RELEVANCE_EVALUATOR)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.py:93
- This validator swap makes bare
messagesless strictly validated than the equivalentconversation. The new validator accepts missing or emptycontentfields, whereasConversationValidatorrejects them; the base converter then defaults missing content to""and Fluency can score invalid input. Please reuse the full conversation-message validation for themessagesbranch so this plumbing alias preserves existing validation semantics.
# Initialize input validator — accepts messages OR query/response.
self._validator = MessagesOrQueryResponseInputValidator(
error_target=ErrorTarget.FLUENCY_EVALUATOR,
requires_query=False,
)
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.py:158
- The newly initialized validator is never called by
SimilarityEvaluator: unlike Relevance and Fluency, this class has no_real_calloverride, and the base class does not invoke_validator. ConsequentlySimilarityEvaluator(messages=[])bypasses the intended empty-input guard and returns{}. Invoke the validator for a baremessagesinput before hoisting; restricting it to this branch preserves the existing scalar error messages.
hoist_messages_to_conversation(kwargs)
sdk/evaluation/azure-ai-evaluation/dev_requirements.txt:24
- This makes CI install OpenAI 2.x while
setup.pystill advertises the unbounded runtime requirementopenai>=1.108.0. Customers will therefore resolve 3.x, but none of this package's tests will exercise that supported major, so runtime incompatibilities can ship undetected. Please update the proxy patcher forhttpx2(or add a separate 3.x test environment) rather than globally pinning tests below a version that remains supported for end users; otherwise the runtime dependency must be capped consistently.
openai<3.0
* [Service Bus] Send server-timeout on management operations (#48563) Management operations (peek, deferred receive, settlement over the management link, lock renewal, session state, session listing, schedule/cancel scheduled) now send `com.microsoft:server-timeout`: the caller's remaining time less a one second buffer, or 60 seconds when none was given, floored at zero and capped at the AMQP uint maximum. Previously no bound was sent, so a stalled service held the call until the AMQP link failed; it now raises a retryable `OperationTimeoutError`, surfacing after roughly four minutes at default retries. A settle over the management link can therefore raise `MessageLockLostError` where the call used to block. The data path is unchanged: `receive_messages`, receiver iteration and `send_messages` are unaffected, as is CBS token auth. Set in the base handler after the retry loop computes the remaining time, so it reflects the time left on the attempt. Both transports pass application properties through unchanged, so no transport change is needed. `REQUEST_RESPONSE_TIMEOUT` already held the correct key and was unused. Tests cover the arithmetic and its boundaries, that the property reaches the outgoing message, that the service's answer surfaces as a retryable `OperationTimeoutError`, and that the value carries the transport's own type. api.metadata.yml carries a gated parserVersion bump only; apiMdSha256 is unchanged, so no public API moved. Matches the .NET, Java and Go SDKs. Refs: AB#38822503 Co-authored-by: Pranjal Patel <pranjalpatel@microsoft.com> * Adding fix workflow to failing pipelines (#48554) * Adding a new github actions workflow to trigger on pipeline failures. * Updated copilot feedback. * Update .github/workflows/pipeline-analysis-next-steps.md Co-authored-by: Daniel Jurek <djurek@microsoft.com> * Updated with feedback. * Add temporary test pipeline for gh-aw trigger validation * Revert "Add temporary test pipeline for gh-aw trigger validation" This reverts commit 12764ed. * . (#48155) * Delete test-trigger-pipeline.yml * Removed manual runs from trigger. Updated comments * Updating the analysis workflow and adding the fix workflow. Also added branch cleanup connected to the agentics-maintenance workflow. * Removed testing checks. * Updated concurrency issues and multiple suite runs. * Updated fix workflow steps with more detail. * Fixed edge cases and broadened branch cleanup. * Implemented feedback. * Removed branch cleanup. Updated analysis workflow to exclude fix PRs. * Switched to azsdk cli to satisfy copilot feedback. * Addressed Copilot feedback. --------- Co-authored-by: Daniel Jurek <djurek@microsoft.com> * Re-enable recorded azure-ai-ml e2e tests (#48538) * Re-enable recorded azure-ai-ml e2e tests Refresh sanitized test-proxy recordings, restore playback coverage for datastore, connection, environment, DSL, and pipeline e2e cases, and update parallel test environments for supported runtimes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 * Fix azure-ai-ml recording placeholder mismatch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 * Fix azure-ai-ml playback sanitizer matching Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 * Refresh remaining azure-ai-ml recordings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 * [Service Bus] Make PEEK_LOCK settlement outcomes observable on pyamqp PEEK_LOCK receiver links negotiate `rcv-settle-mode=second`, meaning settle only once the service confirms the outcome, but the SDK sent every disposition pre-settled and inherited a no-op `_incoming_disposition`. The service's terminal outcome was never requested and would have been discarded anyway, so `complete_message()` and its siblings could not fail: a settlement the service never applied returned success in ~0.4 ms without a round trip, and rejections carrying `com.microsoft:message-lock-lost` or `com.microsoft:session-lock-lost` were dropped. The only symptom was the message reappearing at lock expiry with an incremented delivery count. Verified live: for the same dead lock in the same session, the receiver link reported success in 0.42 ms while the management link raised SessionLockLostError and the message stayed queued. Settlements are now sent unsettled and wait for the service to confirm, raising when it rejects and reporting an unconfirmed outcome so the existing management-link fallback can re-settle authoritatively. A disposition only confirms when the frame is settled; an outcome alone does not, matching SenderLink. This is derived rather than configured, since .NET, Java, JavaScript and Go all await the disposition unconditionally and none exposes a flag to disable it. It is skipped only where there is no outcome to observe: RECEIVE_AND_DELETE is settled by the service on delivery, and uamqp cannot report dispositions. Success compares the echoed outcome against the one requested, because the service echoes what it applied rather than always replying accepted (abandon and defer echo modified, dead_letter echoes rejected); requiring accepted would have failed three of the four operations. This is a behavior change: confirming costs one round trip per settlement (~1-3 ms in region), so settle concurrently rather than serially, for example `await asyncio.gather(*(receiver.complete_message(m) for m in messages))`. The public surface is untouched and api.md is byte-identical to main. Also fixes a catch-all `except AMQPException` that flattened the service's real error condition into a generic ServiceBusConnectionError, hiding message-lock-lost from callers, and documents the symptom in TROUBLESHOOTING.md — a message redelivered despite a successful complete_message() previously had no entry point. * Allow test resource deployment to skip environment setup (#48596) Co-authored-by: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> * Add npm userconfig to typespec emitter template (#48639) Fixed CFSClean Co-authored-by: Ray Chen <raychen@microsoft.com> * Pipeline analysis and fix patch (#48643) * Fix pipeline analysis pull request resolution * Fix check suite repository matching * Fix pipeline auto-fix comment status * Address pipeline workflow review feedback * [CI] Remove api-consistency check (#48638) * Remove api-consistency check. * Restore delted api and metadata files. * Revert eventgrid api.metadata.yml to original content Co-authored-by: tjprescott <5723682+tjprescott@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tjprescott <5723682+tjprescott@users.noreply.github.com> * [CI] Generate ApiHash during extended build (#48628) * Refactoring, and add apiHash generation to build-extended-artifacts.yml * Code review feedback. * Test change for azure.template * Revert azure-template changes (except pyproject.toml) * Pass PI consistency... * [Service Bus] Clarify application_properties returns bytes keys/values on receive (#48078) * docs: Clarify application_properties returns bytes keys on receive (#45082) - Correct the ServiceBusMessage constructor :paramtype for application_properties from Dict[str, ...] to Dict[Union[str, bytes], ...] to match the existing parameter annotation - Add a note on the application_properties property that, when a message is received, keys and string values are returned as bytes, with the bytes-key access and decode pattern; note the same applies to the raw AMQP annotations and delivery_annotations accessed via raw_amqp_message - Add a README sample for reading application properties from received messages - Add a CHANGELOG Other Changes entry Documentation-only change; no behavioral change. * docs: Address review feedback on application_properties note (#45082) - Guard the Optional application_properties against None before calling .get in both the docstring and README examples, so copying the sample does not raise AttributeError on a message that carries no application properties - Correct the value-type note: an AMQP timestamp decodes to an integer (milliseconds), not a datetime; list only the verified native types (int, bool, float, uuid.UUID) and reframe as an AMQP-decode description * eng: Refresh Service Bus API metadata * Release agentserver responses 2.1.0b2 (#48653) Move post-2.1.0b1 fixes into a new release entry dated 2026-08-20 and bump the package version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdc32ee9-9e8f-4a1c-ba27-bdfa318afa1e * [agentserver-responses] Keep newest history item IDs when applying limit (#48560) * Keep newest history item IDs when applying limit get_history_item_ids in the in-memory and file response stores used resolved[:limit], which dropped recent turns. Slice from the end so conversation history keeps the newest N items. Fixes #48514. * Keep newest history ids under the limit and fix changelog * Restore an unreleased changelog section for the history id limit fix. Keep 2.1.0b1 as previously released and name FileResponseStore in the 2.1.0b2 notes. * Bump azure-ai-agentserver-responses to 2.1.0b2 so Analyze validates the unreleased changelog. * Clarify that limit keeps newest item IDs from the resolved chain. --------- Co-authored-by: Shiva S <shivakishore14@gmail.com> * docs(servicebus): clarify and sample session listing (#48645) * Fix Challenge Auth replay bug in Keyvault Security Domain SDK (#48636) * Ported fix * Updated changelog * generated Api.md for the package as its a new CI requirement --------- Co-authored-by: Hari K <kha@microsoft.com> * updated changelog (#48657) Co-authored-by: Hari K <kha@microsoft.com> * [AutoPR azure-mgmt-compute-bulkaction]-generated-from-SDK Generation - Python-6698911 (#48575) * Configurations: 'specification/compute/resource-manager/Microsoft.Compute/Bulkactions/tspconfig.yaml', SDK Release Type: beta, and CommitSHA: 'da7c67564bd3344610ffb0ec21a1974e79ffb00a' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6698911 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release. * update Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3f1ab584-1bba-44a3-ac54-43dee4f2b16a * Update CHANGELOG.md --------- Co-authored-by: azure-sdk <azuresdk@microsoft.com> Co-authored-by: ChenxiJiang333 <v-chenjiang@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: ChenxiJiang333 <119990644+ChenxiJiang333@users.noreply.github.com> Co-authored-by: Yuchao Yan <yuchaoyan@microsoft.com> Copilot-Session: 3f1ab584-1bba-44a3-ac54-43dee4f2b16a * [AutoPR azure-mgmt-chaos]-generated-from-SDK Generation - Python-6627851 (#48301) * Configurations: 'specification/chaos/resource-manager/Microsoft.Chaos/Chaos/tspconfig.yaml', SDK Release Type: beta, and CommitSHA: '358882ad707dcc3b0d1de9df6ba4dfba7e85da1a' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6627851 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release. * update * Update CHANGELOG.md --------- Co-authored-by: azure-sdk <azuresdk@microsoft.com> Co-authored-by: ChenxiJiang333 <v-chenjiang@microsoft.com> Co-authored-by: ChenxiJiang333 <119990644+ChenxiJiang333@users.noreply.github.com> * Howie/httpx2 (#48651) * Update version * Restore tsp-location.yaml * Update emitter skill for Python to deal with prerequisites (#47878) * change log (#47916) * [azure-ai-projects] Emit SDK from TypeSpec (commit fca510e0) (#47914) * Fix TypeSpec paths (#47959) * Remove sample_agent_toolbox_skill.py per bakcned folks and Linda request, add new hosted agent samples for Teams message trigger and reminder preview (#48234) * Remove sample_agent_toolbox_skill.py per bakcned folks and Linda request, add new hosted agent samples for Teams message trigger and reminder preview * change log * Re-emit from latest TypeSpec and do required updates (#48216) * Re-emit, to remove WebIQ tools (#48240) * update report * Updates in prep for a release of 2.4.0 (#48248) * Update rename tsp-location.yaml, so it does not break release build * change log (#48246) * Add sample toolboxes for synchronous and asynchronous AIProjectClient usage; update assets.json tag * Comment out additional sample tests in TestSamples class * Update to version 2.5.0 * Howie/sample 35 (#48310) * Refactor agent name retrieval to use a fallback mechanism - Updated multiple sample scripts to change the way the agent name is retrieved from environment variables. - Replaced the default value assignment using `os.environ.get("FOUNDRY_AGENT_NAME", "MyAgent")` with a more concise approach using `os.environ.get("FOUNDRY_AGENT_NAME") or "MyAgent"`. - This change ensures that if the environment variable is not set, the fallback value "MyAgent" is still used, while improving code readability. - The affected files include various agent tools and hosted agent samples across the project. * rever dataset generation job polling and update assistant prompt * change log (#48427) * Custom LRO pollers to enable easy access to Job ID (#48468) * Re-emit from latest TypeSpec commit (8-10-2026 17:16) (#48526) * update api.md files * Update public methods report. Delete other old report * Howie/reemit (#48608) * Add A2A protocol support and update related models - Introduced A2ATool and A2AToolboxTool classes for A2A protocol implementation. - Added A2AProtocolVersion enum to define supported A2A protocol versions. - Updated DataGenerationJobOptions to include SimulationSeedDataGenerationJobOptions and removed TaskGenerationDataGenerationJobOptions. - Modified existing models and enums to accommodate new A2A features. - Updated sample agent to utilize the new A2ATool class. - Adjusted beta routines API to remove unsupported parameters and enhance pagination handling. - Updated YAML configuration to reflect repository changes. * Update tsp-location.yaml with latest commit and repository details * Update sample_agent_to_agent.py skip reason in test_samples.py * Update CHANGELOG.md to include new A2A tools and breaking changes (#48626) * Restore tsp-location.yaml file with original content * Update API metadata, fix README A2A preview label, and adjust package lock files * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Rename a2_a_version to a2a_version for consistency in A2A tool classes and code re-emit * Refactor upload_session_file method and improve type hints - Updated the `build_agents_upload_session_file_request` function to handle optional content type. - Enhanced the `upload_session_file` method in `AgentsOperations` class to support both bytes and IO[bytes] types for content. - Added overloads for `upload_session_file` to clarify method usage and parameters. - Improved docstrings for better clarity on parameters and return types. - Fixed a typo in the sample agent to agent script. - Updated tsp-location.yaml with the latest commit hash and formatted additional directories for better readability. * change log * Update API metadata and improve type hints for upload_session_file method * Add httpx to development requirements * Update httpx version constraint in development requirements * Add OpenAI v3 transport compatibility for httpx2 in sample executor * version update * Update OpenAI client to use httpx2 and raise minimum openai package version to 3.0.0 * Add cache_write_tokens to input_tokens_details in MemoryStoreOperationUsage * Update Python version requirement to 3.10 and remove deprecated Python 3.9 classifier * Update CHANGELOG for dependency changes and minimum Python version --------- Co-authored-by: Darren Cohen <39422044+dargilco@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Enable bare messages= input on Relevance/Similarity/Fluency/Retrieval/ResponseCompleteness + all RAI evaluators (#48629) * Support bare messages= input on Relevance + Similarity evaluators Both evaluators previously required either a conversation dict or scalar query/ esponse inputs. When the SDK batch engine invokes them from a customer's data_mapping: {"messages": "${data.messages}"} (produced by ACA on unified-messages evaluation groups), it sends a bare messages=[...] kwarg alongside optional scalar context, ground_truth, and ool_definitions kwargs. The old code paths rejected this combination with: RelevanceEvaluator: "Either 'conversation' or individual inputs must be provided." ("No data to process" family) SimilarityEvaluator: "'query' is missing" / "Cannot provide both 'conversation' and individual inputs at the same time." Fix: - Swap ConversationValidator for MessagesOrQueryResponseInputValidator in each evaluator's __init__ so validation accepts either shape. - Add a _convert_kwargs_to_eval_input override in each evaluator that hoists a bare messages=[...] kwarg into conversation={"messages": ...} and merges any top-level context / ool_definitions scalars into the same conversation dict. A top-level ground_truth scalar is stamped onto each assistant turn that doesn't already carry one, so the base _derive_conversation_converter picks it up per-response. Regression paths preserved: - Passing conversation={"messages": [...]} still works (unchanged). - Passing scalar query/ esponse(/ground_truth) still works. - SimilarityEvaluator with query=None still rejects with the pre-existing error message. Verified end-to-end against real Azure OpenAI (gpt-4o-mini): - Relevance with bare messages -> score 5.0, pass. - Similarity with bare messages + per-turn ground_truth -> score 5.0, pass. - Similarity with bare messages + top-level context + ground_truth + tool_definitions -> score 5.0, pass. - Relevance/Similarity with q/r scalars -> score 5.0, pass (regression). - Similarity with query=None -> correctly rejected (regression). This is a partial migration; the same pattern applies to the other prompty/metric/RAI/tool evaluators that today reject messages-format input at turn/conversation level. * Extract hoist_messages_to_conversation helper for shared reuse The _convert_kwargs_to_eval_input normalization added to RelevanceEvaluator and SimilarityEvaluator was identical logic in two places. Extracted it into _evaluators/_common/_conversation_input.py as hoist_messages_to_conversation so: - The other ~21 bucket-3/4 evaluators (fluency, intent_resolution, response_completeness, retrieval, tool_call_*, RAI safety family, metric family) can adopt the same pattern with a ~3-line override each, instead of copying the same 20-line block. - The normalization has a single documented contract about how bare messages= + scalar context/ground_truth/ ool_definitions kwargs get folded into conversation={...}. Net line count in this commit: -39 (removed inline blocks, added helper + imports). Re-verified end-to-end with real gpt-4o-mini scoring — all 6 scenarios (bare messages, bare messages + adjuncts on both evaluators, q/r baselines, query=None regression) behave identically to the pre-extraction state. * Support bare messages= input on Fluency, Retrieval, ResponseCompleteness Apply the shared hoist_messages_to_conversation helper pattern to the remaining prompty-family evaluators with a conversation overload: - FluencyEvaluator: validator swap ConversationValidator -> MessagesOrQueryResponseInputValidator + convert override - RetrievalEvaluator: convert override (no explicit validator to swap) - ResponseCompletenessEvaluator: convert override (no explicit validator) Not included in this commit: IntentResolutionEvaluator. Its __call__ signature only exposes one overload (query: Union[str, List[dict]], response: Union[str, List[dict]], tool_definitions) with NO conversation overload, so hoisting messages=[...] into conversation={...} does not help — the base singleton matcher never routes into a conversation converter. That evaluator needs a different pattern (either a new conversation overload OR a custom transform that splits the messages list into a leading query context + trailing response turns per the intent-resolution semantic). Deferred to a follow-up. Verified end-to-end against real gpt-4o-mini: - Fluency messages input -> score 2.0 (baseline response scalar -> 3.0); no more "No data to process" error, evaluator runs to completion. - Retrieval messages+context input -> reaches the model; both messages and baseline paths return NaN, so any NaN behavior is not tied to the migration (baseline had it too before the change). - ResponseCompleteness messages+ground_truth input -> score 3.0 pass, identical to baseline. * Support bare messages= input on all RAI service evaluators Hoist a bare `messages=[...]` kwarg (plus optional scalar `context` / `ground_truth` / `tool_definitions`) into `conversation={...}` inside `RaiServiceEvaluatorBase._convert_kwargs_to_eval_input` so every RAI safety evaluator inherits messages-input support in one change instead of eight: - ViolenceEvaluator - HateUnfairnessEvaluator - SelfHarmEvaluator - SexualEvaluator - ProtectedMaterialEvaluator - IndirectAttackEvaluator (XPIA) - CodeVulnerabilityEvaluator - ECIEvaluator All 7 publicly-exported RAI evaluators verified to inherit the hoist via MRO inspection. The base class already routes `conversation` into `_evaluate_conversation` which per-turn-splits and calls the RAI service, so no service-side change is required — the hoist just lets the customer's `messages=` kwarg reach that existing path instead of failing at the kwarg matcher. Regression: pre-existing `query`/`response` scalar calls are untouched (hoist is a no-op when `messages` is absent). Legacy-endpoint path (`_use_legacy_endpoint`) still short-circuits with the raw conversation dict when supplied directly. Not exercised end-to-end here: firing a live RAI service call requires an Azure AI project scope with RAI credentials, which is a separate infra concern from the SDK plumbing. The kwarg-level fix (verified above) is what the customer-facing "No data to process" error was tied to. * Black: collapse assistant-turn conditional into one line * Pin openai<3.0 to unblock CI (test-proxy httpx/httpx2 mismatch) openai released v3.0 which migrated its transport layer from httpx to httpx2. The eval SDK test proxy (tests/__openai_patcher.py) still constructs httpx.URL objects, so with openai 3.x the tests fail with: TypeError: Invalid type for url. Expected str or httpx.URL, got <class 'httpx2.URL'> Verified empirically: the passing PR CI run (build 6717764, queued 01:15 UTC) resolved openai==2.54.0 with httpx; the failing run (build 6717844, queued 01:50 UTC) resolved openai==3.0.0 with httpx2. The only relevant change between the two runs was that openai 3.0 was published to PyPI in that ~35 min window, and pip picked it because setup.py had no upper bound. Pinning openai<3.0 restores the CI to the passing configuration. When the test proxy is updated to use httpx2 (or a compatibility shim), the pin should be lifted. This does not affect the messages= input feature under review; it is strictly a CI unblocker. * Address Copilot review: mutation, docs, and unit tests Addresses three of the four Copilot review findings on the hoist_messages_to_conversation helper: Finding 2 (real bug): the helper mutated caller-provided message dicts in place when stamping a top-level ground_truth. Two evaluations reusing the same messages list with different ground_truth values would see the first call's value leak into subsequent calls. Fix: shallow-copy the messages list and shallow-copy each affected assistant-turn dict before injecting ground_truth. Zero runtime cost, purely allocation. Finding 1 (edge case): explicitly document behavior when messages entries are not plain dicts (typed Message objects, dataclasses). The helper skips ground_truth stamping for such items but still hoists the overall input into conversation. The base `_derive_conversation_converter` requires plain dicts and would fail on non-dicts regardless, so this matches existing SDK convention. Finding 4 (test coverage): add tests/unittests/test_conversation_input.py with seven table-driven tests covering: - no messages, no conversation (noop) - conversation already provided (short-circuit) - bare messages only - bare messages + all adjuncts (context, ground_truth, tool_definitions) - non-dict messages entries skipped for stamping - immutability: same messages list reused across two calls with different ground_truth scalars (regression guard for Finding 2) - per-turn ground_truth is not overwritten by top-level scalar Verified end-to-end with live gpt-4o-mini scoring on all 5 direct-file-edit evaluators (Relevance, Similarity, Fluency, Retrieval, ResponseCompleteness). All previously scored scenarios still score identically; the new immutability guarantee is validated at runtime by reusing the same messages list across two consecutive Similarity calls with different ground_truth values and asserting the caller's assistant dict is unchanged after both. Finding 3 (clearer error for mixed messages+q/r inputs) is not addressed here: the base class already rejects the mixed combination correctly, just with a generic error message. Cosmetic; deferred. * Move openai<3.0 pin from install_requires to dev_requirements Addresses reviewer feedback: the previous `openai<3.0` upper bound on install_requires punished end-user installs by making them incompatible with the current openai 3.x line (3.0 released 2026-08-12, latest 3.3 released 2026-08-18) even though the SDK's runtime is fine with openai 3.x. The incompatibility is confined to the CI test proxy: tests/__openai_patcher.py:74 request.url = httpx.URL(config.proxy_url).join(request_path) openai 3.x switched its transport layer to httpx2 and rejects httpx.URL objects with: TypeError: Invalid type for url. Expected str or httpx.URL, got <class 'httpx2.URL'> Production code paths that use `AsyncAzureOpenAI` directly never reach that patcher, so end users installing this SDK alongside openai 3.x are not affected. Revert the setup.py upper bound and move the constraint to dev_requirements.txt so only the test environment is pinned. Removes this pin once tests/__openai_patcher.py is updated for httpx2 compatibility (out of scope for this PR). * Black: collapse multi-line if in ground_truth stamping loop * Add wiring tests for bare messages= input on the 5 direct-edit evaluators Extends test_conversation_input.py from 7 to 25 tests. The existing 7 cover the hoist_messages_to_conversation helper in isolation. The new 18 prove each of the 5 evaluators overridden in this PR actually calls the hoist and that the hoisted output equals the equivalent conversation={"messages": ...} invocation. Coverage matrix: - A. Bare messages=[...] (5 evaluators) - B. messages= + context + ground_truth + tool_definitions (5 evaluators) - C. Legacy (query, response, ...) regression net (5 evaluators) - D. Negative wiring: empty list, mixed query+messages, non-dict message entries (3 tests) Assertion strategy: shape equivalence with the conversation={...} call path. Robust to base-class output-shape changes because both sides re-execute in lockstep. 25 passed, 0 failed locally (Python 3.10). * Add wiring tests for all 8 RAI evaluators fixed via RaiServiceEvaluatorBase Extends test_conversation_input.py from 25 to 51 tests. Every one of the 13 evaluators fixed by this PR now has direct wiring coverage. RAI evaluators covered (parametrised over all 8): - ViolenceEvaluator - HateUnfairnessEvaluator - SelfHarmEvaluator - SexualEvaluator - ProtectedMaterialEvaluator - IndirectAttackEvaluator (XPIA) - CodeVulnerabilityEvaluator - ECIEvaluator Coverage matrix per RAI evaluator (24 tests): - A. Bare messages=[...] - B. messages= + context + ground_truth + tool_definitions - C. Legacy (query, response) regression net Plus 2 branch tests for the RAI base override's short-circuit: - _use_legacy_endpoint=True returns [kwargs] intact with hoisted conversation (single-call pre-sync-migration behavior). - _use_legacy_endpoint=False delegates to super()'s per-turn converter and does not pass raw messages= kwarg through. Same assertion strategy as the 5 direct-edit evaluators: shape equivalence with the equivalent conversation={...} invocation. 51 passed, 0 failed locally (Python 3.10). * Apply Black formatting to test_conversation_input.py (line-length 120) * Update tsp-location.yaml to change commit hash and format additional directories as a list * Update package lock and JSON files; fix variable naming in models and update commit hash in tsp-location.yaml * update version --------- Co-authored-by: Pranjal Patel <patelpranzp25@gmail.com> Co-authored-by: Pranjal Patel <pranjalpatel@microsoft.com> Co-authored-by: ReilleyMilne <91291100+ReilleyMilne@users.noreply.github.com> Co-authored-by: Daniel Jurek <djurek@microsoft.com> Co-authored-by: lavakumarrepala <v-rlava@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: azure-sdk-automation[bot] <191533747+azure-sdk-automation[bot]@users.noreply.github.com> Co-authored-by: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Co-authored-by: Ray Chen <raychen@microsoft.com> Co-authored-by: Travis Prescott <tjprescott@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tjprescott <5723682+tjprescott@users.noreply.github.com> Co-authored-by: Eldert Grootenboer <eldert@eldert.net> Co-authored-by: Shanmukha Pasumarthy <shanmukha98@gmail.com> Co-authored-by: Hashim Khan <64767361+Hashim1999164@users.noreply.github.com> Co-authored-by: Shiva S <shivakishore14@gmail.com> Co-authored-by: Hariharan K <reachk.hariharan@gmail.com> Co-authored-by: Hari K <kha@microsoft.com> Co-authored-by: azure-sdk <azuresdk@microsoft.com> Co-authored-by: ChenxiJiang333 <v-chenjiang@microsoft.com> Co-authored-by: ChenxiJiang333 <119990644+ChenxiJiang333@users.noreply.github.com> Co-authored-by: Yuchao Yan <yuchaoyan@microsoft.com> Co-authored-by: Darren Cohen <39422044+dargilco@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Rabah B <134166186+RabsB@users.noreply.github.com> Copilot-Session: fd9e755b-1b9d-4918-aa65-1774f7f78014 Copilot-Session: bdc32ee9-9e8f-4a1c-ba27-bdfa318afa1e Copilot-Session: 3f1ab584-1bba-44a3-ac54-43dee4f2b16a
Summary
Enable bare
messages=[...]kwarg input (plus optional scalarcontext,ground_truth,tool_definitionsadjuncts) on the evaluators most commonly targeted by cloud batch-execution engines whosedata_mappingproduces this shape. This is a plumbing fix, not a semantics change — every affected evaluator was already capable of scoring the input via either the scalar(query, response, ...)path or theconversation={"messages": [...]}path. The only gap was that no code path unpacked the specificmessages=<list>+ top-level scalar-adjunct call pattern into either of those existing forms.Problem
When a customer's evaluation
data_mappingtargets amessagesfield (plus optional adjuncts):the SDK batch engine invokes each evaluator as:
Evaluators with only
(query, response, ...)and(conversation)overloads then failed either with:EvaluationException: Either 'conversation' or individual inputs must be provided.— the base class singleton matcher found no overload with amessagesparam, so nothing was routed to_do_eval.EvaluationException: Cannot provide both 'conversation' and individual inputs at the same time.— if the evaluator had a custom_convert_kwargs_to_eval_inputthat special-casedconversation, the leftover scalarcontext/ground_truth/tool_definitionsat the top level tripped the ambiguity check.Neither failure mode reflected an actual limitation of the evaluator; both were routing gaps.
Fix
New shared helper:
azure.ai.evaluation._evaluators._common.hoist_messages_to_conversationNormalises a bare
messages=[...]kwarg (plus any scalar adjuncts alongside it) intoconversation={"messages": [...], ...}soEvaluatorBase._derive_conversation_converter— the existing per-turn q/r/context/ground_truth extractor — is what runs. A top-level scalarground_truthis stamped onto each assistant turn that does not already carry a per-turnground_truthso the per-response extraction path picks it up. Zero effect whenconversationis already provided or whenmessagesis absent.Applied to 5 evaluators directly (
PromptyEvaluatorBasesubclasses with aconversationoverload):RelevanceEvaluator— validator swap + convert overrideSimilarityEvaluator— validator init + reordered convert override (retains pre-existing q/r-required checks when neither conversation nor messages is provided)FluencyEvaluator— validator swap + convert overrideRetrievalEvaluator— convert override (no validator to swap)ResponseCompletenessEvaluator— convert override (no validator to swap)Applied once in
RaiServiceEvaluatorBase._convert_kwargs_to_eval_input, inheriting to all 8 RAI safety evaluators in one change:ViolenceEvaluatorHateUnfairnessEvaluatorSelfHarmEvaluatorSexualEvaluatorProtectedMaterialEvaluatorIndirectAttackEvaluator(XPIA)CodeVulnerabilityEvaluatorECIEvaluatorThe RAI base already routes
conversationinto_evaluate_conversation; the hoist just letsmessages=reach that existing path instead of failing kwarg matching. No RAI service backend change required.Total: 13 evaluators fixed across 4 focused commits.
Verified end-to-end
Live gpt-4o-mini scoring against
tiger-5.openai.azure.comfor every affected input shape on the direct-file-edit evaluators (RAI verified at kwarg-processing layer since RAI service calls require an Azure AI project scope):messages=[...]— score returned, no errors.messages=[...]+context=+ground_truth=+tool_definitions=— score returned, no errors.(query, response, ground_truth)— score returned unchanged. Regression clean.query=Noneafter fix — still rejected with the exact pre-existing error message. Safety net intact.Explicitly not in scope
Evaluators without a
conversationoverload —IntentResolutionEvaluator, the metric family (F1ScoreEvaluator,BleuScoreEvaluator,RougeScoreEvaluator,MeteorScoreEvaluator,GleuScoreEvaluator), and the tool family (ToolCallAccuracyEvaluator,ToolInputAccuracyEvaluator,ToolOutputUtilizationEvaluator,ToolCallSuccessEvaluator) — would need a different helper (project last assistant turn → scalarresponse) plus per-evaluator semantic decisions on how a message list splits intoquery/response/tool_calls. All 10 of these are also declared assupported_evaluation_levels: [turn]in the customer-facing evaluator catalog, so they self-skip at conversation level today and the customer-visible impact of extending them to baremessages=is bounded. Left as follow-up.Files changed
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_conversation_input.pysdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/__init__.py— export the new helpersdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py— inherit hoist across all RAI evaluatorssdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_relevance/_relevance.pysdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_similarity/_similarity.pysdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_fluency/_fluency.pysdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_retrieval/_retrieval.pysdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.pyTesting recommendations for reviewers
tests/unittests/test_common_validators.py::TestMessagesOrQueryResponseInputValidatorcontinues to cover the validator swap.hoist_messages_to_conversationhelper is small enough that a targeted unit test file exercising the three transform variants (no conversation, existing conversation, bare messages + adjuncts) would give straight-line coverage. Left for a follow-up per repository-team preference on test placement.