Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLU] support for AAD + docs #24960

Merged
merged 9 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
* Client now uses python dictionaries for method parameters and results instead of classes.
* Many input and result parameter name changes in `analyze_conversation()` method

## 1.0.0 (Unreleased)
## 1.0.0 (2022-06-27)

### Features Added
* Added Azure Active Directory (AAD) authentication support
* Added more resolution types for entities
* Added support for authoring operations with `ConversationAuthoringClient` under the `azure.ai.language.conversations.authoring` namespace.

Expand Down
43 changes: 41 additions & 2 deletions sdk/cognitivelanguage/azure-ai-language-conversations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Conversational Language Understanding - aka **CLU** for short - is a cloud-based
- Workflow app: Acts like an orchestrator to select the best candidate to analyze conversations to get best response from apps like Qna, Luis, and Conversation App


[Source code][conversationallanguage_client_src] | [Package (PyPI)][conversationallanguage_pypi_package] | [API reference documentation][api_reference_documentation] | [Product documentation][conversationallanguage_docs] | [Samples][conversationallanguage_samples]
[Source code][conversationallanguage_client_src] | [Package (PyPI)][conversationallanguage_pypi_package] | [API reference documentation][api_reference_documentation] | [Samples][conversationallanguage_samples] | [Product documentation][conversationallanguage_docs] | [Analysis REST API][conversationanalysis_restdocs] | [Authoring REST API][conversationanalysis_restdocs_authoring]

## _Disclaimer_

