日本オラクルと協創！企業内のあらゆる業務データを元に回答する”生成AIエージェント”とは？ - (Part 1)

## はじめに

こんにちは、株式会社日立製作所の [Lumada Data Science Lab.](https://www.hitachi.co.jp/products/it/lumada/about/ai/ldsl/index.html) の渡邉です！

株式会社日立製作所（以下、日立）と日本オラクル株式会社（以下、オラクル）で3か月の協創プロジェクトを実施しました。

協創プロジェクトで実施した内容は以下の通りです！

1. ユーザーの質問文から次のアクションをLLMに考えさせるAgentアーキテクチャの実装 (本記事Part 1で説明)
2. [めんどうなベクトル化処理不要！今ある業務DBに活用したいテキストデータを組み合わせるだけのOracle Database構築 (Part 2で説明)](https://qiita.com/wtnbrs/private/5977126b84e5f831ebba)
3. [自然言語からSQL文へ！便利なSelect AI機能とAI Vector Search機能 (Part 3で説明)](https://qiita.com/wtnbrs/private/61d91570a1fd89379033)

このプロジェクトは以下のメンバーで、若手が中心になって進めました！
本記事もプロジェクトメンバー全員で執筆しています。

- 株式会社日立製作所　3年目データサイエンティスト　渡邉理沙
- 株式会社日立ソリューションズ・クリエイト　2年目データサイエンティスト　山口蓮
- 株式会社日立製作所　7年目オラクル製品担当エンジニア　野中一鴻
- 日本オラクル株式会社　4年目クラウドソリューションエンジニア　出口龍之介
- 日本オラクル株式会社　4年目クラウドソリューションエンジニア　宮本拓弥

LLMでの業務データ活用をテーマに、若手だけで協力してユースケース検討や実装を進め、**僅か3カ月でプロジェクトを無事完了**しました！

内容は、Part 1、Part 2、Part 3という、３部構成となっています。
この記事では、Part 1として、まず、ユースケースとAgentアーキテクチャの実装について説明します。

## ユースケース決定まで
今回のプロジェクトは自由にユースケースを決めることができたので、異なる分野のチームからアイディアを出し合って進めました。

### RAGアーキテクチャとは
まずは、本プロジェクトのメインの技術となるRetrieval-Augmented Generationアーキテクチャ（以下、RAGアーキテクチャ）について説明します。RAGアーキテクチャとは、質問を受け取り、適切な回答を生成するために大規模なデータベースから情報を引き出す技術です。
1. UI(ユーザインタフェース)画面で①質問文を受け取る
2. UIで受け取った②質問文を基に③検索エンジンで知識DB（テキストデータセットをベクトルなどの形式にEmbeddingしたデータベース）から関連する文書を検索する
3. そして④検索結果を前提知識として質問に加えてLarge Language Model (LLM)を用いて⑤回答を生成する

![RAG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898980/237d99d5-20a8-2cc7-3458-a6f581099ab8.png)

### プロジェクト最初の打合せ
私たちはまず、この基本的なRAGアーキテクチャにおいて、テキストデータから文脈的に近い内容をベクトル検索することはできても、**リレーショナルデータベースから条件を指定して必要なデータのみを抽出する事ができない**ことに問題意識を抱きました。
そこで、ベクトルデータベースのみならず**リレーショナルデータベースから情報をSQL検索して、受け取った質問文に対してSQLクエリを生成し、SQL検索結果を基に回答を生成することもできるRAGアーキテクチャ**を実装できないか、と考えました。

より具体的なユースケースとして、以下を想定しました。

### ユースケース

ユースケースとしては、全国に工場や店舗を持つ家電企業の在庫管理を対象にしました。
全国に工場や店舗を持つ家電企業では、製品ラインナップの多様化や毎年の新機能追加により、各拠点での在庫管理が複雑化しています。
さらに、仕入れ値や在庫数のリアルタイムな変動もあり、タイムリーに情報を把握することが重要です。

従来の基本的なRAGアーキテクチャでは、「東京拠点のエアコンの在庫数を教えてください」などの在庫数を正確に把握するような質問を回答することはできませんでした。

この課題に対して、以下の技術をAgentアーキテクチャで連携することで解決しようと考えました。
- [OCI Autonomous Database Select AI](https://www.oracle.com/jp/autonomous-database/select-ai/)（以下、`Select AI`）
- [Oracle Database 23aiのAI Vector Search](https://www.oracle.com/jp/database/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アーキテクチャ**を実現することができると考えました。

例）「東京拠点のエアコンの在庫数を教えてください」といった質問がユーザーから投げられた場合

1. エアコンの在庫数を聞く場合、通常のSQLクエリで問い合わせ可能なので、Agentが次のアクションとして`Select AI`を選択する。
2. 次に、`Select AI`が実行されると、ユーザーの質問からSQLクエリを生成し、SQL検索結果が「30」と返ってくる。
3. 最後に、Agentはユーザーからの質問とSQL検索結果を基に、最終回答「東京拠点のエアコンの在庫数は30個です。」を生成する。

![Agent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898980/657a7cbd-c9bf-cac8-d7e6-285dd2c453f8.png)


## 開発環境構築

今回のユースケースは国内企業の業務データ活用なので、以下の3つのポイントに注意して構築しました。

- 生成AIサービスを利用するためにChicagoリージョンへ接続すること
- 企業データを取り扱うユースケースのため、通信経路はすべてプライベートとすること
- マルチリージョン構成だが、企業データは東京リージョンで管理すること

![env.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898980/d78faca3-cd92-b60a-902f-d732cc53b9d0.png)

Agentアーキテクチャの実装に注力するために、クラウドサービスで用意されている機能は活用する方針で、今回はSelect AIおよびAI Vector Searchの使えるAutonomous Databaseを採用しました。PaaSを利用することでDBのチューニングや運用からも解放されました。

## ユーザーの質問文から次のアクションをLLMに考えさせるAgentアーキテクチャの実装

今回は、**ベクトル検索もSQL検索も可能なRAGアーキテクチャ**を実現するため、ユーザーから受け取った質問を基に、ベクトル検索をすべきか？SQL検索をすべきか？をAgentに判断させました。
そこで、次のアクションを考えさせるAgentアーキテクチャを実装しました。

このAgentアーキテクチャの核心は、ユーザーの質問文を解析し、2つの検索ツール（ベクトル検索orSQL検索）、または最終回答生成のいずれかを選択することです。


### 実装の流れ

1. **2つの検索方法（ツール）の定義**

    まず、`Select AI` と `AI Vector Search` という2つの検索ツールを関数として定義します。
    それぞれの関数には以下のように`"""`で挟まれた領域にそれぞれのツールの説明文を記入します。
    関数内では、オラクル社の`SelectAI`機能もしくは `AI Vector Search`機能を使って、自然言語からSQLクエリを生成し、そのSQLクエリでデータベースに問い合わせた結果を`return`するようにコーディングします。

    また、ツールの説明文は、後でAgentへの指示プロンプトに挿入するため、**XMLタグを使っています**。
    XMLタグを使うことで、プロンプト内の各部分（指示、注意、例など）を明確に区切り、構造化することができます。
    一般的に、XMLタグを使ってプロンプトを構造化することで、LLMはプロンプトの意図をより正確に理解できるようです。
    そこで、[Cohere社](https://cohere.com/)のLLMモデルでも同じ効果が期待できないか、実施してみたところ、LLMの挙動が良くなり、特に検索方法の選択で精度を安定することができました。

    ```python
    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)
    ```

2. **Agentの定義**

    2.1. **Agentのプロンプトテンプレート**
        
    以下は、Agentに渡すプロンプトになります。
    `{tools}`には、先ほど定義したツールの説明文(`"""`で囲まれた領域の文章)が挿入されます。
    `{tools_names}`には、先ほど定義したツールの関数名が挿入されます。
    `{question}`には、ユーザーからの質問が挿入されます。
    
    Agentは、ツールを選択した場合、下記の`Option1`のjson形式で出力するようにプロンプトで指示しています。
    最終回答を生成した場合は、下記の`Option2`のjson形式で出力するようにプロンプトで指示しています。
    

    ```python
    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のワークフローを定義します。

    ```python
    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)
    ```


3. **Agentの実行**

    最後に、Agentを実行します。
    今回、[LangChainライブラリ](https://www.langchain.com/)固有のChainを簡単に構築するための宣言型の記法であるLangChain Expression Language(LCEL)記法で実装しています。
    この記法で特徴的なのが、Unixのパイプ演算子のイメージで「|」を使える点で、開発者が直感的に実行順序や実行内容を理解しながら実装することができます。
    また、最終出力までの中間生成物へのアクセスができるためデバッグが容易なことも特徴です。
    今回、LangFuseを使用してログのトレースしながら開発しました。
    
    ```python
    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構築について解説します。

最後まで読んでくださり、ありがとうございました。