Skip to content

Commit 7594ad9

Browse files
support for envVars and .env; read credentials file
1 parent 858c5d6 commit 7594ad9

File tree

9 files changed

+212
-59
lines changed

9 files changed

+212
-59
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The package supports the following use cases to authenticate.
5353
- Google AI: [Authentication with an API key](https://ai.google.dev/tutorials/setup)
5454
- Google AI: [Authentication with OAuth](https://ai.google.dev/docs/oauth_quickstart)
5555
- Vertex AI: [Authentication with Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev)
56-
- Vertex AI: [Authentication with OAuth 2.0]() (using [Mscc.GenerativeAI.Google](./src/Mscc.GenerativeAI.Google))
56+
- Vertex AI: [Authentication with OAuth]() (using [Mscc.GenerativeAI.Google](./src/Mscc.GenerativeAI.Google))
5757
- Vertex AI: [Authentication with Service Account]() (using [Mscc.GenerativeAI.Google](./src/Mscc.GenerativeAI.Google))
5858

5959
This applies mainly to the instantiation procedure.
@@ -62,6 +62,28 @@ This applies mainly to the instantiation procedure.
6262

6363
Use of Gemini API in either Google AI or Vertex AI is almost identical. The major difference is the way to instantiate the model handling your prompt.
6464

65+
### Using Environment variables
66+
67+
In the cloud most settings are configured via environment variables (EnvVars). The ease of configuration, their wide spread support and the simplicity of environment variables makes them a very interesting option.
68+
69+
| Variable Name | Description |
70+
|--------------------------------|---------------------------------------------------------------|
71+
| GOOGLE_AI_MODEL | The name of the model to use (default is *Model.Gemini10Pro*) |
72+
| GOOGLE_API_KEY | The API key generated in Google AI Studio |
73+
| GOOGLE_PROJECT_ID | Project ID in Google Cloud to access the APIs |
74+
| GOOGLE_REGION | Region in Google Cloud (default is *us-central1*) |
75+
| GOOGLE_ACCESS_TOKEN | The access token required to use models running in Vertex AI |
76+
| GOOGLE_APPLICATION_CREDENTIALS | Path to the application credentials file. |
77+
| GOOGLE_WEB_CREDENTIALS | Path to a Web credentials file. |
78+
79+
Using any environment variable provides a simplified access to a model.
80+
81+
```csharp
82+
using Mscc.GenerativeAI;
83+
84+
var model = new GenerativeModel();
85+
```
86+
6587
### Choose an API and authentication mode
6688

6789
Google AI with an API key

src/Mscc.GenerativeAI/GenerativeModel.cs

Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@ public class GenerativeModel
2727
private const string MediaType = "application/json";
2828

2929
private readonly bool _useVertexAi;
30-
private readonly bool _useHeaderApiKey;
31-
private readonly bool _useHeaderProjectId;
3230
private readonly string _model;
33-
private readonly string? _apiKey;
34-
private readonly string? _projectId;
35-
private readonly string? _region;
31+
private readonly string _region = "us-central1";
3632
private readonly string _publisher = "google";
3733
private readonly JsonSerializerOptions _options;
34+
35+
private bool _useHeaderApiKey;
36+
private string? _apiKey;
3837
private string? _accessToken;
38+
private bool _useHeaderProjectId;
39+
private string? _projectId;
3940
private List<SafetySetting>? _safetySettings;
4041
private GenerationConfig? _generationConfig;
4142
private List<Tool>? _tools;
@@ -117,48 +118,74 @@ private string Method
117118
/// <returns>Name of the model.</returns>
118119
public string Name => _model;
119120

121+
public string? ApiKey
122+
{
123+
set
124+
{
125+
_apiKey = value;
126+
if (!string.IsNullOrEmpty(_apiKey))
127+
{
128+
_useHeaderApiKey = Client.DefaultRequestHeaders.Contains("x-goog-api-key");
129+
if (!_useHeaderApiKey)
130+
{
131+
Client.DefaultRequestHeaders.Add("x-goog-api-key", _apiKey);
132+
}
133+
_useHeaderApiKey = Client.DefaultRequestHeaders.Contains("x-goog-api-key");
134+
}
135+
}
136+
}
137+
120138
public string? AccessToken
121139
{
122-
get => _accessToken;
123140
set
124141
{
125142
_accessToken = value;
126143
if (value != null)
127144
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
128145
}
129146
}
147+
148+
public string? ProjectId
149+
{
150+
set
151+
{
152+
_projectId = value;
153+
if (!string.IsNullOrEmpty(_projectId))
154+
{
155+
_useHeaderProjectId = Client.DefaultRequestHeaders.Contains("x-goog-user-project");
156+
if (!_useHeaderProjectId)
157+
{
158+
Client.DefaultRequestHeaders.Add("x-goog-user-project", _projectId);
159+
}
160+
_useHeaderProjectId = Client.DefaultRequestHeaders.Contains("x-goog-user-project");
161+
}
162+
}
163+
}
130164

131-
// Todo: Integrate Google.Apis.Auth to retrieve Access_Token on demand.
132-
// Todo: Integrate Application Default Credentials as an alternative.
133-
// Reference: https://cloud.google.com/docs/authentication
165+
/// <summary>
166+
/// Default constructor attempts to read environment variables and
167+
/// sets default values, if available
168+
/// </summary>
134169
public GenerativeModel()
135170
{
136171
_options = DefaultJsonSerializerOptions();
137-
_model = Environment.GetEnvironmentVariable("GOOGLE_AI_MODEL") ??
138-
Model.Gemini10Pro;
139-
_apiKey = Environment.GetEnvironmentVariable("GOOGLE_API_KEY");
140-
_projectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID");
141-
_region = Environment.GetEnvironmentVariable("GOOGLE_REGION");
142-
AccessToken = Environment.GetEnvironmentVariable("GOOGLE_ACCESS_TOKEN") ??
143-
GetAccessTokenFromAdc();
144-
172+
GenerativeModelExtensions.ReadDotEnv();
145173
var credentialsFile =
146174
Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS") ??
147175
Environment.GetEnvironmentVariable("GOOGLE_WEB_CREDENTIALS") ??
148176
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "gcloud",
149-
"application_default_credentials.json");
150-
if (File.Exists(credentialsFile))
151-
{
152-
using (var stream = new FileStream(credentialsFile, FileMode.Open, FileAccess.Read))
153-
{
154-
var json = JsonSerializer.DeserializeAsync<JsonElement>(stream, _options).Result;
155-
_projectId ??= json.GetValue("quota_project_id") ??
156-
json.GetValue("project_id");
157-
}
158-
} //var credentials = GoogleCredential.FromFile()
177+
"application_default_credentials.json");
178+
var credentials = GetCredentialsFromFile(credentialsFile);
179+
180+
ApiKey = Environment.GetEnvironmentVariable("GOOGLE_API_KEY");
181+
AccessToken = Environment.GetEnvironmentVariable("GOOGLE_ACCESS_TOKEN");
182+
ProjectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID") ??
183+
credentials?.ProjectId;
184+
_model = Environment.GetEnvironmentVariable("GOOGLE_AI_MODEL") ??
185+
Model.Gemini10Pro;
186+
_region = Environment.GetEnvironmentVariable("GOOGLE_REGION") ?? _region;
159187
}
160188

