Repository navigation
Conversation
Decision requests, and Azure OpenAI chat model requests that authenticate with Microsoft Entra ID, reuse a credential's token until five minutes before it expires, as azure-core's bearer token policy does, instead of requesting a token for every request. Concurrent requests that share a synchronous credential wait for one token request on a worker thread without blocking their event loops; a credential whose get_token is a coroutine function, or that implements AsyncTokenCredential but not the synchronous context manager protocol, is asked on the caller's event loop. Only an authentication failure is shared with the requests that waited on it, and only with requests of the same kind, so a chat failure is never replayed as a Decision failure or the reverse. A token rejected with HTTP 401 is dropped. Tokens are kept per scope, so Decision keeps the Cognitive Services scope that the hosted evaluation service relies on, and the cache never keeps a credential alive.
|
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. |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The new threading.Lock prevents provider pickling and deepcopy, also breaking serialization of enclosing chat flows; it must be excluded from serialized state and recreated on restore.
1 open issue
What changed in this PR
This PR adds credential-token reuse to azure-ai-evaluation chat and Decision requests, reducing repeated authentication calls while keeping their token scopes separate.
Changes:
- Adds a shared token cache with expiry handling, concurrent synchronous acquisition, and HTTP 401 invalidation.
- Integrates caching into both model paths and coordinates default chat credential creation.
- Adds regression coverage and documents the fix for 1.18.11.
| File | Description |
|---|---|
sdk/evaluation/azure-ai-evaluation/tests/unittests/test_prompty_token_reuse.py |
Tests chat caching, concurrency, authentication failures, and credential lifecycle. |
sdk/evaluation/azure-ai-evaluation/tests/unittests/test_decision_model_support.py |
Tests Decision caching, scope isolation, deadlines, cancellation, and serialization. |
sdk/evaluation/azure-ai-evaluation/CHANGELOG.md |
Documents token reuse and credential behavior. |
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_legacy/prompty/_prompty.py |
Reuses chat tokens and invalidates rejected tokens. |
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_legacy/_common/_token_cache.py |
Implements shared, identity-keyed token caching and acquisition coordination. |
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_legacy/_common/_async_token_provider.py |
Coordinates default credential creation and cleanup. |
sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluators/_common/_model_runners.py |
Integrates Decision token reuse, deadline handling, and rejection invalidation. |
Review effort: Balanced
| """Initialize the AsyncAzureTokenProvider.""" | ||
| self._credential: Optional[TokenCredential] = None | ||
| self._env_client: Optional[AzureEnvironmentClient] = AzureEnvironmentClient(base_url=base_url, **kwargs) | ||
| self._credential_lock = threading.Lock() |
Keeps both sides' additions where Azure#49804 and this change touched the same lines of _model_runners.py and test_decision_model_support.py, and moves the token reuse entry from the released 1.18.11 section to a 1.18.12 section with the version bump.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fix for azure-ai-evaluation 1.18.12. 1.18.11 shipped without it. The token bug affects chat evaluators in 1.18.10 and 1.18.11, and the Decision runner added in 1.18.11.
Token reuse was part of #49804, and before that #49695. Neither carried it, so it lives only in this PR. #49804 shipped the Tool Call Success fixes and the Decision failure reasons in 1.18.11, and #49695 keeps the OpenAI Decisions protocol.
Problem
Each Decision request, and each Azure OpenAI chat request that authenticates with Microsoft Entra ID, asked the credential for a new token. With
AzureCliCredential, which starts a process per request, concurrent evaluations were slow and could fail. On main, 16 concurrent chat rows made 16 token requests (about 39 s of token time), and one row failed with aClientAuthenticationErrorfromaz.Fix
_legacy/_common/_token_cache.pykeeps each credential's token per scope until five minutes before it expires, as azure-core's bearer token policy does._prompty.py):_ReusedTokenProviderreplaces the per-request wrapper. The default credential that a chat evaluator creates when nocredentialis passed is resolved to its inner synchronous credential.AsyncAzureTokenProvider.get_credential()keeps the first credential created, so concurrent first rows share one credential and one token request, and a credential created while the provider closes is closed._model_runners.py):_get_token(deadline)uses the cache. A credential that returns no string token is the user error "Could not acquire a token for the decision model." rather than a timeout. Decision requests keep thehttps://cognitiveservices.azure.com/.defaultscope and their own cached token. Decision endpoints also accept the chat scope, but the hosted evaluation service picks the identity for Decision calls by token audience, so the chat scope would make single-tenant projects call Decision as the end user instead of the project managed identity.get_tokenis a coroutine function, or that implementsAsyncTokenCredentialbut not the synchronous context manager protocol, is asked for its token on the caller's event loop; any other credential on a worker thread, so a plainget_tokenthat needs the running loop must belong to anAsyncTokenCredential.No service change is needed. The hosted evaluation worker does not depend on this cache, and Decision keeps the scope the worker relies on.
Validation
Live runs use gpt-4o-mini and Microsoft-Decision-1 deployments,
AzureCliCredential, and_ToolCallSuccessEvaluatorrows with a tool call.tests/unittestsAzureCliCredential)evaluate()over 8 rows, chat and Decision-1 sharing one credentialhttps://ai.azure.com/.defaultfor chat,https://cognitiveservices.azure.com/.defaultfor Decision), all 8 scoredClientAuthenticationErrorMain merged in (04b36ed), after the 1.18.11 release:
tests/unittestsevaluate()over 8 rows with a Microsoft-Decision-1 judge andAzureCliCredential, 1.18.11 against this PRMerging
Main is merged in (04b36ed). #49804 and this change both added helpers after
_is_finite_numberin_model_runners.pyand imports intest_decision_model_support.py; the merge keeps both. The CHANGELOG entry moved from the released 1.18.11 section to 1.18.12 (Unreleased), and_version.pyis 1.18.12, as in #49821. Whichever of the two merges second needs a CHANGELOG merge that keeps both entries in the one 1.18.12 section.The earlier docs build failure was Sphinx failing to download the
requestsintersphinx inventory (an SSL error from requests.kennethreitz.org), not this change.Not in this PR