This repository demonstrates a local-first AI agent in C# using:
- .NET 10
- Microsoft Agent Framework
- Microsoft.Extensions.AI (
IChatClient) - Foundry Local SDK (in-process, no REST server)
- Documentation location rule: all docs live under
docs\, exceptREADME.mdandLICENSEat root. - Image generation prompts are tracked in
docs\image-prompts.md.
Foundry Local does not currently provide a first-party MEAI provider, while Agent Framework expects an IChatClient-compatible provider.
This sample bridges that gap with a custom FoundryLocalChatClientAdapter.
Console Host (src/ElBruno.MAF.FoundryLocal.Console)
-> ChatClientAgent (Microsoft Agent Framework)
-> IChatClient (Microsoft.Extensions.AI)
-> FoundryLocalChatClientAdapter (src/ElBruno.MAF.FoundryLocal)
-> FoundryLocalModelLifecycleService
-> Foundry Local SDK
using ElBruno.MAF.FoundryLocal;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
var builder = Host.CreateApplicationBuilder(args);
// ElBruno.MAF.FoundryLocal options for model lifecycle (alias, auto-download, unload behavior).
builder.Services.Configure<FoundryLocalOptions>(o =>
{
o.ModelAlias = "qwen2.5-0.5b";
o.DownloadIfMissing = true;
o.UnloadOnExit = true;
});
builder.Services.Configure<ChatRuntimeOptions>(_ => { });
// ElBruno.MAF.FoundryLocal service that handles Foundry Local manager/model/chat client lifecycle.
builder.Services.AddSingleton<FoundryLocalModelLifecycleService>();
// Core value of this library:
// register FoundryLocalChatClientAdapter so Foundry Local is exposed as MEAI IChatClient.
builder.Services.AddSingleton<IChatClient, FoundryLocalChatClientAdapter>();
using var host = builder.Build();
// From here on you code against standard IChatClient, not Foundry Local-specific SDK calls.
var chatClient = host.Services.GetRequiredService<IChatClient>();
var response = await chatClient.GetResponseAsync(
[
new(ChatRole.User, "Explain local AI agents in one short paragraph.")
]);
Console.WriteLine(response.Text);This is the key benefit of this library: once Foundry Local is exposed as IChatClient,
you can pass that same IChatClient to Agent Framework and use local inference through
standard agent APIs.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
// Reuse the IChatClient instance created in the previous snippet.
// No Foundry Local-specific types are needed here.
var agent = new ChatClientAgent(
chatClient: chatClient,
// Agent Framework sends these instructions through the same IChatClient pipeline.
instructions: "You are a concise local assistant.",
name: "LocalFoundryAgent",
description: "Foundry Local + MEAI adapter",
tools: null,
loggerFactory: null,
services: null);
var agentResponse = await agent.RunAsync(
"Give me 3 bullet points about local-first AI in .NET.",
session: null,
options: null);
// The call above is routed to Foundry Local through ElBruno.MAF.FoundryLocal adapter.
Console.WriteLine(agentResponse);dotnet add package ElBruno.MAF.FoundryLocal.Adapter
dotnet add package Microsoft.Agents.AI
dotnet add package Microsoft.Extensions.HostingThis README is intentionally focused on the simplest adoption path:
- Foundry Local SDK wrapped as
IChatClient. - Reusing that same
IChatClientto create agents with Microsoft Agent Framework.
Advanced scenarios (console modes, Aspire demo, RAG/workflow extras) can be expanded later in docs\.
| Capability | Status | Notes |
|---|---|---|
| Local model download/load/unload | Yes | Uses Foundry Local SDK lifecycle |
| Non-streaming chat | Yes | Via adapter GetResponseAsync |
| Streaming chat | Best effort | Via adapter GetStreamingResponseAsync |
| Agent Framework single-agent flow | Yes | ChatClientAgent based |
MEAI IChatClient |
Yes | Custom adapter |
| Tool calling (MEAI -> Foundry Local) | Partial | ChatOptions.Tools are mapped for AIFunction-based tools |
| Tool call response mapping | Partial | Function tool calls are mapped back to FunctionCallContent |
Demo tool (get_current_time) |
Yes | Registered in console ChatClientAgent |
| Embedding adapter path | Yes (best effort) | Uses IModel.GetEmbeddingClientAsync when available |
| Local RAG sample | Yes | In-memory corpus + embedding similarity + grounded prompt |
| REST server usage | No | Explicitly avoided |
The adapter project (src/ElBruno.MAF.FoundryLocal) is prepared with NuGet package metadata so it can be reused without project-to-project coupling.
- Local pack command:
dotnet pack src\ElBruno.MAF.FoundryLocal\ElBruno.MAF.FoundryLocal.csproj -c Release -o .\artifacts\packages
- Packaging and migration guide:
docs\adapter-packaging-and-migration.md
The sample solution continues to use ProjectReference for in-repo development. Consumers can migrate to PackageReference using the guide above.
- Tool schema mapping currently supports JSON-schema object roots with common primitive/object/array shapes; complex schemas may be skipped.
- Tool calling/function invocation behavior remains model/path dependent and intentionally conservative.
- Some advanced chat options are logged as unsupported by the adapter.
- RAG mode is a minimal sample with a tiny in-memory corpus (no persistence, no vector DB, no chunking pipeline).
- Embeddings depend on model/runtime support; if unavailable,
--ragreturns a clear message and exits gracefully. - Workflow mode performs two model calls per user prompt (planner + responder), so expect higher latency than default mode.
--workflowand--ragare intentionally not combined in this sample flow.- This is an experimental bridge, not official Foundry Local MEAI integration.
- First run may take longer due to model download/load.
- If a model alias is invalid, run
--list-modelsand choose an available alias. - If model download is disabled and cache is empty, enable
DownloadIfMissing.
Hi! I'm ElBruno π§‘, a passionate developer and content creator exploring AI, .NET, and modern development practices.
Made with β€οΈ by ElBruno
If you like this project, consider following my work across platforms:
- π Blog: ElBruno.com β Deep dives on embeddings, RAG, .NET, and local AI
- π» YouTube: youtube.com/elbruno β Demos, tutorials, and live coding
- πΊ LinkedIn: @elbruno β Professional updates and insights
- π Twitter: @elbruno β Quick tips, releases, and tech news
- π» Podcast: No Tienen Nombre β Spanish-language episodes on AI, development, and tech culture
This project is licensed under the MIT License.