diff --git a/examples/Python/ChatApp/README.md b/examples/Python/ChatApp/README.md new file mode 100644 index 00000000..197d0192 --- /dev/null +++ b/examples/Python/ChatApp/README.md @@ -0,0 +1,69 @@ +# Azure App Configuration - Python ChatApp Sample + +This sample demonstrates using Azure App Configuration to configure Azure OpenAI settings for a chat application built with Python. + +## Features + +- Integrates with Azure OpenAI for chat completions +- Dynamically refreshes configuration from Azure App Configuration + +## Prerequisites + +- Python 3.8 or later +- An Azure subscription with access to: + - Azure App Configuration service + - Azure OpenAI service +- Required environment variables: + - `AZURE_APPCONFIGURATION_ENDPOINT`: Endpoint URL of your Azure App Configuration instance + - `AZURE_OPENAI_API_KEY`: API key for Azure OpenAI (optional if stored in Azure App Configuration) + +## Setup + +1. Clone the repository +1. Install the required packages: + + ```bash + pip install -r requirements.txt + ``` + +1. Configure your Azure App Configuration store with these settings: + + ```console + ChatApp:AzureOpenAI:Endpoint - Your Azure OpenAI endpoint URL + ChatApp:AzureOpenAI:DeploymentName - Your Azure OpenAI deployment name + ChatApp:AzureOpenAI:ApiVersion - API version for Azure OpenAI (e.g., "2023-05-15") + ChatApp:AzureOpenAI:ApiKey - Your Azure OpenAI API key (Optional only required when not using AAD, preferably as a Key Vault reference) + ChatApp:Model - An AI configuration entry containing the following settings: + - model - Model name (e.g., "gpt-35-turbo") + - max_tokens - Maximum tokens for completion (e.g., 1000) + - temperature - Temperature parameter (e.g., 0.7) + - top_p - Top p parameter (e.g., 0.95) + - messages - An array of messages with role and content for each message + ChatApp:Sentinel - A sentinel key to trigger configuration refreshes + ``` + +1. Set the required environment variables: + + ```bash + export AZURE_APPCONFIGURATION_ENDPOINT="https://your-appconfig.azconfig.io" + export AZURE_OPENAI_API_KEY="your-openai-api-key" # Optional if stored in Azure App Configuration + ``` + +## Running the Application + +Start the console application: + +```bash +python app.py +``` + +The application will: + +1. Display the initial configured messages from Azure App Configuration +2. Generate a response from the AI +3. Prompt you to enter your message (Just select enter to quit) +4. Maintain conversation history during the session + +## Configuration Refresh + +The application refreshes the configuration at the beginning of each conversation cycle, so any changes made to the base configuration in Azure App Configuration will be incorporated into the model parameters (temperature, max_tokens, etc.) while maintaining your ongoing conversation history. Updating the configuration in Azure App Configuration will automatically reflect in the application without requiring a restart, once the `ChatApp:Sentinel` key is updated. diff --git a/examples/Python/ChatApp/app.py b/examples/Python/ChatApp/app.py new file mode 100644 index 00000000..d6285698 --- /dev/null +++ b/examples/Python/ChatApp/app.py @@ -0,0 +1,123 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +""" +Azure OpenAI Chat Application using Azure App Configuration. +This script demonstrates how to create a chat application that uses Azure App Configuration +to manage settings and Azure OpenAI to power chat interactions. +""" + +import os +from azure.identity import DefaultAzureCredential, get_bearer_token_provider +from azure.appconfiguration.provider import load, SettingSelector, WatchKey +from openai import AzureOpenAI +from models import AzureOpenAIConfiguration, ChatCompletionConfiguration + +APP_CONFIG_ENDPOINT_KEY = "AZURE_APPCONFIGURATION_ENDPOINT" + + +# Initialize CREDENTIAL +CREDENTIAL = DefaultAzureCredential() + +APPCONFIG = None +CHAT_COMPLETION_CONFIG = None + + +def main(): + global APPCONFIG + app_config_endpoint = os.environ.get(APP_CONFIG_ENDPOINT_KEY) + if not app_config_endpoint: + raise ValueError(f"The environment variable '{APP_CONFIG_ENDPOINT_KEY}' is not set or is empty.") + + # Load configuration + APPCONFIG = load( + endpoint=app_config_endpoint, + selects=[SettingSelector(key_filter="ChatApp:*")], + credential=CREDENTIAL, + keyvault_credential=CREDENTIAL, + trim_prefixes=["ChatApp:"], + refresh_on=[WatchKey(key="ChatApp:Sentinel")], + on_refresh_success=configure_app, + ) + configure_app() + + azure_openai_config = AzureOpenAIConfiguration( + api_key=APPCONFIG.get("AzureOpenAI:ApiKey", ""), + endpoint=APPCONFIG.get("AzureOpenAI:Endpoint", ""), + deployment_name=APPCONFIG.get("AzureOpenAI:DeploymentName", ""), + api_version=APPCONFIG.get("AzureOpenAI:ApiVersion", ""), + ) + azure_client = create_azure_openai_client(azure_openai_config) + + chat_conversation = [] + + print("Chat started! What's on your mind?") + + while True: + # Refresh the configuration from Azure App Configuration + APPCONFIG.refresh() + + # Get user input + user_input = input("You: ") + + # Exit if user input is empty + if not user_input.strip(): + print("Exiting chat. Goodbye!") + break + + # Add user message to chat conversation + chat_conversation.append({"role": "user", "content": user_input}) + + chat_messages = list(CHAT_COMPLETION_CONFIG.messages) + chat_messages.extend(chat_conversation) + + # Get AI response and add it to chat conversation + response = azure_client.chat.completions.create( + model=azure_openai_config.deployment_name, + messages=chat_messages, + max_tokens=CHAT_COMPLETION_CONFIG.max_tokens, + temperature=CHAT_COMPLETION_CONFIG.temperature, + top_p=CHAT_COMPLETION_CONFIG.top_p, + ) + + ai_response = response.choices[0].message.content + chat_conversation .append({"role": "assistant", "content": ai_response}) + print(f"AI: {ai_response}") + + +def configure_app(): + """ + Configure the chat application with settings from Azure App Configuration. + """ + global CHAT_COMPLETION_CONFIG + # Configure chat completion with AI configuration + CHAT_COMPLETION_CONFIG = ChatCompletionConfiguration(**APPCONFIG["ChatCompletion"]) + + +def create_azure_openai_client(azure_openai_config: AzureOpenAIConfiguration) -> AzureOpenAI: + """ + Create an Azure OpenAI client using the configuration from Azure App Configuration. + """ + if azure_openai_config.api_key: + return AzureOpenAI( + azure_endpoint=azure_openai_config.endpoint, + api_key=azure_openai_config.api_key, + api_version=azure_openai_config.api_version, + azure_deployment=azure_openai_config.deployment_name, + ) + else: + return AzureOpenAI( + azure_endpoint=azure_openai_config.endpoint, + azure_ad_token_provider=get_bearer_token_provider( + CREDENTIAL, + "https://cognitiveservices.azure.com/.default", + ), + api_version=azure_openai_config.api_version, + azure_deployment=azure_openai_config.deployment_name, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/Python/ChatApp/models.py b/examples/Python/ChatApp/models.py new file mode 100644 index 00000000..df34175d --- /dev/null +++ b/examples/Python/ChatApp/models.py @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +""" +Model classes for Azure OpenAI Chat Application. +""" +from dataclasses import dataclass +from typing import List, Optional, Dict + + +@dataclass +class AzureOpenAIConfiguration: + """ + Represents the configuration for Azure OpenAI service. + """ + + api_key: str + endpoint: str + deployment_name: str + api_version: Optional[str] = None + + +@dataclass +class ChatCompletionConfiguration: + """ + Represents the configuration for an AI model including messages and parameters. + """ + + max_tokens: int + temperature: float + top_p: float + model: Optional[str] = None + messages: Optional[List[Dict[str, str]]] = None diff --git a/examples/Python/ChatApp/pyproject.toml b/examples/Python/ChatApp/pyproject.toml new file mode 100644 index 00000000..afe76d22 --- /dev/null +++ b/examples/Python/ChatApp/pyproject.toml @@ -0,0 +1,8 @@ +[tool.black] +line-length = 120 +target-version = ['py38'] +include = '\.pyi?$' + +[tool.pylint.format] +max-line-length = 120 + diff --git a/examples/Python/ChatApp/requirements.txt b/examples/Python/ChatApp/requirements.txt new file mode 100644 index 00000000..e131693e --- /dev/null +++ b/examples/Python/ChatApp/requirements.txt @@ -0,0 +1,3 @@ +azure-identity +azure-appconfiguration-provider +openai diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3d74b6cd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[tool.black] +line-length = 120 +target-version = ['py38'] +include = '\.pyi?$'