RAG構築のためのAzure OpenAI Serviceリファレンスアーキテクチャ詳解

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

こんにちは、サイオステクノロジー武井です。今回は、「RAG構築のためのAzure OpenAI Serviceリファレンスアーキテクチャ詳解」と題しまして、マイクロソフトが提供するRAG導入のためのリファレンスアーキテクチャについて解説します。

※ 本記事の内容がベースとなっているオンラインセミナーのアーカイブが以下のYouTubeで配信されています。ぜひ見てね!!

目次

Azure OpenAI Service リファレンスアーキテクチャとは?

マイクロソフトは、Azureでのシステム構築に役立つ様々な設計パターン(リファレンスアーキテクチャ)を提供しています。これらのパターンを参考にすれば、問題なく簡単にシステムを構築することが可能です。ゼロから設計を始める必要はありません。

https://learn.microsoft.com/ja-jp/azure/architecture/browse/

もちろん、Azure OpenAI Serviceに関するリファレンスアーキテクチャも提供されており、様々なユースケースが紹介されています。

https://www.microsoft.com/ja-jp/events/azurebase/contents/default.aspx?pg=AzureOAIS

特に注目するのは、第5章の「企業内チャットと社内文書検索」です。これは「RAG」と呼ばれる概念を取り入れています。

RAGは、Retrieval-Augmented Generationの略称であり、生成AIに外部の情報源を組み込むことで回答の質を向上させる仕組みを指します。簡単に言えば、ChatGPTのようなインテリジェントな生成AIベースのチャットシステムに、社内の規約などの専門的な情報を取り込み、それをもとにした回答を提供するというものです。「有給は何日取得できるのか」といった類の質問に回答するには、その企業の独自ナレッジが必要になります。そういった要求に答えるのがRAGというわけです。

話はそれましたが、「5章 – 企業内向けChatと社内文書検索」にて提供されているRAGのリファレンスアーキテクチャは以下のような特徴があります。

  • ソースコードが公開されているので、内部の動きを詳細に把握することができる。
  • Azure Developer CLIとBicepによりインフラがコード化されているので、コマンド一発ですぐ動く。
  • 日本語対応による解説がついている!!
  • 他にも色々!!

このような素晴らしいリファレンスアーキテクチャを提供していただいたマイクロソフト様には感謝感激雨あられでございます。

そこで、本記事では、ワタクシの方でソースコードを読み込んで得た内容に基づき、このGoodなリファレンスアーキテクチャのDeep(?)Diveを行おうと思います。全然Deepじゃなかったらすみません(><)

そもそもRAGってなに?

一旦Azure OpenAI Serviceのリファレンスアーキテクチャの話はおいておき、RAGとは何であるかというのを説明します。

OpenAIやAzure OpenAI Serviceは、インターネットなどによって公開されている膨大な量の情報を収集し、モデルをトレーニングして、そのモデルに基づき回答を生成しています。

しかしながら、自社が保有している情報をベースに回答を生成したいというユースケースはあると思います。例えば、社内の就業規約に関するQAなどです。育児休業の申請方法は、企業によってその方法が違うのはもちろんですし、そしてそのような情報は社内にてクローズドに管理されている就業規約に書かれていることがほとんどです。そんな就業規約のような独自データに基づいた回答を生成AIがしてくれたら、とっても便利だというのは言うまでもありません。今までセコセコ就業規約を検索して調べてたのが、生成AIに「育児休業の申請方法ってどうすればいいの?」と聞くだけで、回答が返ってくるなんて、とってもステキです。

モデルの微調整による実現

実現方法として真っ先に思いつくのは、モデルに独自データを追加して学習させることです。一般的には「モデルの微調整」と言われたりしていまして、Azure OpenAI Serviceにもそういうサービスがあります。ただし、モデルの微調整は大変な作業です。モデルの微調整は、学習済みのモデルに追加の情報やデータを組み込むことで、その性能や反応を調整するプロセスなのですが、新しいデータセットの用意、学習の設定やパラメータ調整、そして再学習の実行など、多くの手間と時間がかかります。マイクロソフトも「モデルの微調整は最後の手段」と言っています。

RAGによる実現

