ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

第46回 Tokyo Jazug Night で Azure OpenAI Service の社内導入事例を話してきました

jazug.connpass.com

第46回 Tokyo Jazug Night に参加して、社内の問い合わせ対応業務に Azure OpenAI Service を活用した社内導入事例を話してきました。

www.youtube.com

Azure OpenAI や AI Search の概要や機能に関する技術的なセッションというよりは、実際に作ってみて分かったことや苦労したことをエンジニアの観点で話しました。

このテーマが参加者に方々にとって求めている内容なのか不安でしたが、

  • AOAI を触っている人からは、データが大事、アジャイルと相性がいい、などが共感できた
  • AOAI を触っていない人からは、これから作るので RAG アーキテクチャや精度向上の取り組みが参考になった

といった感じのフィードバックが会場からのコメントや Twitter でいただけたので、話して良かったと思いました。

スライドは、こちらで共有しています。

speakerdeck.com

ちなみに、AlterLock(オルターロック)とは、弊社で販売している自転車用の盗難防止アラーム& GPS 追跡デバイスです。
alterlock.net

Microsoft Ignite 2023 に現地参加してきました

これまで Microsoft Build と MVP Global Summit には現地参加したことがありましたが、Microsoft Ignite は初めての現地参加です。コロナ禍明けということもあり、久しぶりの海外カンファレンスとなりました。
news.microsoft.com

Ignite 2023 でアナウンスされた内容は、このあたりの記事で説明されています。
キーノートやブレイクアウトのセッション動画も公開されているので、気になるトピックは視聴してみてください。

この記事では、現地参加の雰囲気や個人的に感じたことをお伝えしていきます。

Registration

会場は、シアトルにある Seattle Convention Center Summit です。Day1 のキーノート前にレジストすると大行列するので、前日にレジストのために会場に行きました。





Keynote:Day1

いよいよ初日のキーノート、現地参加するなら最前列を確保したいと思い、開始2時間前の7時から並びました。



前から4列を確保でき、キーノートが始まりました。



今回の Ignite の感想を一言で言うと、「Copilot stack が具体化されて、AI の民主化がより現実的になった」です。Satya Nagdella からの「Microsoft is the Copilot Company」というのメッセージと共に発表された Copilot Studio のインパクトが大きかったです。

Day1 のキーノートでは、Copilot stack の下から上の順番で話が進みました。

  • AI ワークロードを支えるインフラ強化(Azure Boost、Azure Cobalt、Azure Maia)
  • OpenAI モデルのアップデート、様々なモデルのサービス化(Model as a Service)
  • Azure AI Studio による統合開発環境の提供
  • AI Search のベクトル検索 や Microsoft Fabric の GA
  • Microsoft Copilot for XX のラインナップ拡充
  • Copilot Studio を使ってローコードで独自のデータソースを参照

ちなみに、5月の build で発表された際は、このようなアーキテクチャでした。だいぶ解像度が上がったことが分かります。


Keynote:Day2

Day2 のキーノートは、Scott Guthrie。昨日とは逆サイドの席を確保。


Day1 のキーノートとは反対に、Copilot stack の上から下の順番で話が進みました。

Copilot Studio のデモ(Teams の Copilot から良い回答が返されなかったので、データソースを追加して専用の Copilot を作る)であったり、Developer 向けの GitHub Copilot や Azure Copilot が紹介されました。

会場の様子

Convention Center には1~5階と地下1階の各フロアに部屋があって、自由に聞きたいセッションを選べます。


ブレイクアウトセッションの様子。Scott Hanselman のデモは、面白い中にもテーマが込められていて流石でした。



キーノートでは触れられませんでしたが、Azure Functions の Flex Consumption の発表がアツかったです。サーバーレスなのに VNET 統合できてコールドスタートも速い、Premium Plan を選択する機会は少なくなりそう。
techcommunity.microsoft.com

Expo 会場の雰囲気。

お昼に配られるランチはこんな感じ。一番美味しかったのはバナナですね。

おやつもいろいろあって、スタバのコーヒーも飲み放題。リンゴにピーナッツクリームが添えられた謎のおやつは意外と美味しかったです。


Microsoft and DocuSign Mixer

Day1 の夜は、Convention Center の5階で Mixer がありました。



Microsoft and Logitech Celebration

Day2 の夜は、スペースニードル近くの Pacific Science Center を貸し切ってのパーティでした。







Closing Keynote

