Skip to content

Commit a00203c

Browse files
committed
add Go chat app sample
1 parent 584e0ed commit a00203c

2 files changed

Lines changed: 314 additions & 0 deletions

File tree

examples/Go/ChatApp/README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Azure App Configuration - Go ChatApp Sample
2+
3+
An interactive console chat application that integrates with Azure OpenAI services using Azure App Configuration for dynamic AI Configuration management.
4+
5+
## Overview
6+
7+
This Go console application provides a seamless chat experience with Azure OpenAI, featuring:
8+
9+
- Integration with Azure OpenAI for chat completions
10+
- Dynamic AI configuration refresh from Azure App Configuration
11+
- Secure authentication options using API key or Microsoft Entra ID
12+
13+
## Prerequisites
14+
15+
- Go 1.23 or later
16+
- Azure subscription
17+
- Azure OpenAI service instance
18+
- Azure App Configuration service instance
19+
20+
## Setup
21+
22+
### Environment Variables
23+
24+
Set the following environment variable:
25+
26+
- `AZURE_APPCONFIGURATION_ENDPOINT`: Endpoint URL of your Azure App Configuration instance
27+
28+
### Azure App Configuration Keys
29+
30+
Configure the following keys in your Azure App Configuration:
31+
32+
#### Azure OpenAI Connection Settings
33+
34+
- `ChatApp:AzureOpenAI:Endpoint` - Your Azure OpenAI endpoint URL
35+
- `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.
36+
- `ChatApp:AzureOpenAI:ApiKey` - Key Vault reference to the API key for Azure OpenAI (optional)
37+
38+
#### Chat Completion Configuration
39+
40+
- `ChatApp:ChatCompletion` - An AI Configuration for chat completion containing the following settings:
41+
- `model` - Model name (e.g., "gpt-4o")
42+
- `max_tokens` - Maximum tokens for completion (e.g., 1000)
43+
- `temperature` - Temperature parameter (e.g., 0.7)
44+
- `top_p` - Top p parameter (e.g., 0.95)
45+
- `messages` - An array of messages with role and content for each message
46+
47+
## Authentication
48+
49+
The application supports the following authentication methods:
50+
51+
- **Azure App Configuration**: Uses `DefaultAzureCredential` for authentication via Microsoft Entra ID.
52+
- **Azure OpenAI**: Supports authentication using either an API key or `DefaultAzureCredential` via Microsoft Entra ID.
53+
- **Azure Key Vault** *(optional, if using Key Vault references for API keys)*: Authenticates using `DefaultAzureCredential` via Microsoft Entra ID.
54+
55+
## Usage
56+
57+
1. **Install dependencies**: `go mod tidy`
58+
2. **Start the Application**: Run the application using `go run main.go`
59+
3. **Begin Chatting**: Type your messages when prompted with "You: "
60+
4. **Continue Conversation**: The AI will respond and maintain conversation context
61+
5. **Exit**: Press Enter without typing a message to exit gracefully
62+
63+
### Example Session
64+
```
65+
Chat started! What's on your mind?
66+
You: Hello, how are you?
67+
AI: Hello! I'm doing well, thank you for asking. How can I help you today?
68+
69+
You: What can you tell me about machine learning?
70+
AI: Machine learning is a subset of artificial intelligence that focuses on...
71+
72+
You: exit
73+
Exiting chat. Goodbye!
74+
```
75+
76+
## Troubleshooting
77+
78+
**"AZURE_APPCONFIGURATION_ENDPOINT environment variable not set"**
79+
- Ensure the environment variable is properly set
80+
- Verify the endpoint URL is correct
81+
82+
**Authentication Failures**
83+
- Ensure you have the `App Configuration Data Reader` role on the Azure App Configuration instance
84+
- For Microsoft Entra ID authentication: Verify you have the `Cognitive Services OpenAI User` role on the Azure OpenAI instance
85+
- For API key authentication:
86+
- Confirm you have secret read access to the Key Vault storing the API key
87+
- Verify that a Key Vault reference for the API key is properly configured in Azure App Configuration
88+
89+
**No AI Response**
90+
- Verify deployment name matches your Azure OpenAI deployment
91+
- Check token limits and quotas

examples/Go/ChatApp/main.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"fmt"
7+
"log"
8+
"os"
9+
"strings"
10+
"time"
11+
12+
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
13+
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
14+
openai "github.com/openai/openai-go"
15+
"github.com/openai/openai-go/azure"
16+
)
17+
18+
type ChatApp struct {
19+
configProvider *azureappconfiguration.AzureAppConfiguration
20+
openAIClient openai.Client
21+
aiConfig AIConfig
22+
}
23+
24+
type AIConfig struct {
25+
ChatCompletion ChatCompletion
26+
AzureOpenAI AzureOpenAI
27+
}
28+
29+
type ChatCompletion struct {
30+
Model string `json:"model"`
31+
Messages []Message `json:"messages"`
32+
MaxTokens int64 `json:"max_tokens"`
33+
Temperature float64 `json:"temperature"`
34+
TopP float64 `json:"top_p"`
35+
}
36+
37+
type AzureOpenAI struct {
38+
Endpoint string
39+
APIVersion string
40+
APIKey string
41+
}
42+
43+
type Message struct {
44+
Role string `json:"role"`
45+
Content string `json:"content"`
46+
}
47+
48+
func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) {
49+
endpoint := os.Getenv("AZURE_APPCONFIGURATION_ENDPOINT")
50+
if endpoint == "" {
51+
return nil, fmt.Errorf("AZURE_APPCONFIGURATION_ENDPOINT environment variable is not set")
52+
}
53+
54+
credential, err := azidentity.NewDefaultAzureCredential(nil)
55+
if err != nil {
56+
return nil, fmt.Errorf("failed to create Azure credential: %w", err)
57+
}
58+
59+
authOptions := azureappconfiguration.AuthenticationOptions{
60+
Endpoint: endpoint,
61+
Credential: credential,
62+
}
63+
64+
options := &azureappconfiguration.Options{
65+
Selectors: []azureappconfiguration.Selector{
66+
// Load all keys that start with "ChatApp:" and have no label
67+
{
68+
KeyFilter: "ChatApp:*",
69+
},
70+
},
71+
TrimKeyPrefixes: []string{"ChatApp:"},
72+
RefreshOptions: azureappconfiguration.KeyValueRefreshOptions{
73+
Enabled: true,
74+
Interval: 10 * time.Second,
75+
},
76+
KeyVaultOptions: azureappconfiguration.KeyVaultOptions{
77+
Credential: credential,
78+
},
79+
}
80+
81+
appConfig, err := azureappconfiguration.Load(ctx, authOptions, options)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to load configuration: %w", err)
84+
}
85+
86+
return appConfig, nil
87+
}
88+
89+
// Create an Azure OpenAI client using API key if available, otherwise use the DefaultAzureCredential
90+
func (app *ChatApp) createAzureOpenAIClient() error {
91+
if app.aiConfig.AzureOpenAI.APIKey != "" {
92+
// Use API key for authentication
93+
client := openai.NewClient(
94+
azure.WithAPIKey(app.aiConfig.AzureOpenAI.APIKey),
95+
azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion),
96+
)
97+
app.openAIClient = client
98+
return nil
99+
}
100+
101+
// Use DefaultAzureCredential for authentication
102+
tokenCredential, err := azidentity.NewDefaultAzureCredential(nil)
103+
if err != nil {
104+
return fmt.Errorf("failed to create Azure credential: %w", err)
105+
}
106+
107+
client := openai.NewClient(
108+
azure.WithEndpoint(app.aiConfig.AzureOpenAI.Endpoint, app.aiConfig.AzureOpenAI.APIVersion),
109+
azure.WithTokenCredential(tokenCredential),
110+
)
111+
112+
app.openAIClient = client
113+
return nil
114+
}
115+
116+
func (app *ChatApp) callAzureOpenAI(userMessage string) (string, error) {
117+
messages := []openai.ChatCompletionMessageParamUnion{}
118+
for _, msg := range app.aiConfig.ChatCompletion.Messages {
119+
switch msg.Role {
120+
case "system":
121+
messages = append(messages, openai.SystemMessage(msg.Content))
122+
case "user":
123+
messages = append(messages, openai.UserMessage(msg.Content))
124+
case "assistant":
125+
messages = append(messages, openai.AssistantMessage(msg.Content))
126+
}
127+
}
128+
129+
// Add the user's input message
130+
messages = append(messages, openai.UserMessage(userMessage))
131+
132+
// Create chat completion parameters
133+
params := openai.ChatCompletionNewParams{
134+
Messages: messages,
135+
Model: app.aiConfig.ChatCompletion.Model,
136+
MaxTokens: openai.Int(app.aiConfig.ChatCompletion.MaxTokens),
137+
Temperature: openai.Float(app.aiConfig.ChatCompletion.Temperature),
138+
TopP: openai.Float(app.aiConfig.ChatCompletion.TopP),
139+
}
140+
141+
ctx := context.Background()
142+
completion, err := app.openAIClient.Chat.Completions.New(ctx, params)
143+
if err != nil {
144+
return "", fmt.Errorf("failed to get chat completion: %w", err)
145+
}
146+
147+
if len(completion.Choices) == 0 {
148+
return "", fmt.Errorf("no choices in response")
149+
}
150+
151+
return completion.Choices[0].Message.Content, nil
152+
}
153+
154+
func (app *ChatApp) runInteractiveChat() {
155+
fmt.Println("Chat started! What's on your mind?")
156+
reader := bufio.NewReader(os.Stdin)
157+
158+
for {
159+
fmt.Print("You: ")
160+
userInput, err := reader.ReadString('\n')
161+
if err != nil {
162+
log.Printf("Error reading input: %v", err)
163+
continue
164+
}
165+
166+
userInput = strings.TrimSpace(userInput)
167+
if userInput == "" {
168+
fmt.Println("Exiting Chat. Goodbye!")
169+
break
170+
}
171+
172+
// Refresh configuration
173+
ctx := context.Background()
174+
if err := app.configProvider.Refresh(ctx); err != nil {
175+
log.Printf("Error refreshing configuration: %v", err)
176+
}
177+
178+
// Get AI response
179+
fmt.Print("AI: ")
180+
response, err := app.callAzureOpenAI(userInput)
181+
if err != nil {
182+
log.Printf("Error calling OpenAI: %v", err)
183+
fmt.Println("Sorry, I encountered an error. Please try again.")
184+
continue
185+
}
186+
187+
fmt.Println(response)
188+
fmt.Println()
189+
}
190+
}
191+
192+
func main() {
193+
ctx := context.Background()
194+
configProvider, err := loadAzureAppConfiguration(ctx)
195+
if err != nil {
196+
log.Fatal("Error loading Azure App Configuration:", err)
197+
return
198+
}
199+
200+
// Load AI configuration from Azure App Configuration
201+
var aiConfig AIConfig
202+
if err := configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"}); err != nil {
203+
log.Fatal("Error loading AI configuration:", err)
204+
return
205+
}
206+
207+
// Register a callback to refresh AI configuration on changes
208+
configProvider.OnRefreshSuccess(func() {
209+
configProvider.Unmarshal(&aiConfig, &azureappconfiguration.ConstructionOptions{Separator: ":"})
210+
})
211+
212+
app := &ChatApp{
213+
configProvider: configProvider,
214+
aiConfig: aiConfig,
215+
}
216+
217+
// Initialize Azure OpenAI client
218+
if err := app.createAzureOpenAIClient(); err != nil {
219+
log.Fatalf("Failed to create Azure OpenAI client: %v", err)
220+
}
221+
222+
app.runInteractiveChat()
223+
}

0 commit comments

Comments
 (0)