Skip to content

Add Azure OpenAI Decisions protocol and evaluation metrics to evaluation runners - #49695

Open
bonanza89 wants to merge 6 commits into
Azure:mainfrom
bonanza89:users/aqardahji/openai-decisions
Open

bonanza89 wants to merge 6 commits into
Azure:mainfrom
bonanza89:users/aqardahji/openai-decisions

Conversation

@bonanza89

@bonanza89 bonanza89 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Existing Decision evaluators speak the Microsoft Decision contract. This adds a small protocol adapter layer to the shared runner from #49486, so decision_protocol="openai" calls Azure OpenAI's /openai/v1/decisions and existing configurations keep the Microsoft route.

DecisionProtocol has three operations: the operation URL, encoding the canonical request, and binding answers back to the canonical shape. MicrosoftDecisionProtocol is the identity; OpenAIDecisionProtocol turns Boolean questions into predicates that state both criteria, ordinal criteria into levels labeled Level 1 to Level n (matching rubric text such as "imply level 2"), and binds answers by name and zero-based level index. Evaluators keep their scoring, applicability and cutoffs, and provenance has the same shape on both protocols apart from protocol and the returned model.

  • Byte budgets measure the canonical request on every protocol, so OpenAI admits the same evidence; invalid evidence fails with the same user error.
  • A refusal fails an evaluation only when it reads that answer (error_kind model_refusal), so skipped evaluations never depend on their grade. A provider content filter's 400 is marked content_filter.
  • A deployment that serves the other protocol fails with a user error that names the decision_protocol value to use. Microsoft Decision answers such a deployment with unknown_model and Azure OpenAI Decisions with model_not_found; other errors are unchanged.
  • Metrics and execution logs carry a bounded protocol, and cache and reasoning counters stay under usage_details. Split conversations sum each counter over the turns whose token usage is counted, on success and on failure.
  • Each evaluation run through DecisionEvaluationStrategy.evaluate records azure.ai.evaluation.decision.evaluations and azure.ai.evaluation.decision.evaluation.duration with protocol, metric, outcome (completed, skipped, error or cancelled), blame and error_kind. A cancellation by the cancellation callback counts as cancelled, and a metric or OpenTelemetry failure never changes the result or the error.
  • The shared transport, credential handling, retries, deadlines and cancellation are unchanged; no OpenAI client upgrade is required.

The same Decision-capable assets are reused unchanged; assets #5402 adds tests only. On the service side, the RAISvc worker selects this adapter from its validated binding in a separate vienna change that has not merged; the worker on vienna master uses the Microsoft protocol only, and its validation no longer treats OpenAI decision-capable deployments as native Decision models. #49781, which lets the SDK's built-in evaluators use Decision models and record these metrics, has merged and this PR is rebased on it; the OpenAI Decisions sample (samples/openai_decisions/README.md) uses the public CoherenceEvaluator. Token reuse moved to #49804.

Validation:

  • On the rebased branch, the Decision and OpenAI Decisions test files: 782 passed, 1 skipped (a POSIX-only test). The full unit suite: 2,400 passed, 31 skipped, none failed.
  • Mutation checks: each new test fails when its defect is reintroduced (serialization, protocol hints).
  • Live, with a wheel built from this PR and Support Decision models in the built-in evaluators #49781. These runs used the earlier head of this PR, which also carried the token reuse now in Fix Tool Call Success scoring, binary_aggregate and Decision input order, and name Decision failure reasons #49804; the protocol code has not changed since:
    • decision_protocol="openai" against a gpt-6-luna deployment: every built-in evaluator, QAEvaluator and three two-turn conversations completed with protocol openai, and the evaluation metric counts matched every call. A Microsoft Decision deployment, a GPT-6 Luna deployment and a chat deployment each returned the protocol hint when configured with the wrong protocol.
    • The Microsoft protocol through evaluate(): every built-in evaluator completed on Decision and chat judges with no errors.
    • Chat judges gpt-4.1, gpt-4o, gpt-5-mini, o3-mini and gpt-6-luna completed every built-in evaluator.
    • On the final code, merged with Support Decision models in the built-in evaluators #49781:
      • The evaluation worker behind a local RAISvc service passed every portal-shaped (50), SDK-shaped (48) and reasoning-judge (10) catalog evaluator case, 21 rubric evaluator cases and 10 probability_threshold incident cases on Decision and chat judges.
      • Direct SDK calls and evaluate() with every built-in evaluator and QAEvaluator passed on the Microsoft Decision judge and on gpt-4.1, gpt-4o, gpt-5-mini and o3-mini. Chat results match 1.18.10.
      • gpt-6-luna passed 171 cases as an Azure OpenAI Decisions model and as a chat judge.

