AutoGenを使ったRAG”も”できるマルチエージェント作り

AutoGenを使って、「RAGをする場合もある」マルチエージェントの試行をした際、<b>はまったことが二つ</b> あったので、共有させていただこうと思います。
### AutoGenをつかったRAGの基本

#### 参考にした解説

AutoGenを使ったRAGについては、マイクロソフトの公式Githubにしっかりした解説があります。

参考１：
https://microsoft.github.io/autogen/0.2/docs/topics/retrieval_augmentation

また、グループチャットを使ってマルチエージェントにした場合についても、解説があります。　

参考２：
https://microsoft.github.io/autogen/0.2/docs/notebooks/agentchat_groupchat_RAG/

これらの基本的なサンプルから、AutoGenで、チャットAIを想定した場合、一番シンプルな組み合わせは以下の二つと考えることができます。
  
* AssistantAgent　
* UserProxyAgent

そして、チャットの開始は UserProxyAgentのinitiate_chatからです。

また、RAG機能を入れようとした場合、一番シンプルな組み合わせは、こちらの二つとなり、

* AssitantAgent
* RetrieveUserProxyAgent　

チャットの開始は、RetrieveUserProxyAgentのinitiate_chatからです。

さらに、マルチエージェントの場合は、参考２のコードにあるように、グループチャット（GroupChat） を作って、様々なAgentをメンバーに登録します。　チャットの開始はやはりRetrieveUserProxyAgentのinitiate_chatからです。

#### RAGする場合のチャットの開始　（いっぱんに）

いずれのinitiate_chatでも、チャットの開始はこんな感じです。
```
# RetrieveUserProxyAgentとして、infoAgentを定義済み
infoAgent.initiate_chat(
    assistant（またはgroupchatのmanager), message = infoAgent.message_generator,  
    <省略＞
) 
```
##### message_generator　のこと
　RetrieveUserProxyAgentのstatic関数である message_generatorが使われていますが、この関数では、 ユーザ入力に関係がある情報検索が行われ、その結果をコンテキストとして含んだ<b>回答を作るためのメッセージ</b>が作成されます。そのメッセージが、RetrieveUserProxyAgentから、Assitant（単独の場合）またはGroupChatのManager（マルチエージェントの場合）に送られることからチャットが開始される、ということになります。　

余談ですが、当初わたしは、なぜだか「RetrieveUserProxyAgentは"RA<b>G</b>"　するエージェント」と思い込んでいまして、また、　message_generator自体の解説は、参考にしたその他の記事を含めてけっこう割愛されていたため、このあたりを理解するまで、ちょっと時間を要してしまいました。こういう妙な思い込みには注意しなきゃ、、ですね。
 
### 今回はまったこと、その結果

 #### （ポイント　その１）RAGから始めるとは決まっていない柔軟なシステムはどう作ったらいいか？ 
　今回わたしがやりたいのは、最初からRAGを必要とするとは決まってるわけではなく、ユーザの質問に応じて、<b>場合によってはRAG機能を発揮する</b>  エージェントの試作です。　ところが、RetrieveUserProxyAgentから開始しないマルチエージェントについては、なかなか参考情報が見つからず、グループチャット側のオプション設定で可能な独自の割り振り関数を頑張ってみる、など変な方向の試行錯誤をアレコレしてしまいました。

結局わかったのは、<b>「RetrieveUserProsyAgentから会話を開始しない場合は、エージェントとしてグループチャットに参加させるのではなく、関数呼び出しの形にする必要がある」</b> 　ということでした。

概要はこうなります。
```
# retrieveした情報を使うエージェントの定義
expert = AssistantAgent(
    name="Expert",
    is_termination_msg=termination_msg,
    system_message="""あなたは、xxxxxxx
    必ず、`retrieve_content` 関数を使って追加の専門情報を取得したうえで、答えてください """,
    llm_config=llm_config,
)

# retrieveする関数を作るためのエージェントの定義
expert_aid = RetrieveUserProxyAgent(
    <各種設定＞
)

#　expert_aidエージェントを使った関数の定義
def retrieve_content(
   　＜省略＞
    #　ここで　message_generatorを使います
    ret_msg =expert_aid.message_generator(expert_aid, None, _context)
    return ret_msg or message

＃expertエージェントに対して、関数の登録
d_retrieve_content =expert.register_for_llm(
            description="専門知識情報を取得する関数", api_style="function")(retrieve_content)

# このあと行うGropuChatへのメンバー登録の際、
# expertはメンバーに入れて、expert_aidは除くようにします
           
```
MicrosoftのAutoGen開発者によるこちらのブログアーカイブに運よくたどり着けて、大変参考になりました。　詳細は当該ブログご覧ください
https://microsoft.github.io/autogen/0.2/blog/2023/10/18/RetrieveChat/


