diff --git a/BlogWriter.csproj b/BlogWriter.csproj
index 8576a75..ba67b23 100644
--- a/BlogWriter.csproj
+++ b/BlogWriter.csproj
@@ -7,6 +7,7 @@
net10.0
enable
enable
+ 162971f9-9e16-430b-9879-eef4676fe8c8
@@ -17,6 +18,9 @@
+
+
+
diff --git a/Program.cs b/Program.cs
index eef316d..a4b37a1 100644
--- a/Program.cs
+++ b/Program.cs
@@ -2,32 +2,34 @@
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Net.Http.Json;
-using System.Text.Json;
using BlogWriter;
using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenAI;
-const string fileName = "config.json";
-
-using var stream = File.OpenRead(fileName);
-using var document = JsonDocument.Parse(stream);
-JsonElement config = document.RootElement;
+// Secrets come from the .NET user-secrets store in development and from
+// environment variables in CI/production (env vars win on key collisions).
+IConfiguration config = new ConfigurationBuilder()
+ .AddUserSecrets()
+ .AddEnvironmentVariables()
+ .Build();
-string? GetValue(string key) =>
- config.TryGetProperty(key, out JsonElement value) ? value.GetString() : null;
+string GetRequired(string key) =>
+ config[key] ?? throw new InvalidOperationException(
+ $"Missing configuration value '{key}'. Set it with: dotnet user-secrets set \"{key}\" \"\"");
-Environment.SetEnvironmentVariable("OPENAI_API_KEY", GetValue("API_KEY"));
-Environment.SetEnvironmentVariable("OPENAI_BASE_URL", GetValue("OPENAI_API_BASE"));
-Environment.SetEnvironmentVariable("TAVILY_API_KEY", GetValue("TAVILY_API_KEY"));
+string openAiApiKey = GetRequired("API_KEY");
+string openAiApiBase = GetRequired("OPENAI_API_BASE");
+string tavilyApiKey = GetRequired("TAVILY_API_KEY");
string modelName = "gpt-4o-mini";
var openAIClient = new OpenAIClient(
- new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!),
+ new ApiKeyCredential(openAiApiKey),
new OpenAIClientOptions
{
- Endpoint = new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL")!)
+ Endpoint = new Uri(openAiApiBase)
});
// Build the IChatClient pipeline once and share it across all agents.
@@ -64,7 +66,7 @@
var tavilyHttpClient = new HttpClient { BaseAddress = new Uri("https://api.tavily.com/") };
tavilyHttpClient.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TAVILY_API_KEY"));
+ new AuthenticationHeaderValue("Bearer", tavilyApiKey);
AIFunction tavilyTool = AIFunctionFactory.Create(
async (string query) =>
diff --git a/README.md b/README.md
index beb8da8..44cfd35 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,24 @@
# Demo code associated with series of blog posts on https://jesseliberty.com
-
+
+## Configuration
+
+Secrets are read from the .NET [user-secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) store in
+development and from environment variables in CI/production. There is no `config.json` file.
+
+Set the required values once per machine:
+
+```pwsh
+dotnet user-secrets set "API_KEY" ""
+dotnet user-secrets set "OPENAI_API_BASE" "https://aibe.mygreatlearning.com/openai/v1"
+dotnet user-secrets set "TAVILY_API_KEY" ""
+```
+
+Alternatively, provide the same keys as environment variables (`API_KEY`, `OPENAI_API_BASE`, `TAVILY_API_KEY`);
+environment variables override user secrets.
+
+Then run:
+
+```pwsh
+dotnet run
+```
+