そこで、モデルの微調整の代わりに、Azure Cognitive Searchによる全文検索システムを利用したRAG(Retrieval Augmented Generation)の手法が推奨されます。Azure Cognitive SearchはAzureが提供するマネージドな全文検索システムであり、社内の独自データを効率よく検索し、その結果をベースにした回答を生成するための基盤として非常に有効です。RAGのアプローチを採用することで、具体的な質問に対して独自データからの情報を取得し、それをもとに質の高い回答を生成することが可能となります。この方法ならば、膨大な時間や手間をかけてモデルを再学習させることなく、独自の情報を活用した質問応答システムを迅速に実現することができます。

Azure OpenAI Service リファレンスアーキテクチャによる実現方法

本章では、Azure OpenAI ServiceリファレンスアーキテクチャによるRAGの実現方法について解説します。

システム構成

やっぱりエンジニアの皆さんは、システム構成がまずは見てみたいですよね。以下の通りとなります(文字がちっちゃいので拡大してみてください)。以降、このシステムを構成するパーツを個別に説明致します。

※ わかりやすさを優先するため一部実際の処理と異なる部分があります(例えば、検索クエリを生成するためのプロンプトなど)

Azure OpenAI Service

みんな大好きAzure OpenAI Serivceです。コレがないと始まりません。RAGのシステムにて、以下の用途に利用します。

  • Azure AI Searchに対してドキュメント検索するための検索クエリ生成
  • Azure AI Searchから取得したドキュメントを元にした回答文生成

Azure AI Search

Azureが提供するマネージドな全文検索サービスです。RAGに必要な外部の情報源、つまりモデル就業規則をAzure AI Searchに取り込みます。

Azure Cosmos DB

Azureが提供するマネージドなNoSQLデータベースです。プロンプトと回答を記録します。

App Service

Azureが提供するマネージドなアプリケーション実行基盤です。RAGのフロントエンド(UI)、バックエンドを動作させるために使います。

Pythonスクリプト

このスクリプトは、RAGに必要な外部情報源、具体的にはモデル就業規則をAzure AI Searchに登録するために使用されます。後ほど説明しますが、PDFからテキストをそのまま抽出して登録するのではなく、テキストを分割して登録する必要があります。このような複雑な処理をこのスクリプトが行います。

Document Intelligence

機械学習モデルを用いてドキュメントの解析を行い、テキスト構造を抽出することができます。これを用いて、PythonスクリプトがPDFファイルからテキストを抜き出します。

Azure Blob Storage

Azureが提供するマネージドなオブジェクトストレージです。Pythonスクリプトによって分割されたPDFをAzure Blob Storageにアップロードします。Pythonスクリプトから与える引数によって、この処理をスキップすることもできます。

処理の流れ

ここでは具体的な処理の流れを説明します。先程の図を再掲します。

1. ドキュメント読み込み

PythonスクリプトがPDFドキュメントを読み込みます。

2. テキスト抽出

Pythonスクリプトが1にて取得したPDFをDocument Intelligenceに読み込ませて、PDFの中のテキストのみを抽出します。

3. 分割してBlobに登録

PythonスクリプトがPDFをページごとに分割して、Azure Blob Storageに登録します。

4. チャンク化してインデックス化

PythonスクリプトがPDFから取得したテキストをチャンク化して、Azure AI Searchにインデックス登録します。

チャンク化とは、ドキュメントをある一定のサイズに分割することです。チャンク化する理由は後述します。

5. プロンプト入力

ユーザーは有給は何日取得できるかを知りたいとします。そこで、ユーザーはブラウザなどのクライアントから、「有給は何日取得できる?」と入力します。

6. 検索クエリ生成依頼

App Serviceで動作しているWebアプリケーションは、Azure OpenAI Serviceに対して、Azure AI Searchに投げるための検索クエリの生成を依頼します。先程ユーザーが入力した「有給は何日取得できる?」をもとに検索クエリの生成を依頼します。

7. 検索クエリ返却

先ほど「6.検索クエリ生成」で取得した検索クエリ「有給取得日数 規定」をWebアプリケーションに返却します。

8. 検索クエリでインデックス検索