The azureml-assets #5402 suite passes on this code (1,259 passed, including all 106 OpenAI Decisions cases) once its SDK pin moves here together with its updated OpenAI tests. No package release is performed by this PR. API contract.

@github-actions github-actions Bot added the Evaluation Issues related to the client library for Azure AI Evaluation label Oct 23, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
10 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@bonanza89
bonanza89 marked this pull request as ready for review October 30, 2026 19:11
@bonanza89
bonanza89 requested a review from a team as a code owner October 30, 2026 19:11
Copilot AI balanced review requested due to automatic review settings October 30, 2026 19:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Copilot AI balanced review requested due to automatic review settings October 30, 2026 21:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

@bonanza89 bonanza89 changed the title Add Azure OpenAI Decisions protocol to evaluation runners Add Azure OpenAI Decisions protocol, evaluation metrics and token reuse to evaluation runners Oct 30, 2026
Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Copilot AI balanced review requested due to automatic review settings October 30, 2026 23:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Copilot AI balanced review requested due to automatic review settings October 31, 2026 01:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Copilot AI balanced review requested due to automatic review settings October 31, 2026 01:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Copilot AI balanced review requested due to automatic review settings October 31, 2026 04:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Ahmad Qardahji added 6 commits November 2, 2026 22:32
Replace the OpenAI-specific runner path with a small DecisionProtocol adapter
layer. The runner selects the Microsoft or OpenAI adapter from
decision_protocol, and each adapter only translates the canonical request and
binds answers back to the canonical shape, so evaluators keep their scoring,
applicability and cutoffs unchanged.

- OpenAI score levels are labeled Level 1 to Level n, matching rubric text
  such as "imply level 2"; answers still bind by zero-based level index.
- Byte budgets measure the canonical request on every protocol, and invalid
  evidence fails with the same user error on every protocol.
- A refusal fails only when an evaluator reads it (error_kind model_refusal),
  and a provider content filter rejection is marked error_kind content_filter.
- Provenance and execution metrics name the protocol and keep provider cache
  and reasoning counters under usage_details. Split conversations sum each
  counter over the turns whose token usage is counted, on success and on
  failure, so the counters never cover requests the token totals omit.
- Each evaluation run through DecisionEvaluationStrategy.evaluate records the
  optional OpenTelemetry metrics azure.ai.evaluation.decision.evaluations and
  azure.ai.evaluation.decision.evaluation.duration with protocol, metric,
  outcome, blame and error_kind. The runner marks a cancellation by the
  cancellation callback, so it is recorded as cancelled even when its
  execution summary is omitted. A metric sink or OpenTelemetry failure never
  changes the result or the error.
The sample now imports CoherenceEvaluator from azure.ai.evaluation, which
accepts a Decision model configuration in 1.18.11, instead of requiring an
azureml-assets checkout. It also notes the error for a deployment that
serves the other protocol, and that GPT-6 Luna used as a chat judge needs
is_reasoning_model=True.
…tocol