Expand Down Expand Up @@ -67,6 +67,36 @@ credential = AzureKeyCredential("<api-key>")
client = ConversationAuthoringClient(endpoint, credential)
```

#### Create a client with an Azure Active Directory Credential

To use an [Azure Active Directory (AAD) token credential][cognitive_authentication_aad],
provide an instance of the desired credential type obtained from the
[azure-identity][azure_identity_credentials] library.
Note that regional endpoints do not support AAD authentication. Create a [custom subdomain][custom_subdomain]
name for your resource in order to use this type of authentication.

Authentication with AAD requires some initial setup:

- [Install azure-identity][install_azure_identity]
- [Register a new AAD application][register_aad_app]
- [Grant access][grant_role_access] to the Language service by assigning the "Cognitive Services User" role to your service principal.
kristapratico marked this conversation as resolved.
Show resolved Hide resolved

After setup, you can choose which type of [credential][azure_identity_credentials] from azure.identity to use.
As an example, [DefaultAzureCredential][default_azure_credential]
can be used to authenticate the client:

Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables:
`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`

Use the returned token credential to authenticate the client:

```python
from azure.ai.textanalytics import ConversationAnalysisClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
client = ConversationAnalysisClient(endpoint="https://<my-custom-subdomain>.cognitiveservices.azure.com/", credential=credential)
```

## Key concepts

Expand Down Expand Up @@ -340,7 +370,8 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
[azure_cli]: https://docs.microsoft.com/cli/azure/
[azure_portal]: https://portal.azure.com/
[azure_subscription]: https://azure.microsoft.com/free/

[conversationanalysis_restdocs]: https://docs.microsoft.com/rest/api/language/conversation-analysis-runtime/
kristapratico marked this conversation as resolved.
Show resolved Hide resolved
[conversationanalysis_restdocs_authoring]: https://docs.microsoft.com/rest/api/language/conversational-analysis-authoring
[cla]: https://cla.microsoft.com
[coc_contact]: mailto:opencode@microsoft.com
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
Expand All @@ -362,4 +393,12 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
[conversationauthoring_client_class]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-ai-language-conversations/latest/azure.ai.language.conversations.html#azure.ai.language.conversations.ConversationAuthoringClient
[azure_core_exceptions]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/README.md
[azure_language_portal]: https://language.cognitive.azure.com/home
[cognitive_authentication_aad]: https://docs.microsoft.com/azure/cognitive-services/authentication#authenticate-with-azure-active-directory
[azure_identity_credentials]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#credentials
[custom_subdomain]: https://docs.microsoft.com/azure/cognitive-services/authentication#create-a-resource-with-a-custom-subdomain
[install_azure_identity]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#install-the-package
[register_aad_app]: https://docs.microsoft.com/azure/cognitive-services/authentication#assign-a-role-to-a-service-principal
[grant_role_access]: https://docs.microsoft.com/azure/cognitive-services/authentication#assign-a-role-to-a-service-principal
[default_azure_credential]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#defaultazurecredential

![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-python%2Fsdk%2Ftemplate%2Fazure-template%2FREADME.png)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from typing import Any, TYPE_CHECKING

from azure.core import PipelineClient
from azure.core.credentials import AzureKeyCredential
from azure.core.rest import HttpRequest, HttpResponse

from ._configuration import ConversationAnalysisClientConfiguration
Expand All @@ -21,6 +20,8 @@
# pylint: disable=unused-import,ungrouped-imports
from typing import Dict

from azure.core.credentials import TokenCredential


class ConversationAnalysisClient(
ConversationAnalysisClientOperationsMixin
Expand All @@ -40,13 +41,13 @@ class ConversationAnalysisClient(
https://:code:`<resource-name>`.cognitiveservices.azure.com). Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:type credential: ~azure.core.credentials.TokenCredential
heaths marked this conversation as resolved.
Show resolved Hide resolved
:keyword api_version: Api Version. Default value is "2022-05-01". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
def __init__(self, endpoint: str, credential: "TokenCredential", **kwargs: Any) -> None:
heaths marked this conversation as resolved.
Show resolved Hide resolved
_endpoint = "{Endpoint}/language"
self._config = ConversationAnalysisClientConfiguration(endpoint=endpoint, credential=credential, **kwargs)
self._client = PipelineClient(base_url=_endpoint, config=self._config, **kwargs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from typing import Any
from typing import Any, TYPE_CHECKING

from azure.core.configuration import Configuration
from azure.core.credentials import AzureKeyCredential
from azure.core.pipeline import policies

from ._version import VERSION

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from azure.core.credentials import TokenCredential


class ConversationAnalysisClientConfiguration(Configuration): # pylint: disable=too-many-instance-attributes
"""Configuration for ConversationAnalysisClient.
Expand All @@ -25,13 +28,13 @@ class ConversationAnalysisClientConfiguration(Configuration): # pylint: disable
https://:code:`<resource-name>`.cognitiveservices.azure.com). Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:type credential: ~azure.core.credentials.TokenCredential
:keyword api_version: Api Version. Default value is "2022-05-01". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
def __init__(self, endpoint: str, credential: "TokenCredential", **kwargs: Any) -> None:
super(ConversationAnalysisClientConfiguration, self).__init__(**kwargs)
api_version = kwargs.pop("api_version", "2022-05-01") # type: str

Expand All @@ -43,6 +46,7 @@ def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any)
self.endpoint = endpoint
self.credential = credential
self.api_version = api_version
self.credential_scopes = kwargs.pop("credential_scopes", ["https://cognitiveservices.azure.com/.default"])
kwargs.setdefault("sdk_moniker", "ai-language-conversations/{}".format(VERSION))
self._configure(**kwargs)

Expand All @@ -60,6 +64,6 @@ def _configure(
self.redirect_policy = kwargs.get("redirect_policy") or policies.RedirectPolicy(**kwargs)
self.authentication_policy = kwargs.get("authentication_policy")
if self.credential and not self.authentication_policy:
self.authentication_policy = policies.AzureKeyCredentialPolicy(
self.credential, "Ocp-Apim-Subscription-Key", **kwargs
self.authentication_policy = policies.BearerTokenCredentialPolicy(
self.credential, *self.credential_scopes, **kwargs
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

ConversationAnalysisClientOperationsMixinGenerated.analyze_conversation.__doc__ = \
"""Analyzes the input conversation utterance.


See https://docs.microsoft.com/rest/api/language/conversation-analysis-runtime/analyze-conversation
for more information.

:param task: A single conversational task to execute. Is either a model type or a IO type.
Required.
:type task: JSON or IO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,63 @@

# This file is used for handwritten extensions to the generated code. Example:
# https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/customize_code/how-to-patch-sdk-code.md

from typing import List, Union, Any
from azure.core.credentials import AzureKeyCredential, TokenCredential
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
from ._client import ConversationAnalysisClient as GeneratedConversationAnalysisClient


def _authentication_policy(credential):
authentication_policy = None
if credential is None:
raise ValueError("Parameter 'credential' must not be None.")
if isinstance(credential, AzureKeyCredential):
authentication_policy = AzureKeyCredentialPolicy(
name="Ocp-Apim-Subscription-Key", credential=credential
)
elif credential is not None and not hasattr(credential, "get_token"):
raise TypeError(
"Unsupported credential: {}. Use an instance of AzureKeyCredential "
"or a token credential from azure.identity".format(type(credential))
)
return authentication_policy


class ConversationAnalysisClient(GeneratedConversationAnalysisClient): # pylint: disable=client-accepts-api-version-keyword
"""The language service conversations API is a suite of natural language processing (NLP) skills
that can be used to analyze structured conversations (textual or spoken). The synchronous API
in this suite accepts a request and mediates among multiple language projects, such as LUIS
Generally Available, Question Answering, Conversational Language Understanding, and then calls
the best candidate service to handle the request. At last, it returns a response with the
candidate service's response as a payload.

See https://docs.microsoft.com/rest/api/language/conversation-analysis-runtime/ for more information about
requests and responses you can pass to this client.

:param endpoint: Supported Cognitive Services endpoint (e.g.,
https://:code:`<resource-name>`.cognitiveservices.azure.com). Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure.
This can be the an instance of AzureKeyCredential if using a Language API key
or a token credential from :mod:`azure.identity`.
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential
:keyword api_version: Api Version. Default value is "2022-05-01". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, TokenCredential], **kwargs: Any) -> None:
super().__init__(
endpoint=endpoint,
credential=credential,
authentication_policy=kwargs.pop("authentication_policy", _authentication_policy(credential)),
**kwargs
)


__all__: List[str] = ["ConversationAnalysisClient"]


def patch_sdk():
pass
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from typing import Any, Awaitable, TYPE_CHECKING

from azure.core import AsyncPipelineClient
from azure.core.credentials import AzureKeyCredential
from azure.core.rest import AsyncHttpResponse, HttpRequest

from .._serialization import Deserializer, Serializer
Expand All @@ -21,6 +20,8 @@
# pylint: disable=unused-import,ungrouped-imports
from typing import Dict

from azure.core.credentials_async import AsyncTokenCredential


class ConversationAnalysisClient(
ConversationAnalysisClientOperationsMixin
Expand All @@ -32,21 +33,21 @@ class ConversationAnalysisClient(
the best candidate service to handle the request. At last, it returns a response with the
candidate service's response as a payload.

In some cases, this API needs to forward requests and responses between the caller and an
In some cases, this API needs to forward requests and responses between the caller and an
upstream service. The asynchronous APIs in this suite enable tasks like Conversation
Summarization and Conversational PII detection.

:param endpoint: Supported Cognitive Services endpoint (e.g.,
https://:code:`<resource-name>`.cognitiveservices.azure.com). Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:type credential: ~azure.core.credentials_async.AsyncTokenCredential
:keyword api_version: Api Version. Default value is "2022-05-01". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
def __init__(self, endpoint: str, credential: "AsyncTokenCredential", **kwargs: Any) -> None:
_endpoint = "{Endpoint}/language"
self._config = ConversationAnalysisClientConfiguration(endpoint=endpoint, credential=credential, **kwargs)
self._client = AsyncPipelineClient(base_url=_endpoint, config=self._config, **kwargs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from typing import Any
from typing import Any, TYPE_CHECKING

from azure.core.configuration import Configuration
from azure.core.credentials import AzureKeyCredential
from azure.core.pipeline import policies

from .._version import VERSION

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from azure.core.credentials_async import AsyncTokenCredential


class ConversationAnalysisClientConfiguration(Configuration): # pylint: disable=too-many-instance-attributes
"""Configuration for ConversationAnalysisClient.
Expand All @@ -25,13 +28,13 @@ class ConversationAnalysisClientConfiguration(Configuration): # pylint: disable
https://:code:`<resource-name>`.cognitiveservices.azure.com). Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:type credential: ~azure.core.credentials_async.AsyncTokenCredential
:keyword api_version: Api Version. Default value is "2022-05-01". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
def __init__(self, endpoint: str, credential: "AsyncTokenCredential", **kwargs: Any) -> None:
kristapratico marked this conversation as resolved.
Show resolved Hide resolved
super(ConversationAnalysisClientConfiguration, self).__init__(**kwargs)
api_version = kwargs.pop("api_version", "2022-05-01") # type: str

Expand All @@ -43,6 +46,7 @@ def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any)
self.endpoint = endpoint
self.credential = credential
self.api_version = api_version
self.credential_scopes = kwargs.pop("credential_scopes", ["https://cognitiveservices.azure.com/.default"])
kwargs.setdefault("sdk_moniker", "ai-language-conversations/{}".format(VERSION))
self._configure(**kwargs)

Expand All @@ -57,6 +61,6 @@ def _configure(self, **kwargs: Any) -> None:
self.redirect_policy = kwargs.get("redirect_policy") or policies.AsyncRedirectPolicy(**kwargs)
self.authentication_policy = kwargs.get("authentication_policy")
if self.credential and not self.authentication_policy:
self.authentication_policy = policies.AzureKeyCredentialPolicy(
self.credential, "Ocp-Apim-Subscription-Key", **kwargs
self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy(
self.credential, *self.credential_scopes, **kwargs
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
ConversationAnalysisClientOperationsMixinGenerated.analyze_conversation.__doc__ = \
"""Analyzes the input conversation utterance.

See https://docs.microsoft.com/rest/api/language/conversation-analysis-runtime/analyze-conversation
for more information.

:param task: A single conversational task to execute. Is either a model type or a IO type.
Required.
:type task: JSON or IO
Expand Down
Loading