From f39337ac2003cb767bcdf9e8abc43c3834f88abd Mon Sep 17 00:00:00 2001 From: Jesse Liberty Date: Tue, 11 Aug 2026 12:07:31 -0400 Subject: [PATCH] switched from config.json to secrets --- BlogWriter.csproj | 4 ++++ Program.cs | 30 ++++++++++++++++-------------- README.md | 24 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 15 deletions(-) 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 +``` +