Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
340 lines
9.4 KiB
Markdown
340 lines
9.4 KiB
Markdown
---
|
|
name: azure-search-documents-dotnet
|
|
description: |
|
|
Azure AI Search SDK for .NET (Azure.Search.Documents). Use for building search applications with full-text, vector, semantic, and hybrid search. Covers SearchClient (queries, document CRUD), SearchIndexClient (index management), and SearchIndexerClient (indexers, skillsets). Triggers: "Azure Search .NET", "SearchClient", "SearchIndexClient", "vector search C#", "semantic search .NET", "hybrid search", "Azure.Search.Documents".
|
|
package: Azure.Search.Documents
|
|
---
|
|
|
|
# Azure.Search.Documents (.NET)
|
|
|
|
Build search applications with full-text, vector, semantic, and hybrid search capabilities.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
dotnet add package Azure.Search.Documents
|
|
dotnet add package Azure.Identity
|
|
```
|
|
|
|
**Current Versions**: Stable v11.7.0, Preview v11.8.0-beta.1
|
|
|
|
## Environment Variables
|
|
|
|
```bash
|
|
SEARCH_ENDPOINT=https://<search-service>.search.windows.net
|
|
SEARCH_INDEX_NAME=<index-name>
|
|
# For API key auth (not recommended for production)
|
|
SEARCH_API_KEY=<api-key>
|
|
```
|
|
|
|
## Authentication
|
|
|
|
**DefaultAzureCredential (preferred)**:
|
|
```csharp
|
|
using Azure.Identity;
|
|
using Azure.Search.Documents;
|
|
|
|
var credential = new DefaultAzureCredential();
|
|
var client = new SearchClient(
|
|
new Uri(Environment.GetEnvironmentVariable("SEARCH_ENDPOINT")),
|
|
Environment.GetEnvironmentVariable("SEARCH_INDEX_NAME"),
|
|
credential);
|
|
```
|
|
|
|
**API Key**:
|
|
```csharp
|
|
using Azure;
|
|
using Azure.Search.Documents;
|
|
|
|
var credential = new AzureKeyCredential(
|
|
Environment.GetEnvironmentVariable("SEARCH_API_KEY"));
|
|
var client = new SearchClient(
|
|
new Uri(Environment.GetEnvironmentVariable("SEARCH_ENDPOINT")),
|
|
Environment.GetEnvironmentVariable("SEARCH_INDEX_NAME"),
|
|
credential);
|
|
```
|
|
|
|
## Client Selection
|
|
|
|
| Client | Purpose |
|
|
|--------|---------|
|
|
| `SearchClient` | Query indexes, upload/update/delete documents |
|
|
| `SearchIndexClient` | Create/manage indexes, synonym maps |
|
|
| `SearchIndexerClient` | Manage indexers, skillsets, data sources |
|
|
|
|
## Index Creation
|
|
|
|
### Using FieldBuilder (Recommended)
|
|
|
|
```csharp
|
|
using Azure.Search.Documents.Indexes;
|
|
using Azure.Search.Documents.Indexes.Models;
|
|
|
|
// Define model with attributes
|
|
public class Hotel
|
|
{
|
|
[SimpleField(IsKey = true, IsFilterable = true)]
|
|
public string HotelId { get; set; }
|
|
|
|
[SearchableField(IsSortable = true)]
|
|
public string HotelName { get; set; }
|
|
|
|
[SearchableField(AnalyzerName = LexicalAnalyzerName.EnLucene)]
|
|
public string Description { get; set; }
|
|
|
|
[SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
|
|
public double? Rating { get; set; }
|
|
|
|
[VectorSearchField(VectorSearchDimensions = 1536, VectorSearchProfileName = "vector-profile")]
|
|
public ReadOnlyMemory<float>? DescriptionVector { get; set; }
|
|
}
|
|
|
|
// Create index
|
|
var indexClient = new SearchIndexClient(endpoint, credential);
|
|
var fieldBuilder = new FieldBuilder();
|
|
var fields = fieldBuilder.Build(typeof(Hotel));
|
|
|
|
var index = new SearchIndex("hotels")
|
|
{
|
|
Fields = fields,
|
|
VectorSearch = new VectorSearch
|
|
{
|
|
Profiles = { new VectorSearchProfile("vector-profile", "hnsw-algo") },
|
|
Algorithms = { new HnswAlgorithmConfiguration("hnsw-algo") }
|
|
}
|
|
};
|
|
|
|
await indexClient.CreateOrUpdateIndexAsync(index);
|
|
```
|
|
|
|
### Manual Field Definition
|
|
|
|
```csharp
|
|
var index = new SearchIndex("hotels")
|
|
{
|
|
Fields =
|
|
{
|
|
new SimpleField("hotelId", SearchFieldDataType.String) { IsKey = true, IsFilterable = true },
|
|
new SearchableField("hotelName") { IsSortable = true },
|
|
new SearchableField("description") { AnalyzerName = LexicalAnalyzerName.EnLucene },
|
|
new SimpleField("rating", SearchFieldDataType.Double) { IsFilterable = true, IsSortable = true },
|
|
new SearchField("descriptionVector", SearchFieldDataType.Collection(SearchFieldDataType.Single))
|
|
{
|
|
VectorSearchDimensions = 1536,
|
|
VectorSearchProfileName = "vector-profile"
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
## Document Operations
|
|
|
|
```csharp
|
|
var searchClient = new SearchClient(endpoint, indexName, credential);
|
|
|
|
// Upload (add new)
|
|
var hotels = new[] { new Hotel { HotelId = "1", HotelName = "Hotel A" } };
|
|
await searchClient.UploadDocumentsAsync(hotels);
|
|
|
|
// Merge (update existing)
|
|
await searchClient.MergeDocumentsAsync(hotels);
|
|
|
|
// Merge or Upload (upsert)
|
|
await searchClient.MergeOrUploadDocumentsAsync(hotels);
|
|
|
|
// Delete
|
|
await searchClient.DeleteDocumentsAsync("hotelId", new[] { "1", "2" });
|
|
|
|
// Batch operations
|
|
var batch = IndexDocumentsBatch.Create(
|
|
IndexDocumentsAction.Upload(hotel1),
|
|
IndexDocumentsAction.Merge(hotel2),
|
|
IndexDocumentsAction.Delete(hotel3));
|
|
await searchClient.IndexDocumentsAsync(batch);
|
|
```
|
|
|
|
## Search Patterns
|
|
|
|
### Basic Search
|
|
|
|
```csharp
|
|
var options = new SearchOptions
|
|
{
|
|
Filter = "rating ge 4",
|
|
OrderBy = { "rating desc" },
|
|
Select = { "hotelId", "hotelName", "rating" },
|
|
Size = 10,
|
|
Skip = 0,
|
|
IncludeTotalCount = true
|
|
};
|
|
|
|
SearchResults<Hotel> results = await searchClient.SearchAsync<Hotel>("luxury", options);
|
|
|
|
Console.WriteLine($"Total: {results.TotalCount}");
|
|
await foreach (SearchResult<Hotel> result in results.GetResultsAsync())
|
|
{
|
|
Console.WriteLine($"{result.Document.HotelName} (Score: {result.Score})");
|
|
}
|
|
```
|
|
|
|
### Faceted Search
|
|
|
|
```csharp
|
|
var options = new SearchOptions
|
|
{
|
|
Facets = { "rating,count:5", "category" }
|
|
};
|
|
|
|
var results = await searchClient.SearchAsync<Hotel>("*", options);
|
|
|
|
foreach (var facet in results.Value.Facets["rating"])
|
|
{
|
|
Console.WriteLine($"Rating {facet.Value}: {facet.Count}");
|
|
}
|
|
```
|
|
|
|
### Autocomplete and Suggestions
|
|
|
|
```csharp
|
|
// Autocomplete
|
|
var autocompleteOptions = new AutocompleteOptions { Mode = AutocompleteMode.OneTermWithContext };
|
|
var autocomplete = await searchClient.AutocompleteAsync("lux", "suggester-name", autocompleteOptions);
|
|
|
|
// Suggestions
|
|
var suggestOptions = new SuggestOptions { UseFuzzyMatching = true };
|
|
var suggestions = await searchClient.SuggestAsync<Hotel>("lux", "suggester-name", suggestOptions);
|
|
```
|
|
|
|
## Vector Search
|
|
|
|
See [references/vector-search.md](references/vector-search.md) for detailed patterns.
|
|
|
|
```csharp
|
|
using Azure.Search.Documents.Models;
|
|
|
|
// Pure vector search
|
|
var vectorQuery = new VectorizedQuery(embedding)
|
|
{
|
|
KNearestNeighborsCount = 5,
|
|
Fields = { "descriptionVector" }
|
|
};
|
|
|
|
var options = new SearchOptions
|
|
{
|
|
VectorSearch = new VectorSearchOptions
|
|
{
|
|
Queries = { vectorQuery }
|
|
}
|
|
};
|
|
|
|
var results = await searchClient.SearchAsync<Hotel>(null, options);
|
|
```
|
|
|
|
## Semantic Search
|
|
|
|
See [references/semantic-search.md](references/semantic-search.md) for detailed patterns.
|
|
|
|
```csharp
|
|
var options = new SearchOptions
|
|
{
|
|
QueryType = SearchQueryType.Semantic,
|
|
SemanticSearch = new SemanticSearchOptions
|
|
{
|
|
SemanticConfigurationName = "my-semantic-config",
|
|
QueryCaption = new QueryCaption(QueryCaptionType.Extractive),
|
|
QueryAnswer = new QueryAnswer(QueryAnswerType.Extractive)
|
|
}
|
|
};
|
|
|
|
var results = await searchClient.SearchAsync<Hotel>("best hotel for families", options);
|
|
|
|
// Access semantic answers
|
|
foreach (var answer in results.Value.SemanticSearch.Answers)
|
|
{
|
|
Console.WriteLine($"Answer: {answer.Text} (Score: {answer.Score})");
|
|
}
|
|
|
|
// Access captions
|
|
await foreach (var result in results.Value.GetResultsAsync())
|
|
{
|
|
var caption = result.SemanticSearch?.Captions?.FirstOrDefault();
|
|
Console.WriteLine($"Caption: {caption?.Text}");
|
|
}
|
|
```
|
|
|
|
## Hybrid Search (Vector + Keyword + Semantic)
|
|
|
|
```csharp
|
|
var vectorQuery = new VectorizedQuery(embedding)
|
|
{
|
|
KNearestNeighborsCount = 5,
|
|
Fields = { "descriptionVector" }
|
|
};
|
|
|
|
var options = new SearchOptions
|
|
{
|
|
QueryType = SearchQueryType.Semantic,
|
|
SemanticSearch = new SemanticSearchOptions
|
|
{
|
|
SemanticConfigurationName = "my-semantic-config"
|
|
},
|
|
VectorSearch = new VectorSearchOptions
|
|
{
|
|
Queries = { vectorQuery }
|
|
}
|
|
};
|
|
|
|
// Combines keyword search, vector search, and semantic ranking
|
|
var results = await searchClient.SearchAsync<Hotel>("luxury beachfront", options);
|
|
```
|
|
|
|
## Field Attributes Reference
|
|
|
|
| Attribute | Purpose |
|
|
|-----------|---------|
|
|
| `SimpleField` | Non-searchable field (filters, sorting, facets) |
|
|
| `SearchableField` | Full-text searchable field |
|
|
| `VectorSearchField` | Vector embedding field |
|
|
| `IsKey = true` | Document key (required, one per index) |
|
|
| `IsFilterable = true` | Enable $filter expressions |
|
|
| `IsSortable = true` | Enable $orderby |
|
|
| `IsFacetable = true` | Enable faceted navigation |
|
|
| `IsHidden = true` | Exclude from results |
|
|
| `AnalyzerName` | Specify text analyzer |
|
|
|
|
## Error Handling
|
|
|
|
```csharp
|
|
using Azure;
|
|
|
|
try
|
|
{
|
|
var results = await searchClient.SearchAsync<Hotel>("query");
|
|
}
|
|
catch (RequestFailedException ex) when (ex.Status == 404)
|
|
{
|
|
Console.WriteLine("Index not found");
|
|
}
|
|
catch (RequestFailedException ex)
|
|
{
|
|
Console.WriteLine($"Search error: {ex.Status} - {ex.ErrorCode}: {ex.Message}");
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use `DefaultAzureCredential`** over API keys for production
|
|
2. **Use `FieldBuilder`** with model attributes for type-safe index definitions
|
|
3. **Use `CreateOrUpdateIndexAsync`** for idempotent index creation
|
|
4. **Batch document operations** for better throughput
|
|
5. **Use `Select`** to return only needed fields
|
|
6. **Configure semantic search** for natural language queries
|
|
7. **Combine vector + keyword + semantic** for best relevance
|
|
|
|
## Reference Files
|
|
|
|
| File | Contents |
|
|
|------|----------|
|
|
| [references/vector-search.md](references/vector-search.md) | Vector search, hybrid search, vectorizers |
|
|
| [references/semantic-search.md](references/semantic-search.md) | Semantic ranking, captions, answers |
|