Skip to content

Add Foundry Local as a chat and embedding provider in the AI Chat Web template#7586

Open
luisquintanilla wants to merge 6 commits into
dotnet:mainfrom
luisquintanilla:foundry-local-nonaspire
Open

Add Foundry Local as a chat and embedding provider in the AI Chat Web template#7586
luisquintanilla wants to merge 6 commits into
dotnet:mainfrom
luisquintanilla:foundry-local-nonaspire

Conversation

@luisquintanilla

@luisquintanilla luisquintanilla commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #7585

What

Adds Foundry Local as a chat and embedding provider option in the AI Chat Web template
(Microsoft.Extensions.AI.Templates, AIChatWeb-CSharp). Foundry Local is Microsoft's
on-device inference runtime, so this gives the template a Microsoft-owned local option
alongside the existing third-party Ollama path. Both the chat IChatClient and the embedding
IEmbeddingGenerator are wired.

Select it with --provider foundrylocal (or the "Foundry Local (on-device)" choice in the
interactive flow).

How it works

Foundry Local is OpenAI-compatible, so it reuses the template's existing
OpenAIClient -> AsIChatClient() / AsIEmbeddingGenerator() shape. The generated app uses
FoundryLocalManager to start the local web service and load the models, then points the
standard official OpenAI client at the local endpoint (keyless on localhost).

Defaults: qwen3-4b for chat (Apache-2.0, tool-calling capable), qwen3-embedding-0.6b for
embeddings (1024-dimension vectors). Both model ids are overridable via template parameters.

Changes

  • template.json: new foundrylocal choice, IsFoundryLocal symbol, default model
    parameters (qwen3-4b / qwen3-embedding-0.6b).
  • Program.cs: #elif (IsFoundryLocal) wiring block.
  • .csproj-in: adds Microsoft.AI.Foundry.Local and Microsoft.Extensions.AI.OpenAI.
  • eng/packages/ProjectTemplates.props: registers Microsoft.AI.Foundry.Local 1.2.3.
  • Services/IngestedChunk.cs: VectorDimensions becomes per-provider (1024 for Foundry Local).
  • README.md, appsettings*.json: Foundry Local notes.
  • Snapshot test + verified snapshot for the new provider.

Testing

  • Snapshot tests pass (6/6).
  • The generated app compiles and runs end to end (chat + RAG over a local PDF + SQLite vector
    store) against Foundry Local.

References

Microsoft Reviewers: Open in CodeFlow

Copilot AI review requested due to automatic review settings June 25, 2026 23:26
@luisquintanilla luisquintanilla requested review from a team as code owners June 25, 2026 23:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Foundry Local as a selectable chat + embeddings provider for the AI Chat Web project template, including generated configuration, package references, and a new snapshot test/verified snapshot for the provider.

