11using Aspire . Hosting . Lifecycle ;
22using Aspire . Hosting . Yarp ;
33using Aspire . Hosting . Yarp . Transforms ;
4- using Microsoft . Extensions . Configuration ;
54using Yarp . ReverseProxy . Configuration ;
65
76namespace eShop . AppHost ;
87
8+ internal enum OpenAITarget
9+ {
10+ OpenAI ,
11+ AzureOpenAI ,
12+ AzureOpenAIExisting ,
13+ AzureOpenAIExistingWithKey
14+ }
15+
916internal static class Extensions
1017{
1118 /// <summary>
@@ -38,7 +45,8 @@ public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationT
3845 /// </summary>
3946 public static IDistributedApplicationBuilder AddOpenAI ( this IDistributedApplicationBuilder builder ,
4047 IResourceBuilder < ProjectResource > catalogApi ,
41- IResourceBuilder < ProjectResource > webApp )
48+ IResourceBuilder < ProjectResource > webApp ,
49+ OpenAITarget openAITarget )
4250 {
4351 const string openAIName = "openai" ;
4452
@@ -48,44 +56,76 @@ public static IDistributedApplicationBuilder AddOpenAI(this IDistributedApplicat
4856 const string chatName = "chatModel" ;
4957 const string chatModelName = "gpt-4.1-mini" ;
5058
51- // to use an existing OpenAI resource as a connection string, add the following to the AppHost user secrets:
52- // "ConnectionStrings": {
53- // "openai": "Key=<API Key>" (to use https://api.openai.com/)
54- // -or-
55- // "openai": "Endpoint=https://<name>.openai.azure.com/" (to use Azure OpenAI)
56- // }
57- if ( builder . Configuration . GetConnectionString ( openAIName ) is string openAIConnectionString )
58- {
59- catalogApi . WithReference (
60- builder . AddConnectionString ( textEmbeddingName , ReferenceExpression . Create ( $ "{ openAIConnectionString } ;Deployment={ textEmbeddingModelName } ") ) ) ;
61- webApp . WithReference (
62- builder . AddConnectionString ( chatName , ReferenceExpression . Create ( $ "{ openAIConnectionString } ;Deployment={ chatModelName } ") ) ) ;
63- }
64- else
59+ if ( openAITarget != OpenAITarget . AzureOpenAI )
6560 {
66- // to use Azure provisioning, add the following to the AppHost user secrets:
67- // "Azure": {
68- // "SubscriptionId": "<your subscription ID>",
69- // "ResourceGroupPrefix": "<prefix>",
70- // "Location": "<location>"
71- // }
61+ #pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
62+ IResourceBuilder < ParameterResource > ? endpoint = null ;
63+ if ( openAITarget != OpenAITarget . OpenAI )
64+ {
65+ endpoint = builder . AddParameter ( "OpenAIEndpointParameter" )
66+ . WithDescription ( "The Azure OpenAI endpoint to use, e.g. https://<name>.openai.azure.com/" )
67+ . WithCustomInput ( p => new ( )
68+ {
69+ Label = "Azure OpenAI Endpoint" ,
70+ InputType = InputType . Text ,
71+ Value = "https://<name>.openai.azure.com/" ,
72+ } ) ;
73+ }
7274
73- var openAI = builder . AddAzureOpenAI ( openAIName ) ;
75+ IResourceBuilder < ParameterResource > ? key = null ;
76+ if ( openAITarget is OpenAITarget . OpenAI or OpenAITarget . AzureOpenAIExistingWithKey )
77+ {
78+ key = builder . AddParameter ( "OpenAIKeyParameter" , secret : true )
79+ . WithDescription ( "The OpenAI API key to use." )
80+ . WithCustomInput ( p => new ( )
81+ {
82+ Label = "API Key" ,
83+ InputType = InputType . SecretText
84+ } ) ;
85+ }
7486
75- // to use an existing Azure OpenAI resource via provisioning, add the following to the AppHost user secrets:
76- // "Parameters": {
77- // "openaiName": "<Azure OpenAI resource name>",
78- // "openaiResourceGroup": "<Azure OpenAI resource group>"
79- // }
80- // - or -
81- // leave the parameters out to create a new Azure OpenAI resource
82- if ( builder . Configuration [ "Parameters:openaiName" ] is not null &&
83- builder . Configuration [ "Parameters:openaiResourceGroup" ] is not null )
87+ var chatModel = builder . AddParameter ( "ChatModelParameter" )
88+ . WithDescription ( "The chat model to use." )
89+ . WithCustomInput ( p => new ( )
90+ {
91+ Label = "Chat Model" ,
92+ InputType = InputType . Text ,
93+ Value = chatModelName ,
94+ } ) ;
95+
96+ var embeddingModel = builder . AddParameter ( "EmbeddingModelParameter" )
97+ . WithDescription ( "The embedding model to use." )
98+ . WithCustomInput ( p => new ( )
99+ {
100+ Label = "Text Embedding Model" ,
101+ InputType = InputType . Text ,
102+ Value = textEmbeddingModelName ,
103+ } ) ;
104+ #pragma warning restore ASPIREINTERACTION001
105+
106+ var openAIConnectionBuilder = new ReferenceExpressionBuilder ( ) ;
107+ if ( endpoint is not null )
84108 {
85- openAI . AsExisting (
86- builder . AddParameter ( "openaiName" ) ,
87- builder . AddParameter ( "openaiResourceGroup" ) ) ;
109+ openAIConnectionBuilder . Append ( $ "Endpoint={ endpoint } ") ;
88110 }
111+ if ( key is not null )
112+ {
113+ openAIConnectionBuilder . Append ( $ ";Key={ key } ") ;
114+ }
115+ var openAIConnectionString = openAIConnectionBuilder . Build ( ) ;
116+
117+ catalogApi . WithReference ( builder . AddConnectionString ( textEmbeddingName , cs =>
118+ {
119+ cs . Append ( $ "{ openAIConnectionString } ;Deployment={ embeddingModel } ") ;
120+ } ) ) ;
121+ webApp . WithReference ( builder . AddConnectionString ( chatName , cs =>
122+ {
123+ cs . Append ( $ "{ openAIConnectionString } ;Deployment={ chatModel } ") ;
124+ } ) ) ;
125+ }
126+ else
127+ {
128+ var openAI = builder . AddAzureOpenAI ( openAIName ) ;
89129
90130 var chat = openAI . AddDeployment ( chatName , chatModelName , "2025-04-14" )
91131 . WithProperties ( d =>
0 commit comments