はじめに
こんにちは、株式会社日立製作所の Lumada Data Science Lab. の渡邉です!
株式会社日立製作所(以下、日立)と日本オラクル株式会社(以下、オラクル)で3か月の協創プロジェクトを実施しました。
協創プロジェクトで実施した内容は以下の通りです!
- ユーザーの質問文から次のアクションをLLMに考えさせるAgentアーキテクチャの実装 (本記事Part 1で説明)
- めんどうなベクトル化処理不要!今ある業務DBに活用したいテキストデータを組み合わせるだけのOracle Database構築 (Part 2で説明)
- 自然言語からSQL文へ!便利なSelect AI機能とAI Vector Search機能 (Part 3で説明)
このプロジェクトは以下のメンバーで、若手が中心になって進めました!
本記事もプロジェクトメンバー全員で執筆しています。
- 株式会社日立製作所 3年目データサイエンティスト 渡邉理沙
- 株式会社日立ソリューションズ・クリエイト 2年目データサイエンティスト 山口蓮
- 株式会社日立製作所 7年目オラクル製品担当エンジニア 野中一鴻
- 日本オラクル株式会社 4年目クラウドソリューションエンジニア 出口龍之介
- 日本オラクル株式会社 4年目クラウドソリューションエンジニア 宮本拓弥
LLMでの業務データ活用をテーマに、若手だけで協力してユースケース検討や実装を進め、僅か3カ月でプロジェクトを無事完了しました!
内容は、Part 1、Part 2、Part 3という、3部構成となっています。
この記事では、Part 1として、まず、ユースケースとAgentアーキテクチャの実装について説明します。
ユースケース決定まで
今回のプロジェクトは自由にユースケースを決めることができたので、異なる分野のチームからアイディアを出し合って進めました。
RAGアーキテクチャとは
まずは、本プロジェクトのメインの技術となるRetrieval-Augmented Generationアーキテクチャ(以下、RAGアーキテクチャ)について説明します。RAGアーキテクチャとは、質問を受け取り、適切な回答を生成するために大規模なデータベースから情報を引き出す技術です。
- UI(ユーザインタフェース)画面で①質問文を受け取る
- UIで受け取った②質問文を基に③検索エンジンで知識DB(テキストデータセットをベクトルなどの形式にEmbeddingしたデータベース)から関連する文書を検索する
- そして④検索結果を前提知識として質問に加えてLarge Language Model (LLM)を用いて⑤回答を生成する
プロジェクト最初の打合せ
私たちはまず、この基本的なRAGアーキテクチャにおいて、テキストデータから文脈的に近い内容をベクトル検索することはできても、リレーショナルデータベースから条件を指定して必要なデータのみを抽出する事ができないことに問題意識を抱きました。
そこで、ベクトルデータベースのみならずリレーショナルデータベースから情報をSQL検索して、受け取った質問文に対してSQLクエリを生成し、SQL検索結果を基に回答を生成することもできるRAGアーキテクチャを実装できないか、と考えました。
より具体的なユースケースとして、以下を想定しました。
ユースケース
ユースケースとしては、全国に工場や店舗を持つ家電企業の在庫管理を対象にしました。
全国に工場や店舗を持つ家電企業では、製品ラインナップの多様化や毎年の新機能追加により、各拠点での在庫管理が複雑化しています。
さらに、仕入れ値や在庫数のリアルタイムな変動もあり、タイムリーに情報を把握することが重要です。
従来の基本的なRAGアーキテクチャでは、「東京拠点のエアコンの在庫数を教えてください」などの在庫数を正確に把握するような質問を回答することはできませんでした。
この課題に対して、以下の技術をAgentアーキテクチャで連携することで解決しようと考えました。
-
OCI Autonomous Database Select AI(以下、
Select AI
) -
Oracle Database 23aiのAI Vector Search(以下、
AI Vector Search
)
Oracle DatabaseのマネージドサービスAutonomous Databaseでは、自然言語からSQLクエリを自動生成する機能である、Select AI
を提供しています。
また、Oracle Databaseの新バージョン23aiでは、ベクトル型のデータ検索機能である、AI Vector Search
を提供しています。
本プロジェクトでは、新バージョンのOracle Database 23aiをOracle Autonomous Database上で構成し、使用することにしました。
Agentアーキテクチャは、自然言語を理解し、ユーザーの質問に応じて柔軟に次のアクションを決定することができます。
Select AI
を実行するアクションでは、自然言語からSQLクエリを自動生成し、業務DB内の構造化データ(在庫数や製品IDなど)をSQL検索します。
AI Vector Search
を実行するアクションでは、ベクトル検索を活用して、製品マニュアルなどの非構造化データをベクトル検索します。
AgentにSelect AI
とAI Vector Search
の検索を選択させることで、従来のRAGアーキテクチャではできなかった、SQL検索も可能なRAGアーキテクチャを実現することができると考えました。
例)「東京拠点のエアコンの在庫数を教えてください」といった質問がユーザーから投げられた場合
- エアコンの在庫数を聞く場合、通常のSQLクエリで問い合わせ可能なので、Agentが次のアクションとして
Select AI
を選択する。 - 次に、
Select AI
が実行されると、ユーザーの質問からSQLクエリを生成し、SQL検索結果が「30」と返ってくる。 - 最後に、Agentはユーザーからの質問とSQL検索結果を基に、最終回答「東京拠点のエアコンの在庫数は30個です。」を生成する。
開発環境構築
今回のユースケースは国内企業の業務データ活用なので、以下の3つのポイントに注意して構築しました。
- 生成AIサービスを利用するためにChicagoリージョンへ接続すること
- 企業データを取り扱うユースケースのため、通信経路はすべてプライベートとすること
- マルチリージョン構成だが、企業データは東京リージョンで管理すること
Agentアーキテクチャの実装に注力するために、クラウドサービスで用意されている機能は活用する方針で、今回はSelect AIおよびAI Vector Searchの使えるAutonomous Databaseを採用しました。PaaSを利用することでDBのチューニングや運用からも解放されました。
ユーザーの質問文から次のアクションをLLMに考えさせるAgentアーキテクチャの実装
今回は、ベクトル検索もSQL検索も可能なRAGアーキテクチャを実現するため、ユーザーから受け取った質問を基に、ベクトル検索をすべきか?SQL検索をすべきか?をAgentに判断させました。
そこで、次のアクションを考えさせるAgentアーキテクチャを実装しました。
このAgentアーキテクチャの核心は、ユーザーの質問文を解析し、2つの検索ツール(ベクトル検索orSQL検索)、または最終回答生成のいずれかを選択することです。
実装の流れ
-
2つの検索方法(ツール)の定義
まず、
Select AI
とAI Vector Search
という2つの検索ツールを関数として定義します。
それぞれの関数には以下のように"""
で挟まれた領域にそれぞれのツールの説明文を記入します。
関数内では、オラクル社のSelectAI
機能もしくはAI Vector Search
機能を使って、自然言語からSQLクエリを生成し、そのSQLクエリでデータベースに問い合わせた結果をreturn
するようにコーディングします。また、ツールの説明文は、後でAgentへの指示プロンプトに挿入するため、XMLタグを使っています。
XMLタグを使うことで、プロンプト内の各部分(指示、注意、例など)を明確に区切り、構造化することができます。
一般的に、XMLタグを使ってプロンプトを構造化することで、LLMはプロンプトの意図をより正確に理解できるようです。
そこで、Cohere社のLLMモデルでも同じ効果が期待できないか、実施してみたところ、LLMの挙動が良くなり、特に検索方法の選択で精度を安定することができました。from langchain.tools import BaseTool, tool # ツールの定義:Select AI機能を使って検索する関数 @tool def SelectAI(query: str) -> str: """ <prompt> <ROLE> この関数はSQLデータベースに対して実行可能なSQLクエリを生成します。 典型的なSQLクエリを生成することが得意です。 構造化されたデータに対するクエリや集計処理に最適です。 この関数は以下のような質問に適しています: - 特定の製品の価格を知りたい場合 - 在庫数や引当可能数を確認したい場合 - 特定の拠点や製品カテゴリに関する情報を取得したい場合 - 複数のテーブルを結合して情報を取得する必要がある場合 </ROLE> <ATTENTION> <criterion> 製品の詳細な説明や機能に関する質問には適していません。 また製品の特徴に基づくフィルタリングが必要な質問には適していません。 </criterion> </ATTENTION> </prompt> """ return select_ai_logic(query)
-
Agentの定義
2.1. Agentのプロンプトテンプレート
以下は、Agentに渡すプロンプトになります。
{tools}
には、先ほど定義したツールの説明文("""
で囲まれた領域の文章)が挿入されます。
{tools_names}
には、先ほど定義したツールの関数名が挿入されます。
{question}
には、ユーザーからの質問が挿入されます。Agentは、ツールを選択した場合、下記の
Option1
のjson形式で出力するようにプロンプトで指示しています。
最終回答を生成した場合は、下記のOption2
のjson形式で出力するようにプロンプトで指示しています。from langchain.prompts import ChatPromptTemplate from langchain.agents import create_json_chat_agent human_template = """ <prompt> <TOOL> {tools} </TOOL> <TASK> (省略) </TASK> <RESPONSE FORMAT INSTRUCTIONS> 回答する際は、以下の2つの形式のいずれかで出力してください: **Option 1:**ユーザーにツールを使用してほしい場合 ```json {{ "action": string, \\The action to take. Must be one of {tool_names} "action_input": {question} }} ``` **Option #2:**ユーザーに最終回答したい場合 ```json {{ "action": "Final Answer", "action_input": string }} ``` </RESPONSE FORMAT INSTRUCTIONS> <USER INPUT> {question} </USER INPUT> """ prompt = ChatPromptTemplate(input_variables=['question', 'tool_names', 'tools']) agent = create_json_chat_agent(model, tools, prompt)
2.2. Agentワークフローの定義
Agentのワークフローを定義します。
from langgraph.graph import StateGraph, END def run_langgraph(inputs): # Agentを実行し、ツールを選択 def run_agent(data): agent_outcome = agent.invoke(data) return {"agent_outcome": agent_outcome} # 選択されたツールの実行 def execute_tools(data): agent_action = data['agent_outcome'] output = tool_executor.invoke(agent_action) return {"intermediate_steps": [(agent_action, str(output))]} # Agentの継続判断 def should_continue(data): if isinstance(data['agent_outcome'], AgentFinish): return "end" else: return "continue" workflow = StateGraph(AgentState) workflow.add_node("agent", run_agent) workflow.add_node("action", execute_tools) workflow.set_entry_point("agent") workflow.add_conditional_edges( "agent", should_continue, {"continue": "action", "end": END} ) workflow.add_edge('action', 'agent') return workflow.compile().invoke(inputs)
-
Agentの実行
最後に、Agentを実行します。
今回、LangChainライブラリ固有のChainを簡単に構築するための宣言型の記法であるLangChain Expression Language(LCEL)記法で実装しています。
この記法で特徴的なのが、Unixのパイプ演算子のイメージで「|」を使える点で、開発者が直感的に実行順序や実行内容を理解しながら実装することができます。
また、最終出力までの中間生成物へのアクセスができるためデバッグが容易なことも特徴です。
今回、LangFuseを使用してログのトレースしながら開発しました。chain = ( _inputs | RunnablePassthrough.assign( agent_outcome=run_langgraph ) | (lambda x: x["agent_outcome"]) ) results = chain.invoke({"question": "特定の製品の価格を教えてください。"})
このようにAgentアーキテクチャを実装できたことで、SQL検索もベクトル検索も可能なRAGアーキテクチャを実装することができました。
おわりに
いかがでしたでしょうか?
Part 1である今回は、日立とオラクルがタッグを組んだ、若手チームが作成したユースケースの概要とAgentアーキテクチャの実装についてご紹介しました。
Part 2の記事では、Oracle Database構築について解説します。
最後まで読んでくださり、ありがとうございました。