Changes:

  • Adds --provider foundrylocal template option and wires Foundry Local startup + OpenAI-compatible clients in the generated app.
  • Updates template defaults/config for Foundry Local (models + endpoint) and adjusts embedding vector dimensions accordingly.
  • Registers the new NuGet dependency and expands snapshot testing to cover the new provider.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Services/SemanticSearch.cs Verified snapshot output for Foundry Local variant (semantic search service).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Services/Ingestion/PdfPigReader.cs Verified snapshot output for Foundry Local variant (PDF ingestion reader).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Services/Ingestion/DocumentReader.cs Verified snapshot output for Foundry Local variant (document reader dispatch).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Services/Ingestion/DataIngestor.cs Verified snapshot output for Foundry Local variant (ingestion pipeline wiring).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Services/IngestedChunk.cs Verified snapshot output for Foundry Local variant (vector schema/dimensions).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/README.md Verified snapshot output for Foundry Local setup instructions.
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Properties/launchSettings.json Verified snapshot output with fixed ports for deterministic testing.
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Program.cs Verified snapshot output for Foundry Local runtime wiring.
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Routes.razor Verified snapshot output (routing).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Error.razor Verified snapshot output (error page).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatSuggestions.razor.css Verified snapshot output (chat UI styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatSuggestions.razor Verified snapshot output (chat suggestions component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatMessageList.razor.js Verified snapshot output (chat message list JS).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatMessageList.razor.css Verified snapshot output (chat message list styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatMessageList.razor Verified snapshot output (chat message list component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatMessageItem.razor.css Verified snapshot output (chat message item styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatMessageItem.razor Verified snapshot output (chat message item rendering/citations).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatInput.razor.js Verified snapshot output (chat input JS).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatInput.razor.css Verified snapshot output (chat input styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatInput.razor Verified snapshot output (chat input component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatHeader.razor.css Verified snapshot output (chat header styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatHeader.razor Verified snapshot output (chat header component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatCitation.razor.css Verified snapshot output (citation styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/ChatCitation.razor Verified snapshot output (citation link construction).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/Chat.razor.css Verified snapshot output (chat page styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Pages/Chat/Chat.razor Verified snapshot output (chat page + tool usage).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/SurveyPrompt.razor.css Verified snapshot output (survey prompt styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/SurveyPrompt.razor Verified snapshot output (survey prompt component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/MainLayout.razor.css Verified snapshot output (layout styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/MainLayout.razor Verified snapshot output (main layout).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/LoadingSpinner.razor.css Verified snapshot output (spinner styles).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/Layout/LoadingSpinner.razor Verified snapshot output (spinner component).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/App.razor Verified snapshot output (app host markup).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/Components/_Imports.razor Verified snapshot output (Razor imports).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/appsettings.json Verified snapshot output (Foundry Local configuration).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/appsettings.Development.json Verified snapshot output (Foundry Local dev configuration).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.foundryl_l_webHttpPort_9996_webHttpsPort_9995.verified/aichatweb/aichatweb.csproj Verified snapshot output (Foundry Local + AI.OpenAI package references).
test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebSnapshotTests.cs Adds snapshot coverage for --provider foundrylocal.
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Services/IngestedChunk.cs Sets embedding vector dimensions for Foundry Local outputs.
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/README.md Documents Foundry Local setup path in non-Aspire template output.
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Program.cs Adds Foundry Local startup + OpenAI-compatible chat/embedding wiring.
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/appsettings.json Adds Foundry Local configuration block (conditional).
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/appsettings.Development.json Adds Foundry Local dev configuration block (conditional).
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/AIChatWeb-CSharp.Web.csproj-in Adds Foundry Local package references for non-Aspire outputs.
src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/.template.config/template.json Adds the new provider choice + computed symbol + default model symbols.
eng/packages/ProjectTemplates.props Registers Microsoft.AI.Foundry.Local package version for templates.

Comment on lines +153 to 157
{
"choice": "foundrylocal",
"displayName": "Foundry Local (on-device)",
"description": "Uses Foundry Local with the qwen3-4b and qwen3-embedding-0.6b models"
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the options mutually exclusive is good. We can follow a different approach though, similar to how we handle qdrant.

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 91.95 🔻
Microsoft.Extensions.AI Line 89 88.54 🔻
Microsoft.Extensions.AI Branch 89 88.57 🔻
Microsoft.Extensions.AI.OpenAI Line 75 62.89 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 50.41 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 94.39 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.96 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 85
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 84
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1482171&view=codecoverage-tab

@jeffhandley jeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a local session to incorporate this feedback and to make Foundry Local the default, replacing GitHub models. I will push to your branch.

Comment on lines +61 to +76
async Task<string> EnsureFoundryModelAsync(string alias)
{
var model = await foundryCatalog.GetModelAsync(alias)
?? throw new InvalidOperationException(
$"Foundry Local model '{alias}' was not found in the catalog. Run 'foundry model list' to see available models.");
if (!await model.IsCachedAsync())
{
Console.WriteLine($"Foundry Local: downloading model '{alias}' (first run only)...");
await model.DownloadAsync(_ => { });
}
if (!await model.IsLoadedAsync())
{
await model.LoadAsync();
}
return model.Id;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async Task<string> EnsureFoundryModelAsync(string alias)
{
var model = await foundryCatalog.GetModelAsync(alias)
?? throw new InvalidOperationException(
$"Foundry Local model '{alias}' was not found in the catalog. Run 'foundry model list' to see available models.");
if (!await model.IsCachedAsync())
{
Console.WriteLine($"Foundry Local: downloading model '{alias}' (first run only)...");
await model.DownloadAsync(_ => { });
}
if (!await model.IsLoadedAsync())
{
await model.LoadAsync();
}
return model.Id;
}
async Task<string> EnsureFoundryModelAsync(string modelAlias)
{
var model = await foundryCatalog.GetModelAsync(modelAlias)
?? throw new InvalidOperationException(
$"Foundry Local model '{modelAlias}' was not found in the catalog. Run 'foundry model list' to see available models.");
if (!await model.IsCachedAsync())
{
Console.WriteLine($"Foundry Local: downloading model '{modelAlias}' (first run only)...");
await model.DownloadAsync(_ => { });
}
if (!await model.IsLoadedAsync())
{
await model.LoadAsync();
}
return model.Id;
}

Comment on lines +153 to 157
{
"choice": "foundrylocal",
"displayName": "Foundry Local (on-device)",
"description": "Uses Foundry Local with the qwen3-4b and qwen3-embedding-0.6b models"
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the options mutually exclusive is good. We can follow a different approach though, similar to how we handle qdrant.

@dotnet-policy-service dotnet-policy-service Bot added the waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue. label Jul 8, 2026
jeffhandley and others added 4 commits July 8, 2026 04:21
…plate

- Emit a build-time #error from Program.Aspire.cs when IsFoundryLocal is set, covering the Qdrant vector store which forces Aspire.
- Add foundrylocal to the execution test provider matrix and skip foundrylocal combined with Aspire or Qdrant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ichatweb template

Update the matching verified snapshot to keep it in sync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… the aichatweb template

GitHub Models is no longer available to new customers:
https://github.blog/changelog/2026-06-16-github-models-is-no-longer-available-to-new-customers/

- Set the AiServiceProvider default to foundrylocal and remove the githubmodels choice and the IsGHModels symbol.
- Remove GitHub Models wiring from Program.cs, Program.Aspire.cs, AppHost.cs, the project file, and both READMEs.
- Remove githubmodels from the execution test matrix and switch the Aspire snapshot case to --provider=openai.
- Regenerate the default snapshot for Foundry Local, add the openai+Aspire snapshot, and remove the orphaned GitHub Models Aspire snapshot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Microsoft.AI.Foundry.Local.Core package injects a RuntimeIdentifier at build time, so restore must be RID-aware for the generated project to resolve its per-RID native assets. Declare RuntimeIdentifiers on the Foundry Local project and update the default and explicit Foundry Local snapshots.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jeffhandley

Copy link
Copy Markdown
Member

Heads-up: reasoning models render an inline <think> block in the chat UI

While demoing the generated Foundry Local app (--provider foundrylocal, default model qwen3-4b), the assistant response rendered a raw <think>...</think> block as part of the answer, and there was no "thinking" status while the model reasoned.

Root cause (not specific to this template): qwen3 is a reasoning model that emits its chain-of-thought inline as <think>...</think> in the standard message content. Foundry Local's OpenAI-compatible endpoint passes that through inline -- it does not surface it via a separate reasoning_content field -- so Microsoft.Extensions.AI's OpenAI client maps it to TextContent, and it flows into ChatMessage.Text and renders as the answer.

For contrast, M.E.AI already handles reasoning that arrives out-of-band:

  • OpenAI-compatible endpoints that expose a reasoning_content field (DeepSeek / vLLM / OpenRouter) are mapped to TextReasoningContent in OpenAIChatClient (it reads $.choices[0].delta.reasoning_content), and reasoning models via the Responses API map to TextReasoningContent as well.
  • ChatMessage.Text / ConcatText() intentionally exclude TextReasoningContent, so reasoning never leaks into the answer -- but only when it is carried as reasoning content.

The gap is inline <think> tags in content, which nothing normalizes today.

Scope across Foundry Local: this is not just qwen3. Most Foundry Local reasoning models use the same <think> convention -- the Qwen3 family, DeepSeek-R1 distills, and Phi-4-reasoning / Phi-4-mini-reasoning. (gpt-oss uses the "harmony" channel format instead, which would need separate handling.)

Suggested direction (follow-up, not blocking this PR):

  1. M.E.AI: an opt-in DelegatingChatClient that splits inline <think>...</think> out of streaming TextContent into TextReasoningContent -- streaming-tolerant (the open/close tags land across chunk boundaries), with a configurable tag pair, pass-through when the tags are absent, and applied only for known reasoning models. Foundry Local's integration could enable it by default for those model IDs.
  2. Template UI: make the chat components reasoning-aware -- show a "thinking..." status while only reasoning is streaming, and render TextReasoningContent as a collapsible section rather than dropping it (today ChatMessageItem renders only TextContent).

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 92.64 🔻
Microsoft.Extensions.AI Line 89 88.64 🔻
Microsoft.Extensions.AI Branch 89 88.57 🔻
Microsoft.Extensions.AI.OpenAI Line 75 62.75 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 50.26 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 92.76 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.36 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 85
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 89
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1500131&view=codecoverage-tab

IngestedChunk.Key was always Guid, but only Qdrant uses a Guid key; the local (SQLite) and Azure AI Search stores use string keys (VectorStoreWriter<string>, the collection registrations, and SemanticSearch), so the generated app threw at runtime on those stores. Make Key conditional (Guid for Qdrant, string otherwise) and regenerate the affected snapshots.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +22 to +30
#if (IsQdrant)
[VectorStoreKey(StorageName = "key")]
[JsonPropertyName("key")]
public required Guid Key { get; set; }
#else
[VectorStoreKey(StorageName = "key")]
[JsonPropertyName("key")]
public required string Key { get; set; }
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsitnik Does this match your understanding that only Qdrant uses Guid keys?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that Azure Foundry local provides an embedding generator (hence to proper VectorDimensions change above) but I don't think it provides any vector store. So we should not change the Key here.

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 92.64 🔻
Microsoft.Extensions.AI Line 89 88.59 🔻
Microsoft.Extensions.AI Branch 89 88.57 🔻
Microsoft.Extensions.AI.OpenAI Line 75 62.75 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 50.26 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 92.76 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.96 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 85
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 85
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1500536&view=codecoverage-tab

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL at my comments, I think that all we need to update for the ingestion is VectorDimensions count, as I expect that Azure Foundry Local does not come with a vector store.

Comment on lines +22 to +30
#if (IsQdrant)
[VectorStoreKey(StorageName = "key")]
[JsonPropertyName("key")]
public required Guid Key { get; set; }
#else
[VectorStoreKey(StorageName = "key")]
[JsonPropertyName("key")]
public required string Key { get; set; }
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that Azure Foundry local provides an embedding generator (hence to proper VectorDimensions change above) but I don't think it provides any vector store. So we should not change the Key here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Foundry Local as a chat and embedding provider in the AI Chat Web template

5 participants