A Decision deployment that serves the other protocol now fails with a hint
that names the decision_protocol value to use: Microsoft Decision answers
such a deployment with unknown_model and Azure OpenAI Decisions with
model_not_found. Other errors are unchanged.
@bonanza89
bonanza89 force-pushed the users/aqardahji/openai-decisions branch from ee99b73 to c0b6931 Compare November 3, 2026 06:55
Copilot AI balanced review requested due to automatic review settings November 3, 2026 06:55
@bonanza89 bonanza89 changed the title Add Azure OpenAI Decisions protocol, evaluation metrics and token reuse to evaluation runners Add Azure OpenAI Decisions protocol and evaluation metrics to evaluation runners Nov 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the Azure OpenAI Decisions integration.

No open issues 🎉

Review effort: Balanced

bonanza89 added a commit that referenced this pull request Nov 4, 2026
…der, and name Decision failure reasons (#49804)

* Reuse credential tokens across model requests and fix Tool Call Success scoring

Token reuse (from #49695, without the OpenAI Decisions protocol):
- Decision requests, and Azure OpenAI chat requests that use Microsoft Entra ID, reuse a credential's token until
  five minutes before it expires instead of requesting one per request.
- Concurrent requests that share a synchronous credential wait for one token request, off the event loop; requests
  queued behind a token that is not cached use it.
- A 401 drops the token, and an acquisition that began before the rejection is not stored.
- The default chat credential shares one credential and token across concurrent first requests.
- A Decision credential that returns no string token is a user error, not a timeout.

Tool Call Success:
- The prompt's 14 example outputs are now valid JSON, which stops gpt-4.1 and gpt-4o nesting score and status
  in properties and scoring successful tool calls 0.
- score, status and reason fall back to properties, and a score other than 0 or 1 raises a system error instead of
  becoming 0.
- A list of messages without tool calls (only text or MCP approvals) is not applicable without a judge call;
  strings, tool traces in text and any other content still go to the judge.

* Share the chat token scope with Decision requests and drop the redundant retry list

Decision requests now request tokens for https://ai.azure.com/.default, the scope
Azure OpenAI chat requests use, so a credential shared by chat and Decision
evaluators needs one token instead of one per scope. Decision endpoints accept
both audiences. The explicit retry status list is removed: azure-core retries
those statuses by default and unions an explicit list with its defaults, so
retry behavior is unchanged. _get_token documents its absolute monotonic
deadline, and the token cache module records why get_bearer_token_provider is
not used in its place.

* Name the reason a failed Decision request was refused, and share only authentication failures with waiting requests

A failed Decision request names a recognized reason in decision_model.error_kind of the error's properties, which is
kept on failed result rows, and in the error message, for example "The decision model returned HTTP 422
(context_window_exceeded).". The provider states these reasons for an HTTP 422 only in text, which is never exposed:
context_window_exceeded, request_token_limit_exceeded, question_limit_exceeded and invalid_question, recognized by the
sentence's fixed opening so a quoted question identifier cannot change the kind. A request over max_request_bytes or
max_state_question_bytes, which is not sent, has the kind request_bytes_exceeded.

Requests that waited on a token request now share its failure only when it failed authentication, as on the chat
path; after any other failure each waiting request makes its own attempt, so a transient failure fails only the
request that hit it.

* Keep Decision on the Cognitive Services scope and keep token failures within each kind of request

Decision endpoints accept both audiences, but the hosted evaluation service selects the identity for Decision calls by token audience: for https://ai.azure.com it forwards the caller's own token, and for the Cognitive Services audience it exchanges that token or uses the project managed identity. With the chat scope, single-tenant projects would call Decision as the end user instead of the project managed identity, and the service's worker tests fail. Decision requests go back to https://cognitiveservices.azure.com/.default and their own cached token, and the redundant retry status list stays removed.

The token cache records a failure per failure formatter and exception type, with the attempt that ended in it, so a request shares only a failure of its own kind that ended after it queued, and a successful token request clears them. A request could otherwise replay another kind's failure with its own exception type: a Decision description raised as a chat ClientAuthenticationError, or a chat authentication failure raised as an EvaluationException with an unknown blame.

A credential that implements AsyncTokenCredential but whose get_token is a plain function returning an awaitable was asked on a worker thread, where a get_token that needs the running event loop failed. A credential whose get_token is a coroutine function, or that implements AsyncTokenCredential, is now asked on the caller's event loop, as released chat requests did.

* Format the Decision error-kind patterns and tests with black

The package enforces black, and these lines were not formatted. No code changes.

* Format the Decision batch persistence test with black

The Analyze job's black check failed on this file. No code changes.

* Ask credentials that also implement the synchronous context manager protocol on a worker thread

A synchronous credential whose class also implements the asynchronous context manager protocol matched the structural AsyncTokenCredential check and was asked on the event loop, which blocked the loop, lost the shared token request and broke a get_token that runs its own event loop. A credential that implements the synchronous context manager protocol is again asked on a worker thread. A plain get_token that needs the caller's running loop must belong to an AsyncTokenCredential, as the CHANGELOG and docstrings now state, and fetch documents that its failure formatter and exception type key the shared failures and must be module-level.

* Move token reuse to its own pull request

Token reuse for Decision and chat requests is not part of the Tool Call
Success and Decision failure reason fixes, so it moves to a separate pull
request. This restores the base _prompty.py and _async_token_provider.py,
removes _token_cache.py and its tests, and returns DecisionModelRunner to
requesting a token for each call, with the Cognitive Services scope as
before. The error_kind reasons, the retry policy cleanup and the Tool Call
Success fixes are unchanged.

* Treat empty tool call fields as absent in Tool Call Success

A tool_calls, function_call or tool_call_id field that is None or an empty
string, list or dict no longer sends a text-only response to the judge.
OpenAI's model_dump() writes tool_calls: None and function_call: None on every
reply, so those replies reached the chat or Decision judge and could pass.
Populated fields and values of any other type still go to the judge.

* Count only passed and failed rows in binary_aggregate

evaluate() divided an evaluator's passes by every row, so not applicable and
errored rows counted as failures and binary_aggregate disagreed with the mean
score. It now divides by the rows that passed or failed, and an evaluator
without such rows gets no binary_aggregate, as one without scores gets no mean.

* Keep evaluator inputs in their declared order

_get_all_singleton_inputs flattened the overloads' input names through a
set, so an evaluator's converted inputs followed the set's order, which
varies with the process's hash seed. An evaluator that sends these inputs
to a Decision model unchanged, as several Microsoft Foundry catalog
evaluators do, could therefore list the same fields in a different order
in each process, and the Decision model could answer the same input
differently. The names now keep the order in which the overloads declare
them. The built-in evaluators in this package build their Decision state
in a fixed order and are unchanged.

* Keep the last result column deciding binary_aggregate

An evaluator with several _result columns reports its last one, as before.
A last column without pass or fail rows now leaves no binary_aggregate
instead of an earlier column's rate. The CHANGELOG says only pass and fail
count, which also affects custom evaluators that return other values.
Tests cover all-fail and boolean result columns, several result columns,
and OpenAI tool calls on the Decision judge.

* Run the hash seed test's processes from an empty directory

python -c puts the working directory first on the path, so in CI jobs that
install the built wheel and run pytest from the package directory, the
child processes imported the checkout instead of the installed package.
They now start in an empty temporary directory, and three seeds keep the
test short.

* Leave unsupported role and content type values to the judge

The no-tool-call check compared role and content type values with sets,
so an unhashable value, such as a list role or a dict type, raised
TypeError. A value that is not a string now fails the check, so the
message goes to the judge or the input validator, as it did before the
check existed.

---------

Co-authored-by: Ahmad Qardahji <aqardahji@microsoft.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Evaluation Issues related to the client library for Azure AI Evaluation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants