概要
専門分野のチャットボットを作るシリーズの第3回目!
前回までのおさらい
✅自社開発している専門的で複雑なソフトのチャットボットを作りたい。
✅しかし専門的すぎて精度を上げるのが難しそう。
✅独自データは「Q&A 4000個」「解説書 4000ページ」を持っている。
👉独自データは重複データをまとめるとよさそう!
👉独自データをカテゴリーごとに分けると精度が上がった!
今回やること
エージェントを使い質問プログラムを工夫することで精度の向上を試みる✅
エージェントとは
エージェントとは、Google検索などの外部の機能を駆使して、良い答えを導く機能。
イメージ図
AIが質問に合った独自データを使って回答してくれるのが理想😊
結論ファースト
✅エージェントで精度良くカテゴリーを自動判断できなかった
✅しかし要件によっては十分実用的だと感じた!
AIに質問しても「どのカテゴリーの質問か」正しく判断できない原因
このようなシチュエーションのとき、カテゴリーを正しく判断できない❌
操作方法に関する複数の独自データをAIに与え、「操作について質問された場合はこのデータを使ってください」と設定する。
セルの書式設定のやり方を教えて!
「セルの書式設定のやり方を教えて!」は操作方法の質問なの…?💦
✅質問とdescription(説明文)の関連性が薄い場合、自動判断が難しい
→「セルの書式設定のやり方を教えて!」と質問しても、この質問が「操作方法」についての質問だと理解できない。
✅description(説明文)を適切に設定すれば正しくカテゴリーを自動判断できそう
→「操作について質問された場合はこのデータを使ってください」と設定するのではなく、「セルの書式設定のやり方、〇〇のやり方、〇〇のやり方について質問された場合はこのデータを使ってください」のように具体的に設定する。
✅しかし独自データが専門的ということもあって、description(説明文)がうまくできなかった
→「セルの書式設定のやり方、〇〇のやり方、〇〇のやり方について質問された場合はこのデータを使ってください」のようにすべて列挙するのは数が多すぎて非現実的。
エージェントの種類
エージェント比較結果
初めにエージェントの比較結果を示す✅
案 | エージェント | 精度 | 結果 |
---|---|---|---|
案1 | zero-shot-react-description | 🔺 | ある程度回答できるが「質問のカテゴリーを誤って判断 → 誤った回答」が多々ある。 |
案2 | chat-zero-shot-react-description | 🔺 | ↑同上。 |
案3 | chat-conversational-react-description | ❌ | 今回はメモリ機能は未使用で、エージェントを活かしきれず精度も微妙。 |
案4 | openai-functions | 🔺 | ある程度質問のカテゴリーを正しく判断できているが、回答を間違えることが多い。 |
案1:zero-shot-react-description
ReActを用いて実行するアクションを決定するエージェント✅
公式ドキュメント
✅イメージ図
コード
※実際はインデックスを10個使ったが、以下では3個だけ記載している。
※ソフトの名前は「myApp(仮称)」としている。
// .envの読み込み
require("dotenv").config();
// モデル
import { OpenAI } from "langchain/llms/openai";
import { ChatOpenAI } from "langchain/chat_models/openai";
// 埋め込み
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// ベクトル検索エンジン
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// チェーン
import { VectorDBQAChain } from "langchain/chains";
// エージェント
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// ツール
import { ChainTool } from "langchain/tools";
async function make_chain( indexPath: string ) {
// 作成済みのインデックスを読み込む
const vectorStore = await HNSWLib.load(
indexPath,
new OpenAIEmbeddings()
);
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// チェーン
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
return chain;
}
export const runLlm = async () => {
// ツール
const chainOpe = await make_chain( "index/ope" );
const qaToolOpe = new ChainTool({
name: "操作方法",
description:
"myApp 操作方法に関する質疑応答 - myAppというソフトの「操作」について質問する必要がある場合に便利です。",
chain: chainOpe,
});
const chainPay = await make_chain( "index/pay" );
const qaToolPay = new ChainTool({
name: "お支払い",
description:
"myApp お支払いに関する質疑応答 - myAppというソフトの「お支払い」について質問する必要がある場合に便利です。",
chain: chainPay,
});
const chainOther = await make_chain( "index/other" );
const qaToolOther = new ChainTool({
name: "その他",
description:
"myApp その他に関する質疑応答 - myAppというソフトの「その他」について質問する必要がある場合に便利です。",
chain: chainOther,
});
const tools = [
qaToolOpe,
qaToolPay,
qaToolOther,
];
// モデル
const model = new OpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// エージェント
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "zero-shot-react-description",
returnIntermediateSteps: true,
verbose: true, // ツールの選択過程を含めて出力する
});
// 質問実行
const input1 = `テーブルにヘッダーを設定する方法を教えてください。`;
const result1 = await executor.call({ input: input1 });
// 結果出力
console.log(`回答1: ${result1.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result1.intermediateSteps,
null,
2
)}`
);
};
runLlm();
✅所感
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
「質問のカテゴリーを特定 → 正しい回答」を正しく実行し、理想の回答をしてくれることもある。 |
「質問のカテゴリーを誤って判断 → 誤った回答」が多い。
例:「お支払い」の質問なのに「操作方法」のインデックスを使ってしまうなど。 回答が出るまで繰り返すのでAPI使用料金が高くなる。 | 🔺 |
✅失敗時のイメージ
✅考察
例えば「操作方法」のインデックスのdescriptionは以下のようにしている。
"myApp 操作方法に関する質疑応答 - myAppというソフトの「操作」について質問する必要がある場合に便利です。"
→複雑な質問をされると、この説明だけでは「操作」についての質問か判断できない。
案2:chat-zero-shot-react-description
案1「zero-shot-react-description」とほぼ同じエージェント✅
公式ドキュメント
✅イメージ図
コード
※実際はインデックスを10個使ったが、以下では3個だけ記載している。
※ソフトの名前は「myApp(仮称)」としている。
// .envの読み込み
require("dotenv").config();
// モデル
import { ChatOpenAI } from "langchain/chat_models/openai";
// 埋め込み
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// ベクトル検索エンジン
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// チェーン
import { VectorDBQAChain } from "langchain/chains";
// エージェント
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// ツール
import { ChainTool } from "langchain/tools";
async function make_chain( indexPath: string ) {
// 作成済みのインデックスを読み込む
const vectorStore = await HNSWLib.load(
indexPath,
new OpenAIEmbeddings()
);
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// チェーン
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
return chain;
}
export const runLlm = async () => {
// ツール
const chainOpe = await make_chain( "index/ope" );
const qaToolOpe = new ChainTool({
name: "操作方法",
description:
"myApp 操作方法に関する質疑応答 - myAppというソフトの「操作」について質問する必要がある場合に便利です。",
chain: chainOpe,
});
const chainPay = await make_chain( "index/pay" );
const qaToolPay = new ChainTool({
name: "お支払い",
description:
"myApp お支払いに関する質疑応答 - myAppというソフトの「お支払い」について質問する必要がある場合に便利です。",
chain: chainPay,
});
const chainOther = await make_chain( "index/other" );
const qaToolOther = new ChainTool({
name: "その他",
description:
"myApp その他に関する質疑応答 - myAppというソフトの「その他」について質問する必要がある場合に便利です。",
chain: chainOther,
});
const tools = [
qaToolOpe,
qaToolPay,
qaToolOther,
];
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// エージェント
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "chat-zero-shot-react-description",
returnIntermediateSteps: true,
verbose: true, // ツールの選択過程を含めて出力する
});
// 質問実行
const input1 = `テーブルにヘッダーを設定する方法を教えてください。`;
const result1 = await executor.call({ input: input1 });
// 結果出力
console.log(`回答1: ${result1.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result1.intermediateSteps,
null,
2
)}`
);
};
runLlm();
✅所感
案1「zero-shot-react-description」と同じような結果だった。
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
「質問のカテゴリーを特定 → 正しい回答」を正しく実行し、理想の回答をしてくれることもある。 |
「質問のカテゴリーを誤って判断 → 誤った回答」が多い。
例:「お支払い」の質問なのに「操作方法」のインデックスを使ってしまうなど。 回答が出るまで繰り返すのでAPI使用料金が高くなる。 | 🔺 |
✅考察
案3:chat-conversational-react-description
ユーザーと会話することに適したエージェント✅
公式ドキュメント
✅イメージ図
コード
※実際はインデックスを10個使ったが、以下では3個だけ記載している。
※ソフトの名前は「myApp(仮称)」としている。
// .envの読み込み
require("dotenv").config();
// モデル
import { ChatOpenAI } from "langchain/chat_models/openai";
// 埋め込み
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// ベクトル検索エンジン
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// チェーン
import { VectorDBQAChain } from "langchain/chains";
// エージェント
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// ツール
import { ChainTool } from "langchain/tools";
async function make_chain( indexPath: string ) {
// 作成済みのインデックスを読み込む
const vectorStore = await HNSWLib.load(
indexPath,
new OpenAIEmbeddings()
);
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// チェーン
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
return chain;
}
export const runLlm = async () => {
// ツール
const chainOpe = await make_chain( "index/ope" );
const qaToolOpe = new ChainTool({
name: "操作方法",
description:
"myApp 操作方法に関する質疑応答 - myAppというソフトの「操作」について質問する必要がある場合に便利です。",
chain: chainOpe,
});
const chainPay = await make_chain( "index/pay" );
const qaToolPay = new ChainTool({
name: "お支払い",
description:
"myApp お支払いに関する質疑応答 - myAppというソフトの「お支払い」について質問する必要がある場合に便利です。",
chain: chainPay,
});
const chainOther = await make_chain( "index/other" );
const qaToolOther = new ChainTool({
name: "その他",
description:
"myApp その他に関する質疑応答 - myAppというソフトの「その他」について質問する必要がある場合に便利です。",
chain: chainOther,
});
const tools = [
qaToolOpe,
qaToolPay,
qaToolOther,
];
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// エージェント
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "chat-conversational-react-description",
verbose: true, // ツールの選択過程を含めて出力する
});
// 質問実行
const input1 = `テーブルにヘッダーを設定する方法を教えてください。`;
const result1 = await executor.call({ input: input1 });
// 結果出力
console.log(`回答1: ${result1.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result1.intermediateSteps,
null,
2
)}`
);
};
runLlm();
✅所感
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
会話型のプログラムが作りやすい。 |
「質問のカテゴリーを誤って判断 → 誤った回答」が多い。
例:「お支払い」の質問なのに「操作方法」のインデックスを使ってしまうなど。 | ❌ |
✅考察
案4:openai-functions
OpenAIのFunction Callingを使うエージェント✅
公式ドキュメント
✅イメージ図
※案2「chat-zero-shot-react-description」と似ている
コード
※実際はインデックスを10個使ったが、以下では3個だけ記載している。
※ソフトの名前は「myApp(仮称)」としている。
// .envの読み込み
require("dotenv").config();
// モデル
import { ChatOpenAI } from "langchain/chat_models/openai";
// 埋め込み
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// ベクトル検索エンジン
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// チェーン
import { VectorDBQAChain } from "langchain/chains";
// エージェント
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// ツール
import { ChainTool } from "langchain/tools";
async function make_chain( indexPath: string ) {
// 作成済みのインデックスを読み込む
const vectorStore = await HNSWLib.load(
indexPath,
new OpenAIEmbeddings()
);
// モデル
const model = new ChatOpenAI({
modelName : "gpt-3.5-turbo-0613",
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// チェーン
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
return chain;
}
export const runLlm = async () => {
// ツール
const chainOpe = await make_chain( "index/ope" );
const qaToolOpe = new ChainTool({
name: "操作方法",
description:
"myApp 操作方法に関する質疑応答 - myAppというソフトの「操作」について質問する必要がある場合に便利です。",
chain: chainOpe,
});
const chainPay = await make_chain( "index/pay" );
const qaToolPay = new ChainTool({
name: "お支払い",
description:
"myApp お支払いに関する質疑応答 - myAppというソフトの「お支払い」について質問する必要がある場合に便利です。",
chain: chainPay,
});
const chainOther = await make_chain( "index/other" );
const qaToolOther = new ChainTool({
name: "その他",
description:
"myApp その他に関する質疑応答 - myAppというソフトの「その他」について質問する必要がある場合に便利です。",
chain: chainOther,
});
const tools = [
qaToolOpe,
qaToolPay,
qaToolOther,
];
// モデル
const model = new ChatOpenAI({
modelName : "gpt-3.5-turbo-0613",
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// エージェント
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "openai-functions",
returnIntermediateSteps: true,
verbose: true, // ツールの選択過程を含めて出力する
});
// 質問実行
const input1 = `テーブルにヘッダーを設定する方法を教えてください。`;
const result1 = await executor.call({ input: input1 });
// 結果出力
console.log(`回答1: ${result1.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result1.intermediateSteps,
null,
2
)}`
);
};
runLlm();
✅所感
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
案2「chat-zero-shot-react-description」に比べて、「質問のカテゴリー分類」がやや正確に感じた。 | 間違いも多々あり、精度はそこまで高くない。 | ❌ |
→「質問のカテゴリー分類」はこちらの方が正確に感じた!
→「質問の回答」は微妙…
✅考察
description(説明文)を調整
課題
descriptionとは「ツールの説明文」のこと。
エージェントはdescriptionを参考に「どのツールを使うか」決定している。
理想の動作
例えば「操作」について質問した場合、AIが「これは操作の質問だ!」と判断して操作のツールを使って回答してほしい!
現状の動作
前述のエージェントの比較では「これは操作の質問だ!」の判断を間違えることが多かった。
この原因はdescription(説明文)が悪いせいだと考えた💭
description比較結果
初めにdescription(説明文)の比較結果を示す✅
案 | description | 精度 | 結果 |
---|---|---|---|
案1 | タイトル + 抽象的な説明 | ❌ | 説明が抽象的すぎて正しく判断できない。 |
案2 | タイトル+具体的な説明 | 🔺 | 多少は正しく判断できるようになった。 |
案1:タイトル + 抽象的な説明
✅descriptionの例
※ソフトの名前は「myApp(仮称)」としている。
✅所感
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
シンプルな質問なら、どのカテゴリーの質問か正しく判断してくれる。 | 「セルの書式設定」についての質問など一見「操作」についての質問か分からない質問は、どのカテゴリーの質問か判断できない。 | ❌ |
✅考察
案2:タイトル+具体的な説明
✅descriptionの例
※ソフトの名前は「myApp(仮称)」としている。
✅所感
メリット⭕️ | デメリット❌ | 精度 |
---|---|---|
「セルの書式設定」などの明記した言葉について質問されると、どのカテゴリーの質問か正しく判断できることが多い。 | descriptionに具体的な操作をすべて書く必要がある。 | 🔺 |
✅考察
エージェントのまとめ
エージェントでは理想的な回答は得られなかった😫
一番精度がよかったエージェント
エージェントの中で一番精度が良いと感じたのは以下の組み合わせ⭐️
エージェントの種類 | 案2:chat-zero-shot-react-description |
---|---|
description | 案2:タイトル+具体的な説明 |
コード
※実際はインデックスを10個使ったが、以下では3個だけ記載している。
※ソフトの名前は「myApp(仮称)」としている。
// .envの読み込み
require("dotenv").config();
// モデル
import { ChatOpenAI } from "langchain/chat_models/openai";
// 埋め込み
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// ベクトル検索エンジン
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// チェーン
import { VectorDBQAChain } from "langchain/chains";
// エージェント
import { initializeAgentExecutorWithOptions } from "langchain/agents";
// ツール
import { ChainTool } from "langchain/tools";
async function make_chain( indexPath: string ) {
// 作成済みのインデックスを読み込む
const vectorStore = await HNSWLib.load(
indexPath,
new OpenAIEmbeddings()
);
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// チェーン
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
return chain;
}
export const runLlm = async () => {
// ツール
const chainOpe = await make_chain( "index/ope" );
const qaToolOpe = new ChainTool({
name: "操作方法",
description:
"myApp 操作方法に関する質疑応答 - myAppというソフトの「セルの入力」「セルの書式設定」「テーブルの作り方」「データの集計」「グラフの作成」について質問する必要がある場合に便利です。",
chain: chainOpe,
});
const chainPay = await make_chain( "index/pay" );
const qaToolPay = new ChainTool({
name: "お支払い",
description:
"myApp お支払いに関する質疑応答 - myAppというソフトの「お支払い」「クレジットカードの登録」「サブスクリプション」について質問する必要がある場合に便利です。",
chain: chainPay,
});
const chainOther = await make_chain( "index/other" );
const qaToolOther = new ChainTool({
name: "その他",
description:
"myApp その他に関する質疑応答 - myAppというソフトの「ファイルの拡張子」「自動バックアップ」「詳細設定」について質問する必要がある場合に便利です。",
chain: chainOther,
});
const tools = [
qaToolOpe,
qaToolPay,
qaToolOther,
];
// モデル
const model = new ChatOpenAI({
temperature : 0,
maxTokens : 500,
frequencyPenalty : 0.0,
presencePenalty : 0.0,
});
// エージェント
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "chat-zero-shot-react-description",
returnIntermediateSteps: true,
verbose: true, // ツールの選択過程を含めて出力する
});
// 質問実行
const input1 = `テーブルにヘッダーを設定する方法を教えてください。`;
const result1 = await executor.call({ input: input1 });
// 結果出力
console.log(`回答1: ${result1.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result1.intermediateSteps,
null,
2
)}`
);
};
runLlm();
しかしそれでもAIに「どのデータを使えばいいか?」を判断してもらうのが難しく、思うような精度にはならなかった💦
考察
思うような精度が出ない理由を2つのポイントに分けて考察してみた。
ツール(独自データ)の選択
- 独自データに専門的な言葉が多いせいで、AIが質問のカテゴリーを正しく判断できない💦
- この問題はツールのdescription(説明文)を詳細に書けば解決できそう!しかし取り扱っているソフトが多機能かつ大規模なためdescriptionを書ききれない💦
ツール(独自データ)を使って回答
- 前述のツールの選択が正しくできていないと、この部分の精度を上げても意味がない💦
-
今回この部分の改良はしていない。次回以降試す予定!
- 他のモデルの使用(GPT-4)
- モデルのパラメータの調整
- プロンプトの調整
エージェントの課題
- ツールの選択(=カテゴリーの自動選択)の精度を上げるのが難しい。
- 回答が出るまで何度も質問を繰り返すため、トークンの消費が激しくAPI利用料金が高くなる。
今回の要件では難しいと感じる部分が多かった💦
エージェントの採用は見送ろうと思う…😫
次回
今回はここまで!
試した中では「chat-zero-shot-react-description」を使い、具体的なdescriptionを書くのが一番よさそうだった!
次回はチェーンを使った質問プログラムでもっと精度を上げる〜🙌