RAG(Retrieval-Augmented Generation)は、情報検索と生成モデルを組み合わせた自然言語処理技術の一種です。関連する文書の断片を生成モデルのコンテキストとして取り込むことで、生成されるテキストの正確性と関連性を高めます。RAGは、QAシステム、チャットボット、テキスト要約など、効率性と柔軟性を兼ね備えた多くの分野で広く使用されており、企業のカスタマーサポートボットに非常に適しています。
以下の例では、GPTのエンベディングを使用して関連情報をベクトル化し、キーワード検索を行い、最終的にこれらの情報をユーザーの質問と組み合わせてGPT-4oに送り、比較的友好的な回答を得る方法を示しています。
具体的な実装は以下の通りです:
using Azure.AI.OpenAI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Connectors.Redis;
using Microsoft.SemanticKernel.Memory;
using StackExchange.Redis;
using System.Runtime.CompilerServices;
using System.Text;
var chatModelId = "gpt-4o";
var embeddingId = "text-embedding-ada-002";
var key = File.ReadAllText(@"C:\GPT\key.txt");
# pragma warning disable SKEXP0020
# pragma warning disable SKEXP0010
# pragma warning disable SKEXP0001
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(chatModelId, key)
.Build();
var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(new ConfigurationOptions
{
EndPoints = { "localhost:6379" },
ConnectTimeout = 10000,
ConnectRetry = 3
});
var database = connectionMultiplexer.GetDatabase();
var store = new RedisMemoryStore(database, vectorSize: 1536);
var embeddingGenerator = new OpenAITextEmbeddingGenerationService(embeddingId, key);
var memory = new SemanticTextMemory(store, embeddingGenerator);
var dic = new Dictionary<string, string>
{
{"name","私の名前は桂素伟" },
{"age","私の年齢は30歳です" },
{"job","私は.netの上級講師です" },
{"experience","私は10年の.net開発経験があります" },
{"skill","私は.net core、asp.net core、マイクロサービス、docker、k8sなどの技術に精通しています" },
{"hobby","私は読書、執筆、旅行が好きです" },
{"motto","私の座右の銘は:「行成于思,毁于随」です" }
};
foreach (var item in dic)
{
await memory.SaveInformationAsync("ask", id: item.Key, text: item.Value);
}
var chatHistory = new ChatHistory();
var chat = kernel.GetRequiredService<IChatCompletionService>();
var settings = new PromptExecutionSettings
{
ExtensionData = new Dictionary<string, object>
{
["max_tokens"] = 1000,
["temperature"] = 0.2,
["top_p"] = 0.8,
["presence_penalty"] = 0.0,
["frequency_penalty"] = 0.0
}
};
while (true)
{
Console.ResetColor();
Console.WriteLine("----------学生の質問:----------");
var ask = Console.ReadLine();
chatHistory.Clear();
chatHistory.AddSystemMessage("以下の情報に基づいて質問に回答してください:");
await foreach (var answer in memory.SearchAsync(
collection: "ask",
query: ask,
limit: 3,
minRelevanceScore: 0.65d,
withEmbeddings: true))
{
chatHistory.AddSystemMessage(answer.Metadata.Text);
}
chatHistory.AddUserMessage(ask);
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("==========講師の回答:==========");
AuthorRole? role = AuthorRole.Assistant;
var contentBuilder = new StringBuilder();
await foreach (var reply in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings))
{
if (reply.Role.HasValue && role != reply.Role)
{
role = reply.Role;
}
Console.Write(reply.Content);
contentBuilder.Append(reply.Content);
}
chatHistory.AddMessage(role.Value, contentBuilder.ToString());
Console.WriteLine();
}
ユーザーが質問をすると、最初にその質問をmemoryに送り、67行目でSearchを利用して結果を確認します。SemanticTextMemoryは複数の(設定パラメータに依存)関連結果を返し、74行目でこれらをchatHistoryに追加します。その後、ユーザーの質問を77行目で組み合わせ、GPTに送ります。これにより、関連する情報と質問がGPTに送られ、GPTが結果をまとめて返します。
RAGはある程度、個人化されたデータをLLMの能力と組み合わせて返すことができますが、固定されたマッピングであるため、大規模なコンテキストが必要な場合や結果が求められるクエリには対応できないことがあります。
最近、多く登場しているGraphRAGはこの問題の良い解決策となっていますが、構築および検索時には少しコストがかかります。技術の進歩と共に、より洗練された解決策が登場することを期待しています。
(Translated by GPT)