azure-search-documents-dotnet について
Azure AI Search を使う .NET 開発者向けのスキルです。インデックスやドキュメントの管理に加え、全文・ベクトル・セマンティック・ハイブリッド検索の実装例を扱います。
主な機能
- 01全文検索とファセット検索
- 02ベクトル・セマンティッククエリ
- 03ハイブリッド検索パターン
- 04インデックスとドキュメント管理
- 05オートコンプリートと候補表示
活用シーン
- 01検索インデックスの作成と設定
- 02ドキュメントのアップロード・更新・削除
- 03ベクトル検索やセマンティック検索の実装
- 04検索画面へのオートコンプリート追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill azure-search-documents-dotnetインストール後、Claude Code で/azure-search-documents-dotnetを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
UPSTREAM-NOTICE.mdUPSTREAM-LICENSE.txtreferences/semantic-search.mdvector-search.md詳細
同梱されたアップストリームのサポート
サポートファイルは、スキルからの相対パスに含まれています。固定されたソース、ライセンス、ハッシュ、およびインポート時の変更については、UPSTREAM-NOTICE.md を参照してください。
Azure.Search.Documents (.NET)
フルテキスト、ベクトル、セマンティック、およびハイブリッド検索機能を備えた検索アプリケーションを構築します。
インストール
dotnet add package Azure.Search.Documents
dotnet add package Azure.Identity
これらのコード片を使用するには、互換性のある固定バージョンの SDK が必要です。使用前に API リファレンスを確認してください。これらは、コンパイル可能なアプリケーション一式ではありません。
環境変数
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>
認証
DefaultAzureCredential(推奨):
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 キー:
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);
クライアントの選択
| クライアント | 用途 |
|---|---|
SearchClient | インデックスのクエリ、ドキュメントのアップロード/更新/削除 |
SearchIndexClient | インデックス、シノニムマップの作成/管理 |
SearchIndexerClient | インデクサー、スキルセット、データソースの管理 |
インデックスの作成
FieldBuilder を使用する(推奨)
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);
フィールドの手動定義
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"
}
}
};
ドキュメントの操作
var searchClient = new SearchClient(endpoint, indexName, credential);
// Upload inserts or replaces the entire existing document with the same key.
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);
検索パターン
基本的な検索
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})");
}
ファセット検索
var options = new SearchOptions
{
Facets = { "Rating,count:5" }
};
var results = await searchClient.SearchAsync<Hotel>("*", options);
foreach (var facet in results.Value.Facets["Rating"])
{
Console.WriteLine($"Rating {facet.Value}: {facet.Count}");
}
オートコンプリートと候補表示
// 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);
ベクトル検索
詳細なパターンについては、references/vector-search.md を参照してください。
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);
セマンティック検索
詳細なパターンについては、references/semantic-search.md を参照してください。
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}");
}
ハイブリッド検索(ベクトル + キーワード + セマンティック)
var vectorQuery = new VectorizedQuery(embedding)
{
KNearestNeighborsCount = 50,
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);
フィールド属性のリファレンス
| 属性 | 用途 |
|---|---|
SimpleField | 検索対象外のフィールド(フィルター、並べ替え、ファセット) |
SearchableField | フルテキスト検索可能なフィールド |
VectorSearchField | ベクトル埋め込みフィールド |
IsKey = true | ドキュメントキー(必須、インデックスごとに 1 つ) |
IsFilterable = true | $filter 式を有効化 |
IsSortable = true | $orderby を有効化 |
IsFacetable = true | ファセットナビゲーションを有効化 |
IsHidden = true | 結果から除外 |
AnalyzerName | テキストアナライザーを指定 |
エラー処理
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}");
}
ベストプラクティス
- 本番環境では API キーよりも
DefaultAzureCredentialを使用する - 型安全なインデックス定義には、モデル属性と
FieldBuilderを使用する - 冪等なインデックス作成には
CreateOrUpdateIndexAsyncを使用する - スループットを向上させるため、ドキュメント操作をバッチ処理する
- 必要なフィールドのみを返すため、
Selectを使用する - 自然言語クエリに対応するため、セマンティック検索を構成する
- ベクトル + キーワード + セマンティックを組み合わせ、代表的なクエリで関連性を評価する
リファレンスファイル
| ファイル | 内容 |
|---|---|
| references/vector-search.md | ベクトル検索、ハイブリッド検索、ベクトライザー |
| references/semantic-search.md | セマンティックランキング、キャプション、回答 |
使用する場合
このスキルは、概要に記載されたワークフローやアクションを実行する場合に適用できます。
制限事項
- 上記に記載された範囲とタスクが明確に一致する場合にのみ、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、処理を停止して確認を求めてください。
運用上の境界
FieldBuilder は設定済みのシリアライザーを使用します。ここでは属性モデルに PascalCase を使用し、代替の手動スキーマでは小文字の名前を使用します。明示的な JSON プロパティマッピングと、それに対応するクライアントシリアライゼーションなしに、両者を混在させないでください。後続の Article/Product の例には、それぞれに対応するスキーマとシリアライザーが必要です。オートコンプリートを使う前にサジェスターを設定し、セマンティッククエリを実行する前に名前付きセマンティック構成を設定してください。
環境値、テナント/ID、最小権限のデータロールを検証してください。インデックスの作成とドキュメントの書き込みには、認可と、置き換えが重要となる場合のバックアップが必要です。すべての IndexingResult を確認するか、ThrowOnAnyError を有効にしてください。受理されたバッチにドキュメントの失敗が含まれる場合があります。適切な一時的失敗アクションのみを、上限を設けたバックオフで再試行してください。信頼できない OData フィルターを連結せず、値には SearchFilter.Create を使用してください。スニペット、キャプション、インデックス化されたテキストは、表示時に信頼できないコンテンツとして扱い、プライベートなドキュメントや認証情報を含むエラーをログに記録しないでください。クラウド環境や .NET ランタイムでの検証は含まれていません。