161-
// Todo: Add parameters for GenerationConfig, SafetySettings, Transport? and Tools
162189
/// <summary>
163190
/// Constructor to initialize access to Google AI Gemini API.
164191
/// </summary>
@@ -171,20 +198,10 @@ public GenerativeModel(string? apiKey = null,
171198
GenerationConfig? generationConfig = null,
172199
List<SafetySetting>? safetySettings = null) : this()
173200
{
174-
_apiKey = apiKey ?? _apiKey;
175-
_model = model.SanitizeModelName() ?? _model;
201+
ApiKey = apiKey ?? _apiKey;
202+
_model = model?.SanitizeModelName() ?? _model;
176203
_generationConfig ??= generationConfig;
177204
_safetySettings ??= safetySettings;
178-
179-
if (!string.IsNullOrEmpty(apiKey))
180-
{
181-
_useHeaderApiKey = Client.DefaultRequestHeaders.Contains("x-goog-api-key");
182-
if (!_useHeaderApiKey)
183-
{
184-
Client.DefaultRequestHeaders.Add("x-goog-api-key", _apiKey);
185-
}
186-
_useHeaderApiKey = Client.DefaultRequestHeaders.Contains("x-goog-api-key");
187-
}
188205
}
189206

190207
/// <summary>
@@ -195,27 +212,19 @@ public GenerativeModel(string? apiKey = null,
195212
/// <param name="model">Model to use</param>
196213
/// <param name="generationConfig"></param>
197214
/// <param name="safetySettings"></param>
198-
internal GenerativeModel(string projectId, string region,
199-
string model = Model.Gemini10Pro,
215+
internal GenerativeModel(string? projectId = null, string? region = null,
216+
string? model = null,
200217
GenerationConfig? generationConfig = null,
201218
List<SafetySetting>? safetySettings = null) : this()
202219
{
203220
_useVertexAi = true;
204-
_projectId = projectId;
205-
_region = region;
206-
_model = model.SanitizeModelName();
221+
AccessToken = Environment.GetEnvironmentVariable("GOOGLE_ACCESS_TOKEN") ??
222+
GetAccessTokenFromAdc();
223+
ProjectId = projectId ?? _projectId;
224+
_region = region ?? _region;
225+
_model = model?.SanitizeModelName() ?? _model;
207226
_generationConfig = generationConfig;
208227
_safetySettings = safetySettings;
209-
210-
if (!string.IsNullOrEmpty(projectId))
211-
{
212-
_useHeaderProjectId = Client.DefaultRequestHeaders.Contains("x-goog-user-project");
213-
if (!_useHeaderProjectId)
214-
{
215-
Client.DefaultRequestHeaders.Add("x-goog-user-project", _projectId);
216-
}
217-
_useHeaderProjectId = Client.DefaultRequestHeaders.Contains("x-goog-user-project");
218-
}
219228
}
220229

221230
/// <summary>
@@ -643,6 +652,32 @@ internal JsonSerializerOptions DefaultJsonSerializerOptions()
643652
return options;
644653
}
645654

655+
/// <summary>
656+
///
657+
/// </summary>
658+
/// <param name="credentialsFile"></param>
659+
/// <returns></returns>
660+
private Credentials? GetCredentialsFromFile(string credentialsFile)
661+
{
662+
Credentials? credentials = null;
663+
if (File.Exists(credentialsFile))
664+
{
665+
var options = DefaultJsonSerializerOptions();
666+
options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
667+
using (var stream = new FileStream(credentialsFile, FileMode.Open, FileAccess.Read))
668+
{
669+
credentials = JsonSerializer.Deserialize<Credentials>(stream, options);
670+
}
671+
}
672+
673+
return credentials;
674+
}
675+
676+
/// <summary>
677+
/// Retrieve access token from Application Default Credentials (ADC)
678+
/// </summary>
679+
/// <returns>The access token.</returns>
680+
// Reference: https://cloud.google.com/docs/authentication
646681
private string GetAccessTokenFromAdc()
647682
{
648683
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))

