個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

C# だけで Azure OpenAI の RAG を試してみたい (2)C# によるテキストのクレンジング・チャンクの方針

 
前の記事
hiro128.hatenablog.jp
 

C# のみで RAG を構築する場合の留意点

  1. C# では Python と比べて日本語の言語処理にやチャンク(テキスト分割)に関して圧倒的にライブラリが少ないが、html ファイルのクレンジングやテキストをチャンクする際のトークンカウンターをどうするか。
  2. ソースのブログ記事が更新されたとき、インデックス上の当該ドキュメントを削除する判断基準となるはてなブログ記事の更新日時の管理はどうするか。
  3. インデックスはキー属性が付与された値でしか削除できないので、ソースデータ更新時にどうやって削除するか。

 

留意点①:C# によるテキストのクレンジング・チャンク

C# では Python と比べて日本語の言語処理にやチャンク(テキスト分割)に関して圧倒的にライブラリが少ないが、html ファイルのクレンジングやテキストをチャンクする際のトークンカウンターをどうするか検討したところ、以下の方法がありそうです。

  1. 現状提供されている SDK/ライブラリでどうにかする
    • Python にあるようなリッチなライブラリは現状ない
      • markdown とプレーンテキストならプレビューではあるが、 Microsoft.SemanticKernel.TextChunker が対応している
  2. Python の処理を C# に移植する
    • Python の標準ライブラリは言語処理関連が充実しているが、必要な処理すべてを C# に移植するのは量的に困難
      • 現状提供されているライブラリがない機能を移植する

1 をメインに機能が足りない部分は 2 も併用する。
 

SemanticKernel.TextChunker を使う

SemanticKernel.TextChunker の公式ドキュメントは以下を参照ください。
learn.microsoft.com


提供メソッドは以下の4種類(現状 markdown とプレーンテキストのみ対応)

SplitMarkDownLines(String, Int32, TextChunker.TokenCounter) markdownテキストを行に分割します。
SplitMarkdownParagraphs(List<String>, Int32, Int32, String, TextChunker.TokenCounter) markdownテキストを段落に分割します。
SplitPlainTextLines(String, Int32, TextChunker.TokenCounter) プレーンテキストを行に分割します。
SplitPlainTextParagraphs(List<String>, Int32, Int32, String, TextChunker.TokenCounter) プレーンテキストを段落に分割します。

html からプレーンテキストを抽出およびクレンジングし、③, ④のメソッドでチャンクする方針とします。
 

SplitPlainTextParagraphs メソッドの留意点

パラメータ名 説明
lines List<String> テキストの行。
maxTokensPerParagraph Int32 段落ごとのトークンの最大数。
overlapTokens Int32 段落間で重なるトークンの数。
chunkHeader String 個々のチャンクの前に追加されるテキスト。
tokenCounter delegate int TokenCounter(string input) 文字列内のトークンをカウントする関数。指定しない場合は、デフォルトのカウンターが使用される。

正しくトークンをカウントする必要があるので、ポイントになるのが、TokenCounter ですが、この実体はソースコードを確認すると、string をパラメータとし、int を返すデリゲートになっています。
よって、自分がデプロイした Enbedding モデルに適合した TokenCounter のデリゲートを渡すようにします。

[Experimental("SKEXP0055")]
public static class TextChunker
{
    /// <summary>
    /// Delegate for counting tokens in a string.
    /// </summary>
    /// <param name="input">The input string to count tokens in.</param>
    /// <returns>The number of tokens in the input string.</returns>
    public delegate int TokenCounter(string input);

 
次の記事
hiro128.hatenablog.jp

C# だけで Azure OpenAI の RAG を試してみたい (1)検討したシステム構成と処理フロー

 

はじめに

最近 Azure OpenAI の RAG について色々調べていますが、サンプルアプリは Python が多いです。
自分は C# が大好きなので、C# でやってみたいな~と思いました。
 
とりあえず、参考になりそうなサンプルを探してたところ以下のようなものを発見しました。
 
github.com
 
上記サンプルは Python で作成されておりとても素晴らしいもので、このサンプルだけで Azure Open AI の Add your data と組み合わせて独自ナレッジの連携を試せるのですが、逆に全自動すぎて何が行われているか理解できませんでした(笑)。そこで、知見を増やすためにこのサンプルをカスタマイズして何か作ってみて理解を深めることにしました。
 

カスタマイズ方針

