<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[.NET - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[.NET - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/jp/search-labs/blog/category/dot-net-programming</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/blog/category/dot-net-programming</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/category/dot-net-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 15:07:26 GMT</lastBuildDate>
  <item>
    <title><![CDATA[AIエージェント開発におけるMicrosoftセマンティックカーネル向けElasticsearch Vector Store Connectorの使い方]]></title>
    <description><![CDATA[Microsoft Semantic Kernel は、AI エージェントを簡単に構築し、最新の AI モデルを C#、Python、または Java コードベースに統合できる軽量のオープンソース開発キットです。Semantic Kernel Elasticsearch Vector Store Connector のリリースにより、AI エージェントの構築に Semantic Kernel を使用する開発者は、Semantic Kernel の抽象化を引き続き使用しながら、Elasticsearch をスケーラブルなエンタープライズ グレードのベクター ストアとしてプラグインできるようになりました。]]></description>
    <content:encoded><![CDATA[<p><a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">Microsoft Semantic Kernel</a> チームと連携して、<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> Microsoft Semantic</a> Kernel (.NET) ユーザー向けに<a href="https://github.com/elastic/semantic-kernel-net/"> Semantic Kernel Elasticsearch Vector Store Connector が利用可能になったことを発表します。</a>セマンティック カーネルは、ベクター ストアからのより関連性の高いデータ駆動型の応答を使用して大規模言語モデル (LLM) を強化する機能など、エンタープライズ グレードの AI エージェントの構築を簡素化します。Semantic Kernel は、Elasticsearch などの Vector Stores と対話するためのシームレスな抽象化レイヤーを提供し、レコードのコレクションの作成、一覧表示、削除や、個々のレコードのアップロード、取得、削除などの重要な機能を提供します。</p><p><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">すぐに使用できるセマンティック カーネル Elasticsearch ベクター ストア コネクタは、</a>セマンティック カーネル<a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">ベクター ストアの抽象化</a>をサポートしており、開発者は AI エージェントの構築時に Elasticsearch をベクター ストアとしてプラグインすることが非常に簡単になります。</p><p>Elasticsearch はオープンソース コミュニティに強固な基盤を持ち、最近<a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL ライセンスを</a>採用しました。これらのツールは、オープンソースの Microsoft Semantic Kernel と組み合わせることで、強力なエンタープライズ対応ソリューションを提供します。このコマンド<code>curl -fsSL https://elastic.co/start-local | sh </code> (詳細については<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a>を参照) を実行して数分で Elasticsearch を起動し、ローカルで開始できます。その後、AI エージェントを本番稼働させながら、<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">クラウドホスト バージョン</a>または<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">セルフホスト</a>バージョンに移行できます。</p><p>このブログでは、Semantic Kernel を使用する際に<a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector を</a>使用する方法について説明します。コネクタの Python バージョンは将来提供される予定です。</p><h2>高レベルのシナリオ: Semantic Kernel と Elasticsearch を使用した RAG アプリの構築</h2><p>次のセクションでは例を見ていきます。大まかに言うと、ユーザーの質問を入力として受け取り、回答を返す RAG (Retrieval Augmented Generation) アプリケーションを構築しています。LLM として Azure OpenAI (<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">ローカル LLM</a>も使用可能)、ベクター ストアとして Elasticsearch、すべてのコンポーネントを結び付けるフレームワークとして Semantic Kernel (.net) を使用します。</p><p>RAG アーキテクチャに精通していない場合は、次の記事で簡単に概要を把握できます: <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag</a> 。</p><p>回答は、Elasticsearch vectorstore から取得され、質問に関連するコンテキストが入力する LLM によって生成されます。応答には、LLM によってコンテキストとして使用されたソースも含まれます。</p><h3>RAGの例</h3><p>この具体的な例では、社内のホテル データベースに保存されているホテルについてユーザーが質問できるアプリケーションを構築します。ユーザーは例えばさまざまな基準に基づいて特定のホテルを検索したり、ホテルのリストを要求したりできます。</p><p>サンプル データベースでは、100 件のエントリを含む<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">ホテルのリスト</a>を生成しました。コネクタのデモをできるだけ簡単に試せるように、サンプル サイズは意図的に小さくなっています。実際のアプリケーションでは、特に非常に大量のデータを扱う場合、Elasticsearch コネクタは `InMemory` ベクトル ストア実装などの他のオプションよりも優位性を発揮します。</p><p>完全なデモ アプリケーションは、Elasticsearch ベクター ストア コネクタ<a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">リポジトリ</a>にあります。</p><p>まず、必要な NuGet パッケージと using ディレクティブをプロジェクトに追加することから始めましょう。</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>これで、データ モデルを作成し、セマンティック カーネル固有の属性を指定して、ストレージ モデル スキーマとテキスト検索のヒントを定義できるようになりました。</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>ストレージ モデル スキーマ属性 (`VectorStore*`) は、Elasticsearch Vector Store Connector の実際の使用に最も関連しています。具体的には次のようになります。</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> レコード クラスのプロパティを、ベクトル ストアにレコードが格納されるキーとしてマークします。</p></li><li><p><code>VectorStoreRecordData</code> レコード クラスのプロパティを 'data' としてマークします。</p></li><li><p><code>VectorStoreRecordVector</code> レコード クラスのプロパティをベクトルとしてマークします。</p></li></ul><p>これらの属性はすべて、ストレージ モデルをさらにカスタマイズするために使用できるさまざまなオプション パラメーターを受け入れます。たとえば、 <code>VectorStoreRecordKey </code>の場合、異なる距離関数や異なるインデックス タイプを指定することが可能です。</p><p>テキスト検索属性 ( <code>TextSearch*</code> ) は、この例の最後のステップで重要になります。これらについては後ほど説明します。</p><p>次のステップでは、セマンティック カーネル エンジンを初期化し、コア サービスへの参照を取得します。実際のアプリケーションでは、サービス コレクションに直接アクセスするのではなく、<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">依存性注入を</a>使用する必要があります。同じことがハードコードされた構成とシークレットにも当てはまります。これらは、代わりに<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">構成プロバイダー</a>を使用して読み取る必要があります。</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p><code>vectorStoreCollection</code>サービスを使用してコレクションを作成し、いくつかの<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">デモ レコード</a>を取り込むことができるようになりました。</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>これは、セマンティック カーネルが、複雑なベクトル ストアの使用を、いくつかの単純なメソッド呼び出しにまで削減する方法を示しています。</p><p>内部的には、Elasticsearch に新しいインデックスが作成され、必要なすべてのプロパティ マッピングが作成されます。その後、データ セットは完全に透過的にストレージ モデルにマッピングされ、最終的にインデックスに保存されます。以下は Elasticsearch でのマッピングの様子です。</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p><code>embeddings.GenerateEmbeddingsAsync()</code>は、構成された Azure AI Embeddings Generation サービスを透過的に呼び出しました。</p><p>このデモの最後のステップでは、さらに多くの魔法が観察できます。</p><p><code>InvokePromptAsync</code>を 1 回呼び出すだけで、ユーザーがデータについて質問したときに、次のすべての操作が実行されます。</p><p>1.ユーザーの質問の埋め込みが生成される</p><p>2. ベクトルストアで関連するエントリを検索する</p><p>3. クエリの結果はプロンプトテンプレートに挿入されます</p><p>4. 最終プロンプトの形式で実際のクエリがAIチャット補完サービスに送信されます。</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>以前データ モデルで定義した<code>TextSearch*</code>属性を覚えていますか?これらの属性により、プロンプト テンプレート内の対応するプレースホルダーを使用できるようになります。これらのプレースホルダーには、ベクター ストア内のエントリからの情報が自動的に入力されます。</p><p>「屋上バーがあるホテルをすべて教えてください。」という質問に対する最終的な回答は次のとおりです。</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>答えは、hotels.csvの次のエントリを正しく参照しています。</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>この例は、Microsoft Semantic Kernel を使用すると、よく考えられた抽象化によって複雑さが大幅に軽減され、非常に高いレベルの柔軟性が実現されることを示しています。たとえば、コードの 1 行を変更するだけで、コードの他の部分をリファクタリングすることなく、使用されているベクトル ストアまたは AI サービスを置き換えることができます。</p><p>同時に、このフレームワークは、`InvokePrompt` 関数やテンプレート、検索プラグイン システムなどの膨大な高レベル機能を提供します。</p><p>完全なデモ アプリケーションは、Elasticsearch ベクター ストア コネクタ リポジトリにあります。</p><h2>Elasticsearchで他に何ができるのか</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearchの新しいsemantic_textマッピング：セマンティック検索の簡素化</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Elasticsearch におけるセマンティックリランキング（リトリーバー使用）</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">高度なRAGテクニックパート1：データ処理</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">高度なRAGテクニックパート2：クエリとテスト</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Llama 3オープンソースとElasticでRAGを構築する</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">LangGraph、LLaMA3、Elasticsearchベクターストアを使用してローカルエージェントをゼロから構築するチュートリアル</a></p></li></ul><h2>Elasticsearch とセマンティックカーネル: 次は何?</h2><ul><li><p>.NET で GenAI アプリケーションを構築する際に、Elasticsearch ベクター ストアを Semantic Kernel に簡単にプラグインする方法を示しました。次回の Python 統合にご期待ください。</p></li><li><p>Semantic Kernel は<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">ハイブリッド検索</a>などの高度な検索機能の抽象化を構築するため、Elasticsearch Connect を使用すると、.NET 開発者は Semantic Kernel を使用しながらそれらを簡単に実装できるようになります。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[BlazorとElasticsearchを使用した検索アプリの構築]]></title>
    <description><![CDATA[Blazor と Elasticsearch を使用して検索アプリケーションを構築する方法と、ハイブリッド検索に Elasticsearch .NET クライアントを使用する方法を学習します。]]></description>
    <content:encoded><![CDATA[<p>この記事では、C# スキルを活用して Blazor と Elasticsearch を使用した検索アプリケーションを構築する方法を学習します。<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a>クライアントを使用して、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">フルテキスト</a>、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">セマンティック</a>、<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">ハイブリッド</a>検索クエリを実行します。</p><p><strong>注:</strong> Elasticsearch C# クライアント<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">NEST</a>の古いバージョンに精通している場合は、NEST クライアントの廃止と新機能に関するこちらの<a href="https://www.elastic.co/search-labs/blog/net-client-evolution">ブログ投稿を</a>お読みください。<em>NESTは.NETクライアントの前世代であり、</em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="Blazor アプリの構築: Blazor ダイアグラムを使用した esre" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">Blazor とは何ですか?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">ESRE とは何ですか?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">ELSER の設定</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">データのインデックス作成</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">建築申請</a></p></li></ul><h2>Blazor とは何ですか?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Blazorサーバー" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor は</a>、開発者がクライアントまたはサーバー上で実行される Web アプリケーションを構築できるようにするために Microsoft によって作成された、オープン ソースの HTML、CSS、および C# ベースの Web フレームワークです。Blazor を使用すると、再利用可能なコンポーネントを作成してアプリケーションをより速く構築することもできます。開発者は、同じファイル内に C# で HTML ビューとアクションを構築できるため、読みやすくクリーンなコードを維持できます。さらに、Blazor Hybrid を使用すると、.NET コードを介してネイティブ プラットフォーム機能にアクセスするネイティブ モバイル アプリを構築できます。</p><p>Blazor を優れたフレームワークにする機能の一部を以下に示します。</p><ul><li><p>サーバー側とクライアント側のレンダリングオプション</p></li><li><p>再利用可能なUIコンポーネント</p></li><li><p>SignalRによるリアルタイム更新</p></li><li><p>組み込みの状態管理</p></li><li><p>組み込みルーティングシステム</p></li><li><p>強力な型付けとコンパイル時のチェック</p></li></ul><h3>Blazor を選ぶ理由</h3><p>Blazor は他のフレームワークやライブラリに比べていくつかの利点があります。開発者はクライアント コードとサーバー コードの両方に C# を使用できるため、強力な型指定とコンパイル時のチェックが可能になり、信頼性が向上します。.NET エコシステムとシームレスに統合され、.NET ライブラリとツールの再利用を可能にし、強力なデバッグ サポートを提供します。</p><h2>ESRE とは何ですか?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">Elasticsearch Relevance Engine ™ (ESRE)</a>は、強力な Elasticsearch 検索エンジンをベースに機械学習と人工知能を使用して<a href="https://www.elastic.co/guide/en/esre/current/learn.html">検索アプリケーションを構築するためのツール</a>セットです。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="エスレ" /><p>ESREについて詳しく知りたい方は、<a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">こちらの</a>ブログ記事をご覧ください。</p><h2>ELSER の設定</h2><p>Elastic の<a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a>機能を活用するために、モデルプロバイダーとして<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER を</a>使用します。</p><p><em>ElasticsearchのELSERモデルを使用するには、PlatinumまたはEnterpriseライセンスと、最低4GBの専用機械学習（ML）ノードが必要です。詳細は</em><a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>こちらをご覧ください。</em></a></p><p>まず推論エンドポイントを作成します。</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>ELSER を初めて使用する場合は、モデルがバックグラウンドで読み込まれるときに 502 Bad Gateway エラーが発生する可能性があります。Kibana の<code>Machine Learning &gt; Trained Models</code>でモデルのステータスを確認できます。デプロイが完了したら、次のステップに進むことができます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="訓練済みモデルのチェック" /><h2>データのインデックス作成</h2><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">ここから</a>データセットをダウンロードし、Kibana を使用してデータをインポートできます。これを行うには、ホームページにアクセスして「データのアップロード」をクリックします。次に、ファイルをアップロードして<code>Import</code>をクリックします。最後に、 <code>Advanced</code>タブに移動して、次のマッピングを貼り付けます。</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="データのインポート" /><p>セマンティック クエリとフルテキスト クエリを実行できるインデックスを作成します。<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">semantic_text</a>フィールド タイプは、データのチャンク化と埋め込みを処理します。として インデックス付けしていることに注意してください 。フィールドを<em> と text` の</em><em> 両方としてインデックス付けしたい場合は、 copy_to を使用できます</em><em> 。</em><em><code>longDescription</code></em><em><code>semantic_text</code></em><em><code>semantic_text</code></em></p><h2>BlazorとElasticsearchを使ったアプリ構築</h2><h3>APIキー</h3><p>最初に行う必要があるのは、Elasticsearch へのリクエストを認証するための API キーを作成することです。API キーは読み取り専用であり、 <code>books-blazor</code>インデックスのクエリのみに許可される必要があります。</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>次のような画面が表示されます。
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>後で必要になるので、 <code>encoded</code>応答フィールドの値を保存します。<a href="https://www.elastic.co/cloud/">Elastic Cloud</a>で実行している場合は、Cloud ID も必要になります。(<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">こちらで</a>確認できます)。</p><h4>Blazorプロジェクトの作成</h4><p>まず Blazor をインストールし、<a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">公式の手順</a>に従ってサンプル プロジェクトを作成します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Blazor チュートリアル - Blazor プロジェクトの作成" /><p>プロジェクトを作成すると、フォルダー構造とファイルは次のようになります。</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p>テンプレートアプリケーションには<a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a>が含まれていますスタイリング用。</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET</a>クライアントをインストールしてプロジェクトのセットアップを完了します。</p>dotnet add package Elastic.Clients.Elasticsearch<p>この手順を完了すると、ページは次のようになります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor の hello world" /><h3>フォルダ構造</h3><p>ここで、フォルダーを次のように整理します。</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>ファイルの説明:</p><ul><li><p>Components/Pages/Search.razor: 検索バー、結果、フィルターを含むメイン ページ。</p></li><li><p>コンポーネント/ページ/Search.razor.css:ページ スタイル。</p></li><li><p>Components/Elasticsearch/SearchBar.razor: 検索バー コンポーネント。</p></li><li><p>Components/Elasticsearch/Results.razor: 結果コンポーネント。</p></li><li><p>Components/Elasticsearch/Facet.razor: フィルター コンポーネント。</p></li><li><p>Components/Svg/GlassIcon.razor: 検索アイコン。</p></li><li><p>Components/_Imports.razor: これにより、すべてのコンポーネントがインポートされます。</p></li><li><p>Models/Book.cs: ブックフィールドのスキーマを保存します。</p></li><li><p>Models/Response.cs: 検索結果、ファセット、ヒット数の合計を含む応答スキーマを保存します。</p></li><li><p>Services/ElasticsearchService.cs: Elasticsearch サービス。Elasticsearch への接続とクエリを処理します。</p></li></ul><h4>初期設定</h4><p>まずはクリーンアップから始めましょう。</p><p>ファイルを削除します:</p><ul><li><p>コンポーネント/ページ/Counter.razor</p></li><li><p>コンポーネント/ページ/Weather.razor</p></li><li><p>コンポーネント/ページ/ホーム.razor</p></li><li><p>コンポーネント/レイアウト/NavMenu.razor</p></li><li><p>コンポーネント/レイアウト/NavMenu.razor.css</p></li></ul><p><code>/Components/_Imports.razor</code>ファイルを確認してください。次のインポートが必要です。</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>プロジェクトにElasticを統合する</h4><p>次に、Elasticsearch コンポーネントをインポートします。</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p>アプリケーションのスペースを増やすために、 <code>/Components/Layout/MainLayout.razor</code>ファイルからデフォルトのサイドバーを削除します。</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="ナビゲーションバーをレイアウトから削除しました" /><p>次に、<a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">ユーザー シークレット</a>の Elasticsearch 資格情報を入力します。</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>このアプローチを使用すると、.Net 8 は機密データをプロジェクト フォルダー外の別の場所に保存し、 <code>IConfiguration</code>インターフェースを使用してアクセスできるようになります。これらの変数は、同じユーザー シークレットを使用するすべての .Net プロジェクトで使用できます。</p><p>次に、 <code>Program.cs</code>ファイルを変更してシークレットを読み取り、Elasticsearch クライアントをマウントします。</p><p>まず、必要なライブラリをインポートします。</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services: Elasticsearch サービスが含まれます。</p></li><li><p>Elastic.Clients.Elasticsearch: Elasticsearch クライアント .Net 8 ライブラリをインポートします。</p></li><li><p>Elastic.Transport: Elasticsearch トランスポート ライブラリをインポートします。これにより、ApiKey クラスを使用してリクエストを認証できるようになります。</p></li></ul><p>次に、 <code>var app = builder.Build()</code>行の前に次のコードを挿入します。</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>このコードは、ユーザー シークレットから Elasticsearch 資格情報を読み取り、Elasticsearch クライアント インスタンスを作成します。</p><p>ElasticSearch クライアントの初期化後、次の行を追加して Elasticsearch サービスを登録します。</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>次のステップでは、 <code>/Services/ElasticsearchService.cs</code>ファイルに検索ロジックを構築します。</p><p>まず、必要なライブラリとモデルをインポートします。</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>次に、クラス<code>ElasticsearchService</code> 、コンストラクター、および変数を追加します。</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>検索の設定</h4><p>それでは、検索ロジックを構築してみましょう。</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> ユーザーが選択したファセットを使用して、検索クエリのフィルターを構築します。</p></li><li><p><code>BuildHybridQuery</code> 全文検索とセマンティック検索を組み合わせた<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">ハイブリッド検索</a>クエリを構築します。</p></li></ul><p>次に、検索メソッドを追加します。</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>: ハイブリッド クエリを使用して検索を実行し、ファセットを構築するための集計が含まれた結果を返します。</p></li><li><p><code>FormatFacets</code>: 集計応答を辞書にフォーマットします。</p></li><li><p><code>ConvertFacetDictionary</code>: ファセット辞書をより読みやすい形式に変換します。</p></li></ul><p>次のステップでは、検索ページの結果として出力される Elasticsearch クエリの<code>hits</code>で返されるデータを表すモデルを作成します。</p><p>まず、ファイル<code>/Models/Book.cs</code>を作成し、次の内容を追加します。</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>次に、 <code>/Models/Response.cs</code>ファイルに Elastic レスポンスを設定し、次のコードを追加します。</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>基本的なUIの設定</h4><p>次に、SearchBar コンポーネントを追加します。ファイル<code>/Components/Elasticsearch/SearchBar.razor</code>に次の内容を追加します。</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>このコンポーネントには、検索バーと検索を実行するためのボタンが含まれています。</p><p>Blazor は、同じファイル内で C# コードを使用して HTML を動的に生成できるようにすることで、優れた柔軟性を提供します。</p><p>その後、ファイル<code>/Components/Elasticsearch/Results.razor</code>で、検索結果を表示する結果コンポーネントを構築します。</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>最後に、検索結果をフィルタリングするためのファセットを作成する必要があります。</p><p><em>注: ファセットとは、製品タイプ、価格帯、ブランドなど、特定の属性やカテゴリに基づいて検索結果を絞り込むことができるフィルターです。これらのフィルターは通常、クリック可能なオプション（多くの場合チェックボックス）として表示され、ユーザーが検索を絞り込み、関連性の高い結果を見つけやすくします。Elasticsearchでは、ファセットは</em><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>集計を</em></a>使用して作成されます<em>。</em></p><p>ファイル<code>/Components/Elasticsearch/Facet.razor</code>に次のコードを配置してファセットを設定します。</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>このコンポーネントは、 <code>author</code> 、 <code>categories</code> 、および<code>status</code>フィールドの<code>terms</code>集計を読み取り、Elasticsearch に送り返すフィルターのリストを生成します。</p><p>さて、すべてをまとめてみましょう。</p><p><code>/Components/Pages/Search.razor</code>ファイル内:</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>私たちのページは機能しています!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Blazorページの例" /><p>ご覧のとおり、ページは機能しますが、スタイルが欠けています。見た目をもっと整理してレスポンシブにするために、CSS を追加してみましょう。</p><p>レイアウト スタイルの置き換えを始めましょう。<code>Components/Layout/MainLayout.razor.css</code>ファイル内:</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p><code>Components/Pages/Search.razor.css</code>ファイルで検索ページのスタイルを追加します。</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>ページの見た目が良くなり始めました:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="検索ページにスタイルを追加した後の Blazor ページ" /><p>最後に仕上げましょう:</p><p>次のファイルを作成します。</p><ul><li><p>コンポーネント/Elasticsearch/Facet.razor.css</p></li><li><p>コンポーネント/Elasticsearch/Results.razor.css</p></li></ul><p><code>Facet.razor.css</code>のスタイルを追加します:</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p><code>Results.razor.css</code>の場合:</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>最終結果:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="Blazor アプリページの構築の最終結果" /><p>アプリケーションを実行するには、次のコマンドを使用できます。</p><p><code>dotnet watch</code></p><p>やったね！検索バーを使用して Elasticsearch インデックス内の書籍を検索し、著者、カテゴリ、ステータス別に結果をフィルタリングできるようになりました。</p><h3>全文検索とセマンティック検索の実行</h3><p>デフォルトでは、アプリは<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html"> 全文検索</a> と <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">セマンティック検索の</a><a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search"> 両方を使用して ハイブリッド検索を</a> 実行します。フルテキスト検索用とセマンティック検索用の 2 つの別個のメソッドを作成し、いずれかのメソッドを選択してユーザーの入力に基づいてクエリを構築することで、検索ロジックを変更できます。</p><p><code>/Services/ElasticsearchService.cs</code>ファイルの<code>ElasticsearchService</code>クラスに次のメソッドを追加します。</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>どちらのメソッドも<code>BuildHybridQuery</code>メソッドと同様に動作しますが、全文検索またはセマンティック検索のみを実行します。</p><p>選択した検索方法を使用するように<code>SearchBooksAsync</code>メソッドを変更できます。</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>完全な申請書は<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">ここから</a>ご覧いただけます</p><h2>まとめ</h2><p>Blazor は、C# を使用して Web アプリケーションを構築できる効果的なフレームワークです。Elasticsearch は、検索アプリケーションを構築できる強力な検索エンジンです。両方を組み合わせることで、ESRE のパワーを活用して、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">セマンティック検索エクスペリエンス</a>を短期間で実現し、堅牢な検索アプリケーションを簡単に構築できます。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch .NET クライアントの進化: NEST から Elastic.Clients.Elasticsearch へ]]></title>
    <description><![CDATA[Elasticsearch .NET クライアントの進化と、NEST から Elastic.Clients.Elasticsearch への移行について学習します。]]></description>
    <content:encoded><![CDATA[<h2>.NET クライアントと NEST の紹介</h2><p>.NET の世界では、Elasticsearch との統合は長い間<code>NEST</code>ライブラリによって促進されてきました。このライブラリは、開発者が Elasticsearch の強力な検索機能や分析機能と対話するための堅牢なインターフェースとして機能します。<code>NEST</code> 、Elasticsearch 用のネイティブ .NET クライアントの必要性から生まれ、豊富な機能セットとシームレスな統合機能により、開発者の間で急速に人気を博しました。</p><p><a href="https://github.com/elastic/elasticsearch/commit/ec72ca8b7a115f9b2eea3c76c518062b99a1d015">Elasticsearch の最初のコミット</a> から約<a href="https://github.com/elastic/elasticsearch-net/commit/724f932ba598915c8c3c35a19827fdfa4f782c1d"> 14 年</a> とわずか 8 か月間、NEST は Elasticsearch のリリースを忠実に追跡してきました。</p><h2>NEST から Elastic.Clients.Elasticsearch への移行</h2><p>Elasticsearch が進化するにつれて、 <code>NEST</code>の複雑なコードベースの維持がますます困難になりました。私たちは、クライアント開発に対してより持続可能なアプローチが必要であることを認識し、.NET クライアントを根本から再設計する取り組みを始めました。最初のベータ版をリリースするのにほぼ 1 年かかり、すべてのサーバー エンドポイントのサポートに近づくまでにさらに 1 年かかりました。最も難しい決断の 1 つは、保守性を優先するためにライブラリの範囲を縮小することでした。</p><p>現在の Elasticsearch API サーフェスのサイズを考えると、450 を超えるエンドポイントと 3000 近くのタイプ (リクエスト、レスポンス、クエリ、集計など) を手動で管理することはもはや現実的ではありません。言語クライアントと Elasticsearch 間の一貫性、正確性、およびタイムリーな調整を保証するために、8.x クライアントと多くの関連タイプは、<a href="https://github.com/elastic/elasticsearch-specification">共有仕様</a>から自動的にコード生成されるようになりました。これは、Azure、AWS、Google Cloud Platform などの SDK とライブラリ間でクライアントとサーバーの整合性を維持するための一般的なソリューションです。</p><p>Elasticsearch 仕様は、 <code>NEST</code>から型マッピングをエクスポートすることによって 8 年以上前に作成されましたが、クライアント チームの懸命な努力により、同じ仕様を使用して新しい .NET クライアント (および Java、Go などの複数の他の言語のクライアント) を作成できるようになりました。</p><p>バージョン 8.13 のリリースにより、 <code>NEST</code>の廃止が正式に発表されました。Elasticsearch が<code>Elastic.Clients.Elasticsearch</code>に移行するにつれて、 <code>NEST</code>段階的に廃止され、年末にサポートが終了します。スムーズな移行を実現し、潜在的な中断を最小限に抑えるために、開発者は移行作業を早期に開始することを強くお勧めします。<code>Elastic.Clients.Elasticsearch</code>を採用すると、最新のサーバー機能との互換性が確保されるだけでなく、廃止される機能に対してもアプリケーションの将来性が保証されます。</p><h2>Elastic.Clients.Elasticsearch: 機能と変更点の概要</h2><p>v8 クライアント<code>Elastic.Clients.Elasticsearch</code>に切り替えると、Elasticsearch 8 のすべての新機能にアクセスできるようになり、ライブラリ自体も大幅に最新化されますが、以前のバージョンに比べて便利な機能が少なくなります。新しいコア機能には、クエリ言語<code>ES|QL</code> 、最新の機械学習 (ML) 機能、OpenTelemetry 互換アクティビティの形式での改善された診断機能などがあります。バージョン 8.13 以降、 <code>Elastic.Clients.Elasticsearch</code> Elasticsearch 8 のほぼすべてのサーバー機能をサポートします。</p><p>たとえば、重要な重大な変更は集計に関連しています。<code>NEST</code>では、Fluent API の使用は次のようになります。</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
    .Children&lt;CommitActivity&gt;("name_of_child_agg", child =&gt; child
        .Aggregations(childAggs =&gt; childAggs
            .Average("average_per_child", avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor))
            .Max("max_per_child", max =&gt; max.Field(p =&gt; p.ConfidenceFactor))
            .Min("min_per_child", min =&gt; min.Field(p =&gt; p.ConfidenceFactor))
        )
    )
)
<p>一方、v8 クライアントでは次の構文が必要です。</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
	.Add("name_of_child_agg", agg =&gt; agg
		.Children(_ =&gt; {})
		.Aggregations(childAggs =&gt; childAggs
			.Add("average_per_child", agg =&gt; agg.Avg(avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor)))
			.Add("max_per_child", agg =&gt; agg.Max(max =&gt; max.Field(p =&gt; p.ConfidenceFactor)))
			.Add("min_per_child", agg =&gt; agg.Min(min =&gt; min.Field(p =&gt; p.ConfidenceFactor)))
		)
	)
)
<h2>NEST v7 から .NET クライアント v8 への移行</h2><p>包括的な移行ガイドは、こちらでご覧いただけます:<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.18/migration-guide.html">移行ガイド: NEST v7 から .NET Client v8 へ</a>。</p><h2>参考資料</h2><ul><li><p><a href="https://github.com/elastic/elasticsearch-net">GitHub の Elastic.Clients.Elasticsearch v8 クライアント</a></p></li><li><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch">NuGet 上の Elastic.Clients.Elasticsearch v8 クライアント</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/net-client-evolution</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/net-client-evolution</guid>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Florian Bernd]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac0adcdaa85d703f/6a17f5816df731db170a1079/d09f7f5cb468d5e84f7f4636d92b3476e6604e11-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>