refactor: flatten Microsoft skills from nested to flat directory structure
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)
This commit is contained in:
339
skills/azure-search-documents-dotnet/SKILL.md
Normal file
339
skills/azure-search-documents-dotnet/SKILL.md
Normal file
@@ -0,0 +1,339 @@
|
||||
---
|
||||
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 |
|
||||
Reference in New Issue
Block a user