src/Mscc.GenerativeAI/GenerativeModelExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if NET472_OR_GREATER || NETSTANDARD2_0
22
using System;
3+
using System.IO;
34
using System.Linq;
45
using System.Text.Json;
56
#endif
@@ -32,5 +33,19 @@ public static class GenerativeModelExtensions
3233

3334
return result;
3435
}
36+
37+
public static void ReadDotEnv(string dotEnvFile = ".env")
38+
{
39+
if (!File.Exists(dotEnvFile)) return;
40+
41+
foreach (var line in File.ReadAllLines(dotEnvFile))
42+
{
43+
var parts = line.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
44+
45+
if (parts.Length != 2) continue;
46+
47+
Environment.SetEnvironmentVariable(parts[0], parts[1]);
48+
}
49+
}
3550
}
3651
}

src/Mscc.GenerativeAI/Mscc.GenerativeAI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
2424
<PackageReleaseNotes>
2525
- use EnvVars for default values (parameterless constructor)
26+
- support of .env file
2627
- improve Function Calling
2728
- improve Chat streaming
2829
- improve Embeddings

src/Mscc.GenerativeAI/RELEASE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Release Notes
22

3+
## 0.7.0
4+
5+
- use Environment Variables for default values (parameterless constructor)
6+
- support of .env file
7+
- improve Function Calling
8+
- improve Chat streaming
9+
- improve Embeddings
10+
311
## 0.6.1
412

