in cli/azd/extensions/microsoft.azd.ai.builder/internal/cmd/start.go [738:1739]
func (a *startAction) createQuestions(ctx context.Context) (map[string]qna.Question, error) {
resourceTypes, err := a.azdClient.Compose().ListResourceTypes(ctx, &azdext.EmptyRequest{})
if err != nil {
return nil, fmt.Errorf("failed to list resource types: %w", err)
}
dbResourceMap := make(map[string]*azdext.ComposedResourceType)
vectorStoreMap := make(map[string]*azdext.ComposedResourceType)
messagingResourceMap := make(map[string]*azdext.ComposedResourceType)
for _, resourceType := range resourceTypes.ResourceTypes {
key := resourceType.Name
if strings.HasPrefix(key, "db.") {
dbResourceMap[key] = resourceType
} else if strings.HasPrefix(key, "messaging.") {
messagingResourceMap[key] = resourceType
}
if strings.Contains(key, "ai.search") || strings.Contains(key, "db.cosmos") {
vectorStoreMap[key] = resourceType
}
}
return map[string]qna.Question{
"root": {
Binding: &a.scenarioData.SelectedScenario,
Heading: "Identify AI Scenario",
Message: "Let's start drilling into your AI scenario to identify all the required infrastructure we will need.",
Prompt: &qna.SingleSelectPrompt{
Client: a.azdClient,
Message: "What type of AI scenario are you building?",
HelpMessage: "Choose the scenario that best fits your needs.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "RAG Application (Retrieval-Augmented Generation)", Value: "rag"},
{Label: "AI Agent", Value: "agent"},
{Label: "Other Scenarios (Coming Soon)", Value: "other-scenarios"},
},
},
Branches: map[any][]qna.QuestionReference{
"rag": {{Key: "use-custom-data"}},
"agent": {{Key: "agent-tasks"}},
},
},
"use-custom-data": {
Binding: &a.scenarioData.UseCustomData,
Prompt: &qna.ConfirmPrompt{
Client: a.azdClient,
Message: "Does your application require custom data?",
HelpMessage: "Custom data is data that is not publicly available and is specific to your application.",
DefaultValue: to.Ptr(true),
},
AfterAsk: func(ctx context.Context, q *qna.Question, _ any) error {
switch a.scenarioData.SelectedScenario {
case "rag":
q.Branches = map[any][]qna.QuestionReference{
true: {{Key: "choose-data-types"}},
false: {{Key: "rag-user-interaction"}},
}
case "agent":
q.Branches = map[any][]qna.QuestionReference{
true: {{Key: "choose-data-types"}},
false: {{Key: "agent-tasks"}},
}
}
return nil
},
},
"choose-data-types": {
Binding: &a.scenarioData.DataTypes,
Heading: "Data Sources",
Message: "Lets identify all the data source that will be used in your application.",
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "What type of data are you using?",
HelpMessage: "Select all the data types that apply to your application.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Structured documents, ex. JSON, CSV", Value: "structured-documents"},
{Label: "Unstructured documents, ex. PDF, Word", Value: "unstructured-documents"},
{Label: "Videos", Value: "videos"},
{Label: "Images", Value: "images"},
{Label: "Audio", Value: "audio"},
},
},
Next: []qna.QuestionReference{{Key: "data-location"}},
},
"data-location": {
Binding: &a.scenarioData.DataLocations,
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "Where is your data located?",
HelpMessage: "Select all the data locations that apply to your application.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Azure Blob Storage", Value: "blob-storage"},
{Label: "Azure Database", Value: "databases"},
{Label: "Local file system", Value: "local-file-system"},
{Label: "Other", Value: "other-datasource"},
},
},
Branches: map[any][]qna.QuestionReference{
"blob-storage": {{Key: "choose-storage"}},
"databases": {{Key: "choose-database"}},
"local-file-system": {{Key: "local-file-system"}},
},
Next: []qna.QuestionReference{{Key: "choose-vector-store"}},
},
"choose-storage": {
Heading: "Storage Account",
Message: "We'll need to setup a storage account to store the data for your application.",
BeforeAsk: func(ctx context.Context, q *qna.Question, _ any) error {
hasStorageResource := false
for _, resource := range a.composedResources {
if resource.Type == "storage" {
hasStorageResource = true
break
}
}
promptMessage := "It looks like you already have a configured storage account. Do you want to reuse it?"
if hasStorageResource {
q.Prompt = &qna.ConfirmPrompt{
Client: a.azdClient,
Message: promptMessage,
DefaultValue: to.Ptr(true),
HelpMessage: "Using an existing storage account will save you time and resources.",
}
}
q.State["hasStorageResource"] = hasStorageResource
return nil
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
hasStorageResource := q.State["hasStorageResource"].(bool)
reuseStorage, ok := value.(bool)
if !hasStorageResource || ok && !reuseStorage {
q.Next = []qna.QuestionReference{{Key: "choose-storage-resource"}}
}
return nil
},
},
"choose-storage-resource": {
Binding: &a.scenarioData.StorageAccountId,
Prompt: &qna.SubscriptionResourcePrompt{
Client: a.azdClient,
ResourceType: "Microsoft.Storage/storageAccounts",
ResourceTypeDisplayName: "Storage Account",
HelpMessage: "Select an existing storage account or create a new one.",
AzureContext: a.azureContext,
},
},
"choose-database": {
Heading: "Database",
Message: "We'll need to setup a database that will be used by your application to power AI model(s).",
BeforeAsk: func(ctx context.Context, q *qna.Question, _ any) error {
hasDatabaseResource := false
for _, resource := range a.composedResources {
if strings.HasPrefix(resource.Type, "db.") {
hasDatabaseResource = true
break
}
}
if hasDatabaseResource {
q.Prompt = &qna.ConfirmPrompt{
Client: a.azdClient,
Message: "It looks like you already have a configured database. Do you want to reuse it?",
DefaultValue: to.Ptr(true),
HelpMessage: "Using an existing database will save you time and resources.",
}
}
q.State["hasDatabaseResource"] = hasDatabaseResource
return nil
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
hasDatabaseResource := q.State["hasDatabaseResource"].(bool)
reuseDatabase, ok := value.(bool)
if !hasDatabaseResource || ok && !reuseDatabase {
q.Next = []qna.QuestionReference{{Key: "choose-database-type"}}
}
return nil
},
},
"choose-database-type": {
Binding: &a.scenarioData.DatabaseType,
Prompt: &qna.SingleSelectPrompt{
Message: "Which type of database?",
HelpMessage: "Select the type of database that best fits your needs.",
Client: a.azdClient,
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "CosmosDB", Value: "db.cosmos"},
{Label: "PostgreSQL", Value: "db.postgres"},
{Label: "MySQL", Value: "db.mysql"},
{Label: "Redis", Value: "db.redis"},
{Label: "MongoDB", Value: "db.mongo"},
},
},
Next: []qna.QuestionReference{{Key: "choose-database-resource"}},
},
"choose-database-resource": {
Binding: &a.scenarioData.DatabaseId,
Prompt: &qna.SubscriptionResourcePrompt{
HelpMessage: "Select an existing database or create a new one.",
Client: a.azdClient,
AzureContext: a.azureContext,
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SubscriptionResourcePrompt) error {
resourceType, has := dbResourceMap[a.scenarioData.DatabaseType]
if !has {
return fmt.Errorf(
"unknown resource type for database: %s",
a.scenarioData.DatabaseType,
)
}
p.ResourceType = resourceType.Type
p.Kinds = resourceType.Kinds
p.ResourceTypeDisplayName = resourceType.DisplayName
return nil
},
},
},
"local-file-system": {
Binding: &a.scenarioData.LocalFilePath,
Heading: "Local File System",
Message: "Lets identify the files that will be used in your application. " +
"Later on we will upload these files to Azure so they can be used by your application.",
Prompt: &qna.TextPrompt{
Client: a.azdClient,
Message: "Path to the local files",
HelpMessage: "This path can be absolute or relative to the current working directory. " +
"Please make sure the path is accessible from the machine running this command.",
Placeholder: "./data",
},
Next: []qna.QuestionReference{{Key: "local-file-choose-files"}},
},
"local-file-choose-files": {
Binding: &a.scenarioData.LocalFileSelection,
Prompt: &qna.SingleSelectPrompt{
Client: a.azdClient,
Message: "Which files?",
HelpMessage: "Select all files or use a glob expression to filter the files.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "All Files", Value: "all-files"},
{Label: "Glob Expression", Value: "glob-expression"},
},
},
Branches: map[any][]qna.QuestionReference{
"glob-expression": {{Key: "local-file-glob"}},
},
},
"local-file-glob": {
Binding: &a.scenarioData.LocalFileGlobFilter,
Prompt: &qna.TextPrompt{
Client: a.azdClient,
Message: "Enter a glob expression to filter files",
HelpMessage: "A glob expression is a string that uses wildcard characters to match file names. " +
" For example, *.txt will match all text files in the current directory.",
Placeholder: "*.json",
},
},
"choose-vector-store": {
Heading: "Vector Store",
Message: "Based on your choices we're going to need a vector store to store the text embeddings for your data.",
BeforeAsk: func(ctx context.Context, q *qna.Question, _ any) error {
hasVectorStoreResource := false
for _, resource := range a.composedResources {
if resource.Type == "ai.search" {
hasVectorStoreResource = true
break
}
}
if hasVectorStoreResource {
q.Prompt = &qna.ConfirmPrompt{
Client: a.azdClient,
Message: "It looks like you already have a configured vector store. Do you want to reuse it?",
DefaultValue: to.Ptr(true),
HelpMessage: "Using an existing vector store will save you time and resources.",
}
}
q.State["hasVectorStoreResource"] = hasVectorStoreResource
return nil
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
hasVectorStoreResource := q.State["hasVectorStoreResource"].(bool)
reuseVectorStore, ok := value.(bool)
var next string
if a.scenarioData.SelectedScenario == "rag" {
next = "rag-user-interaction"
} else {
next = "agent-interaction"
}
if !hasVectorStoreResource || ok && !reuseVectorStore {
next = "choose-vector-store-type"
}
q.Next = []qna.QuestionReference{{Key: next}}
return nil
},
},
"choose-vector-store-type": {
Binding: &a.scenarioData.VectorStoreType,
Prompt: &qna.SingleSelectPrompt{
Message: "What type of vector store do you want to use?",
HelpMessage: "Select the type of vector store that best fits your needs.",
Client: a.azdClient,
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Choose for me", Value: "ai.search"},
{Label: "AI Search", Value: "ai.search"},
{Label: "CosmosDB", Value: "db.cosmos"},
},
},
Branches: map[any][]qna.QuestionReference{
"ai.search": {{Key: "choose-vector-store-resource"}},
"db.cosmos": {{Key: "choose-vector-store-resource"}},
},
AfterAsk: func(ctx context.Context, q *qna.Question, _ any) error {
switch a.scenarioData.SelectedScenario {
case "rag":
q.Next = []qna.QuestionReference{{Key: "rag-user-interaction"}}
case "agent":
q.Next = []qna.QuestionReference{{Key: "agent-interaction"}}
}
return nil
},
},
"choose-vector-store-resource": {
Binding: &a.scenarioData.VectorStoreId,
Prompt: &qna.SubscriptionResourcePrompt{
HelpMessage: "Select an existing vector store or create a new one.",
Client: a.azdClient,
AzureContext: a.azureContext,
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SubscriptionResourcePrompt) error {
resourceType, has := vectorStoreMap[a.scenarioData.VectorStoreType]
if !has {
return fmt.Errorf(
"unknown resource type for vector store: %s",
a.scenarioData.VectorStoreType,
)
}
p.ResourceType = resourceType.Type
p.Kinds = resourceType.Kinds
p.ResourceTypeDisplayName = resourceType.DisplayName
return nil
},
},
},
"rag-user-interaction": {
Binding: &a.scenarioData.InteractionTypes,
Heading: "User Interaction",
Message: "Now we will figure out all the different ways users will interact with your application.",
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "How do you want users to interact with the data?",
HelpMessage: "Select all the data interaction types that apply to your application.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Chatbot UI Frontend", Value: "rag-ui"},
{Label: "API Backend Application", Value: "rag-api"},
},
},
Branches: map[any][]qna.QuestionReference{
"rag-ui": {{Key: "choose-app"}},
"rag-api": {{Key: "choose-app"}},
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["interactionTypes"] = value
return nil
},
Next: []qna.QuestionReference{{Key: "start-choose-models"}},
},
"choose-app": {
BeforeAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.Heading = fmt.Sprintf("Configure '%s' Application", value)
q.Message = fmt.Sprintf("Lets collect some information about your %s application.", value)
q.State["interactionType"] = value
hasHostAppResource := false
appHostCount := 0
for _, resource := range a.composedResources {
if strings.HasPrefix(resource.Type, "host.") {
appHostCount++
hasHostAppResource = true
}
}
hostName := "host"
hostName2 := "it"
if appHostCount > 1 {
hostName = "hosts"
hostName2 = "them"
}
msg := fmt.Sprintf(
"It looks like you project already contains %d application %s. Do you want to reuse %s?",
appHostCount,
hostName,
hostName2,
)
if hasHostAppResource {
q.Prompt = &qna.ConfirmPrompt{
Client: a.azdClient,
Message: msg,
DefaultValue: to.Ptr(true),
HelpMessage: "Using an existing application host will save you time and resources.",
}
}
q.State["hasHostAppResource"] = hasHostAppResource
return nil
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
hasHostAppResource := q.State["hasHostAppResource"].(bool)
reuseHostApp, ok := value.(bool)
if !hasHostAppResource || ok && !reuseHostApp {
q.Next = []qna.QuestionReference{{Key: "choose-app-type"}}
}
delete(q.State, "hasHostAppResource")
return nil
},
},
"choose-app-type": {
Binding: &a.scenarioData.AppHostTypes,
Prompt: &qna.SingleSelectPrompt{
Message: "Which application host do you want to use?",
Client: a.azdClient,
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Choose for me", Value: "choose-app"},
{Label: "Container App", Value: "host.containerapp"},
{Label: "App Service (Coming Soon)", Value: "host.webapp"},
{Label: "Function App (Coming Soon)", Value: "host.functionapp"},
{Label: "Static Web App (Coming Soon)", Value: "host.staticwebapp"},
{Label: "Other", Value: "other-app"},
},
},
Branches: map[any][]qna.QuestionReference{
"host.containerapp": {{Key: "choose-app-resource"}},
},
Next: []qna.QuestionReference{
{Key: "choose-app-language"},
},
},
"choose-app-language": {
Prompt: &qna.SingleSelectPrompt{
Client: a.azdClient,
Message: "Which programming language do you want to use?",
HelpMessage: "Select the programming language that best fits your needs.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Choose for me", Value: "default"},
{Label: "C#", Value: "csharp"},
{Label: "Python", Value: "python"},
{Label: "JavaScript", Value: "js"},
{Label: "TypeScript", Value: "ts"},
{Label: "Java", Value: "java"},
{Label: "Other", Value: "other"},
},
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
selectedLanguage := value.(string)
// Find the default language for the selected interaction type if available.
if selectedLanguage == "default" {
interactionType := q.State["interactionType"].(string)
interactionDefault, has := defaultAppLanguageMap[interactionType]
if has {
selectedLanguage = interactionDefault
} else {
selectedLanguage = "python"
}
}
a.scenarioData.AppLanguages = append(a.scenarioData.AppLanguages, selectedLanguage)
return nil
},
},
"choose-app-resource": {
Binding: &a.scenarioData.AppResourceIds,
BeforeAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["appType"] = value
return nil
},
Prompt: &qna.SubscriptionResourcePrompt{
HelpMessage: "Select an existing application or create a new one.",
Client: a.azdClient,
AzureContext: a.azureContext,
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SubscriptionResourcePrompt) error {
appType := q.State["appType"].(string)
resourceType, has := appResourceMap[appType]
if !has {
return fmt.Errorf(
"unknown resource type for database: %s",
appType,
)
}
p.ResourceType = resourceType.ResourceType
p.Kinds = resourceType.Kinds
p.ResourceTypeDisplayName = resourceType.ResourceTypeDisplayName
return nil
},
},
},
"agent-interaction": {
Binding: &a.scenarioData.InteractionTypes,
Heading: "Agent Hosting",
Message: "Now we will figure out all the different ways users and systems will interact with your agent.",
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "How do you want users to interact with the agent?",
HelpMessage: "Select all the data interaction types that apply to your application.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Chatbot UI Frontend", Value: "agent-ui"},
{Label: "API Backend Application", Value: "agent-api"},
{Label: "Message based Backed Queue", Value: "agent-messaging"},
},
},
Branches: map[any][]qna.QuestionReference{
"agent-ui": {{Key: "choose-app"}},
"agent-api": {{Key: "choose-app"}},
"agent-messaging": {{Key: "choose-app"}, {Key: "choose-messaging"}},
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["interactionTypes"] = value
return nil
},
Next: []qna.QuestionReference{{Key: "start-choose-models"}},
},
"agent-tasks": {
Binding: &a.scenarioData.ModelTasks,
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "What tasks do you want the AI agent to perform?",
HelpMessage: "Select all the tasks that apply to your application.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Custom Function Calling", Value: "custom-function-calling"},
{Label: "Integrate with Open API based services", Value: "openapi"},
{Label: "Run Azure Functions", Value: "azure-functions"},
{Label: "Other", Value: "other-model-tasks"},
},
},
Next: []qna.QuestionReference{{Key: "use-custom-data"}},
},
"choose-messaging": {
BeforeAsk: func(ctx context.Context, q *qna.Question, _ any) error {
hasMessagingResource := false
for _, resource := range a.composedResources {
if strings.HasPrefix(resource.Type, "messaging.") {
hasMessagingResource = true
break
}
}
if hasMessagingResource {
promptMessage := "It looks like you already have a configured messaging source. Do you want to reuse it?"
q.Prompt = &qna.ConfirmPrompt{
Client: a.azdClient,
Message: promptMessage,
DefaultValue: to.Ptr(true),
HelpMessage: "Using an existing database will save you time and resources.",
}
}
q.State["hasMessagingResource"] = hasMessagingResource
return nil
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
hasMessagingResource := q.State["hasMessagingResource"].(bool)
reuseMessaging, ok := value.(bool)
if !hasMessagingResource || ok && !reuseMessaging {
q.Next = []qna.QuestionReference{{Key: "choose-messaging-type"}}
}
return nil
},
},
"choose-messaging-type": {
Binding: &a.scenarioData.MessagingType,
Prompt: &qna.SingleSelectPrompt{
Client: a.azdClient,
Message: "Which messaging service do you want to use?",
HelpMessage: "Select the messaging service that best fits your needs.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Choose for me", Value: "messaging.servicebus"},
{Label: "Azure Service Bus", Value: "messaging.servicebus"},
{Label: "Azure Event Hubs", Value: "messaging.eventhubs"},
},
},
Branches: map[any][]qna.QuestionReference{
"messaging.eventhubs": {{Key: "choose-messaging-resource"}},
"messaging.servicebus": {{Key: "choose-messaging-resource"}},
},
},
"choose-messaging-resource": {
Binding: &a.scenarioData.MessagingId,
Prompt: &qna.SubscriptionResourcePrompt{
HelpMessage: "Select an existing messaging service or create a new one.",
Client: a.azdClient,
AzureContext: a.azureContext,
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SubscriptionResourcePrompt) error {
resourceType, has := messagingResourceMap[a.scenarioData.MessagingType]
if !has {
return fmt.Errorf(
"unknown resource type for messaging: %s",
a.scenarioData.MessagingType,
)
}
p.ResourceType = resourceType.Type
p.Kinds = resourceType.Kinds
p.ResourceTypeDisplayName = resourceType.DisplayName
return nil
},
},
},
"start-choose-models": {
Heading: "AI Model Selection",
Message: "Now we will figure out the best AI model(s) for your application.",
AfterAsk: func(ctx context.Context, question *qna.Question, value any) error {
if err := a.loadAiCatalog(ctx); err != nil {
return fmt.Errorf("failed to load AI model catalog: %w", err)
}
allModelTypes := map[string]struct {
Heading string
Description string
QuestionReference qna.QuestionReference
}{
"llm": {
Heading: "Large Language Model (LLM) (For generating responses)",
Description: "Processes user queries and retrieved documents to generate intelligent responses.",
QuestionReference: qna.QuestionReference{
Key: "start-choose-model",
State: map[string]any{
"modelSelectMessage": "Lets choose a chat completion model",
"capabilities": []string{"chatCompletion"},
},
},
},
"embeddings": {
Heading: "Embedding Model (For vectorizing text)",
Description: "Used to convert documents and queries into vector representations " +
"for efficient similarity searches.",
QuestionReference: qna.QuestionReference{
Key: "start-choose-model",
State: map[string]any{
"modelSelectMessage": "Lets choose a text embedding model",
"capabilities": []string{"embeddings"},
},
},
},
"audio": {
Heading: "Audio Model (For transcribing audio)",
Description: "Used to convert audio files into text for further processing.",
QuestionReference: qna.QuestionReference{
Key: "start-choose-model",
State: map[string]any{
"modelSelectMessage": "Lets choose a audio model",
"capabilities": []string{"audio"},
},
},
},
"images": {
Heading: "Image Generation Model (For generating images)",
Description: "Used to generate images based on text prompts.",
QuestionReference: qna.QuestionReference{
Key: "start-choose-model",
State: map[string]any{
"modelSelectMessage": "Lets choose a image generation model",
"capabilities": []string{"imageGenerations"},
},
},
},
}
requiredModels := []string{"llm"}
if slices.Contains(a.scenarioData.DataTypes, "structured-documents") ||
slices.Contains(a.scenarioData.DataTypes, "unstructured-documents") {
requiredModels = append(requiredModels, "embeddings")
}
if slices.Contains(a.scenarioData.DataTypes, "audio") {
requiredModels = append(requiredModels, "audio")
}
if slices.Contains(a.scenarioData.DataTypes, "images") ||
slices.Contains(a.scenarioData.DataTypes, "videos") {
requiredModels = append(requiredModels, "images")
}
nextQuestions := []qna.QuestionReference{}
fmt.Printf(" Based on your choices, you will need the following AI models:\n\n")
for _, model := range requiredModels {
if modelType, ok := allModelTypes[model]; ok {
fmt.Printf(" - %s\n", output.WithBold("%s", modelType.Heading))
fmt.Printf(" %s\n", output.WithGrayFormat(modelType.Description))
fmt.Println()
nextQuestions = append(nextQuestions, modelType.QuestionReference)
}
}
question.Next = nextQuestions
return nil
},
},
"start-choose-model": {
BeforeAsk: func(ctx context.Context, question *qna.Question, value any) error {
if err := a.loadAiCatalog(ctx); err != nil {
return fmt.Errorf("failed to load AI model catalog: %w", err)
}
return nil
},
Prompt: &qna.SingleSelectPrompt{
Client: a.azdClient,
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SingleSelectPrompt) error {
// Override the message
if message, ok := q.State["modelSelectMessage"].(string); ok {
p.Message = message
}
return nil
},
Message: "How do you want to find the right model?",
HelpMessage: "Select the option that best fits your needs.",
EnableFiltering: to.Ptr(false),
Choices: []qna.Choice{
{Label: "Choose for me", Value: "choose-model"},
{Label: "Help me choose", Value: "guide-model"},
{Label: "I will choose model", Value: "user-model"},
},
},
Branches: map[any][]qna.QuestionReference{
"guide-model": {{Key: "guide-model-select"}},
"user-model": {{Key: "user-model-select"}},
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
selectedValue := value.(string)
if selectedValue == "choose-model" {
capabilities, ok := q.State["capabilities"].([]string)
if !ok {
return nil
}
// If the user selected "choose-model", we need to set the model selection
for key, value := range defaultModelMap {
if slices.Contains(capabilities, key) {
a.scenarioData.ModelSelections = append(
a.scenarioData.ModelSelections,
value,
)
}
}
}
return nil
},
},
"guide-model-select": {
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "Filter AI Models",
HelpMessage: "Select all the filters that apply to your application. " +
"These filters will help you narrow down the type of models you need.",
EnableFiltering: to.Ptr(false),
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.MultiSelectPrompt) error {
choices := []qna.Choice{}
if _, has := q.State["capabilities"]; !has {
choices = append(choices, qna.Choice{
Label: "Filter by capabilities",
Value: "filter-model-capability",
})
}
if _, has := q.State["formats"]; !has {
choices = append(choices, qna.Choice{
Label: "Filter by author",
Value: "filter-model-format",
})
}
if _, has := q.State["status"]; !has {
choices = append(choices, qna.Choice{
Label: "Filter by status",
Value: "filter-model-status",
})
}
if _, has := q.State["locations"]; !has {
choices = append(choices, qna.Choice{
Label: "Filter by location",
Value: "filter-model-location",
})
}
p.Choices = choices
return nil
},
},
Branches: map[any][]qna.QuestionReference{
"filter-model-capability": {{Key: "filter-model-capability"}},
"filter-model-format": {{Key: "filter-model-format"}},
"filter-model-status": {{Key: "filter-model-status"}},
"filter-model-location": {{Key: "filter-model-location"}},
},
Next: []qna.QuestionReference{{Key: "user-model-select"}},
},
"user-model-select": {
Binding: &a.scenarioData.ModelSelections,
Prompt: &qna.SingleSelectPrompt{
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.SingleSelectPrompt) error {
var capabilities []string
var formats []string
var statuses []string
var locations []string
if val, ok := q.State["capabilities"]; ok {
capabilities = val.([]string)
}
if val, ok := q.State["formats"]; ok {
formats = val.([]string)
}
if val, ok := q.State["status"]; ok {
statuses = val.([]string)
}
if val, ok := q.State["locations"]; ok {
locations = val.([]string)
}
filterOptions := &ai.FilterOptions{
Capabilities: capabilities,
Formats: formats,
Statuses: statuses,
Locations: locations,
}
filteredModels := a.modelCatalogService.ListFilteredModels(ctx, a.modelCatalog, filterOptions)
choices := make([]qna.Choice, len(filteredModels))
for i, model := range filteredModels {
choices[i] = qna.Choice{
Label: fmt.Sprintf("%s %s",
model.Name,
output.WithGrayFormat("(%s)", *model.Locations[0].Model.Model.Format),
),
Value: model.Name,
}
}
p.Choices = choices
return nil
},
Client: a.azdClient,
Message: "Which model do you want to use?",
HelpMessage: "Select the model that best fits your needs.",
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
delete(q.State, "capabilities")
delete(q.State, "formats")
delete(q.State, "status")
delete(q.State, "locations")
return nil
},
},
"filter-model-capability": {
Prompt: &qna.MultiSelectPrompt{
Client: a.azdClient,
Message: "What capabilities do you want the model to have?",
HelpMessage: "Select all the capabilities that apply to your application.",
Choices: []qna.Choice{
{Label: "Audio", Value: "audio"},
{Label: "Chat Completion", Value: "chatCompletion"},
{Label: "Text Completion", Value: "completion"},
{Label: "Generate Vector Embeddings", Value: "embeddings"},
{Label: "Image Generation", Value: "imageGenerations"},
},
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["capabilities"] = value
return nil
},
},
"filter-model-format": {
Prompt: &qna.MultiSelectPrompt{
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.MultiSelectPrompt) error {
formats := a.modelCatalogService.ListAllFormats(ctx, a.modelCatalog)
choices := make([]qna.Choice, len(formats))
for i, format := range formats {
choices[i] = qna.Choice{
Label: format,
Value: format,
}
}
p.Choices = choices
return nil
},
Client: a.azdClient,
Message: "Filter my by company or creator",
HelpMessage: "Select all the companies or creators that apply to your application.",
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["formats"] = value
return nil
},
},
"filter-model-status": {
Prompt: &qna.MultiSelectPrompt{
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.MultiSelectPrompt) error {
statuses := a.modelCatalogService.ListAllStatuses(ctx, a.modelCatalog)
choices := make([]qna.Choice, len(statuses))
for i, status := range statuses {
choices[i] = qna.Choice{
Label: status,
Value: status,
}
}
p.Choices = choices
return nil
},
Client: a.azdClient,
Message: "Filter by model release status?",
HelpMessage: "Select all the model release status that apply to your application.",
EnableFiltering: to.Ptr(false),
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["status"] = value
return nil
},
},
"filter-model-location": {
Prompt: &qna.MultiSelectPrompt{
BeforeAsk: func(ctx context.Context, q *qna.Question, p *qna.MultiSelectPrompt) error {
spinner := ux.NewSpinner(&ux.SpinnerOptions{
Text: "Loading Locations",
})
err := spinner.Run(ctx, func(ctx context.Context) error {
locations, err := a.azureClient.ListLocations(ctx, a.azureContext.Scope.SubscriptionId)
if err != nil {
return fmt.Errorf("failed to list locations: %w", err)
}
choices := make([]qna.Choice, len(locations))
for i, location := range locations {
choices[i] = qna.Choice{
Label: fmt.Sprintf("%s (%s)", *location.DisplayName, *location.Name),
Value: *location.Name,
}
}
p.Choices = choices
return nil
})
if err != nil {
return fmt.Errorf("failed to load locations: %w", err)
}
return nil
},
Client: a.azdClient,
Message: "Filter by model location?",
HelpMessage: "Select all the model locations that apply to your application.",
},
AfterAsk: func(ctx context.Context, q *qna.Question, value any) error {
q.State["locations"] = value
return nil
},
},
}, nil
}