先程取得した検索クエリ「有給取得日数 規定」をもとに、Azure AI Searchに検索をかけます。

9. ドキュメント取得

先程の検索によって取得したドキュメントをWebアプリケーションに返却します。ここでは、検索クエリ「有給取得日数 規定」というクエリの検索に対して、Azure AI Search内にあるドキュメント「…最低5日間は労働者が自由に取得できる日数として残し、5日を超 える…」を取得して返却するとします。

10. ドキュメントをもとに回答生成依頼

「9. ドキュメント取得」で取得したドキュメントをもとにAzure OpenAI Serviceに回答依頼を生成します。ここがRAGの真骨頂です。以下のようなプロンプトをAzure OpenAI Serviceに投げます。

「有給は何日取得できる?」という質問に、以下の内容をもとに回答して。

…最低5日間は労働者が自由に取得できる日数として残し、5日を超 える…

「5. プロンプト入力」でユーザーが問いかけた質問に対して、Azure AI Searchに登録済みの分割されたモデル就業規約をもとに回答を生成します。

これで、Azure OpenAI Serviceは、独自データに基づく回答を生成していることがわかりますでしょうか?

つまり、ユーザーのプロンプトをもとに、Azure AI Searchに検索をかけるためのクエリをLLMに生成させて、その検索クエリでAzure AI Searchから取得したドキュメントをもとにして、ユーザーのプロンプトに対する回答の生成をさらにLLMにさせているというわけです。

そして、ここで「4.  チャンクしてインデックス化」で分割したメリットが活きてきます。例えば、GPT-3系では4000トークンまでしか処理できません。先に説明したプロンプトを4000トークン以内に収めるためには、回答のもととなるドキュメントを少なくとも4000トークン以下に収める必要があり、ということは、Azure AI Searchに登録するドキュメントを4000トークン以下に収める必要があります。

11. Webアプリケーションに回答返却

10のプロンプトをもとにAzure OpenAI Serviceが生成した「年次有給休暇は、最低でも10日与えられます。」という回答をWebアプリケーションに返却します。

12. プロンプトと回答の記録

プロンプトと回答をAzure Cosmos DBに記録します。期待通りの回答を返しているかを分析して、チューニングするために使ったりします。

13. ユーザーに回答返却

Webアプリケーションがユーザーに回答を返却します。

画面の構成

本章では、画面の構成について説明します。Azure OpenAI Serviceリファレンスアーキテクチャが提供するデモアプリ(長いので以降「デモアプリ」と呼びます)は、2つの機能「企業向けChat」「社内文書検索」を提供します。

企業向けChat

いわゆるChatGPTライクな機能を提供するものです。RAGではありません。いわゆる事前にトレーニングされたモデルの知識を利用しています。

プロンプト入力画面

プロンプト入力画面になります。

 

設定画面

先の画面で「設定」をクリックした際に表示される設定画面です。

社内文書検索

社内文書検索、いわゆるRAGの機能を提供する画面です。

プロンプト入力画面

プロンプト入力画面です。企業向けChatと構成は大きく変わりません。

ドキュメント

回答の下にある「引用」に表示されているファイル名は、この回答を作成するために参考にしたドキュメントのファイルです。①のファイル名をクリックすると、②の部分にそのファイルの内容が表示されます。

補助資料

回答を生成するための情報の元となったドキュメントを表示します。回答のボックスにあるドキュメントマーク(①)をクリックすると、②の部分に表示されます。複数ある場合もあります。

思考プロセス

回答を生成するためにどのようなプロセスが行われたかを表します。回答のボックスにある電球マーク(①)をクリックすると、②の「思考プロセス」の部分にプロセスが表示されます。

 

先の画面の「思考プロセス」の部分の詳細になります。長すぎるので一部省略しております。

①は、「8. 検索クエリでインデックス検索」でAzure AI Searchに投げた検索クエリを表します。

②は、システムのロールです。かなり長いのですが、要はプロンプトの下に表示される情報をもとに回答を生成してくださいという指示をAIにしています。

③は、ユーザーのロールです。「有給は何日取得できる?」はユーザーが入力したプロンプトです。その下に回答を作成するための情報が列挙されています。これは①のクエリでAzure AI Searchに検索をして返ってきたドキュメントになります。システムのロールであったように、LLMはこれらのドキュメントに基づいてプロンプトに対する回答を生成します。