最後のセッションは、Mark Russinovich の Closing Keynote でした。所々にジョークを織り交ぜながらのセッションで、恒例の CPU コアを色付けするスーパーメカゴジラビーストのデモをライブで見れて良かったです。


Closing Keynote 終了後は、スタッフが盛大にお見送りしてくれました。日本のイベントにはない光景でアメリカらしいなと思いました。


今回の戦利品。バックパックの色とマーク、Tシャツの柄も自由に選べました。

Microsoft campus

レドモンドにある Microsoft 本社の Visitor Center にも行きました。来年3月のグロサミでまた来れるといいな。




まとめ

Microsoft Ignite 2023 にシアトルで現地参加してきました。
日本からオンライン視聴する際もライブで見ることが多いですが、現地の会場でセッションを聞く方が明らかに伝わってくるものが多く、心に響いてくるなと感じました。この技術をあのユースケースで使ってみよう、あの技術を試しに触ってみよう、といったモチベーションも高まります。
また機会があれば、海外カンファレンスに現地参加しようと思います。

シアトルを観光した話は、こちらの記事を参照してください。
gooner.hateblo.jp

Azure Cognitive Search のセマンティック検索を .NET アプリケーションから使ってみた

まだプレビューですが Azure Cognitive Search にセマンティック検索機能が提供されました。前回の記事ではベクトル検索を試したので、今回はセマンティック検索を試してみました。
gooner.hateblo.jp

セマンティック検索とは

従来のキーワードによる全文検索とは異なり、質問の意図を理解して関連性の高い検索結果を返すこと(Semantic re-ranking)ができます。
そのほかにも、下記のような機能があります。

  • Semantic answers
    • 検索結果に関連するドキュメントのほかに、質問に対する回答を最上位に表示する。
    • Google やBing で検索した際にトップに表示されるアレです。

  • Semantic captions and highlights
    • 検索結果に関連性がある理由を知らせるために、要約を表示したりキーワードを強調表示したりする。
    • Google やBing の検索結果の表示形式のことです。


なかなか説明が難しい機能なので、こちらの記事が分かりやすいです。
qiita.com

セマンティック検索に対応したインデックスを作成する

前回の記事で作成した .NET アプリケーションにセマンティック検索を追加します。
まずは、Azure ポータルで Cognitive Search のセマンティック検索を有効化します。

セマンティック検索に対応したインデックスを作成します。SemanticSettings プロパティを追加し、titlecontentcategory のフィールドを設定しました。

static SearchIndex CreateSearchIndex(string indexName)
{
    var vectorSearchConfigName = "vector-config";
    var modelDimensions = 1536;

    return new SearchIndex(indexName)
    {
        VectorSearch = new VectorSearch
        {
            AlgorithmConfigurations =
            {
                new HnswVectorSearchAlgorithmConfiguration(vectorSearchConfigName)
            }
        },
        SemanticSettings = new SemanticSettings
        {
            Configurations =
            {
                new SemanticConfiguration(SemanticSearchConfigName,
                    new PrioritizedFields
                    {
                        TitleField = new SemanticField{ FieldName = "title" },
                        ContentFields =
                        {
                            new SemanticField { FieldName = "content" }
                        },
                        KeywordFields =
                        {
                            new SemanticField { FieldName = "category" }
                        }
                    })
            },
        },
        Fields =
        {
            new SimpleField("id", SearchFieldDataType.String) { IsKey = true, IsFilterable = true, IsSortable = true, IsFacetable = true },
            new SearchableField("title") { IsFilterable = true, IsSortable = true, AnalyzerName = LexicalAnalyzerName.JaLucene },
            new SearchableField("content") { IsFilterable = true, AnalyzerName = LexicalAnalyzerName.JaLucene },
            new SearchField("contentVector", SearchFieldDataType.Collection(SearchFieldDataType.Single))
            {
                IsSearchable = true,
                VectorSearchDimensions = modelDimensions,
                VectorSearchConfiguration = vectorSearchConfigName
            },
            new SearchableField("category") { IsFilterable = true, IsSortable = true, IsFacetable = true, AnalyzerName = LexicalAnalyzerName.JaLucene }
        }
    };
}

コードを実行すると、インデックスにセマンティック検索の構成が設定されていることを確認できます。


セマンティック検索を使ってみる

セマンティック検索を実行する SemanticSearchAsync メソッドを作成します。ベクトル検索の処理に加えて、searchOptions プロパティでセマンティック検索の設定を実装しています。前回と同様の検索結果に加えて、セマンティック アンサーを表示する実装を追加しています。

