Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions examples/Go/ChatApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# 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. **Initialize a new Go module**: `go mod init chatapp-quickstart`
1. **Install dependencies**: `go mod tidy`
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
```
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: [Press Enter to 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
160 changes: 160 additions & 0 deletions examples/Go/ChatApp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package main

import (
"bufio"
"context"
"fmt"
"log"
"os"

"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 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"`
}

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")
}

authOptions := azureappconfiguration.AuthenticationOptions{
Endpoint: endpoint,
Credential: tokenCredential,
}

options := &azureappconfiguration.Options{
Selectors: []azureappconfiguration.Selector{
// Load all keys that start with "ChatApp:" and have no label
{
KeyFilter: "ChatApp:*",
},
},
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,
},
KeyVaultOptions: azureappconfiguration.KeyVaultOptions{
Credential: tokenCredential,
},
}

return azureappconfiguration.Load(ctx, authOptions, options)
}

func getAIResponse(openAIClient openai.Client, chatConversation []openai.ChatCompletionMessageParamUnion) (string, error) {
var completionMessages []openai.ChatCompletionMessageParamUnion

for _, msg := range aiConfig.ChatCompletion.Messages {
switch msg.Role {
case "system":
completionMessages = append(completionMessages, openai.SystemMessage(msg.Content))
case "user":
completionMessages = append(completionMessages, openai.UserMessage(msg.Content))
case "assistant":
completionMessages = append(completionMessages, openai.AssistantMessage(msg.Content))
}
}

// Add the chat conversation history
completionMessages = append(completionMessages, chatConversation...)

// Create chat completion parameters
params := openai.ChatCompletionNewParams{
Messages: completionMessages,
Model: aiConfig.ChatCompletion.Model,
MaxTokens: openai.Int(aiConfig.ChatCompletion.MaxTokens),
Temperature: openai.Float(aiConfig.ChatCompletion.Temperature),
TopP: openai.Float(aiConfig.ChatCompletion.TopP),
}

if completion, err := openAIClient.Chat.Completions.New(context.Background(), params); err != nil {
return "", err
} else {
return completion.Choices[0].Message.Content, nil
}
}