Skip to content

Commit 5064f99

Browse files
committed
Updated to match dotnet
1 parent 334fb2c commit 5064f99

2 files changed

Lines changed: 182 additions & 108 deletions

File tree

examples/Python/ChatApp/app.py

Lines changed: 162 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,134 +4,188 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
"""
7-
Azure App Configuration Chat Application using Azure OpenAI.
8-
This module provides a simple chat application that uses configurations from Azure App Configuration
9-
and connects to Azure OpenAI services for chat completions.
7+
Azure OpenAI Chat Application using Azure App Configuration.
8+
This script demonstrates how to create a chat application that uses Azure App Configuration
9+
to manage settings and Azure OpenAI to power chat interactions.
1010
"""
11+
1112
import os
12-
from typing import TypeVar, List
13+
from typing import List, Dict, Any
14+
from azure.core.credentials import TokenCredential
1315
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
14-
from azure.appconfiguration.provider import load, SettingSelector
16+
from azure.appconfiguration.provider import load, SettingSelector, WatchKey
1517
from openai import AzureOpenAI
16-
from models import ModelConfiguration, Message
18+
from models import AzureOpenAIConfiguration, Message, ModelConfiguration
1719

18-
# Get Azure App Configuration endpoint from environment variable
19-
ENDPOINT = os.environ.get("AZURE_APPCONFIG_ENDPOINT")
20-
if not ENDPOINT:
21-
raise ValueError(
22-
"The environment variable 'AZURE_APPCONFIG_ENDPOINT' is not set or is empty."
23-
)
2420

