D4S.Agent
1.1.5
dotnet add package D4S.Agent --version 1.1.5
NuGet\Install-Package D4S.Agent -Version 1.1.5
<PackageReference Include="D4S.Agent" Version="1.1.5" />
<PackageVersion Include="D4S.Agent" Version="1.1.5" />
<PackageReference Include="D4S.Agent" />
paket add D4S.Agent --version 1.1.5
#r "nuget: D4S.Agent, 1.1.5"
#:package D4S.Agent@1.1.5
#addin nuget:?package=D4S.Agent&version=1.1.5
#tool nuget:?package=D4S.Agent&version=1.1.5
D4S.Agent
Reusable Microsoft Agent Framework implementation for Microsoft 365 / Teams bots. Add one DI
call, depend on IMafAgent, and the package owns the agent turn, guardrail, middlewares, tools, and
prompt composition — your host keeps only auth, storage, and bot wiring.
Features
- Single-call setup —
AddMafAgent(configuration)wires chat clients, middlewares, plugins, guardrail, and MCP tools. - Agent turn —
InvokeAgentAsyncwith session persistence; returns text or an Adaptive Card. - Topic guardrail — off-topic messages get a polite redirect, no main-model call.
- Middlewares — thinking-UX labels, audit logging, loop cap, Teams keep-alive heartbeat.
- Citations — retrieval sources are captured from tool results and appended as a short source list.
- Tools — built-in Adaptive Card tool, host-contributed tools, and MCP server tools.
- Prompt composer — one folder per customer under
Prompts/<customer>(basealways-on generic +specificalways-on customer prompts and on-demand tool/MCP instructions files), selected viaBot:PromptsFolder.
Install
dotnet add package D4S.Agent
Quick start
Register everything the agent needs in one call:
builder.Services.AddMafAgent(builder.Configuration);
Depend on IMafAgent from your bot and render its response:
var response = await _agent.InvokeAgentAsync(turnContext, turnState, cancellationToken);
// response.ContentType ∈ { Text, AdaptiveCard }; response.Content is the payload.
Your host must:
- provide the
MicrosoftFoundryconfig — a validEndpointis required at startup; - set
Bot:PromptsFolderto a customer folder name underPrompts/(e.g.Customer1); - copy that customer's
Prompts/<customer>/base/*.mdandPrompts/<customer>/specific/*.mdto its output directory — the composer reads them fromAppContext.BaseDirectory(plusSkills/whenBot:AgentSkillsEnabledis on).
Configuration
| Section | Purpose |
|---|---|
MicrosoftFoundry |
Azure OpenAI Endpoint / ApiKey / Deployment for the main chat client. |
MicrosoftFoundryGuardrail |
Smaller deployment for the guardrail. Endpoint/ApiKey fall back to MicrosoftFoundry when empty; Deployment does not, and is required when Bot:TopicGuardEnabled is true — leave it empty with the toggle off and nothing is constructed. |
Bot |
Feature toggles + tuning (topic guard, Adaptive Cards, skills, heartbeat, loop cap…). |
Mcp |
MCP servers whose tools are exposed to the agent. |
Citations |
Source-list settings: Enabled, ToolNames (default ["search", "ask"]), Header (default Sources), MaxCitations (0 = unlimited), CollapseByDocument (default false). |
Citation tool contract
For each tool listed in Citations:ToolNames, the citation middleware reads a citations collection
from the result root or from the MCP structuredContent object. Every item can contain title,
sourceUrl, and score; at least title or sourceUrl must be present. A #page=N fragment in
sourceUrl is displayed as the source page, and score controls source ordering.
Sources are deduplicated on sourceUrl, falling back to title when there is no URL, and the entry
kept is always the highest-scoring one. Citations:CollapseByDocument decides how much the key
covers:
false(the default) — the whole URL, fragment included, so a retrieval that projects page anchors lists the same document once per cited page. On a long document that is the useful shape: the reader gets the sections that answered the question, not one link to the document.true— the URL without its fragment, so a document is listed once, at the page it ranked best.
Turning it on is also the way to shorten the list for ask, whose citations the retrieval side never
collapses — one per [ref_id:N] reference by design.
The D4S Knowledge Retrieval MCP conforms to this contract for both search and ask. Its search
response still contains passages for grounding and compatibility, but the Agent reads citations
only from the common citations collection.
Architecture
MafAgent is a thin orchestrator. Each turn it delegates to three focused services, every one behind
an interface and registered as a replaceable default:
| Interface | Default | Responsibility |
|---|---|---|
IAgentBuilder |
AgentBuilderService |
Composes the instructions, tools, and middleware pipeline into the AIAgent. |
IMcpToolkit |
McpToolkitService |
Discovers MCP server tools (per turn) and disposes their clients. |
ITopicGuard |
TopicGuardService, or NoOpTopicGuard when Bot:TopicGuardEnabled is false |
Off-topic classification, redirect generation, and guard history. |
The whole turn loop itself is IMafAgent (default MafAgent).
MafAgent is transient, so all three are built on every turn. That is why ITopicGuard resolves to
NoOpTopicGuard while Bot:TopicGuardEnabled is false: registering the classifying implementation
would construct its keyed chat client — and demand a guardrail deployment — for a bot that never
classifies anything.
Extending
AddMafAgent returns a MafAgentBuilder, so overrides chain off it. Each WithCustomXxx replaces
the matching default registration, so call order doesn't matter and your implementation always wins.
Add a tool (code):
AddMafAgent(config, o => o.AdditionalTools.Add(new AgentTool(sp => AIFunctionFactory.Create(...), InstructionsFile: "my-tool.md"))). The optional.md(shipped in the customer'sPrompts/<customer>/specificfolder) is appended to the instructions on demand.Add MCP servers (config): list them under
Mcp:Servers(HttporStdio). An optionalToolsallow-list selects a subset; unreachable servers are logged and skipped (fail-soft).Replace a single concern: chain the matching builder method, passing your implementation of that seam's interface:
builder.Services .AddMafAgent(builder.Configuration) .WithCustomAgentBuilder<MyAgentBuilder>() // IAgentBuilder — instruction/tool/middleware composition .WithCustomMcpToolkit<MyMcpToolkit>() // IMcpToolkit — MCP discovery + disposal .WithCustomTopicGuard<MyTopicGuard>(); // ITopicGuard — classification, redirect, historyReplace the whole turn loop:
.WithCustomAgent<MyAgent>()with your ownIMafAgent— the heaviest seam; use only for a fundamentally different flow.
Requirements
net10.0- Key dependencies:
Microsoft.Agents.AI,Microsoft.Extensions.AI(.OpenAI),Microsoft.Agents.Builder,Azure.AI.OpenAI,ModelContextProtocol.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Azure.AI.OpenAI (>= 2.9.0-beta.1)
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.29.1)
- Microsoft.Agents.AI (>= 1.13.0)
- Microsoft.Agents.Builder (>= 1.6.150)
- Microsoft.Extensions.AI (>= 10.8.0)
- Microsoft.Extensions.AI.OpenAI (>= 10.8.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- ModelContextProtocol (>= 1.4.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.