Skip to content

Commit c3151ea

Browse files
authored
Merge pull request #48 from microsoft/azure_auth_support
Azure auth support
2 parents 37afa62 + 6907d02 commit c3151ea

5 files changed

Lines changed: 112 additions & 30 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
113113
python3 clients/gpt.py # you can also change the problem to solve in the main() function
114114
```
115115

116+
Our repository comes with a variety of pre-integrated agents, including agents that enable **secure authentication with Azure OpenAI endpoints using identity-based access**. Please check out [Clients](/clients) for a comprehensive list of all implemented clients.
117+
116118
The clients will automatically load API keys from your .env file.
117119

118120
You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

clients/README.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ These clients are some baselines that we have implemented and evaluated to help
99
- [DeepSeek](/clients/deepseek.py): A naive DeepSeek series LLM agent with only shell access.
1010
- [Qwen](/clients/qwen.py): A naive Qwen series LLM agent with only shell access.
1111
- [vLLM](/clients/vllm.py): A naive vLLM agent with any open source LLM deployed locally and only shell access.
12+
- [GPT with Azure OpenAI](/clients/gpt_azure_identity.py): A naive GPT4-based LLM agent for Azure OpenAI, using identity-based authentication.
1213
- [ReAct](/clients/react.py): A naive LLM agent that uses the ReAct framework.
1314
- [FLASH](/clients/flash.py): A naive LLM agent that uses status supervision and hindsight integration components to ensure the high reliability of workflow execution.
1415
- [OpenRouter](/clients/openrouter.py): A naive OpenRouter LLM agent with only shell access.
@@ -83,21 +84,36 @@ cp .env.example .env
8384
- `OPENROUTER_MODEL`: OpenRouter model to use (default: `openai/gpt-4o-mini`)
8485
- `USE_WANDB`: Enable Weights & Biases logging (default: `false`)
8586
86-
<!--
87-
Note: The script [GPT-managed-identity](/clients/gpt_managed_identity.py) uses the `DefaultAzureCredential` method from the `azure-identity` package to authenticate. This method simplifies authentication by supporting various credential types, including managed identities.
87+
### Keyless Authentication
8888
89-
We recommend using a [user-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp) for this setup. Ensure the following steps are completed:
89+
The script [`gpt_azure_identity.py`](/clients/gpt_azure_identity.py) supports keyless authentication for **securely** accessing Azure OpenAI endpoints. It supports two authentication methods:
9090
91-
1. **Role Assignment**: Assign the managed identity appropriate roles:
92-
- A role that provides read access to the VM, such as the built-in **Reader** role.
93-
- A role that grants read/write access to the Azure OpenAI Service, such as the **Azure AI Developer** role.
91+
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest)
92+
- [User-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp)
9493
95-
2. **Attach the Managed Identity to the Controller VM**:
96-
Follow the steps in the official documentation to add the managed identity to the VM:
97-
[Add a user-assigned managed identity to a VM](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-configure-managed-identities?pivots=qs-configure-portal-windows-vm#user-assigned-managed-identity).
94+
#### 1. Azure CLI Authentication
9895
99-
Please ensure the required Azure configuration is provided using the /configs/example_azure_config.yml file, or use it as a template to create a new configuration file
96+
**Steps**
97+
- The user must have the appropriate role assigned (e.g., `Cognitive Services OpenAI User`) on the Azure OpenAI resource.
98+
- Run the following command to authenticate ([How to install the Azure CLI?](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)):
99+
100+
```bash
101+
az login --scope https://cognitiveservices.azure.com/.default
102+
```
103+
104+
#### 2. Managed Identity Authentication
105+
106+
- Follow the official documentation to assign a user-assigned managed identity to the VM where the client script would be run:
107+
[Add a user-assigned managed identity to a VM](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-configure-managed-identities?pivots=qs-configure-portal-windows-vm#user-assigned-managed-identity)
108+
- The managed identity must have the appropriate role assigned (e.g., `Cognitive Services OpenAI User`) on the Azure OpenAI resource.
109+
- Specify the managed identity to use by setting the following environment variable before running the script:
110+
111+
```bash
112+
export AZURE_CLIENT_ID=<client-id>
113+
```
114+
115+
Please ensure the required Azure configuration is provided using the /configs/example_azure_config.yml file, or use it as a template to create a new configuration file.
100116
101117
### Useful Links
102-
1. [How to configure Azure OpenAI Service with Microsoft Entra ID authentication](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/managed-identity)
103-
2. [Azure Identity client library for Python](https://learn.microsoft.com/en-us/python/api/overview/azure/identity-readme?view=azure-python#defaultazurecredential) -->
118+
1. [How to configure Azure OpenAI Service with Microsoft Entra ID authentication](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/managed-identity)
119+
2. [Azure Identity client library for Python](https://learn.microsoft.com/en-us/python/api/overview/azure/identity-readme?view=azure-python#defaultazurecredential)
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
subscription_id: <subscription_id>
2-
resource_group_name: <resource_group_name>
3-
workspace_name: <workspace_name>
41
azure_endpoint: <azure_endpoint>
52
api_version: <api_version>
Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@
99

1010
import sys
1111
import asyncio
12+
import os
1213

1314
from aiopslab.orchestrator import Orchestrator
1415
from clients.utils.llm import GPTClient
1516
from clients.utils.templates import DOCS_SHELL_ONLY
1617

1718

1819
class Agent:
19-
def __init__(self, azure_config_file: str):
20+
def __init__(self, auth_type: str, azure_config_file: str):
2021
self.history = []
21-
self.llm = GPTClient(auth_type="managed", azure_config_file=azure_config_file)
22+
self.llm = GPTClient(auth_type=auth_type, azure_config_file=azure_config_file)
2223

2324
def init_context(self, problem_desc: str, instructions: str, apis: str):
2425
"""Initialize the context for the agent."""
@@ -59,12 +60,24 @@ def _filter_dict(self, dictionary, filter_func):
5960

6061

6162
if __name__ == "__main__":
62-
if len(sys.argv) < 2:
63-
raise Exception(
64-
"Please provide a filename as argument. Usage: python gpt_managed_identity.py <azure_config_file>"
65-
)
66-
67-
agent = Agent(azure_config_file=sys.argv[1])
63+
#TODO: use argparse for better argument handling
64+
script_name = sys.argv[0]
65+
if len(sys.argv) < 3:
66+
print(f"Error: Missing required arguments.")
67+
print(f"\nUsage: python {script_name} <authentication_type> <azure_config_file>")
68+
print("\nArguments:")
69+
print(" <authentication_type> : Required. The method to use for authentication.")
70+
print(" Accepted values: 'cli', 'managed_identity'")
71+
print(" <azure_config_file> : Required. Path to the JSON configuration file for Azure OpenAI.")
72+
print("\nExamples:")
73+
print(f" python {script_name} cli ./configs/dev_config.json", file=sys.stderr)
74+
print(f" python {script_name} managed-identity ./configs/prod_config.json", file=sys.stderr)
75+
76+
# Exit with a non-zero status code to indicate an error
77+
sys.exit(1)
78+
auth_type = sys.argv[1]
79+
azure_config_file = sys.argv[2]
80+
agent = Agent(auth_type=auth_type, azure_config_file=azure_config_file)
6881

6982
orchestrator = Orchestrator()
7083
orchestrator.register_agent(agent, name="gpt-w-shell")

clients/utils/llm.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4-
"""An common abstraction for a cached LLM inference setup. Currently supports OpenAI's gpt-4-turbo and other models."""
4+
"""A common abstraction for a cached LLM inference setup. Currently supports OpenAI's gpt-4-turbo and other models."""
55

66

77
import os
8-
from openai import OpenAI
8+
import json
9+
import yaml
910
from groq import Groq
1011
from pathlib import Path
11-
import json
12+
from typing import Optional, List, Dict
13+
from dataclasses import dataclass
14+
15+
from groq import Groq
16+
from openai import OpenAI, AzureOpenAI
17+
from azure.identity import get_bearer_token_provider, AzureCliCredential, ManagedIdentityCredential
18+
1219
from dotenv import load_dotenv
1320

1421
# Load environment variables from the .env file
1522
load_dotenv()
23+
"""An common abstraction for a cached LLM inference setup. Currently supports OpenAI's gpt-4-turbo and other models."""
24+
1625

1726
CACHE_DIR = Path("./cache_dir")
1827
CACHE_PATH = CACHE_DIR / "cache.json"
28+
GPT_MODEL = "gpt-4o"
29+
30+
31+
@dataclass
32+
class AzureConfig:
33+
azure_endpoint: str
34+
api_version: str
1935

2036

2137
class Cache:
@@ -53,20 +69,58 @@ def save_cache(self):
5369
class GPTClient:
5470
"""Abstraction for OpenAI's GPT series model."""
5571

56-
def __init__(self):
72+
def __init__(self, auth_type: str = "key", api_key: Optional[str] = None, azure_config_file: Optional[str] = None, use_cache: bool = True):
5773
self.cache = Cache()
74+
self.client = self._setup_client(auth_type, api_key, azure_config_file)
75+
76+
def _load_azure_config(self, yaml_file_path: str) -> AzureConfig:
77+
with open(yaml_file_path, "r") as file:
78+
azure_config_data = yaml.safe_load(file)
79+
return AzureConfig(
80+
azure_endpoint=azure_config_data.get("azure_endpoint"),
81+
api_version=azure_config_data.get("api_version"),
82+
)
83+
84+
def _setup_client(self, auth_type: str, api_key: Optional[str], azure_config_file: Optional[str]):
85+
azure_identity_opts = ["cli", "managed_identity"]
86+
if auth_type == "key":
87+
# TODO: support Azure OpenAI client.
88+
api_key = api_key or os.getenv("OPENAI_API_KEY")
89+
if not api_key:
90+
raise ValueError("API key must be provided or set in OPENAI_API_KEY environment variable")
91+
return OpenAI(api_key=api_key)
92+
elif auth_type in azure_identity_opts:
93+
if not azure_config_file:
94+
raise ValueError("Azure configuration file must be provided for access via managed identity.\n Check AIOpsLab/clients/configs/example_azure_config.yml for an example.")
95+
azure_config = self._load_azure_config(azure_config_file)
96+
if auth_type == "cli":
97+
credential = AzureCliCredential()
98+
elif auth_type == "managed_identity":
99+
client_id = os.getenv("AZURE_CLIENT_ID")
100+
if client_id is None:
101+
raise ValueError("Managed identity selected but AZURE_CLIENT_ID is not set.")
102+
credential = ManagedIdentityCredential(client_id=client_id)
103+
token_provider = get_bearer_token_provider(
104+
credential, "https://cognitiveservices.azure.com/.default"
105+
)
106+
return AzureOpenAI(
107+
api_version=azure_config.api_version,
108+
azure_endpoint=azure_config.azure_endpoint,
109+
azure_ad_token_provider=token_provider
110+
)
111+
else:
112+
raise ValueError("auth_type must be one of 'key', 'cli', or 'managed_identity'")
58113

59114
def inference(self, payload: list[dict[str, str]]) -> list[str]:
60115
if self.cache is not None:
61116
cache_result = self.cache.get_from_cache(payload)
62117
if cache_result is not None:
63118
return cache_result
64119

65-
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
66120
try:
67-
response = client.chat.completions.create(
121+
response = self.client.chat.completions.create(
68122
messages=payload, # type: ignore
69-
model="gpt-4-turbo-2024-04-09",
123+
model=GPT_MODEL,
70124
max_tokens=1024,
71125
temperature=0.5,
72126
top_p=0.95,

0 commit comments

Comments
 (0)