設定画面

設定画面になります。

①は、「企業向けChat」と同様に、利用するモデルを選択します。

②は、AIの回答のランダム性を制御するパラメータになります。

③は、「8. 検索クエリでインデックス検索」で検索クエリで検索した際に取得するドキュメント数を指定します。つまり、ここで10と指定すると、検索クエリで検索して取得した10個のドキュメントをもとに回答を生成します。

④は、ここに入力した値と、Azure AI Searchのcategoryというフィールドに一致するドキュメントを検索対象から除外します。ただし、このデモアプリではcategoryはすべてのドキュメントでnullとなるので、何を設定しても事実上変化はありません。

⑤は、セマンティック検索を使うかどうかを設定します。セマンティック検索については、以下のブログを参照ください。

生成AI時代の様々な検索手法を検証する 〜Azure AI Searchによるベクトル/セマンティック/ハイブリッド検索〜

⑥は、セマンティック検索の機能であるセマンティックキャプションを使うかどうかを設定します。セマンティックキャプションを使うと、リランクされたそれぞれの検索結果に対して抽出的要約(検索結果から最も関連の高いと思われる文章の一部を抽出)を作り出してくれて、回答の精度が高くなります。

Deep Dive

では、もっと深く突っ込んでみたいと思います。

フロントエンドとバックエンドの連携

フロントエンドとバックエンドの連携について、以下の図を基に説明します。実際にはもっと複雑に様々な要素が絡み合っていますが、理解しやすいように簡略化して説明しています。

 

フロントエンドはReactで構築されています。会話の履歴は「messages」という名前のステートに保存されています。このステートはReactのステートを指します。そして、「messages」というステート内のJSONの「user」というフィールドにはプロンプトが、「response」というフィールドには回答が格納されています。

バックエンドはPythonで構築されています。バックエンドの役割は、フロントエンドから送られてくるプロンプトを受け取り、大規模言語モデルであるAzure OpenAI Serviceや、外部情報源であるAzure AI Searchに渡し、その結果を受け取って、フロントエンドに返すことです。

上図のそれぞれの処理を以下に詳細に記載します。

① プロンプト取得

Java Script(React)、ユーザーが入力したプロンプトを取得します。

② API呼び出し

Pythonで提供されているAPIのエンドポイントを呼び出します。

③ LLM呼び出し

大規模言語モデルであるAzure OpenAI Service、外部情報源であるAzure AI Search、そしてプロンプトを記録するデータベースであるAzure Cosmos DBにプロンプトを渡します。

④ LLMからの結果返却

Azure OpenAI ServiceとAzure AI Searchを連携させて、ユーザーのプロンプトに対する回答を生成し、それをPythonのプログラムに返します。

⑤ APIレスポンス返却

Pythonは④でLLMから返却された結果をJava Script(React)にさらに返却します。

 

⑥ messagesのステートに追加

Reactのステートであり、会話の履歴を格納するmessagesという変数に対して、最新のプロンプトとそれに対する回答を格納する。

⑦ 再レンダリング

ステートが変更されたので、DOMの再構築が走り画面が再レンダリングされる。結果、最新のプロンプトとそれに対する回答が画面に反映される。

Reactのコンポーネント構成

これは、Reactで構築されたフロントエンドの主要なコンポーネント構造の概要です。実際にはもっと多くのコンポーネントで構成されていますが、この説明では重要なコンポーネントのみを取り上げます。

Layout

すべてのコンポーネントの親となるコンポーネントです。「企業向けChat」「社内文書検索」をクリックすると、Layoutの子にChatコンポーネント、Docsearchコンポーネントがそれぞれレンダリングされます。本章では、Docsearchコンポーネントがレンダリングされた場合のみ説明します(ChatコンポーネントもDocsearchコンポーネントもその子コンポーネントの構成は変わらないため)。

Docsearch

「社内文書検索」をクリックしたときにレンダリングされるコンポーネントです。

UserChatMessage

ユーザーが入力したプロンプトが表示されるコンポーネントです。

AnswerChatMessage

