# ExtAIBot — Microsoft.Extensions.AI sample
A Teams bot powered by [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) and Azure OpenAI. Demonstrates streaming responses, per-conversation memory, a local clarification tool, remote MCP server tools, inline citations, follow-up suggestions, and custom feedback.
## Features
- **Streaming** — token-by-token replies via `TeamsStreamingWriter`
- **Conversation memory** — each conversation keeps its own `List<ChatMessage>` so the bot remembers context across turns
- **Local tool** — the model calls `request_clarification` when the user's request is ambiguous; the bot replies with an Adaptive Card listing 2–4 candidate interpretations
- **MCP client** — connects to the [Microsoft Learn docs MCP server](https://learn.microsoft.com/api/mcp) at startup; its tools are passed alongside the local tool in every `ChatOptions`
- **Inline citations** — MCP tool results are intercepted by `CitationCapturingTool` to extract source URLs; citations render as `[1]`, `[2]`, etc. in the Teams message
- **Follow-up suggestions** — after each reply, a structured-output call produces two short follow-up prompts shown as suggested-action chips
- **Custom feedback** — every text reply enables `FeedbackType.Custom`; clicking thumbs up/down opens a bot-rendered task module form, and submissions are handled by the typed `OnMessageSubmitFeedback` route
## Prerequisites
- .NET 10 SDK
- Azure OpenAI resource with a deployed model (e.g. `gpt-4o`)
- Teams bot registration (App ID + client secret)
## Setup
Fill in `appsettings.json` with your Azure OpenAI details:
```json
{
"AzureOpenAI": {
"Endpoint": "https://<your-resource>.openai.azure.com",
"ApiKey": "<your-api-key>",
"ModelId": "<deployment-name>"
}
}
```
`ModelId` is the **deployment name**, not the base model name.
Configure bot credentials via environment variables (or `launchSettings.json`):
```
AzureAD__TenantId=<tenant-id>
AzureAD__ClientId=<app-id>
AzureAD__ClientCredentials__0__SourceType=ClientSecret
AzureAD__ClientCredentials__0__ClientSecret=<client-secret>
```
Then point your bot's messaging endpoint at this service (e.g. using [Dev Tunnels](https://learn.microsoft.com/azure/developer/dev-tunnels/overview) for local development).
## Running
```bash
cd samples/ExtAIBot
dotnet run
```
The bot initializes the MS Learn MCP tool set at startup before accepting messages. If the MCP server is unreachable the app will fail to start.
## Example interactions
- `Tell me about streaming` — ambiguous request: the model calls `request_clarification` and the bot replies with a clarification card.
- `How do I stream in teams.net?` — model calls an MS Learn search tool, replies with docs-grounded answer and inline citations, plus two follow-up chips
- `How do I list users with Microsoft Graph?` — same MCP search path, but lands on Graph documentation; reply cites the relevant `/users` endpoint docs and shows a code snippet
### Clarification flow
When the user's message is ambiguous, the model calls `request_clarification` with a question and 2–4 options. `LocalTools` builds an Adaptive Card with an `Action.Execute` whose `verb` is `"clarification"`. The bot finalizes the reply as an attachment-only message (no text, no feedback loop) so the card stands alone.
When the user picks an option, Teams sends an `adaptiveCard/action` invoke. `OnAdaptiveCardAction` reads `clarificationChoice` from the action's data and feeds it back through the agent as the next user turn.
### Feedback flow
Every text reply is finalized with `FeedbackType.Custom`, which renders thumbs up/down on the bot bubble. Clicking either button sends a `message/fetchTask` invoke; `OnMessageFetchTask` returns a task module containing a follow-up text form built with `Microsoft.Teams.Cards`. On submit, the typed `OnMessageSubmitFeedback` route fires with `context.Activity.Value` already deserialized to `MessageSubmitFeedbackValue { Reaction, Feedback }`. (`Feedback` is the form payload as a JSON-encoded string — Teams wraps the inputs the bot defined in its task module.)
### How MCP tools are wired in
At startup, `McpToolSet.CreateAsync` connects to the MS Learn MCP server using the Streamable HTTP transport and lists its tools. Each `McpClientTool` is an `AIFunction` that holds a reference to the client and calls the server when invoked.
Each tool is wrapped in a `CitationCapturingTool` (a `DelegatingAIFunction`) that intercepts the result to extract citation URLs before passing it back to the model. These are spread into `ChatOptions.Tools` alongside the local clarification tool:
```csharp
ChatOptions options = new()
{
Tools =
[
LocalTools.CreateClarificationCardTool(pendingCards, _logger),
.. _mcpTools.GetTools(citations)
]
};
```
`UseFunctionInvocation()` then handles all tool calls — local or remote — transparently during streaming.microsoft/teams.net
Publicmirrored fromhttps://github.com/microsoft/teams.netAvailable
core/samples/ExtAIBot/README.md
91lines · modepreview