  • 言語は C# にする。(Python のサンプルが多いので C# のサンプルを作成したい)
  • ソースデータはファイルとしていつでも開けるように Blob にファイルとして保存する。
  • 各々の処理は関数アプリにして自動化する。
  • あまり複雑にしないで、シンプルに必要な要素を学べるサンプルにしたい。
  • 自分にとって役に立ち、運用できるサンプルにしたい。

 

検討したシステム構成と処理フロー

  1. はてなブログの記事を関数アプリで10分ごとにクロールし、Blob に html として保存します。
  2. Blob にアイテムが追加されると、Event Grid の Blob Trigger の関数アプリで AI Search にベクトルを含むドキュメントを登録する。

注意
  • 自分が利用できるようにはてなブログの記事をソースデータにしていますが、ソースデータはその他ブログでも社内のドキュメントでもなんでもかまいません。
  • 個人的な「遊び」で作ったものなのでセキュリティの対応は省いています。業務利用するなら閉域化などの対応をする必要があります。

 

ソースコードと各プロジェクトの解説

上記構成のサンプルアプリを作ったので興味ある方はソースコードを参照ください。
github.com

プロジェクト名 説明
AIClient Open AI, AI Search などのクライアントの Facade。
AzureAISearchIndexInitializer AI Search のインデックスを(既存インデックスが存在すればいったん削除し)作成するコンソールアプリ。
IndexCreator Blob 項目の追加、更新をトリガーにインデックスを作成・更新する関数アプリ。
TextChunkerSample TextChunker の挙動を確認するためのサンプルアプリ。
WebPageCrawler はてなブログの記事をクロールして Blob にアーカイブする関数アプリ。

 
次の記事
hiro128.hatenablog.jp

Azure Functions のローカルデバッグで Azure Blob Storage トリガーのリトライ回数、同時実行数を制御する方法

ブレークポイントやコンソールに出力させてデバッグしたいときに、リトライが発生して、関数が複数実行されると非常にデバッグがやりにくいです。
それを制御する設定です。

なお、この設定値は Source = BlobTriggerSource.EventGridの場合でも有効です。

Azure 上でホストする場合は下記の記事の通りです。
learn.microsoft.com

maxDegreeOfParallelism (1 ~ 8)は特定の関数アプリ内で、BLOB によってトリガーされるすべての関数について、同時呼び出しに許可される数です。
poisonBlobThreshold (1 ~ 5)は、有害キューに移動する前に、メッセージの処理を試行する回数です。

問題は、ローカルデバッグの場合です。
記述方法が違うのでご注意下さい。
Jsonの階層構造をアンダースコア2つ[__]で表現します。

{
  "Values": {
    "AzureFunctionsJobHost__extensions__blobs__maxDegreeOfParallelism": "1",
    "AzureFunctionsJobHost__extensions__blobs__poisonBlobThreshold": "1",
}

GitHub Copilot Chat の AI-Powered Rename Suggestions を試してみた(2)

前回 Visual Studio Preview の既知の問題で時間を溶かしてあまり詳しく挙動を確認できなかったので、再度どれくらいいい感じにリネームしてくれるか確認してみました。
hiro128.hatenablog.jp

 

well-known なアルゴリズム

well-known なアルゴリズムのネーミングを不親切なものにして、Copilot が適切な名前にリネームできるか試してみました
 

リストの逆順並び替え

これは本来

list.Reverse();

で1発ですが、あえてロジックを手書きして確認しました。

public static List<string> aaa01(List<string> a)
{
    var b = new List<string>();
    for (int i = a.Count - 1; i >= 0; i--)
    {
        b.Add(a[i]);
    }
    return b;
}

 

メソッド名


 

パラメーター名


 

戻り値名

少し微妙な感じもしますが、充分適切な名前を提案してくれました。

他にバブルソート、ヒープソートなど試してみましたが、こういう well-known なものに関しては問題なく適切な名前を提案してくれました。
 

バブルソート


 

ヒープソート

パラメータや変数のネーミングが不適切であっても、しっかりコードの中身を見て適切な名前を提案してくれることがわかりました。

GitHub Copilot Chat の AI-Powered Rename Suggestions を試してみた(ら詰みかけた話)

AI-Powered Rename Suggestionsを試しました。以下のブログで紹介されていた記事です。
devblogs.microsoft.com

動作させるのに地獄を見る…

この機能、2023/12/24 現在、Visual Studio 2022 Preview Version 17.9.0 Preview 2 (この時点での最新版)では不具合があり動作しません。

この issue にやられました。基本的に Preview はリリースされたらすぐインストールするので Preview 2 にアップデート済みでした。

さらに、以下の注意書き(Preview 2 では不具合があり動作せず、Preview 1 をでは動作する)を読んでいなかったために、

自分の Preview で動作しないので( 17.9.0 Preview 2 か Preview 1 かは全く意識していなかった)、ひたすら原因を調べた結果 Visual Studio から GitHub Copilot Chat の APIをコールしていないように見えました。少なくとも GitHub Copilot Chat 側の問題ではなさそうと分かりました。

Preview 2 動作していない。そもそも、API リクエストが投げられていない

Preview 1.1 正常動作。API リクエストとレスポンスが確認できる

そこで、Visual Studio の問題かと思い Preview 2 を一旦アンインストールし、再インストールし、再度試しましたがやはり動作しません。

ここで、再度 Microsoft のブログを再度読み直し、 Preview 2 では不具合があり動作せず、Preview 1 をでは動作することを認識しました。

Visual Studio 2022 Preview Version 17.9.0 Preview 2 -> Visual Studio 2022 Preview Version 17.9.0 Preview 1 のロールバックを行おうとしましたが、一度アンインストールして、再インストールしているため、以前のバージョン自体が PC 内に存在せずロールバックできないことに気づきました(詰んだか??)

Visual Studio 2022 Preview Version 17.9.0 Preview 1 のインストーラーを取得できないか調査。それらしきURLに気付き、ダウンロードを試したところ、MS アカウントの認証を求められ、権限がないと拒否されました(もう詰んだと思いました)。

ここで、Mac の Parallels の Windows 上の Visual Studio 2022 Preview があることに気づき、確認したところ、Visual Studio 2022 Preview Version 17.9.0 Preview 1.1 にロールバックできることが判明しましたので、すぐロールバックしました。

公式Blog での記載は Preview 1 であったため、Visual Studio 2022 Preview Version 17.9.0 Preview 1.1 で操作するか一抹の不安はありましたが、試したところ運良く動作!

ここまで6時間溶かしました。疲れた…

教訓:説明は一字一句逃さず読め!!

一応、 Visual Studio Blog に「Visual Studio 2022 Preview Version 17.9.0 Preview 1 のダウンロードURLはどこにあるの」とコメントは残しました。

設定

拡張機能 GitHub Copilot Chat 0.1.1986.61475 以上

[ツール]->[オプション]->[GitHub]->[Copilot チャット]->[Enabele rename suggestions]に✅

使ってみてどうだった…

前置きが長くなりました。動作を試してみましたが、時間溶かしすぎてあまり調べられていません。(追加調査します)

最近、GitHub Copilot / GitHub Coplilot Chat でポーカーを実装してみたので、そのコードで検証してみました。
www.docswell.com

自分の手持ちのカードで「保持」としなかったカードを交換するメソッド、ChangeCards() メソッドのリネームを提案してもらったところ、以下の感じになりました。

確かに、このメソッドのネーミングは、Change より Swap の方がしっくりきます。

次に、自分の手持ちのカードを表示するメソッド、ShowHand()メソッドのリネームを提案してもらったところ、以下の感じになりました。

こちらも、手札の役の開示というよりは、カード自体を表示するだけなので、ShowCards() の方がいいかもしれません。

これはネーミングで悩んだ時にまずは多少いい加減でもいいから命名して後から提案してもらうと、効率が良さそうですし、リファクタリング時にひどいネーミングになっている場合のリネームに役に立ちそうです。

これはかなり使える機能ではないでしょうか。
 
 

ちょっと不完全燃焼に終わったのでもう少し調べてみました。
hiro128.hatenablog.jp