回答が表示されるコンポーネントです。

QuestionInput

プロンプトを入力するためのテキストボックを構成するコンポーネントです。

Azure AI Searchのインデックス

Pythonスクリプトでチャンク化されたドキュメントは、次に示すAzure AI Searchのインデックスに取り込まれます。

id

レコードを一意に識別するIDで、その形式は

[ファイル名]-[チャンクに一意に割り振られた連番].pdf

となる。例えば、contosodoc.pdfの場合、contosodoc-1.pdfとなる。

category

ドキュメントをチャンク化するときに、すべてのチャンクに指定できるカテゴリ。デモアプリではnullとなるが、指定も可能。

content

チャンク化されたテキスト

sourcepage

チャンク化されたテキストが含まれるPDFファイル名。contosodoc-1.pdfやcontosodoc-2.pdfとなる。

sourcefile

ページごとに分割される前のPDFのファイル名。contosodoc.pdfとなる。

ドキュメントの取り込み

RAGでは、外部情報源のデータを基にして、ユーザーのプロンプトに対する回答を生成します。今回のデモアプリケーションでは、厚生労働省が提供する「モデル就業規則」という90ページにわたるPDFドキュメントを取り込みます。このようなドキュメントをどのように取り込むか、チャンク化などの処理を含めて説明します。

チャンク化をする主な理由は、Azure OpenAI Serviceで提供される文章をベクトル化するEmbeddings APIのトークン数の制限に引っかからないようにするためです。このAPIはリクエストあたりのトークン数に上限があり(たとえばtext-embedding-ada-002では8191トークン)、そのためテキストを細かく分割する(チャンク化する)必要があります。

 

またチャンク化する際にオーバーラップという処理を施します。チャンクのオーバーラップとは、テキストを小さな部分(チャンク)に分割する際に、隣接するチャンク間で一部のテキストが重複するようにすることです。

チャンクのオーバーラップにより、バラバラになった文章を関連のある一つの文章としてAIが認識できるようになります。これにより、テキストの意味が途中で切断されるのを防ぎ、チャンク間での文脈の連続性が保たれます。結果として、検索や自然言語処理の精度が向上し、AIによる回答生成がより正確かつ関連性の高いものとなります。オーバーラップは、テキストをチャンクに分割する際に重要な情報が欠落するのを防ぐと同時に、バラバラだった文章を一つの関連性のある文章として認識するのに役立ちます。このように、チャンクのオーバーラップは、テキストの処理と理解を効率的に行うための重要な手法となります。

イメージにしますと以下のようにドキュメントを分割します。それぞれのチャンクが重なっているところがオーバーラップしている部分です。

 

さらに、ドキュメント全体をページごとに分割し、それぞれのチャンクがどのページに含まれるかを関連付けます。先に紹介したAzure AI Searchのインデックスの「sourcepage」フィールドには、チャンクに関連するドキュメントのファイル名が格納されます。

このような方法を取る理由は、回答を表示する際に、その回答がどのドキュメントのどのページに基づいて生成されたものなのかをユーザーに示すためです。

例えば、以下の画面は「有給は何日取得できる?」というプロンプトに対する回答を示しています。①の「引用」の部分に表示されているのは、この回答を生成する際に基礎として使用されたチャンク化されたドキュメントが含まれるPDFファイルの名前です。回答を表示するだけでなく、その回答がどの外部情報に基づいて生成されたのかを示すことで、ユーザーはより納得感のある回答を得ることができます。

 

では、このデモアプリでは、具体的にどのようにドキュメントをチャンク化しているかを説明します。

上図を見ていただければわかるのですが、ざっくり概要をご説明します。

まず1000文字取得します(①)。

次に、①で取得した1000文字の末尾から1文字づつ進み、「単語の区切り文字」「文の終わりを表す文字」を探します。これが上図②の処理です。「文の終わりを表す文字」を見つけたら、そこで処理は終わり、その部分がチャンクの末尾になります。要は、チャンクが単語の途中で不自然に切れて、尻切れトンボみたいにならないように、自然な文章の終わり方になるようにしています。

