Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions BlogWriter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>162971f9-9e16-430b-9879-eef4676fe8c8</UserSecretsId>
</PropertyGroup>

<ItemGroup>
Expand All @@ -17,6 +18,9 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.AI" Version="10.7.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.7.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.10" />
<PackageReference Include="OpenAI" Version="2.11.0" />
Expand Down
30 changes: 16 additions & 14 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Program>()
.AddEnvironmentVariables()
.Build();
Comment on lines +11 to +16

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}\" \"<value>\"");
Comment on lines +18 to +20

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.
Expand Down Expand Up @@ -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) =>
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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" "<your-openai-api-key>"
dotnet user-secrets set "OPENAI_API_BASE" "https://aibe.mygreatlearning.com/openai/v1"
dotnet user-secrets set "TAVILY_API_KEY" "<your-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
```