Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
232 changes: 232 additions & 0 deletions examples/Go/ChatApp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
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) {
Comment thread
zhenlan marked this conversation as resolved.
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,
},
Comment thread
linglingye001 marked this conversation as resolved.
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
}
Comment thread
zhenlan marked this conversation as resolved.
Outdated

// 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) {
Comment thread
zhenlan marked this conversation as resolved.
Outdated
var completionMessages []openai.ChatCompletionMessageParamUnion

for _, msg := range app.aiConfig.ChatCompletion.Messages {
switch msg.Role {
Comment thread
linglingye001 marked this conversation as resolved.
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: 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("chat completion returned zero choices")
}

return completion.Choices[0].Message.Content, nil
}

func (app *ChatApp) runInteractiveChat() {
Comment thread
zhenlan marked this conversation as resolved.
Outdated
// 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)
Comment thread
zhenlan marked this conversation as resolved.
Outdated
if userInput == "" {
Comment thread
linglingye001 marked this conversation as resolved.
Outdated
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)
}
Comment thread
zhenlan marked this conversation as resolved.
Outdated

app.runInteractiveChat()
}