#### (ポイント　その２）RetrieveUserProxyAgentが自動生成するプロンプトのカスタマイズ

　前述のように関数化することで、必要な時にRAGが走るようなエージェントが動くようになりました。しかしやりとりされるメッセージを見ていると、検索された情報の扱いについて、もっと違った指示をしたいとも思いました。
　前述したとおり、検索された情報をどう使うかのシステムプロンプトはRetrieveUserProxyAgentの関数であるmessage_generator 内部で生成されています。　やりとりされているメッセージからは、これは以下の部分が該当すると思われます。

~~~
You're a retrieve augmented chatbot. You answer user's questions based on your own knowledge and the     
context provided by the user.
If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.
You must give as short an answer as possible.

User's question is:＜ここにユーザ入力が表示＞

Context is: ＜ここに検索された文書情報が表示＞
~~~

You're　から始まる頭の４行の部分を、自前の指示に入れ替える、がやりたいこと、ですね。

　公式のRetrieveUserProxyAgentクラスのりリファレンス
https://microsoft.github.io/autogen/0.2/docs/reference/agentchat/contrib/retrieve_user_proxy_agent/#retrieveuserproxyagent
 を見ると、<b>customized_prompt</b> というものが、どうやら使えそうです。　しかし、試してみると、うまく機能せず、情報が検索されてないような回答になってしまいます。　ちょっと紆余曲折ありましたが、結局AutoGenで該当するソースコードをたどって確認してみることにしました。
 
 私の場合はローカルPCでVS ccodeの仮想環境にインストールしており、該当ソースはどうやらこのファイル。
 ~~~
 C:\....\.ven0\Lib\site-packages\autogen\agentchat\contrib\retrieve_user_proxy_agent.py
 ~~~
このファイルで、”customized_promptが設定されてない場合のオリジナルのプロンプト”　と、"customized_prompt"の使われ方を調べたうえで、以下のように設定することで、うまくいきました。
~~~
MY_PROMPT_QA="""
＜ここに、自分が好きなようにカスタマイズした指示プロンプト＞

User's question is: {input_question}

Context is: {input_context}
"""

# そして、このMY_PROMPT_QAを、RetrieveUserProxyAgentの初期化で# retrieve_configの中で設定します。

rag_aid= RetrieveUserProxyAgent(
   <各種パラメタ設定>,
    retrieve_config={
      <各種config設定＞,
      "customized_prompt":MY_PROMPT_QA,
    },
)
~~~
　このように、ソースコード調査を通じて、　customized_prompt　を通じたRetrieveUserProxyAgentが生成するシステムプロンプトのカスタマイズは以下のポイントがあるとわかりました。
* このカスタマイズは、通常必須な定型部分も含めて、丸ごと書き換えるものである
* 定型部分で用いるべき変数はinput_question とinput_context

### まとめ

　AutoGenは、魅力的でそそられるエージェントフレームワークですが、サンプルや、ネットの記事を、適当に見ていくだけでは肝心なところがわからず、また現時点ではChatGPTに尋ねても、少なくとも今回ご紹介した２点については、あまりうまくいきませんでした。コミュニティが充実しているともききますのでWeb検索ありでもやってはみたのですが、ダメでしたー。
 
 　AutoGenのソースコードを調査することで、その２のほうは解決できたわけですが、その最中で、「ああ、これは（ＲＡＧ機能ではなくて）単にサーチ機能だよなあ」とはハッキリ認識できましたので、その１のほうも、「関数にして活用」が発想できた可能性もゼロではないと思えます。もちろん、AutoGenの頼れるコミュニティ情報の在り処を理解し、そちらをあたれば、すらっと解決できたのかもしれません。　ただ、少なくとも今回得た教訓として、こうしたフレームワークを活用するうえでは、<b>情報取得をあまり粘らず、さっさとソースコード調査にいったほうがいいこともあって、その見極めが大事</b> 　はちょっと肝に銘じとこうと思ったのでした。