static async Task SemanticSearchAsync(SearchClient searchClient, OpenAIClient openAIClient, string deploymentOrModelName, string query)
{
    try
    {
        var queryEmbeddings = await GetEmbeddingsAsync(deploymentOrModelName, query, openAIClient);

        var searchOptions = new SearchOptions
        {
            Vectors = { new SearchQueryVector { Value = queryEmbeddings.ToArray(), KNearestNeighborsCount = 3, Fields = { "contentVector" } } },
            Size = 10,
            QueryType = SearchQueryType.Semantic,
            QueryLanguage = QueryLanguage.JaJp,
            SemanticConfigurationName = SemanticSearchConfigName,
            QueryCaption = QueryCaptionType.Extractive,
            QueryAnswer = QueryAnswerType.Extractive,
            QueryCaptionHighlightEnabled = true,
            Select = { "title", "content", "category" },
        };

        SearchResults<SearchDocument> response = await searchClient.SearchAsync<SearchDocument>(query, searchOptions);

        int count = 0;
        Console.WriteLine("Semantic Search Results:\n");

        Console.WriteLine("Semantic Answer:");
        foreach (AnswerResult result in response.Answers)
        {
            Console.WriteLine($"Answer Highlights: {result.Highlights}");
            Console.WriteLine($"Answer Text: {result.Text}\n");
        }

        await foreach (SearchResult<SearchDocument> result in response.GetResultsAsync())
        {
            count++;
            Console.WriteLine($"Title: {result.Document["title"]}");
            Console.WriteLine($"Score: {result.Score}");
            Console.WriteLine($"Content: {result.Document["content"]}");
            Console.WriteLine($"Category: {result.Document["category"]}");

            if (result.Captions != null)
            {
                var caption = result.Captions.FirstOrDefault();
                if (caption != null)
                {
                    if (!string.IsNullOrEmpty(caption.Highlights))
                    {
                        Console.WriteLine($"Caption Highlights: {caption.Highlights}\n");
                    }
                    else
                    {
                        Console.WriteLine($"Caption Text: {caption.Text}\n");
                    }
                }
            }
        }
        Console.WriteLine($"Total Results: {count}");
    }
    catch (NullReferenceException)
    {
        Console.WriteLine("Total Results: 0");
    }
}

セマンティック検索のメソッドを呼び出すコードを実装します。

var inputQuery = "暑い日に食べられている京菓子は?";
Console.WriteLine($"Query: {inputQuery}\n");

Console.WriteLine("Choose a query approach:");
Console.WriteLine("1. Vector Search");
Console.WriteLine("2. Keyword Search");
Console.WriteLine("3. Hybrid Search");
Console.WriteLine("4. Semantic Search");
Console.Write("Enter the number of the desired approach: ");
var choice = int.Parse(Console.ReadLine() ?? "0");
Console.WriteLine("");

switch (choice)
{
    case 1:
        await VectorSearchAsync(searchClient, openAIClient, settings.DeploymentOrModelName, inputQuery);
        break;
    case 2:
        await KeywordSearchAsync(searchClient, inputQuery);
        break;
    case 3:
        await HybridSearchAsync(searchClient, openAIClient, settings.DeploymentOrModelName, inputQuery);
        break;
    case 4:
        await SemanticSearchAsync(searchClient, openAIClient, settings.DeploymentOrModelName, inputQuery);
        break;
    default:
        Console.WriteLine("Invalid choice. Exiting...");
        break;
}

前回と同様に、暑い日に食べられている京菓子を検索してみます。

ベクトル検索と同様に若鮎がトップに来ており、Caption Text に回答の要約が追加されています。一方、Semantic Answer が空で返されているのは、明確な回答を求めるような質問ではなかったことが原因だと思われます。

「若鮎とはどのような京菓子ですか?」という内容に、質問を変えてみます。

今度は、Answer HighlightsAnswer Text が返され、Caption Highlights も追加されています。
さらに、質問に対する回答に最も近い部分となる「若鮎は、カステラ生地で求肥を包んだ和菓子で、鮎の形を模しています。」には、em タグによるハイライトで強調表示されています。

まとめ

Azure Cognitive Search のセマンティック検索を .NET アプリケーションから使ってみました。
セマンティック検索では、これまでの Cognitive Search による検索では実現できなかった Google やBing のような検索ができます。このような形で社内ドキュメントからナレッジを検索できたら便利そうだと思いました。

今回のサンプルアプリは、こちらで公開しています。
github.com