513
- implement Function Calling
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace Mscc.GenerativeAI
2+
{
3+
public class Credentials : ClientSecrets
4+
{
5+
private string _projectId;
6+
7+
public ClientSecrets Web { get; set; }
8+
public ClientSecrets Installed { get; set; }
9+
10+
public string Account { get; set; }
11+
public string RefreshToken { get; set; }
12+
public string Type { get; set; }
13+
public string UniverseDomain { get; set; }
14+
15+
public string ProjectId
16+
{
17+
get => _projectId;
18+
set => _projectId = value;
19+
}
20+
21+
public virtual string QuotaProjectId
22+
{
23+
get => _projectId;
24+
set => _projectId = value;
25+
}
26+
}
27+
28+
public class ClientSecrets
29+
{
30+
public string ClientId { get; set; }
31+
public string ClientSecret { get; set; }
32+
public string[] RedirectUris { get; set; }
33+
public string AuthUri { get; set; }
34+
public string AuthProviderX509CertUrl { get; set; }
35+
public string TokenUri { get; set; }
36+
}
37+
}

tests/Mscc.GenerativeAI.Google/ConfigurationFixture.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class ConfigurationFixture : ICollectionFixture<ConfigurationFixture>
2626
// Ref: https://cloud.google.com/vertex-ai/docs/start/client-libraries
2727
public ConfigurationFixture()
2828
{
29+
ReadDotEnv();
30+
2931
Configuration = new ConfigurationBuilder()
3032
.AddJsonFile("appsettings.json", optional: true)
3133
.AddJsonFile("appsettings.user.json", optional: true)
@@ -126,5 +128,19 @@ private string Format(string filename, string arguments)
126128
((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
127129
"'";
128130
}
131+
132+
private void ReadDotEnv(string dotEnvFile = ".env")
133+
{
134+
if (!File.Exists(dotEnvFile)) return;
135+
136+
foreach (var line in File.ReadAllLines(dotEnvFile))
137+
{
138+
var parts = line.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
139+
140+
if (parts.Length != 2) continue;
141+
142+
Environment.SetEnvironmentVariable(parts[0], parts[1]);
143+
}
144+
}
129145
}
130146
}

tests/Mscc.GenerativeAI/ConfigurationFixture.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class ConfigurationFixture : ICollectionFixture<ConfigurationFixture>
2525
// Ref: https://cloud.google.com/vertex-ai/docs/start/client-libraries
2626
public ConfigurationFixture()
2727
{
28+
ReadDotEnv();
29+
2830
Configuration = new ConfigurationBuilder()
2931
.AddJsonFile("appsettings.json", optional: true)
3032
.AddJsonFile("appsettings.user.json", optional: true)
@@ -120,5 +122,19 @@ private string Format(string filename, string arguments)
120122
((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
121123
"'";
122124
}
125+
126+
private void ReadDotEnv(string dotEnvFile = ".env")
127+
{
128+
if (!File.Exists(dotEnvFile)) return;
129+
130+
foreach (var line in File.ReadAllLines(dotEnvFile))
131+
{
132+
var parts = line.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
133+
134+
if (parts.Length != 2) continue;
135+
136+
Environment.SetEnvironmentVariable(parts[0], parts[1]);
137+
}
138+
}
123139
}
124140
}

0 commit comments

Comments
 (0)