25-
# Initialize Azure credentials
26-
credential = DefaultAzureCredential()
27-
28-
# Create selector for ChatApp configuration
29-
chat_app_selector = SettingSelector(key_filter="ChatApp:*")
30-
31-
# Load configuration from Azure App Configuration
32-
config = load(
33-
endpoint=ENDPOINT,
34-
selects=[chat_app_selector],
35-
credential=credential,
36-
keyvault_credential=credential, # Use the same credential for Key Vault references
37-
trim_prefixes=["ChatApp:"],
38-
)
39-
40-
# Get OpenAI configuration
41-
def get_openai_client():
42-
"""Create and return an Azure OpenAI client"""
43-
endpoint = config.get("AzureOpenAI:Endpoint")
44-
# Get API key from App Configuration or fall back to environment variable
45-
api_key = config.get("AzureOpenAI:ApiKey", os.environ.get("AZURE_OPENAI_API_KEY"))
46-
api_version = config.get(
47-
"AzureOpenAI:ApiVersion", "2023-05-15"
48-
) # Read API version from config or use default
49-
50-
# For DefaultAzureCredential auth if no API key is available
51-
if not api_key:
52-
token_provider = get_bearer_token_provider(
53-
DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
21+
def main():
22+
# Create a credential using DefaultAzureCredential
23+
credential = DefaultAzureCredential()
24+
refresher = None
25+
26+
# Get the App Configuration endpoint from environment variables
27+
app_config_endpoint = os.environ.get("AZURE_APPCONFIGURATION_ENDPOINT")
28+
if not app_config_endpoint:
29+
raise ValueError(
30+
"The environment variable 'AZURE_APPCONFIGURATION_ENDPOINT' is not set or is empty."
5431
)
55-
return AzureOpenAI(
56-
azure_endpoint=endpoint,
57-
api_version=api_version,
58-
azure_ad_token_provider=token_provider,
32+
33+
try:
34+
azure_openai_config = None
35+
azure_client = None
36+
37+
def on_refresh_success():
38+
azure_openai_config, azure_client = _create_ai_client(provider)
39+
40+
chat_app_selector = SettingSelector(key_filter="ChatApp:*")
41+
42+
global provider
43+
# Create the configuration provider with refresh settings
44+
provider = load(
45+
endpoint=app_config_endpoint,
46+
selects=[chat_app_selector],
47+
credential=credential,
48+
keyvault_credential=credential, # Use the same credential for Key Vault references
49+
trim_prefixes=["ChatApp:"],
50+
refresh_on=[WatchKey(key="ChatApp:ChatCompletion:Messages")],
51+
on_refresh_success=on_refresh_success,
5952
)
6053

61-
# For API key auth
62-
return AzureOpenAI(
63-
azure_endpoint=endpoint, api_key=api_key, api_version=api_version
54+
azure_openai_config, azure_client = _create_ai_client(provider, credential)
55+
56+
# Initialize chat conversation
57+
chat_conversation = []
58+
print("Chat started! What's on your mind?")
59+
60+
while True:
61+
# Refresh the configuration from Azure App Configuration
62+
provider.refresh()
63+
64+
# Configure chat completion with AI configuration
65+
chat_completion_config = _extract_chat_completion_config(provider)
66+
67+
# Get user input
68+
user_input = input("You: ")
69+
70+
# Exit if user input is empty
71+
if not user_input.strip():
72+
print("Exiting chat. Goodbye!")
73+
break
74+
75+
# Add user message to chat conversation
76+
chat_conversation.append({"role": "user", "content": user_input})
77+
78+
# Get latest system message from AI configuration
79+
chat_messages = _get_chat_messages(chat_completion_config)
80+
chat_messages.extend(chat_conversation)
81+
82+
# Get AI response and add it to chat conversation
83+
response = azure_client.chat.completions.create(
84+
model=azure_openai_config.deployment_name,
85+
messages=chat_messages,
86+
max_tokens=chat_completion_config.max_tokens,
87+
temperature=chat_completion_config.temperature,
88+
top_p=chat_completion_config.top_p,
89+
)
90+
91+
ai_response = response.choices[0].message.content
92+
print(f"AI: {ai_response}")
93+
94+
except Exception as e:
95+
print(f"Error: {e}")
96+
finally:
97+
# Stop the refresher when done
98+
if refresher:
99+
refresher.stop()
100+
101+
102+
def _create_ai_client(
103+
azure_openai_config: AzureOpenAIConfiguration, credential: TokenCredential = None
104+
) -> AzureOpenAI:
105+
# Extract Azure OpenAI configuration
106+
azure_openai_config = _extract_openai_config(provider)
107+
# Create an Azure OpenAI client
108+
if azure_openai_config.api_key:
109+
return azure_openai_config, AzureOpenAI(
110+
azure_endpoint=azure_openai_config.endpoint,
111+
api_key=azure_openai_config.api_key,
112+
api_version=azure_openai_config.api_version,
113+
)
114+
token_provider = get_bearer_token_provider(
115+
credential or DefaultAzureCredential(),
116+
"https://cognitiveservices.azure.com/.default",
117+
)
118+
return azure_openai_config, AzureOpenAI(
119+
azure_endpoint=azure_openai_config.endpoint,
120+
azure_ad_token_provider=token_provider,
121+
api_version=azure_openai_config.api_version,
64122
)
65123

66124

67-
def get_chat_messages(messages: List[Message]):
68-
"""Convert from model Message objects to OpenAI messages format"""
69-
return [{"role": msg.role, "content": msg.content} for msg in messages]
125+
def _extract_openai_config(config_data: Dict[str, Any]) -> AzureOpenAIConfiguration:
126+
"""
127+
Extract Azure OpenAI configuration from the configuration data.
70128
129+
:param config_data: The configuration data from Azure App Configuration
130+
:return: An AzureOpenAIConfiguration object
131+
"""
132+
prefix = "AzureOpenAI:"
133+
return AzureOpenAIConfiguration(
134+
api_key=config_data.get(f"{prefix}ApiKey", ""),
135+
endpoint=config_data.get(f"{prefix}Endpoint", ""),
136+
deployment_name=config_data.get(f"{prefix}DeploymentName", ""),
137+
api_version=config_data.get(f"{prefix}ApiVersion", "2023-05-15"),
138+
)
71139

72-
def main():
73-
"""Main entry point for the console app"""
74-
# Get OpenAI client
75-
client = get_openai_client()
76-
77-
# Get deployment name
78-
deployment_name = config.get("AzureOpenAI:DeploymentName")
79-
80-
# Initialize conversation history with the configuration messages
81-
conversation_history = []
82-
first_run = True
83-
84-
print("Chat Application - type 'exit' to quit\n")
85-
86-
while True:
87-
# Refresh configuration from Azure App Configuration
88-
config.refresh()
89-
90-
# Get model configuration using data binding
91-
model_config = ModelConfiguration.from_dict(config.get("Model"))
92-
93-
# On first run, initialize conversation history with configuration messages
94-
# and display the initial messages
95-
if first_run:
96-
conversation_history = model_config.messages.copy()
97-
for msg in conversation_history:
98-
print(f"{msg.role}: {msg.content}")
99-
first_run = False
100-
101-
# Get chat messages for the API
102-
messages = get_chat_messages(conversation_history)
103-
104-
# Get response from OpenAI
105-
response = client.chat.completions.create(
106-
model=deployment_name,
107-
messages=messages,
108-
max_tokens=model_config.max_tokens,
109-
temperature=model_config.temperature,
110-
top_p=model_config.top_p,
111-
)
112140

113-
# Extract assistant message
114-
assistant_message = response.choices[0].message.content
141+
def _extract_chat_completion_config(config_data: Dict[str, Any]) -> ModelConfiguration:
142+
"""
143+
Extract chat completion configuration from the configuration data.
115144
116-
# Display the response
117-
print(f"assistant: {assistant_message}")
145+
:param config_data: The configuration data from Azure App Configuration
146+
"""
147+
prefix = "ChatApp:ChatCompletion:"
118148

119-
# Add assistant response to conversation history
120-
conversation_history.append(
121-
Message(role="assistant", content=assistant_message)
122-
)
149+
# Extract messages from configuration
150+
messages_data = []
151+
for i in range(10): # Assuming a reasonable maximum of 10 messages
152+
role_key = f"{prefix}Messages:{i}:Role"
153+
content_key = f"{prefix}Messages:{i}:Content"
154+
155+
if role_key in config_data and content_key in config_data:
156+
messages_data.append(
157+
{"role": config_data[role_key], "content": config_data[content_key]}
158+
)
159+
160+
messages = [Message.from_dict(msg) for msg in messages_data]
161+
162+
return ModelConfiguration(
163+
model=config_data.get(f"{prefix}Model", ""),
164+
messages=messages,
165+
max_tokens=config_data.get(f"{prefix}MaxTokens", 1024),
166+
temperature=config_data.get(f"{prefix}Temperature", 0.7),
167+
top_p=config_data.get(f"{prefix}TopP", 0.95),
168+
)
169+
170+
171+
def _get_chat_messages(
172+
chat_completion_config: ModelConfiguration,
173+
) -> List[Dict[str, str]]:
174+
"""
175+
Convert configuration messages to chat message dictionaries.
123176
124-
# Get user input for the next message
125-
print("\nuser: ", end="")
126-
user_input = input().strip()
177+
:param chat_completion_config: The chat completion configuration
178+
:return: A list of chat message dictionaries
179+
"""
180+
chat_messages = []
127181

128-
# Check if user wants to exit
129-
if user_input.lower() == "exit":
130-
print("Exiting application...")
131-
break
182+
for message in chat_completion_config.messages:
183+
if message.role in ["system", "user", "assistant"]:
184+
chat_messages.append({"role": message.role, "content": message.content})
185+
else:
186+
raise ValueError(f"Unknown role: {message.role}")
132187

133-
# Add user input to conversation history
134-
conversation_history.append(Message(role="user", content=user_input))
188+
return chat_messages
135189

136190

137191
if __name__ == "__main__":

examples/Python/ChatApp/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@
1111
from typing import List, Optional, Dict, Any
1212

1313

14+
class AzureOpenAIConfiguration:
15+
"""
16+
Represents the configuration for Azure OpenAI service.
17+
Maps to configuration values with keys 'api_key', 'endpoint', and 'deployment_name'.
18+
"""
19+
20+
def __init__(
21+
self,
22+
api_key: str,
23+
endpoint: str,
24+
deployment_name: Optional[str] = None,
25+
api_version: Optional[str] = None,
26+
):
27+
"""Initialize Azure OpenAI configuration with API key and endpoint."""
28+
self.api_key = api_key
29+
self.endpoint = endpoint
30+
self.deployment_name = deployment_name
31+
self.api_version = api_version
32+
33+
1434
class Message:
1535
"""
1636
Represents a chat message with a role and content.

0 commit comments

Comments
 (0)