Skip to content

Commit 3509d08

Browse files
authoredOct 31, 2024··
[PY] feat: Add managed identity auth support to AssistantsPlanner (#2153)
## Linked issues closes: #minor tracking: #1918 ## Details Add ability to authenticate `AssistantsPlanner` using Azure Managed Identity by providing a `azure_ad_token_bearer` field in the initializer options. #### Change details Updated samples - math bot - order bot ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes
1 parent afdd0aa commit 3509d08

File tree

8 files changed

+131
-47
lines changed

8 files changed

+131
-47
lines changed
 

‎python/packages/ai/teams/ai/planners/assistants_planner.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import json
1010
from dataclasses import dataclass
1111
from importlib.metadata import version
12-
from typing import Dict, Generic, List, Optional, TypeVar, Union
12+
from typing import Callable, Dict, Generic, List, Optional, TypeVar, Union
1313

1414
import openai
1515
from botbuilder.core import TurnContext
@@ -49,10 +49,6 @@ class AzureOpenAIAssistantsOptions:
4949
"""
5050
Options for configuring the AssistantsPlanner for AzureOpenAI.
5151
"""
52-
53-
api_key: str
54-
"The AzureOpenAI API key."
55-
5652
default_model: str
5753
"Default name of the Azure OpenAI deployment (model) to use."
5854

@@ -62,6 +58,14 @@ class AzureOpenAIAssistantsOptions:
6258
endpoint: str
6359
"Deployment endpoint to use."
6460

61+
api_key: Optional[str] = None
62+
"The AzureOpenAI API key."
63+
64+
azure_ad_token_provider: Optional[Callable[..., str]] = None
65+
"""Optional. A function that returns an access token for Microsoft Entra
66+
(formerly known as Azure Active Directory), which will be invoked in every request.
67+
"""
68+
6569
polling_interval: float = DEFAULT_POLLING_INTERVAL
6670
"Optional. Polling interval in seconds. Defaults to 1 second"
6771

@@ -140,6 +144,7 @@ def __init__(
140144
self._client = openai.AsyncAzureOpenAI(
141145
api_key=options.api_key,
142146
api_version=options.api_version,
147+
azure_ad_token_provider=options.azure_ad_token_provider,
143148
azure_endpoint=options.endpoint,
144149
organization=options.organization if options.organization else None,
145150
default_headers={"User-Agent": self.user_agent},
@@ -196,7 +201,8 @@ async def continue_task(self, context: TurnContext, state: TurnState) -> Plan:
196201

197202
@staticmethod
198203
async def create_assistant(
199-
api_key: str,
204+
api_key: Optional[str],
205+
azure_ad_token_provider: Optional[Callable[..., str]],
200206
api_version: Optional[str],
201207
organization: Optional[str],
202208
endpoint: Optional[str],
@@ -221,6 +227,7 @@ async def create_assistant(
221227
user_agent = f"teamsai-py/{version('teams-ai')}"
222228
client = openai.AsyncAzureOpenAI(
223229
api_key=api_key,
230+
azure_ad_token_provider=azure_ad_token_provider,
224231
api_version=api_version if api_version else "2024-02-15-preview",
225232
azure_endpoint=endpoint,
226233
organization=organization if organization else None,

‎python/packages/ai/tests/ai/planners/test_assistants_planner.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,12 @@ async def test_create_openai_assistant(self, mock_async_openai):
560560
params = beta.AssistantCreateParams(model="123")
561561

562562
assistant = await AssistantsPlanner.create_assistant(
563-
api_key="", api_version="", organization="", endpoint="", request=params
563+
api_key="",
564+
azure_ad_token_provider=None,
565+
api_version="",
566+
organization="",
567+
endpoint="",
568+
request=params
564569
)
565570

566571
self.assertTrue(mock_async_openai.called)
@@ -573,6 +578,24 @@ async def test_create_azure_openai_assistant(self, mock_async_azure_openai):
573578

574579
assistant = await AssistantsPlanner.create_assistant(
575580
api_key="",
581+
azure_ad_token_provider=None,
582+
api_version="",
583+
organization="",
584+
endpoint="this is my endpoint",
585+
request=params,
586+
)
587+
588+
self.assertTrue(mock_async_azure_openai.called)
589+
self.assertEqual(assistant.id, ASSISTANT_ID)
590+
self.assertEqual(assistant.model, ASSISTANT_MODEL)
591+
592+
@mock.patch("openai.AsyncAzureOpenAI", return_value=MockAsyncOpenAI())
593+
async def test_create_azure_openai_assistant_with_az_token_provider(self, mock_async_azure_openai):
594+
params = beta.AssistantCreateParams(model="123")
595+
596+
assistant = await AssistantsPlanner.create_assistant(
597+
api_key=None,
598+
azure_ad_token_provider=lambda: "test-token",
576599
api_version="",
577600
organization="",
578601
endpoint="this is my endpoint",

‎python/samples/06.assistants.a.mathBot/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ packages = [
1212
python = ">=3.8,<4.0"
1313
teams-ai = "^1.2.2"
1414
python-dotenv = "^1.0.1"
15+
azure-identity = "^1.19.0"
1516

1617
[tool.poetry.group.dev.dependencies]
1718
pytest = "^7.4.0"

‎python/samples/06.assistants.a.mathBot/src/bot.py

+45-20
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,40 @@
2222

2323
from config import Config
2424
from state import AppTurnState
25+
from azure.identity import get_bearer_token_provider, DefaultAzureCredential
2526

2627
config = Config()
2728

28-
if config.OPENAI_KEY is None and config.AZURE_OPENAI_KEY is None:
29+
if config.OPENAI_KEY is None and config.AZURE_OPENAI_ENDPOINT is None:
2930
raise RuntimeError(
30-
"Missing environment variables - please check that OPENAI_KEY or AZURE_OPENAI_KEY is set."
31+
"Missing environment variables - please check that OPENAI_KEY or AZURE_OPENAI_ENDPOINT is set."
3132
)
3233

3334
planner: AssistantsPlanner
3435

3536
# Create Assistant Planner
36-
if config.AZURE_OPENAI_KEY and config.AZURE_OPENAI_ENDPOINT:
37-
planner = AssistantsPlanner[AppTurnState](
38-
AzureOpenAIAssistantsOptions(
39-
api_key=config.AZURE_OPENAI_KEY,
40-
api_version="2024-02-15-preview",
41-
endpoint=config.AZURE_OPENAI_ENDPOINT,
42-
default_model="gpt-4",
43-
assistant_id=config.ASSISTANT_ID,
37+
if config.AZURE_OPENAI_ENDPOINT:
38+
if config.AZURE_OPENAI_KEY:
39+
planner = AssistantsPlanner[AppTurnState](
40+
AzureOpenAIAssistantsOptions(
41+
api_key=config.AZURE_OPENAI_KEY,
42+
api_version="2024-05-01-preview",
43+
endpoint=config.AZURE_OPENAI_ENDPOINT,
44+
default_model="gpt-4o",
45+
assistant_id=config.ASSISTANT_ID,
46+
)
47+
)
48+
else:
49+
# Managed Identity Auth
50+
planner = AssistantsPlanner[AppTurnState](
51+
AzureOpenAIAssistantsOptions(
52+
azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'),
53+
api_version="2024-05-01-preview",
54+
endpoint=config.AZURE_OPENAI_ENDPOINT,
55+
default_model="gpt-4o",
56+
assistant_id=config.ASSISTANT_ID,
57+
)
4458
)
45-
)
4659
else:
4760
planner = AssistantsPlanner[AppTurnState](
4861
OpenAIAssistantsOptions(api_key=config.OPENAI_KEY, assistant_id=config.ASSISTANT_ID)
@@ -69,22 +82,34 @@ async def setup_assistant(context: TurnContext, state: AppTurnState):
6982
name="Math Tutor",
7083
instructions="You are a personal math tutor. Write and run code to answer math questions.",
7184
tools=[CodeInterpreterToolParam(type="code_interpreter")],
72-
model="gpt-4",
85+
model="gpt-4o",
7386
)
7487

7588
assistant: Assistant
7689

77-
if config.AZURE_OPENAI_KEY and config.AZURE_OPENAI_ENDPOINT:
78-
assistant = await AssistantsPlanner.create_assistant(
79-
api_key=config.AZURE_OPENAI_KEY,
80-
api_version="",
81-
organization="",
82-
endpoint=config.AZURE_OPENAI_ENDPOINT,
83-
request=params,
84-
)
90+
if config.AZURE_OPENAI_ENDPOINT:
91+
if config.AZURE_OPENAI_KEY:
92+
assistant = await AssistantsPlanner.create_assistant(
93+
api_key=config.AZURE_OPENAI_KEY,
94+
azure_ad_token_provider=None,
95+
api_version="2024-05-01-preview",
96+
organization="",
97+
endpoint=config.AZURE_OPENAI_ENDPOINT,
98+
request=params,
99+
)
100+
else:
101+
assistant = await AssistantsPlanner.create_assistant(
102+
api_key=None,
103+
azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'),
104+
api_version="2024-05-01-preview",
105+
organization="",
106+
endpoint=config.AZURE_OPENAI_ENDPOINT,
107+
request=params,
108+
)
85109
else:
86110
assistant = await AssistantsPlanner.create_assistant(
87111
api_key=config.OPENAI_KEY,
112+
azure_ad_token_provider=None,
88113
api_version="",
89114
organization="",
90115
endpoint="",

‎python/samples/06.assistants.a.mathBot/teamsapp.yml

+1
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,4 @@ deploy:
9797
# You can replace it with your existing Azure Resource id
9898
# or add it to your environment variable file.
9999
resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}}
100+
projectId: c5366cf6-846e-4dfb-bf8c-4fe0afa40c1d

‎python/samples/06.assistants.b.orderBot/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ packages = [
1212
python = ">=3.8,<4.0"
1313
teams-ai = "^1.2.2"
1414
python-dotenv = "^1.0.1"
15+
azure-identity = "^1.19.0"
1516

1617
[tool.poetry.group.dev.dependencies]
1718
pytest = "^7.4.0"

‎python/samples/06.assistants.b.orderBot/src/bot.py

+45-20
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
AzureOpenAIAssistantsOptions,
2424
OpenAIAssistantsOptions,
2525
)
26+
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
2627

2728
from config import Config
2829
from food_order_card import generate_card_for_order
@@ -31,24 +32,36 @@
3132

3233
config = Config()
3334

34-
if config.OPENAI_KEY is None and config.AZURE_OPENAI_KEY is None:
35+
if config.OPENAI_KEY is None and config.AZURE_OPENAI_ENDPOINT is None:
3536
raise RuntimeError(
36-
"Missing environment variables - please check that OPENAI_KEY or AZURE_OPENAI_KEY is set."
37+
"Missing environment variables - please check that OPENAI_KEY or AZURE_OPENAI_ENDPOINT is set."
3738
)
3839

3940
planner: AssistantsPlanner
4041

4142
# Create Assistant Planner
42-
if config.AZURE_OPENAI_KEY and config.AZURE_OPENAI_ENDPOINT:
43-
planner = AssistantsPlanner[AppTurnState](
44-
AzureOpenAIAssistantsOptions(
45-
api_key=config.AZURE_OPENAI_KEY,
46-
api_version="2024-02-15-preview",
47-
endpoint=config.AZURE_OPENAI_ENDPOINT,
48-
default_model="gpt-4",
49-
assistant_id=config.ASSISTANT_ID,
43+
if config.AZURE_OPENAI_ENDPOINT:
44+
if config.AZURE_OPENAI_KEY:
45+
planner = AssistantsPlanner[AppTurnState](
46+
AzureOpenAIAssistantsOptions(
47+
api_key=config.AZURE_OPENAI_KEY,
48+
api_version="2024-05-01-preview",
49+
endpoint=config.AZURE_OPENAI_ENDPOINT,
50+
default_model="gpt-4o",
51+
assistant_id=config.ASSISTANT_ID,
52+
)
53+
)
54+
else:
55+
# Managed Identity Auth
56+
planner = AssistantsPlanner[AppTurnState](
57+
AzureOpenAIAssistantsOptions(
58+
azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'),
59+
api_version="2024-05-01-preview",
60+
endpoint=config.AZURE_OPENAI_ENDPOINT,
61+
default_model="gpt-4o",
62+
assistant_id=config.ASSISTANT_ID,
63+
)
5064
)
51-
)
5265
else:
5366
planner = AssistantsPlanner[AppTurnState](
5467
OpenAIAssistantsOptions(api_key=config.OPENAI_KEY, assistant_id=config.ASSISTANT_ID)
@@ -96,22 +109,34 @@ async def setup_assistant(context: TurnContext, state: AppTurnState):
96109
),
97110
)
98111
],
99-
model="gpt-4",
112+
model="gpt-4o",
100113
)
101114

102115
assistant: Assistant
103116

104-
if config.AZURE_OPENAI_KEY and config.AZURE_OPENAI_ENDPOINT:
105-
assistant = await AssistantsPlanner.create_assistant(
106-
api_key=config.AZURE_OPENAI_KEY,
107-
api_version="",
108-
organization="",
109-
endpoint=config.AZURE_OPENAI_ENDPOINT,
110-
request=params,
111-
)
117+
if config.AZURE_OPENAI_ENDPOINT:
118+
if config.AZURE_OPENAI_KEY:
119+
assistant = await AssistantsPlanner.create_assistant(
120+
api_key=config.AZURE_OPENAI_KEY,
121+
azure_ad_token_provider=None,
122+
api_version="2024-05-01-preview",
123+
organization="",
124+
endpoint=config.AZURE_OPENAI_ENDPOINT,
125+
request=params,
126+
)
127+
else:
128+
assistant = await AssistantsPlanner.create_assistant(
129+
api_key=None,
130+
azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'),
131+
api_version="2024-05-01-preview",
132+
organization="",
133+
endpoint=config.AZURE_OPENAI_ENDPOINT,
134+
request=params,
135+
)
112136
else:
113137
assistant = await AssistantsPlanner.create_assistant(
114138
api_key=config.OPENAI_KEY,
139+
azure_ad_token_provider=None,
115140
api_version="",
116141
organization="",
117142
endpoint="",

‎python/samples/06.assistants.b.orderBot/teamsapp.yml

+1
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,4 @@ deploy:
9797
# You can replace it with your existing Azure Resource id
9898
# or add it to your environment variable file.
9999
resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}}
100+
projectId: 821a55d5-585b-4805-8558-793d811b1215

0 commit comments

Comments
 (0)
Please sign in to comment.