diff --git a/global.json b/global.json index 4d9009f7346..f77fee427fe 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "10.0.105" + "version": "10.0.105", + "rollForward": "latestMajor" }, "tools": { "dotnet": "10.0.105", diff --git a/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/README.md b/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/README.md index 0285f27fb3d..d9f87c737fa 100644 --- a/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/README.md +++ b/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/README.md @@ -6,7 +6,9 @@ The [Microsoft.Extensions.DataIngestion.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.DataIngestion.Abstractions) package provides the core exchange types, including [`IngestionDocument`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dataingestion.ingestiondocument), [`IngestionChunker`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dataingestion.ingestionchunker-1), [`IngestionChunkProcessor`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dataingestion.ingestionchunkprocessor-1), and [`IngestionChunkWriter`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dataingestion.ingestionchunkwriter-1). Any .NET library that provides document processing capabilities can implement these abstractions to enable seamless integration with consuming code. -The [Microsoft.Extensions.DataIngestion](https://www.nuget.org/packages/Microsoft.Extensions.DataIngestion) package has an implicit dependency on the `Microsoft.Extensions.DataIngestion.Abstractions` package. This package enables you to easily integrate components such as enrichment processors, vector storage writers, and telemetry into your applications using familiar dependency injection and pipeline patterns. For example, it provides processors for sentiment analysis, keyword extraction, and summarization that can be chained together in ingestion pipelines. +The [Microsoft.Extensions.DataIngestion](https://www.nuget.org/packages/Microsoft.Extensions.DataIngestion) package has an implicit dependency on the `Microsoft.Extensions.DataIngestion.Abstractions` package. This package enables you to easily integrate components such as enrichment processors, vector storage writers, and telemetry into your applications using familiar dependency injection and pipeline patterns. + +> **Note:** Retrieval abstractions (`RetrievalPipeline`, `RetrievalQuery`, `RetrievalQueryProcessor`, etc.) live in the separate [`Microsoft.Extensions.DataRetrieval`](../Microsoft.Extensions.DataRetrieval/) package family. ## Which package to reference diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IReranker.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IReranker.cs new file mode 100644 index 00000000000..e135c94cde3 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IReranker.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Defines a re-ranking strategy for retrieval results. +/// +/// +/// Re-rankers score and reorder retrieval chunks based on relevance to the query. +/// Implementations may use LLM-based scoring, cross-encoder models (e.g., ONNX), +/// or other ranking strategies. +/// +public interface IReranker +{ + /// + /// Re-ranks the provided chunks based on their relevance to the query. + /// + /// The search query. + /// The chunks to re-rank. + /// The token to monitor for cancellation requests. + /// The re-ranked chunks, ordered by relevance (highest first). + Task> RerankAsync(string query, IReadOnlyList chunks, CancellationToken cancellationToken = default); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IRetriever.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IRetriever.cs new file mode 100644 index 00000000000..6627caa98f4 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/IRetriever.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Defines the contract for a retrieval pipeline that processes queries and returns results. +/// +/// +/// Enables DI registration and testability. Consumers depend on +/// rather than a concrete pipeline implementation, allowing mocking in tests and +/// swappable retrieval strategies. +/// +public interface IRetriever +{ + /// + /// Retrieves results for the specified query. + /// + /// The user query. + /// Maximum number of results to retrieve. + /// The token to monitor for cancellation requests. + /// The retrieval results. + Task RetrieveAsync( + string query, + int topK = 5, + CancellationToken cancellationToken = default); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/Microsoft.Extensions.DataRetrieval.Abstractions.csproj b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/Microsoft.Extensions.DataRetrieval.Abstractions.csproj new file mode 100644 index 00000000000..d91c0a770a0 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/Microsoft.Extensions.DataRetrieval.Abstractions.csproj @@ -0,0 +1,23 @@ + + + + $(TargetFrameworks);netstandard2.0 + Microsoft.Extensions.DataRetrieval + Abstractions representing Data Retrieval components for RAG. + RAG + RAG;retrieval;search;reranking + true + preview + false + 75 + 75 + + $(NoWarn);S1694;S2368 + + + + + + + + diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/README.md b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/README.md new file mode 100644 index 00000000000..292d86bfdfd --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/README.md @@ -0,0 +1,40 @@ +# Microsoft.Extensions.DataRetrieval.Abstractions + +Abstractions for building composable retrieval pipelines in .NET RAG (Retrieval-Augmented Generation) applications. The retrieval abstractions are the symmetric counterpart to [`Microsoft.Extensions.DataIngestion`](../Microsoft.Extensions.DataIngestion.Abstractions/) — ingestion writes data in, retrieval reads relevant data out. + +## Core Types + +| Type | Description | +|------|-------------| +| `RetrievalQuery` | Query text with support for variants (multi-query expansion) and metadata for inter-processor communication. | +| `RetrievalChunk` | A single retrieved chunk with content, relevance score, and record metadata. | +| `RetrievalResults` | Collection of retrieved chunks with pipeline-level metadata (e.g., CRAG scores, reranking info). | +| `RetrievalQueryProcessor` | Abstract base class for pre-search processors (query expansion, HyDE, adaptive routing). | +| `RetrievalResultProcessor` | Abstract base class for post-search processors (re-ranking, CRAG quality gating). | +| `IReranker` | Interface for re-ranking strategies (LLM-based, cross-encoder, ONNX models). | + +## Which package to reference + +Libraries that provide implementations of the abstractions (e.g., custom query processors, re-rankers) should reference only `Microsoft.Extensions.DataRetrieval.Abstractions`. + +Applications that need the full pipeline orchestrator (`RetrievalPipeline`) should reference `Microsoft.Extensions.DataRetrieval` instead (which itself references the abstractions). + +## Install the package + +From the command-line: + +```console +dotnet add package Microsoft.Extensions.DataRetrieval.Abstractions --prerelease +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalChunk.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalChunk.cs new file mode 100644 index 00000000000..e002b0c80f2 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalChunk.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Represents a single chunk returned from retrieval. +/// +public sealed class RetrievalChunk +{ + /// + /// Initializes a new instance of the class. + /// + /// The text content of the chunk. + /// The relevance score from vector search. + public RetrievalChunk(string content, double score) + { + Content = content; + Score = score; + } + + /// + /// Gets the text content of this chunk. + /// + public string Content { get; } + + /// + /// Gets or sets the relevance score. + /// + public double Score { get; set; } + + /// + /// Gets the underlying record data as key-value pairs. + /// + /// + /// Contains the full record fields from the vector store, enabling + /// downstream consumers to reconstruct strongly-typed records. + /// + public IDictionary Record { get; } = new Dictionary(); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQuery.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQuery.cs new file mode 100644 index 00000000000..71d084778eb --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQuery.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Represents a retrieval query with optional expanded variants and metadata. +/// +public sealed class RetrievalQuery +{ + /// + /// Initializes a new instance of the class. + /// + /// The original query text. + public RetrievalQuery(string text) + { + Text = text; + Variants = [text]; + } + + /// + /// Gets the original query text. + /// + public string Text { get; } + + /// + /// Gets or sets the query variants to search with. + /// + /// + /// Pre-query processors may expand a single query into multiple variants + /// (e.g., multi-query expansion, HyDE). Each variant is searched independently + /// and results are merged using Reciprocal Rank Fusion. + /// + public IList Variants { get; set; } + + /// + /// Gets the metadata associated with this query. + /// + public IDictionary Metadata { get; } = new Dictionary(); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQueryProcessor.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQueryProcessor.cs new file mode 100644 index 00000000000..e5c9f9bbe2e --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalQueryProcessor.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Processes a retrieval query before vector search is performed. +/// +/// +/// Pre-search processors transform or expand a +/// before it is sent to the vector store. Examples include multi-query expansion, +/// HyDE (Hypothetical Document Embeddings), and adaptive routing. +/// +public abstract class RetrievalQueryProcessor +{ + /// + /// Processes the query asynchronously before vector search. + /// + /// The retrieval query to process. + /// The token to monitor for cancellation requests. + /// The processed query. + public abstract Task ProcessAsync(RetrievalQuery query, CancellationToken cancellationToken = default); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResultProcessor.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResultProcessor.cs new file mode 100644 index 00000000000..12a54060247 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResultProcessor.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Processes retrieval results after vector search is performed. +/// +/// +/// Post-search processors transform or filter +/// after they are returned from the vector store. Examples include re-ranking, +/// CRAG (Corrective RAG) quality validation, and deduplication. +/// +public abstract class RetrievalResultProcessor +{ + /// + /// Processes the results asynchronously after vector search. + /// + /// The retrieval results to process. + /// The original query (for context during processing). + /// The token to monitor for cancellation requests. + /// The processed results. + public abstract Task ProcessAsync(RetrievalResults results, RetrievalQuery query, CancellationToken cancellationToken = default); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResults.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResults.cs new file mode 100644 index 00000000000..2011fbd28f7 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval.Abstractions/RetrievalResults.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Represents the results of a retrieval operation. +/// +public sealed class RetrievalResults +{ + /// + /// Gets or sets the retrieved chunks, ordered by relevance. + /// + public IList Chunks { get; set; } = []; + + /// + /// Gets the metadata from the retrieval pipeline. + /// + /// + /// Pipeline processors may add metadata such as CRAG quality scores, + /// reranking diagnostics, or query expansion details. + /// + public IDictionary Metadata { get; } = new Dictionary(); +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/DiagnosticsConstants.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/DiagnosticsConstants.cs new file mode 100644 index 00000000000..bf208788b7d --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/DiagnosticsConstants.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.DataRetrieval; + +internal static class DiagnosticsConstants +{ + internal const string ActivitySourceName = "Experimental.Microsoft.Extensions.DataRetrieval"; + internal const string ErrorTypeTagName = "error.type"; +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/Log.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/Log.cs new file mode 100644 index 00000000000..29fd9351487 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/Log.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; + +#pragma warning disable S109 // Magic numbers should not be used + +namespace Microsoft.Extensions.DataRetrieval +{ + internal static partial class Log + { + [LoggerMessage(0, LogLevel.Debug, "Running query processor: {processor}.")] + internal static partial void RunningQueryProcessor(this ILogger logger, string processor); + + [LoggerMessage(1, LogLevel.Debug, "Searching variant: {variant}.")] + internal static partial void SearchingVariant(this ILogger logger, string variant); + + [LoggerMessage(2, LogLevel.Debug, "Running result processor: {processor}.")] + internal static partial void RunningResultProcessor(this ILogger logger, string processor); + } +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/Microsoft.Extensions.DataRetrieval.csproj b/src/Libraries/Microsoft.Extensions.DataRetrieval/Microsoft.Extensions.DataRetrieval.csproj new file mode 100644 index 00000000000..7a4c37421a8 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/Microsoft.Extensions.DataRetrieval.csproj @@ -0,0 +1,34 @@ + + + + $(TargetFrameworks);netstandard2.0 + Microsoft.Extensions.DataRetrieval + Data Retrieval utilities for RAG. + RAG + RAG;retrieval;search;reranking + true + false + true + preview + false + 75 + 75 + true + + + + + + + + + + + + + + + + + + diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/README.md b/src/Libraries/Microsoft.Extensions.DataRetrieval/README.md new file mode 100644 index 00000000000..2e273797139 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/README.md @@ -0,0 +1,37 @@ +# Microsoft.Extensions.DataRetrieval + +Retrieval pipeline orchestration for .NET RAG (Retrieval-Augmented Generation) applications. Built on the abstractions defined in [`Microsoft.Extensions.DataRetrieval.Abstractions`](../Microsoft.Extensions.DataRetrieval.Abstractions/). + +## Key Components + +| Type | Description | +|------|-------------| +| `RetrievalPipeline` | Orchestrates query processing → vector search → result processing with RRF dedup and tree-aware hierarchical search. | +| `RetrievalPipelineOptions` | Configuration for the pipeline (collection name, top-K, processors). | + +## Features + +- **Multi-query deduplication** — Reciprocal Rank Fusion (RRF) merges results from expanded query variants +- **Tree-aware retrieval** — hierarchical traversal from root summaries to leaf chunks +- **Extensible processors** — plug in custom `RetrievalQueryProcessor` and `RetrievalResultProcessor` implementations +- **OpenTelemetry** — built-in distributed tracing and structured logging + +## Install the package + +From the command-line: + +```console +dotnet add package Microsoft.Extensions.DataRetrieval --prerelease +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipeline.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipeline.cs new file mode 100644 index 00000000000..844b98c7b9c --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipeline.cs @@ -0,0 +1,248 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.VectorData; +using Microsoft.Shared.Diagnostics; +using static Microsoft.Extensions.DataRetrieval.DiagnosticsConstants; + +namespace Microsoft.Extensions.DataRetrieval; + +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0063 // Use simple 'using' statement + +/// +/// Represents a pipeline for retrieving data from a vector store with pre- and post-processing. +/// +/// +/// Flow: query → → vector search → → results. +/// With an empty processor list, this behaves identically to a raw +/// call. +/// +public sealed class RetrievalPipeline : IDisposable +{ + private readonly ActivitySource _activitySource; + private readonly ILogger? _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The options for the retrieval pipeline. + /// The logger factory for creating loggers. + public RetrievalPipeline( + RetrievalPipelineOptions? options = null, + ILoggerFactory? loggerFactory = null) + { + _activitySource = new((options ?? new()).ActivitySourceName); + _logger = loggerFactory?.CreateLogger(); + } + + /// + public void Dispose() + { + _activitySource.Dispose(); + } + + /// + /// Gets the pre-search query processors (e.g., query expansion, HyDE). + /// + public IList QueryProcessors { get; } = []; + + /// + /// Gets the post-search result processors (e.g., re-ranking, CRAG). + /// + public IList ResultProcessors { get; } = []; + + /// + /// Processes a query through the retrieval pipeline: query processing → vector search → result processing. + /// + /// The vector store key type. + /// The vector store record type. + /// The vector store collection to search. + /// The user query. + /// Maximum results to retrieve per search variant. + /// Extracts text content from a record for result processing. + /// The token to monitor for cancellation requests. + /// The retrieval results. + /// is . + /// is or empty. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075", + Justification = "Record properties are accessed for diagnostic metadata population only.")] + public async Task ProcessAsync( + VectorStoreCollection collection, + string query, + int topK = 5, + Func? contentSelector = null, + CancellationToken cancellationToken = default) + where TKey : notnull + where TRecord : class + { + Throw.IfNull(collection); + Throw.IfNullOrEmpty(query); + + using (Activity? rootActivity = _activitySource.StartActivity("RetrievalPipeline.Process")) + { + rootActivity?.SetTag("rag.query", query) + .SetTag("rag.topK", topK); + + // Phase 1: Pre-query processing + var retrievalQuery = new RetrievalQuery(query); + + foreach (var processor in QueryProcessors) + { + _logger?.RunningQueryProcessor(processor.GetType().Name); + retrievalQuery = await processor.ProcessAsync(retrievalQuery, cancellationToken).ConfigureAwait(false); + } + + rootActivity?.SetTag("rag.query.variants", retrievalQuery.Variants.Count); + + // Phase 2: Vector search (paradigm-aware) + var allChunks = new List(); + bool isTreeTraversal = retrievalQuery.Metadata.TryGetValue("search_paradigm", out var paradigm) + && "TreeTraversal".Equals(paradigm?.ToString(), StringComparison.OrdinalIgnoreCase); + int searchTopK = isTreeTraversal ? topK * 3 : topK; + + foreach (string variant in retrievalQuery.Variants) + { + _logger?.SearchingVariant(variant.Length > 80 ? variant[..80] : variant); + var searchResults = collection.SearchAsync(variant, top: searchTopK, cancellationToken: cancellationToken); + + await foreach (var result in searchResults.ConfigureAwait(false)) + { + string content = contentSelector is not null && result.Record is not null + ? contentSelector(result.Record) + : result.Record?.ToString() ?? string.Empty; + + var chunk = new RetrievalChunk(content, result.Score ?? 0.0); + + // Populate record dictionary for downstream reconstruction + if (result.Record is not null) + { + foreach (var prop in result.Record.GetType().GetProperties()) + { + chunk.Record[prop.Name] = prop.GetValue(result.Record); + } + } + + allChunks.Add(chunk); + } + } + + // Deduplicate by content if multiple variants returned overlapping results + if (retrievalQuery.Variants.Count > 1) + { + allChunks = DeduplicateWithRrf(allChunks); + } + + // Tree traversal: group by level and apply top-down selection + if (isTreeTraversal) + { + int resultsPerLevel = retrievalQuery.Metadata.TryGetValue("results_per_level", out var rpl) && rpl is int r ? r : 3; + allChunks = ApplyTreeTraversal(allChunks, resultsPerLevel, topK); + rootActivity?.SetTag("rag.search_paradigm", "TreeTraversal"); + } + + var results = new RetrievalResults { Chunks = allChunks }; + rootActivity?.SetTag("rag.results.count", results.Chunks.Count); + + // Phase 3: Post-search result processing + foreach (var processor in ResultProcessors) + { + _logger?.RunningResultProcessor(processor.GetType().Name); + results = await processor.ProcessAsync(results, retrievalQuery, cancellationToken).ConfigureAwait(false); + } + + rootActivity?.SetTag("rag.results.final_count", results.Chunks.Count); + return results; + } + } + + /// + /// Deduplicates chunks using Reciprocal Rank Fusion across multiple query variants. + /// + private static List DeduplicateWithRrf(List chunks) + { + const int RrfK = 60; + + var grouped = chunks + .GroupBy(c => c.Content) + .Select(g => new + { + Chunk = g.First(), + RrfScore = g.Sum(c => + { + int rank = chunks.IndexOf(c) + 1; + return 1.0 / (RrfK + rank); + }) + }) + .OrderByDescending(x => x.RrfScore) + .ToList(); + + foreach (var item in grouped) + { + item.Chunk.Score = item.RrfScore; + } + + return grouped.Select(x => x.Chunk).ToList(); + } + + /// + /// Applies top-down tree traversal: groups chunks by level and returns a + /// prioritized mix of leaf chunks with branch/root summaries for context. + /// + /// + /// Expects chunks to have a "Level" or "level" entry in + /// (set by TreeIndexProcessor during ingestion). Chunks without level metadata are treated as leaves. + /// + private static List ApplyTreeTraversal( + List chunks, int resultsPerLevel, int topK) + { + static int GetLevel(RetrievalChunk chunk) + { + if (chunk.Record.TryGetValue("Level", out var lvl) || chunk.Record.TryGetValue("level", out lvl)) + { + return lvl switch + { + int i => i, + long l => (int)l, + string s when int.TryParse(s, out var parsed) => parsed, + _ => 0 + }; + } + + return 0; // Default to leaf + } + + var byLevel = chunks.GroupBy(GetLevel).ToDictionary(g => g.Key, g => g.OrderByDescending(c => c.Score).ToList()); + + var result = new List(); + + // Leaves (level 0) — primary results + if (byLevel.TryGetValue(0, out var leaves)) + { + result.AddRange(leaves.Take(topK)); + } + + // Branch summaries (level 1) — document-level context + if (byLevel.TryGetValue(1, out var branches)) + { + result.AddRange(branches.Take(resultsPerLevel)); + } + + // Root summaries (level 2) — corpus-level context + if (byLevel.TryGetValue(2, out var roots)) + { + result.AddRange(roots.Take(resultsPerLevel)); + } + + // Score: keep original scores but ensure leaves rank first + return result.OrderByDescending(c => c.Score).Take(topK).ToList(); + } +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineExtensions.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineExtensions.cs new file mode 100644 index 00000000000..64960df8c9b --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineExtensions.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Extensions.VectorData; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Extension methods for . +/// +public static class RetrievalPipelineExtensions +{ + /// + /// Creates an that binds this pipeline to a specific vector store collection. + /// + /// The vector store key type. + /// The vector store record type. + /// The retrieval pipeline. + /// The vector store collection to search. + /// Optional function to extract text content from a record. + /// An that processes queries through this pipeline against the specified collection. + /// + /// This is symmetric with IngestionPipeline.ProcessAsync — both pipelines are engines that + /// operate on a data source. AsRetriever creates a ready-to-use endpoint from the engine. + /// + /// var pipeline = new RetrievalPipeline(loggerFactory: loggerFactory); + /// pipeline.QueryProcessors.Add(new MultiQueryExpander(chatClient)); + /// pipeline.ResultProcessors.Add(new LlmReranker(chatClient)); + /// + /// IRetriever retriever = pipeline.AsRetriever(collection, r => r.Content); + /// var results = await retriever.RetrieveAsync("What are the retention policies?"); + /// + /// + public static IRetriever AsRetriever( + this RetrievalPipeline pipeline, + VectorStoreCollection collection, + Func? contentSelector = null) + where TKey : notnull + where TRecord : class + { + return new VectorStoreRetriever(pipeline, collection, contentSelector); + } +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineOptions.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineOptions.cs new file mode 100644 index 00000000000..2f49c2dc2d3 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/RetrievalPipelineOptions.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// Options for configuring the retrieval pipeline. +/// +public sealed class RetrievalPipelineOptions +{ + /// + /// Gets or sets the name of the used for diagnostics. + /// + public string ActivitySourceName + { + get; + set => field = Throw.IfNullOrEmpty(value); + } = DiagnosticsConstants.ActivitySourceName; +} diff --git a/src/Libraries/Microsoft.Extensions.DataRetrieval/VectorStoreRetriever.cs b/src/Libraries/Microsoft.Extensions.DataRetrieval/VectorStoreRetriever.cs new file mode 100644 index 00000000000..f662b5e2f27 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DataRetrieval/VectorStoreRetriever.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.VectorData; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.DataRetrieval; + +/// +/// An implementation that retrieves results from a +/// using a . +/// +/// The vector store key type. +/// The vector store record type. +/// +/// Register via DI to enable constructor injection of : +/// +/// services.AddSingleton<IRetriever>(sp => +/// new VectorStoreRetriever<string, MyRecord>( +/// sp.GetRequiredService<RetrievalPipeline>(), +/// sp.GetRequiredService<VectorStoreCollection<string, MyRecord>>(), +/// record => record.Content)); +/// +/// +public sealed class VectorStoreRetriever : IRetriever + where TKey : notnull + where TRecord : class +{ + private readonly RetrievalPipeline _pipeline; + private readonly VectorStoreCollection _collection; + private readonly Func? _contentSelector; + + /// + /// Initializes a new instance of the class. + /// + /// The retrieval pipeline to use. + /// The vector store collection to search. + /// Optional function to extract text content from a record. + public VectorStoreRetriever( + RetrievalPipeline pipeline, + VectorStoreCollection collection, + Func? contentSelector = null) + { + _pipeline = Throw.IfNull(pipeline); + _collection = Throw.IfNull(collection); + _contentSelector = contentSelector; + } + + /// + public Task RetrieveAsync( + string query, + int topK = 5, + CancellationToken cancellationToken = default) + { + return _pipeline.ProcessAsync(_collection, query, topK, _contentSelector, cancellationToken); + } +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/dotnetcli.host.json new file mode 100644 index 00000000000..5be51dd6357 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/dotnetcli.host", + "symbolInfo": {}, + "usageExamples": [ + "" + ] +} \ No newline at end of file diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide.host.json new file mode 100644 index 00000000000..5edf447bbd4 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide.host.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/ide.host", + "order": 0, + "icon": "ide/icon.ico", + "symbolInfo": [] +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide/icon.ico b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide/icon.ico new file mode 100644 index 00000000000..954709ffd6b Binary files /dev/null and b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/ide/icon.ico differ diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/template.json new file mode 100644 index 00000000000..06b0ff96e55 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/.template.config/template.json @@ -0,0 +1,134 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ + "Common", + "AI", + "Agents" + ], + "identity": "Microsoft.Extensions.AI.Templates.AgentsWebAPI.CSharp", + "name": "Local Agents Web API", + "description": "A project template for creating an Agents Web API using C#.", + "shortName": "agentswebapi", + "defaultName": "AgentsWebAPI", + "sourceName": "AgentsWebAPI-CSharp", + "preferNameDirectory": true, + "tags": { + "language": "C#", + "type": "project" + }, + "symbols": { + "hostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "ai-service-provider": { + "type": "parameter", + "displayName": "_AI service provider", + "datatype": "choice", + "choices": [ + { + "choice": "azureopenai", + "displayName": "Azure OpenAI", + "description": "Uses Azure OpenAI service" + }, + { + "choice": "githubmodels", + "displayName": "GitHub Models", + "description": "Uses GitHub Models" + }, + { + "choice": "ollama", + "displayName": "Ollama (for local development)", + "description": "Uses Ollama models" + }, + { + "choice": "openai", + "displayName": "OpenAI Platform", + "description": "Uses the OpenAI Platform" + } + ] + }, + "managed-identity": { + "type": "parameter", + "displayName": "Use keyless authentication for Azure services", + "datatype": "bool", + "defaultValue": "true", + "isEnabled": "(AiServiceProvider == \"azureopenai\")", + "description": "Use managed identity to access Azure services" + }, + "IsAzureOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"azureopenai\")" + }, + "IsOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"openai\")" + }, + "IsGHModels": { + "type": "computed", + "value": "(AiServiceProvider == \"githubmodels\")" + }, + "IsOllama": { + "type": "computed", + "value": "(AiServiceProvider == \"ollama\")" + }, + "ChatModel": { + "type": "parameter", + "displayName": "Model/deployment for chat completions. Example: gpt-4o-mini", + "description": "Model/deployment for chat completions. Example: gpt-4o-mini" + }, + "OpenAiChatModelDefault": { + "type": "generated", + "generator": "constant", + "parameters": { + "value": "gpt-4o-mini" + } + }, + "OpenAiChatModel": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "ChatModel", + "fallbackVariableName": "OpenAiChatModelDefault" + }, + "replaces": "gpt-4o-mini" + }, + "OllamaChatModelDefault": { + "type": "generated", + "generator": "constant", + "parameters": { + "value": "llama3.2" + } + }, + "OllamaChatModel": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "ChatModel", + "fallbackVariableName": "OllamaChatModelDefault" + }, + "replaces": "llama3.2" + } + }, + "primaryOutputs": [ + { + "path": "./README.md" + }, + { + "path": "./AgentsWebAPI-CSharp.csproj" + } + ], + "postActions": [ + { + "condition": "(hostIdentifier != \"dotnetcli\" && hostIdentifier != \"dotnetcli-preview\")", + "description": "Opens README file in the editor", + "manualInstructions": [], + "actionId": "84C0DA21-51C8-4541-9940-6CA19AF04EE6", + "args": { + "files": "0" + }, + "continueOnError": true + } + ] +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.csproj b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.csproj new file mode 100644 index 00000000000..015e80a4ba6 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + AgentsWebAPI_CSharp + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.http b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.http new file mode 100644 index 00000000000..bcd332375d5 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/AgentsWebAPI-CSharp.http @@ -0,0 +1,6 @@ +@AgentsWebAPI_CSharp_HostAddress = http://localhost:5003 + +GET {{AgentsWebAPI_CSharp_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/McpServer-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/McpServer-CSharp.csproj.in new file mode 100644 index 00000000000..2eca37df228 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/McpServer-CSharp.csproj.in @@ -0,0 +1,33 @@ + + + + net8.0 + Major + Exe + enable + enable + + + true + McpServer + + + README.md + SampleMcpServer + 0.1.0-beta + AI; MCP; server; stdio + An MCP server using the MCP C# SDK. + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/Program.cs new file mode 100644 index 00000000000..ee9d65d633f --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/Program.cs @@ -0,0 +1,41 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast"); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/README.md b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/README.md new file mode 100644 index 00000000000..cb11ac30eb5 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/README.md @@ -0,0 +1,85 @@ +# MCP Server + +This README was created using the C# MCP server project template. It demonstrates how you can easily create an MCP server using C# and publish it as a NuGet package. + +See [aka.ms/nuget/mcp/guide](https://aka.ms/nuget/mcp/guide) for the full guide. + +Please note that this template is currently in an early preview stage. If you have feedback, please take a [brief survey](http://aka.ms/dotnet-mcp-template-survey). + +## Checklist before publishing to NuGet.org + +- Test the MCP server locally using the steps below. +- Update the package metadata in the .csproj file, in particular the ``. +- Update `.mcp/server.json` to declare your MCP server's inputs. + - See [configuring inputs](https://aka.ms/nuget/mcp/guide/configuring-inputs) for more details. +- Pack the project using `dotnet pack`. + +The `bin/Release` directory will contain the package file (.nupkg), which can be [published to NuGet.org](https://learn.microsoft.com/nuget/nuget-org/publish-a-package). + +## Developing locally + +To test this MCP server from source code (locally) without using a built MCP server package, you can configure your IDE to run the project directly using `dotnet run`. + +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "" + ] + } + } +} +``` + +## Testing the MCP Server + +Once configured, you can ask Copilot Chat for a random number, for example, `Give me 3 random numbers`. It should prompt you to use the `get_random_number` tool on the `McpServer-CSharp` MCP server and show you the results. + +## Publishing to NuGet.org + +1. Run `dotnet pack -c Release` to create the NuGet package +2. Publish to NuGet.org with `dotnet nuget push bin/Release/*.nupkg --api-key --source https://api.nuget.org/v3/index.json` + +## Using the MCP Server from NuGet.org + +Once the MCP server package is published to NuGet.org, you can configure it in your preferred IDE. Both VS Code and Visual Studio use the `dnx` command to download and install the MCP server package from NuGet.org. + +- **VS Code**: Create a `/.vscode/mcp.json` file +- **Visual Studio**: Create a `\.mcp.json` file + +For both VS Code and Visual Studio, the configuration file uses the following server definition: + +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "stdio", + "command": "dnx", + "args": [ + "", + "--version", + "", + "--yes" + ] + } + } +} +``` + +## More information + +.NET MCP servers use the [ModelContextProtocol](https://www.nuget.org/packages/ModelContextProtocol) C# SDK. For more information about MCP: + +- [Official Documentation](https://modelcontextprotocol.io/) +- [Protocol Specification](https://spec.modelcontextprotocol.io/) +- [GitHub Organization](https://github.com/modelcontextprotocol) + +Refer to the VS Code or Visual Studio documentation for more information on configuring and using MCP servers: + +- [Use MCP servers in VS Code (Preview)](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) +- [Use MCP servers in Visual Studio (Preview)](https://learn.microsoft.com/visualstudio/ide/mcp-servers) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.Development.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/Agents/AgentsWebAPI-CSharp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj new file mode 100644 index 00000000000..eef4be85a4a --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj @@ -0,0 +1,32 @@ + + + + + + Exe + net9.0 + enable + enable + true + b2f4f5e9-1083-472c-8c3b-f055ac67ba54 + + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.ServiceDefaults/ChatWithCustomData-CSharp.ServiceDefaults.csproj b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.ServiceDefaults/ChatWithCustomData-CSharp.ServiceDefaults.csproj new file mode 100644 index 00000000000..fdec3e59666 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.ServiceDefaults/ChatWithCustomData-CSharp.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj new file mode 100644 index 00000000000..0de8abf55d9 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj @@ -0,0 +1,60 @@ + + + + net9.0 + enable + enable + d5681fae-b21b-4114-b781-48180f08c0c4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Directory.Build.targets b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Directory.Build.targets new file mode 100644 index 00000000000..d321029dd95 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Directory.Build.targets @@ -0,0 +1,21 @@ + + + + + + <_LocalChatTemplateVariant>aspire + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/Directory.Build.props b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/Directory.Build.props new file mode 100644 index 00000000000..0eb47a5ac25 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/Directory.Build.props @@ -0,0 +1,5 @@ + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/McpServer/McpServer-CSharp/McpServer-CSharp.csproj b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/McpServer/McpServer-CSharp/McpServer-CSharp.csproj new file mode 100644 index 00000000000..46b9c83818a --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/McpServer/McpServer-CSharp/McpServer-CSharp.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + Major + Exe + enable + enable + + + true + McpServer + + + README.md + SampleMcpServer + 0.1.0-beta + AI; MCP; server; stdio + An MCP server using the MCP C# SDK. + + + + + + + + + + + + + +