AutoGenをガッツリ試してみる:Function CallingでRAGの結果を修正してみよう
概要
今日はAutoGenとFunction Callingを組み合わせてみようと思います。Function Callingは以前OpenAI APIに実装された機能で、適応的に外部のAPIを呼び出すことができる仕組みです。例えば、会話の中で必要に応じて天気のAPIを呼び出すなどの使い方がされます。Function CallingとAgentの組み合わせも色々ありますが、ここではRAGの仕組みをFunction Calling経由で呼び出し、その結果を利用して出力を生成してみましょう。
RAGを利用する際に地味に面倒なのが、出力が英語になってしまうケースがあることです。これに対してプロンプト側で日本語にしてもらうのが一般的ですが、プロンプトを変えるのは面倒ですよね。そこでAgentのゴールとして日本語の出力を指定してやれば、仮にRAGが英語で返したとしても、最終的な解答は日本語になってくれます。
データの準備
今回は、RAG用のデータとして、Self-RAGという論文のpdfを利用してみましょう。RAGとしてはLangChainの機能を用いることとします。まずはpdfをテキストに変換し、埋め込みを計算しましょう。ローカルのpaperディレクトリに保存しておきます。
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import UnstructuredPDFLoader
from langchain.vectorstores import FAISS
loader = UnstructuredPDFLoader("2310.11511.pdf")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=256,
chunk_overlap=32,
)
docs = loader.load_and_split(text_splitter)
embeddings = OpenAIEmbeddings(openai_api_key="API Key")
db = FAISS.from_documents(docs, embeddings)
db.save_local("./paper")
RAGを試してみよう
ここでは、RAGの部分を単体で試してみましょう。ConversationalRetrievalChainを使って、論文の手法について聞いてみました。
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.llms import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
embeddings = OpenAIEmbeddings(openai_api_key="API Key")
db = FAISS.load_local("./paper", embeddings)
qa = ConversationalRetrievalChain.from_llm(
OpenAI(model="gpt-3.5-turbo-instruct", temperature=0, max_tokens=2048),
db.as_retriever(),
memory=ConversationBufferMemory(memory_key="chat_history", return_messages=True)
)
result = qa(({"question": "論文で述べられている手法について教えて"}))
print(result['answer'])
結果は以下になりました。なんか偶然英語になってくれました。もちろん殆どの場合は日本語で返ってきますけど。
The paper mentions a method called CoVE65B (Dhuliawala et al., 2023) which uses iterative prompt engineering to improve the factuality of LLM generations.
AutoGenのFunction Callingを使ってみる
続いて、この検索を関数にして、それをAutoGenのFunction Callで呼び出してみましょう。最初に設定周りのコードを書いていきましょう。
import autogen
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.llms import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
config_list = [
{
"model": "gpt-4-1106-preview",
"api_key": "API Key",
}
]
gpt4_config = {
"seed": 0, # change the seed for different trials
"temperature": 0,
"config_list": config_list,
"request_timeout": 600
}
続いてRAGの関数を作っていきます。先ほどのRAGのコードを関数から呼べるようにしただけです。
embeddings = OpenAIEmbeddings(openai_api_key="API Key")
db = FAISS.load_local("./paper", embeddings)
qa = ConversationalRetrievalChain.from_llm(
OpenAI(model="gpt-3.5-turbo-instruct", temperature=0, max_tokens=2048),
db.as_retriever(),
memory=ConversationBufferMemory(memory_key="chat_history", return_messages=True),
)
def answer_question(question):
response = qa({"question": question})
return response["answer"]
Function Callingの設定は以下です。この辺はOpenAI APIと同じ仕様です。
llm_config = {
"functions": [
{
"name": "answer_question",
"description": "論文の内容に関する質問に答えることができます。",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "論文の内容に関する質問",
}
},
"required": ["question"],
},
},
],
"config_list": config_list,
"timeout": 120,
}
最後にAgent周りを作っていきましょう。UserProxyAgentの引数にfunction_mapを
assistant = autogen.AssistantAgent(
name="assistant",
llm_config=llm_config,
)
# create a UserProxyAgent instance named "user_proxy"
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
code_execution_config={"work_dir": "coding"},
function_map={"answer_question": answer_question}
)
質問は下記のようにしてみます。
user_proxy.initiate_chat(
assistant,
message="""
論文で述べられている提案手法の具体的な内容について日本語で教えてください。
"""
)
結果は以下のようになりました(読み易くなるように改行を追加しています)。AgentがFunction Callingを呼ぶタイミングで英語で聞いちゃってますね。RAGの結果は、「Iterative prompt engineering~」と英語で返ってきていることが確認できます。最初のクエリで日本語で教えてくださいと言っていることもあり、最終的な解答は「提案手法では、LLM(言語モデル)〜」と日本語になっていることが確認できます。このようにRAGの結果をそのまま使うのではなく、翻訳のプロセスを自動で入れられていることが確認できました。
user_proxy (to assistant):
論文で述べられている提案手法の具体的な内容について日本語で教えてください。
--------------------------------------------------------------------------------
assistant (to user_proxy):
***** Suggested function Call: answer_question *****
Arguments:
{"question":"What is the specific content of the proposed method described in the paper?"}
****************************************************
--------------------------------------------------------------------------------
>>>>>>>> EXECUTING FUNCTION answer_question...
user_proxy (to assistant):
***** Response from calling function "answer_question" *****
Iterative prompt engineering is a technique used in the proposed method to improve the
factuality of LLM (Language Model) generations. It involves prompting the model to generate
task output, natural language feedback, and refined task output iteratively.
This process helps to refine and improve the factuality of the generated outputs.
However, this method may also affect the efficiency of the model's inference process.
************************************************************
--------------------------------------------------------------------------------
assistant (to user_proxy):
提案手法では、LLM(言語モデル)の生成する情報の事実性を向上させるために、反復的なプロンプト工学技術を使用しています。
この技術は、モデルにタスクの出力、自然言語フィードバック、そして洗練されたタスクの出力を反復的に生成させることを含んでいます。
このプロセスにより、生成された出力の事実性が洗練され、向上します。
ただし、この方法はモデルの推論プロセスの効率にも影響を及ぼす可能性があります。
TERMINATE
--------------------------------------------------------------------------------
user_proxy (to assistant):
まとめ
今回は、AutoGenを使う際にFunction Callingを組み合わせてみました。今回は翻訳という簡単なタスクを対象にしましたが、欲しい形式を入力することで、単純なRAGで直接返せないようなテーブルやグラフといった形式での出力も可能になりそうですね。