これと同じことをチャンクの開始位置を決定するときも行います。①で取得した1000文字の冒頭から1文字ずつ戻り、「単語の区切り文字」「文の終わりを表す文字」を探します。これが上図③の処理です。「文の終わりを表す文字」を見つけたら、そこで処理は終わり、その部分がチャンクの開始位置になります。

結果としてチャンクは以下のようになります。

 

そして、次のチャンクを取得します。ここで注意いただきたいのが、次のチャンクの開始位置は、前のチャンクの終了位置から100文字戻ったところです。ここから1000文字取得して、また先ほどと同様の処理を行います。前のチャンクの終了位置から100文字戻ったところをチャンクの開始位置としているので、前のチャンクと一部重複している部分が発生します。これがオーバーラップです。

このような処理でドキュメントの取り込みを行います。

Azure Cosmos DBへのプロンプトの記録

Azure Cosmos DBに記録されるプロンプトと回答のJSONフォーマットは以下の通りとなります。

approach

ChatGPTライクな機能である「企業向けChat」を使ったときには「chat」、いわゆるRAGの機能である「社内文書検索」を使った場合には「docsearch」が入る。

user

プロンプトを入力したユーザーのユーザー名が入る。デモ アプリでは認証の処理が入っていないので、固定値で「anonymous」となる。

tokens

プロンプトと回答のトークン数が入る。

input

ユーザーが入力したプロンプトが入る。

response

Azure OpenAI Serviceが生成した回答が入る。

ビルド&デプロイ

Azureへのリソース作成、ビルド、デプロイなどデモアプリ環境構築は、Azure Developer CLIによってコマンド一発で全て行われます。Azure Developer CLIは、Azureへのリソース作成・アプリケーションのビルド/デプロイ、リソースの破棄といった開発に必要な環境構築のライフサイクルを円滑に回すためのコマンドラインです。Azureへのリソース作成部分はBicepもしくはTerraformと連携して行います。本当に楽ちんです。その処理の流れを解説します。

① Azureリソース作成

リポジトリのトップディレクトリにてazd upコマンドを実行すると、Azure Developer CLIによって、まずAzureへのリソース作成が行われます。

② ビルド

フロントエンドを構成するReactのプロジェクトのビルドを行います。

③ コピー

②にてビルドした成果物をbackend/static/assetsディレクトリ配下にコピーします。

④ デプロイ

③にてビルドされたフロントエンドの成果物とバックエンドのスクリプトをAzure App Serviceにデプロイします。つまりフロントエンドもバックエンドも同じAzure App Service上で稼働します。

⑤ インデックス作成

Azure AI Search上にインデックスを作成して、ドキュメントを取り込みます。「ドキュメントの取り込み」で説明した処理が行われます。

リポジトリの構成

デモアプリのリポジトリ構成となります。もっと多くのファイルがありますが、本章では主要な役割を担うファイルのみ記載致します。

大事なファイルは2つありまして、Azure AI Searchへのインデックス作成やドキュメントの取り込みを行う「prepdocs.py」、ユーザーが入力したプロンプトをもとに回答を生成する「chatreadretrieveread.py」です。

prepdocs.pyは、RAGの主要なコンポーネントである外部情報源の生成を担っておりまして、中でもチャンク化のロジックは非常に参考になります。

chatreadretrieveread.pyは、Azure AI Searchへの検索クエリの生成、取得したドキュメントに基づいた回答生成、プロンプトのロギングなど、回答を生成するための主要な処理を行うPythonスクリプトであり、一番きちんと抑えておきたいところです。

まとめ

いかがでしたでしょうか?RAGって言うけれど、どんな構成で構築したらいいの?とかとりあえず動かしてみたい!!てな感じの人にはぴったりだと思います。このリファレンスアーキテクチャとともに、よりよりRAGライフを送ってくださいませ♥

アバター画像
About 武井 宜行 269 Articles
Microsoft MVP for Azure🌟「最新の技術を楽しくわかりやすく」をモットーにブログtech-lab.sios.jp)で情報を発信🎤得意分野はAzureによるクラウドネイティブな開発(Javaなど)💻「世界一わかりみの深いクラウドネイティブ on Azure」の動画を配信中📹 https://t.co/OMaJYb3pRN
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

6人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる