From a00203c4eceb89a912be04c148c02f7b04383cd2 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Mon, 14 Jul 2025 16:57:14 +0800 Subject: [PATCH 1/6] add Go chat app sample --- examples/Go/ChatApp/README.md | 91 ++++++++++++++ examples/Go/ChatApp/main.go | 223 ++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 examples/Go/ChatApp/README.md create mode 100644 examples/Go/ChatApp/main.go diff --git a/examples/Go/ChatApp/README.md b/examples/Go/ChatApp/README.md new file mode 100644 index 00000000..363937b3 --- /dev/null +++ b/examples/Go/ChatApp/README.md @@ -0,0 +1,91 @@ +# Azure App Configuration - Go ChatApp Sample + +An interactive console chat application that integrates with Azure OpenAI services using Azure App Configuration for dynamic AI Configuration management. + +## Overview + +This Go console application provides a seamless chat experience with Azure OpenAI, featuring: + +- Integration with Azure OpenAI for chat completions +- Dynamic AI configuration refresh from Azure App Configuration +- Secure authentication options using API key or Microsoft Entra ID + +## Prerequisites + +- Go 1.23 or later +- Azure subscription +- Azure OpenAI service instance +- Azure App Configuration service instance + +## Setup + +### Environment Variables + +Set the following environment variable: + +- `AZURE_APPCONFIGURATION_ENDPOINT`: Endpoint URL of your Azure App Configuration instance + +### Azure App Configuration Keys + +Configure the following keys in your Azure App Configuration: + +#### Azure OpenAI Connection Settings + +- `ChatApp:AzureOpenAI:Endpoint` - Your Azure OpenAI endpoint URL +- `ChatApp:AzureOpenAI:APIVersion` - the Azure OpenAI API version to target. See [Azure OpenAI apiversions](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning) for current API versions. +- `ChatApp:AzureOpenAI:ApiKey` - Key Vault reference to the API key for Azure OpenAI (optional) + +#### Chat Completion Configuration + +- `ChatApp:ChatCompletion` - An AI Configuration for chat completion containing the following settings: + - `model` - Model name (e.g., "gpt-4o") + - `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 + +## Authentication + +The application supports the following authentication methods: + +- **Azure App Configuration**: Uses `DefaultAzureCredential` for authentication via Microsoft Entra ID. +- **Azure OpenAI**: Supports authentication using either an API key or `DefaultAzureCredential` via Microsoft Entra ID. +- **Azure Key Vault** *(optional, if using Key Vault references for API keys)*: Authenticates using `DefaultAzureCredential` via Microsoft Entra ID. + +## Usage + +1. **Install dependencies**: `go mod tidy` +2. **Start the Application**: Run the application using `go run main.go` +3. **Begin Chatting**: Type your messages when prompted with "You: " +4. **Continue Conversation**: The AI will respond and maintain conversation context +5. **Exit**: Press Enter without typing a message to exit gracefully + +### Example Session +``` +Chat started! What's on your mind? +You: Hello, how are you? +AI: Hello! I'm doing well, thank you for asking. How can I help you today? + +You: What can you tell me about machine learning? +AI: Machine learning is a subset of artificial intelligence that focuses on... + +You: exit +Exiting chat. Goodbye! +``` + +## Troubleshooting + +**"AZURE_APPCONFIGURATION_ENDPOINT environment variable not set"** +- Ensure the environment variable is properly set +- Verify the endpoint URL is correct + +**Authentication Failures** +- Ensure you have the `App Configuration Data Reader` role on the Azure App Configuration instance +- For Microsoft Entra ID authentication: Verify you have the `Cognitive Services OpenAI User` role on the Azure OpenAI instance +- For API key authentication: + - Confirm you have secret read access to the Key Vault storing the API key + - Verify that a Key Vault reference for the API key is properly configured in Azure App Configuration + +**No AI Response** +- Verify deployment name matches your Azure OpenAI deployment +- Check token limits and quotas \ No newline at end of file diff --git a/examples/Go/ChatApp/main.go b/examples/Go/ChatApp/main.go new file mode 100644 index 00000000..1c4e85ee --- /dev/null +++ b/examples/Go/ChatApp/main.go @@ -0,0 +1,223 @@ +package main + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + openai "github.com/openai/openai-go" + "github.com/openai/openai-go/azure" +) + +type ChatApp struct { + configProvider *azureappconfiguration.AzureAppConfiguration + openAIClient openai.Client + aiConfig AIConfig +} + +type AIConfig struct { + ChatCompletion ChatCompletion + AzureOpenAI AzureOpenAI +} + +type ChatCompletion struct { + Model string `json:"model"` + Messages []Message `json:"messages"` + MaxTokens int64 `json:"max_tokens"` + Temperature float64 `json:"temperature"` + TopP float64 `json:"top_p"` +} + +type AzureOpenAI struct { + Endpoint string + APIVersion string + APIKey string +} + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) { + endpoint := os.Getenv("AZURE_APPCONFIGURATION_ENDPOINT") + if endpoint == "" { + return nil, fmt.Errorf("AZURE_APPCONFIGURATION_ENDPOINT environment variable is not set") + } + + credential, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, fmt.Errorf("failed to create Azure credential: %w", err) + } + + authOptions := azureappconfiguration.AuthenticationOptions{ + Endpoint: endpoint, + Credential: credential, + } + + options := &azureappconfiguration.Options{ + Selectors: []azureappconfiguration.Selector{ + // Load all keys that start with "ChatApp:" and have no label + { + KeyFilter: "ChatApp:*", + }, + }, + TrimKeyPrefixes: []string{"ChatApp:"}, + RefreshOptions: azureappconfiguration.KeyValueRefreshOptions{ + Enabled: true, + Interval: 10 * time.Second, + }, + KeyVaultOptions: azureappconfiguration.KeyVaultOptions{ + Credential: credential, + }, + } + + appConfig, err := azureappconfiguration.Load(ctx, authOptions, options) + if err != nil { + return nil, fmt.Errorf("failed to load configuration: %w", err) + } + + return appConfig, nil +} + +// Create an Azure OpenAI client using API key if available, otherwise use the DefaultAzureCredential +func (app *ChatApp) createAzureOpenAIClient() error { + if app.aiConfig.AzureOpenAI.APIKey != "" { + // Use API key for authentication + client := openai.NewClient( + azure.WithAPIKey(app.aiConfig.AzureOpenAI.APIKey), + azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion), + ) + app.openAIClient = client + return nil + } + + // Use DefaultAzureCredential for authentication + tokenCredential, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return fmt.Errorf("failed to create Azure credential: %w", err) + } + + client := openai.NewClient( + azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion), + azure.WithTokenCredential(tokenCredential), + ) + + app.openAIClient = client + return nil +} + +func (app *ChatApp) callAzureOpenAI(userMessage string) (string, error) { + messages := []openai.ChatCompletionMessageParamUnion{} + for _, msg := range app.aiConfig.ChatCompletion.Messages { + switch msg.Role { + case "system": + messages = append(messages, openai.SystemMessage(msg.Content)) + case "user": + messages = append(messages, openai.UserMessage(msg.Content)) + case "assistant": + messages = append(messages, openai.AssistantMessage(msg.Content)) + } + } + + // Add the user's input message + messages = append(messages, openai.UserMessage(userMessage)) + + // Create chat completion parameters + params := openai.ChatCompletionNewParams{ + Messages: messages, + Model: app.aiConfig.ChatCompletion.Model, + MaxTokens: openai.Int(app.aiConfig.ChatCompletion.MaxTokens), + Temperature: openai.Float(app.aiConfig.ChatCompletion.Temperature), + TopP: openai.Float(app.aiConfig.ChatCompletion.TopP), + } + + ctx := context.Background() + completion, err := app.openAIClient.Chat.Completions.New(ctx, params) + if err != nil { + return "", fmt.Errorf("failed to get chat completion: %w", err) + } + + if len(completion.Choices) == 0 { + return "", fmt.Errorf("no choices in response") + } + + return completion.Choices[0].Message.Content, nil +} + +func (app *ChatApp) runInteractiveChat() { + fmt.Println("Chat started! What's on your mind?") + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Print("You: ") + userInput, err := reader.ReadString('\n') + if err != nil { + log.Printf("Error reading input: %v", err) + continue + } + + userInput = strings.TrimSpace(userInput) + if userInput == "" { + fmt.Println("Exiting Chat. Goodbye!") + break + } + + // Refresh configuration + ctx := context.Background() + if err := app.configProvider.Refresh(ctx); err != nil { + log.Printf("Error refreshing configuration: %v", err) + } + + // Get AI response + fmt.Print("AI: ") + response, err := app.callAzureOpenAI(userInput) + if err != nil { + log.Printf("Error calling OpenAI: %v", err) + fmt.Println("Sorry, I encountered an error. Please try again.") + continue + } + + fmt.Println(response) + fmt.Println() + } +} + +func main() { + ctx := context.Background() + configProvider, err := loadAzureAppConfiguration(ctx) + if err != nil { + log.Fatal("Error loading Azure App Configuration:", err) + return + } + + // Load AI configuration from Azure App Configuration + var aiConfig AIConfig + if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { + log.Fatal("Error loading AI configuration:", err) + return + } + + // Register a callback to refresh AI configuration on changes + configProvider.OnRefreshSuccess(func() { + configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}) + }) + + app := &ChatApp{ + configProvider: configProvider, + aiConfig: aiConfig, + } + + // Initialize Azure OpenAI client + if err := app.createAzureOpenAIClient(); err != nil { + log.Fatalf("Failed to create Azure OpenAI client: %v", err) + } + + app.runInteractiveChat() +} From f066f1b70e8f0a4690615c12ba76c3c616ba64ee Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Mon, 14 Jul 2025 17:10:09 +0800 Subject: [PATCH 2/6] update --- examples/Go/ChatApp/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/Go/ChatApp/main.go b/examples/Go/ChatApp/main.go index 1c4e85ee..4817635b 100644 --- a/examples/Go/ChatApp/main.go +++ b/examples/Go/ChatApp/main.go @@ -145,7 +145,7 @@ func (app *ChatApp) callAzureOpenAI(userMessage string) (string, error) { } if len(completion.Choices) == 0 { - return "", fmt.Errorf("no choices in response") + return "", fmt.Errorf("chat completion returned zero choices") } return completion.Choices[0].Message.Content, nil @@ -201,14 +201,15 @@ func main() { var aiConfig AIConfig if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { log.Fatal("Error loading AI configuration:", err) - return } // Register a callback to refresh AI configuration on changes configProvider.OnRefreshSuccess(func() { - configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}) + if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { + log.Printf("Error refreshing AI configuration: %v", err) + } }) - + app := &ChatApp{ configProvider: configProvider, aiConfig: aiConfig, From 2ba30f6976c26fedf3fd986ae1dc6ca37eb0a757 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Tue, 22 Jul 2025 16:34:53 +0800 Subject: [PATCH 3/6] update --- examples/Go/ChatApp/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/Go/ChatApp/README.md b/examples/Go/ChatApp/README.md index 363937b3..01ea6a4a 100644 --- a/examples/Go/ChatApp/README.md +++ b/examples/Go/ChatApp/README.md @@ -54,11 +54,12 @@ The application supports the following authentication methods: ## Usage +1. **Initialize a new Go module**: `go mod init chatapp-quickstart` 1. **Install dependencies**: `go mod tidy` -2. **Start the Application**: Run the application using `go run main.go` -3. **Begin Chatting**: Type your messages when prompted with "You: " -4. **Continue Conversation**: The AI will respond and maintain conversation context -5. **Exit**: Press Enter without typing a message to exit gracefully +1. **Start the Application**: Run the application using `go run main.go` +1. **Begin Chatting**: Type your messages when prompted with "You: " +1. **Continue Conversation**: The AI will respond and maintain conversation context +1. **Exit**: Press Enter without typing a message to exit gracefully ### Example Session ``` @@ -69,7 +70,7 @@ AI: Hello! I'm doing well, thank you for asking. How can I help you today? You: What can you tell me about machine learning? AI: Machine learning is a subset of artificial intelligence that focuses on... -You: exit +You: [Press Enter to exit] Exiting chat. Goodbye! ``` From 1f9601d3b58df8e10c65228037df4e3b81ea1a8f Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Wed, 10 Sep 2025 13:49:11 +0800 Subject: [PATCH 4/6] update --- examples/Go/ChatApp/main.go | 64 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/examples/Go/ChatApp/main.go b/examples/Go/ChatApp/main.go index 4817635b..c80ae36c 100644 --- a/examples/Go/ChatApp/main.go +++ b/examples/Go/ChatApp/main.go @@ -16,9 +16,9 @@ import ( ) type ChatApp struct { - configProvider *azureappconfiguration.AzureAppConfiguration - openAIClient openai.Client - aiConfig AIConfig + configProvider *azureappconfiguration.AzureAppConfiguration + openAIClient openai.Client + aiConfig AIConfig } type AIConfig struct { @@ -37,7 +37,7 @@ type ChatCompletion struct { type AzureOpenAI struct { Endpoint string APIVersion string - APIKey string + APIKey string } type Message struct { @@ -57,7 +57,7 @@ func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.Azur } authOptions := azureappconfiguration.AuthenticationOptions{ - Endpoint: endpoint, + Endpoint: endpoint, Credential: credential, } @@ -65,7 +65,7 @@ func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.Azur Selectors: []azureappconfiguration.Selector{ // Load all keys that start with "ChatApp:" and have no label { - KeyFilter: "ChatApp:*", + KeyFilter: "ChatApp:*", }, }, TrimKeyPrefixes: []string{"ChatApp:"}, @@ -113,25 +113,26 @@ func (app *ChatApp) createAzureOpenAIClient() error { return nil } -func (app *ChatApp) callAzureOpenAI(userMessage string) (string, error) { - messages := []openai.ChatCompletionMessageParamUnion{} +func (app *ChatApp) callAzureOpenAI(chatConversation []openai.ChatCompletionMessageParamUnion) (string, error) { + var completionMessages []openai.ChatCompletionMessageParamUnion + for _, msg := range app.aiConfig.ChatCompletion.Messages { switch msg.Role { case "system": - messages = append(messages, openai.SystemMessage(msg.Content)) + completionMessages = append(completionMessages, openai.SystemMessage(msg.Content)) case "user": - messages = append(messages, openai.UserMessage(msg.Content)) + completionMessages = append(completionMessages, openai.UserMessage(msg.Content)) case "assistant": - messages = append(messages, openai.AssistantMessage(msg.Content)) + completionMessages = append(completionMessages, openai.AssistantMessage(msg.Content)) } } - // Add the user's input message - messages = append(messages, openai.UserMessage(userMessage)) + // Add the chat conversation history + completionMessages = append(completionMessages, chatConversation...) // Create chat completion parameters params := openai.ChatCompletionNewParams{ - Messages: messages, + Messages: completionMessages, Model: app.aiConfig.ChatCompletion.Model, MaxTokens: openai.Int(app.aiConfig.ChatCompletion.MaxTokens), Temperature: openai.Float(app.aiConfig.ChatCompletion.Temperature), @@ -152,10 +153,19 @@ func (app *ChatApp) callAzureOpenAI(userMessage string) (string, error) { } func (app *ChatApp) runInteractiveChat() { - fmt.Println("Chat started! What's on your mind?") + // Initialize chat conversation + var chatConversation []openai.ChatCompletionMessageParamUnion + fmt.Println("Chat started! What's on your mind?") reader := bufio.NewReader(os.Stdin) for { + // Refresh the configuration from Azure App Configuration + ctx := context.Background() + if err := app.configProvider.Refresh(ctx); err != nil { + log.Printf("Error refreshing configuration: %v", err) + } + + // Get user input fmt.Print("You: ") userInput, err := reader.ReadString('\n') if err != nil { @@ -169,22 +179,20 @@ func (app *ChatApp) runInteractiveChat() { break } - // Refresh configuration - ctx := context.Background() - if err := app.configProvider.Refresh(ctx); err != nil { - log.Printf("Error refreshing configuration: %v", err) - } + // Add user message to chat conversation + chatConversation = append(chatConversation, openai.UserMessage(userInput)) - // Get AI response - fmt.Print("AI: ") - response, err := app.callAzureOpenAI(userInput) + // Get AI response and add it to chat conversation + response, err := app.callAzureOpenAI(chatConversation) if err != nil { log.Printf("Error calling OpenAI: %v", err) fmt.Println("Sorry, I encountered an error. Please try again.") continue } - fmt.Println(response) + fmt.Printf("AI: %s\n", response) + chatConversation = append(chatConversation, openai.AssistantMessage(response)) + fmt.Println() } } @@ -196,11 +204,11 @@ func main() { log.Fatal("Error loading Azure App Configuration:", err) return } - - // Load AI configuration from Azure App Configuration + + // Configure chat completion with AI configuration var aiConfig AIConfig if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { - log.Fatal("Error loading AI configuration:", err) + log.Fatal("Error unmarshaling AI configuration", err) } // Register a callback to refresh AI configuration on changes @@ -212,7 +220,7 @@ func main() { app := &ChatApp{ configProvider: configProvider, - aiConfig: aiConfig, + aiConfig: aiConfig, } // Initialize Azure OpenAI client From 640784c3ca3f4d841c873d639c05fea0cd918b33 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Mon, 22 Sep 2025 17:02:52 +0800 Subject: [PATCH 5/6] simiplify code --- examples/Go/ChatApp/main.go | 214 ++++++++++++------------------------ 1 file changed, 71 insertions(+), 143 deletions(-) diff --git a/examples/Go/ChatApp/main.go b/examples/Go/ChatApp/main.go index c80ae36c..2bf21467 100644 --- a/examples/Go/ChatApp/main.go +++ b/examples/Go/ChatApp/main.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "os" - "strings" "time" "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" @@ -15,12 +14,6 @@ import ( "github.com/openai/openai-go/azure" ) -type ChatApp struct { - configProvider *azureappconfiguration.AzureAppConfiguration - openAIClient openai.Client - aiConfig AIConfig -} - type AIConfig struct { ChatCompletion ChatCompletion AzureOpenAI AzureOpenAI @@ -45,20 +38,72 @@ type Message struct { Content string `json:"content"` } +var aiConfig AIConfig +var tokenCredential, _ = azidentity.NewDefaultAzureCredential(nil) + +func main() { + configProvider, err := loadAzureAppConfiguration(context.Background()) + if err != nil { + log.Fatal("Error loading Azure App Configuration:", err) + } + + // Configure chat completion with AI configuration + configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}) + + // Register a callback to refresh AI configuration on changes + configProvider.OnRefreshSuccess(func() { + configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}) + }) + + // Create a chat client using API key if available, otherwise use the DefaultAzureCredential + var openAIClient openai.Client + if aiConfig.AzureOpenAI.APIKey != "" { + openAIClient = openai.NewClient(azure.WithAPIKey(aiConfig.AzureOpenAI.APIKey), azure.WithEndpoint(aiConfig.AzureOpenAI.Endpoint, aiConfig.AzureOpenAI.APIVersion)) + } else { + openAIClient = openai.NewClient(azure.WithEndpoint(aiConfig.AzureOpenAI.Endpoint, aiConfig.AzureOpenAI.APIVersion), azure.WithTokenCredential(tokenCredential)) + } + + // Initialize chat conversation + var chatConversation []openai.ChatCompletionMessageParamUnion + fmt.Println("Chat started! What's on your mind?") + reader := bufio.NewReader(os.Stdin) + + for { + // Refresh the configuration from Azure App Configuration + configProvider.Refresh(context.Background()) + + // Get user input + fmt.Print("You: ") + userInput, _ := reader.ReadString('\n') + + // Exit if user input is empty + if userInput == "" { + fmt.Println("Exiting Chat. Goodbye!") + break + } + + // Add user message to chat conversation + chatConversation = append(chatConversation, openai.UserMessage(userInput)) + + // Get AI response and add it to chat conversation + response, _ := getAIResponse(openAIClient, chatConversation) + fmt.Printf("AI: %s\n", response) + chatConversation = append(chatConversation, openai.AssistantMessage(response)) + + fmt.Println() + } +} + +// Load configuration from Azure App Configuration func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) { endpoint := os.Getenv("AZURE_APPCONFIGURATION_ENDPOINT") if endpoint == "" { return nil, fmt.Errorf("AZURE_APPCONFIGURATION_ENDPOINT environment variable is not set") } - credential, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return nil, fmt.Errorf("failed to create Azure credential: %w", err) - } - authOptions := azureappconfiguration.AuthenticationOptions{ Endpoint: endpoint, - Credential: credential, + Credential: tokenCredential, } options := &azureappconfiguration.Options{ @@ -71,52 +116,20 @@ func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.Azur TrimKeyPrefixes: []string{"ChatApp:"}, RefreshOptions: azureappconfiguration.KeyValueRefreshOptions{ Enabled: true, - Interval: 10 * time.Second, + Interval: 1 * time.Hour, }, KeyVaultOptions: azureappconfiguration.KeyVaultOptions{ - Credential: credential, + Credential: tokenCredential, }, } - appConfig, err := azureappconfiguration.Load(ctx, authOptions, options) - if err != nil { - return nil, fmt.Errorf("failed to load configuration: %w", err) - } - - return appConfig, nil + return azureappconfiguration.Load(ctx, authOptions, options) } -// Create an Azure OpenAI client using API key if available, otherwise use the DefaultAzureCredential -func (app *ChatApp) createAzureOpenAIClient() error { - if app.aiConfig.AzureOpenAI.APIKey != "" { - // Use API key for authentication - client := openai.NewClient( - azure.WithAPIKey(app.aiConfig.AzureOpenAI.APIKey), - azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion), - ) - app.openAIClient = client - return nil - } - - // Use DefaultAzureCredential for authentication - tokenCredential, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return fmt.Errorf("failed to create Azure credential: %w", err) - } - - client := openai.NewClient( - azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion), - azure.WithTokenCredential(tokenCredential), - ) - - app.openAIClient = client - return nil -} - -func (app *ChatApp) callAzureOpenAI(chatConversation []openai.ChatCompletionMessageParamUnion) (string, error) { +func getAIResponse(openAIClient openai.Client, chatConversation []openai.ChatCompletionMessageParamUnion) (string, error) { var completionMessages []openai.ChatCompletionMessageParamUnion - for _, msg := range app.aiConfig.ChatCompletion.Messages { + for _, msg := range aiConfig.ChatCompletion.Messages { switch msg.Role { case "system": completionMessages = append(completionMessages, openai.SystemMessage(msg.Content)) @@ -133,100 +146,15 @@ func (app *ChatApp) callAzureOpenAI(chatConversation []openai.ChatCompletionMess // Create chat completion parameters params := openai.ChatCompletionNewParams{ Messages: completionMessages, - Model: app.aiConfig.ChatCompletion.Model, - MaxTokens: openai.Int(app.aiConfig.ChatCompletion.MaxTokens), - Temperature: openai.Float(app.aiConfig.ChatCompletion.Temperature), - TopP: openai.Float(app.aiConfig.ChatCompletion.TopP), - } - - ctx := context.Background() - completion, err := app.openAIClient.Chat.Completions.New(ctx, params) - if err != nil { - return "", fmt.Errorf("failed to get chat completion: %w", err) + Model: aiConfig.ChatCompletion.Model, + MaxTokens: openai.Int(aiConfig.ChatCompletion.MaxTokens), + Temperature: openai.Float(aiConfig.ChatCompletion.Temperature), + TopP: openai.Float(aiConfig.ChatCompletion.TopP), } - if len(completion.Choices) == 0 { - return "", fmt.Errorf("chat completion returned zero choices") + if completion, err := openAIClient.Chat.Completions.New(context.Background(), params); err != nil { + return "", err + } else { + return completion.Choices[0].Message.Content, nil } - - return completion.Choices[0].Message.Content, nil -} - -func (app *ChatApp) runInteractiveChat() { - // Initialize chat conversation - var chatConversation []openai.ChatCompletionMessageParamUnion - fmt.Println("Chat started! What's on your mind?") - reader := bufio.NewReader(os.Stdin) - - for { - // Refresh the configuration from Azure App Configuration - ctx := context.Background() - if err := app.configProvider.Refresh(ctx); err != nil { - log.Printf("Error refreshing configuration: %v", err) - } - - // Get user input - fmt.Print("You: ") - userInput, err := reader.ReadString('\n') - if err != nil { - log.Printf("Error reading input: %v", err) - continue - } - - userInput = strings.TrimSpace(userInput) - if userInput == "" { - fmt.Println("Exiting Chat. Goodbye!") - break - } - - // Add user message to chat conversation - chatConversation = append(chatConversation, openai.UserMessage(userInput)) - - // Get AI response and add it to chat conversation - response, err := app.callAzureOpenAI(chatConversation) - if err != nil { - log.Printf("Error calling OpenAI: %v", err) - fmt.Println("Sorry, I encountered an error. Please try again.") - continue - } - - fmt.Printf("AI: %s\n", response) - chatConversation = append(chatConversation, openai.AssistantMessage(response)) - - fmt.Println() - } -} - -func main() { - ctx := context.Background() - configProvider, err := loadAzureAppConfiguration(ctx) - if err != nil { - log.Fatal("Error loading Azure App Configuration:", err) - return - } - - // Configure chat completion with AI configuration - var aiConfig AIConfig - if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { - log.Fatal("Error unmarshaling AI configuration", err) - } - - // Register a callback to refresh AI configuration on changes - configProvider.OnRefreshSuccess(func() { - if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil { - log.Printf("Error refreshing AI configuration: %v", err) - } - }) - - app := &ChatApp{ - configProvider: configProvider, - aiConfig: aiConfig, - } - - // Initialize Azure OpenAI client - if err := app.createAzureOpenAIClient(); err != nil { - log.Fatalf("Failed to create Azure OpenAI client: %v", err) - } - - app.runInteractiveChat() } From 833cfd3c986753a6880ff56811fe5db1de93e6c3 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Tue, 23 Sep 2025 10:07:41 +0800 Subject: [PATCH 6/6] default refresh interval for key values --- examples/Go/ChatApp/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Go/ChatApp/main.go b/examples/Go/ChatApp/main.go index 2bf21467..538055e1 100644 --- a/examples/Go/ChatApp/main.go +++ b/examples/Go/ChatApp/main.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "os" - "time" "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" @@ -114,9 +113,10 @@ func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.Azur }, }, TrimKeyPrefixes: []string{"ChatApp:"}, + // Reload configuration if any selected key-values have changed. + // Use the default refresh interval of 30 seconds. It can be overridden via RefreshOptions.Interval RefreshOptions: azureappconfiguration.KeyValueRefreshOptions{ Enabled: true, - Interval: 1 * time.Hour, }, KeyVaultOptions: azureappconfiguration.KeyVaultOptions{ Credential: tokenCredential,