OCI Generative AI の Cohere Command R+とOracle Database 23aiを使ってチャットアプリを試してみた

# はじめに
Oracle Cloud Infrastructure (OCI) の生成AIサービスから、Cohere社の「Command R+」が登場し遂に日本語にも対応しました。それに伴い、OCIネイティブな生成AIを使った日本語のチャットアプリを簡単に作成できるようになりました。
チャットアプリに使用される生成AIで使われている大規模言語モデル (LLM) は、文章として意味のある回答を生成することに非常に優れていますが、応答に無意味な内容や不正確な情報を含めてしまうこともあります。これが特定のユースケースでは問題となる場合もあります。
そこでこの問題を解決するために使われているのが「検索拡張生成」（Retrieval-Augmented Generation, RAG）という技術です。RAGを使うことで、外部情報をソースとして回答させることができ、生成AIの精度を向上させることができます。この技術を活用して、より正確で信頼性の高い応答を生成できるようになります。

実際にRAGを使ったチャットアプリを試してみると、以下のような具体的な製品情報を踏まえた回答を出力させることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/894fa427-7928-dfae-180f-8828a0739278.png" width="100%">

今回はRAGの技術に触れつつ、OCI上でチャットアプリを構築していきたいと思います。

# Retrieval Augmented Generation (RAG)とは
RAGは、テキスト生成に外部ソースの情報検索を組み合わせることで回答精度を高める方法です。この技術の背景には、生成AIとのやり取りにおいて、LLMのみを使って回答を作成した場合、LLMが事前に学習した情報からしか回答が作成されないという問題があります。最新の情報を出力に反映できなかったり、根拠がなく信頼性に欠ける回答が出力されるハルシネーションという現象が起こるという課題があり、この課題を解決する方法としてRAGが期待されています。

# ベクトル検索とは
RAGにおいて、外部ソースの情報を検索する場合に、質問に関係した内容をデータソースから検索する必要があります。その情報検索の方法の一つにベクトル検索があります。
ベクトル検索の技術を理解する上で、まずはベクトルについて理解する必要があります。ベクトルとは、向きと大きさを持つ量で、数学や物理学でよく使われています。具体的には、ベクトルは矢印で表され、矢印の長さが大きさ、矢印の指す方向が向きを示しています。この矢印はある始点から終点への移動を表していると考えることができ、x, yの成分を持つ矢印として、2次元空間に落とし込むことができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/dcd68623-7704-86f3-8d97-87201e533b9e.png" width="70%">

文章や単語をベクトル化することをEmbed(埋め込み)と言います。
埋め込みとは、システムがテキストや文章などの現実世界のデータを理解するためにベクトル (数値列) で表現したものです。画像のように意味の近い文章は近いベクトルを持ち、意味が反対の文章は遠いベクトルを持つため、文章をベクトル化することで文章の意味を計算できるようになります。ベクトルを比較することで類似性や相違性を捉えることができるため、その類似性を計算して近しいベクトルを検索することをベクトル検索 (類似度検索) と言います。
埋め込みはデータ内の関係性の発見、センチメント分析、言語翻訳、レコメンドシステムなどに使われます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/04176f38-89e4-ba2d-c143-3c3277a86d35.png" width="70%">

そこで今回はRAGの技術を使った簡単なアプリケーションをOCI上で構築してみたいと思います。

# OCIで利用するサービス
利用する主なサービスは、以下の通りです。
* Virtual Machine
* Oracle Database 23ai
* OCI Generative AI

## Oracle Database 23aiとは
[Oracle Database 23ai](https://blogs.oracle.com/database/post/oracle-23ai-now-generally-available)ではAIに重点を置いており、AIを使って生成されたベクトルデータを保存し、効率的にオブジェクトの類似性を検索できるAI Vector Searchという機能が追加されています。ドキュメント、画像、ビデオ、サウンドなど様々なオブジェクトをベクトル化することで、それらの間の類似度を検索できるようになります。Oracle Database 23aiでは、この類似度検索とビジネスデータの検索を簡単に組み合わせることができます。
Oracle Database 23aiでVector Searchを試してみたい方は[OCI Tutorial](https://oracle-japan.github.io/ocitutorials/ai-vector-search)をご確認ください。

## OCI Generative AIとは
OCI Generative AIは、Cohere社のモデルを使ったOracle Cloud Infrastructureのフルマネージドな生成AIのサービスです。詳しくはこちらの記事をご覧ください。

https://qiita.com/shukawam/items/067850711cd0d008bbbd

OCI Generative AIには4つの機能があります。
| 機能名 | モデル |
| ----| -----|
| チャット・モデル | `cohere.command-r-plus`, `cohere.command-r-16k`, `meta.llama-3-70b-instruct` |
| テキスト生成 | `cohere.command`, `cohere.command-light`, `meta.llama-2-70b-chat` |
| テキスト要約 | `cohere.command`|
| テキスト埋め込み| `cohere.embed-english-v3.0`, `cohere.embed-multilingual-v3.0`,<br> `cohere.embed-english-light-v3.0`, `cohere.embed-multilingual-light-v3.0` |

今回は、2024/6/18から新しく使えるようになった[`Command R+`](https://docs.oracle.com/en-us/iaas/releasenotes/changes/6f178aec-2ca1-4c54-a4b5-505e3c94f70f/)のチャット・モデル`cohere.command-r-plus`と、テキスト埋め込みのモデル`cohere.embed-multilingual-v3.0`を使います。

## Command R+とは
`Command R+`は、Cohere社のインタラクティブな会話と長文のタスクに最適化されたLLMです。非常に高いパフォーマンスを実現しており、エンタープライズ企業の本番利用向けに適したモデルです。RAGと[`multi-step tool`](https://docs.cohere.com/docs/multi-step-tool-use)を使った複雑なワークフローに適しています。日本語を含む13カ国語に対応しています。
詳細はこちらをご確認ください。

https://docs.cohere.com/docs/command-r-plus

# アーキテクチャ
今回は以下のようなアーキテクチャを考えます。Base Databaseにpdfのデータをベクトル化して保存します。Virtual Machine上でベクトル検索を行うAPIの提供とチャット画面となるアプリを起動させます。

今回APIでフロントエンドとバックエンドを連携している理由は、疎結合なアーキテクチャにも対応したかったからです。実際の本番環境では、フロントとバックの環境をVMを分割して作成したり、APIをサーバレスなOCI Functionなどの環境で動かす可能性があるため、このような疎結合なアーキテクチャにも対応した構成にしました。
![rag_architecture_detail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/555acb0b-fa23-f35c-d61e-80def661ed10.png)

# 前提
* Oracle Cloud Infrastructureの環境
  * ネットワークリソースの構築
  * Virtual Machineの構築
  * Oracle Base Database 23aiの構築
  * Chicago or Frankfurt Regionのサブスクライブ
* Virtual Machineの環境構築
  * Python環境
  * oci-sdkが利用できる環境

# 手順
0. [事前準備](#0-事前準備)
1. [Tableの作成](#1-tableの作成)
2. [pdfデータのInsert](#2-pdfデータのinsert)
3. [Textデータのベクトル化](#3-textデータのベクトル化)
4. [ベクトル検索](#4-ベクトル検索)
5. [FAST APIを使ったAPI作成](#5-fast-apiを使ったapi作成)
6. [Streamlitを使ったフロントエンドの作成](#6-streamlit)
7. [Table削除](#7-tableの削除)


# 0. 事前準備
## OCIの環境構築
上記のアーキテクチャを構築します。

### Oracle Database 23aiの構築
今回はデータソースと保存先としてOracle Database 23aiを使います。
Oracle Database 23aiの構築方法は、OCI Tutorialの「[Oracle Database編 - Base Database Service (BaseDB) を使ってみよう](https://oracle-japan.github.io/ocitutorials/basedb)」か、「[Oracle Database - Oracle AI Vector Searchを使ってみよう](https://oracle-japan.github.io/ocitutorials/ai-vector-search)」を参考にしてください。
今回のコードは、oracleユーザーで実行してください。

### Virtual Machineの構築
今回はPythonの実行環境としてOCI上のVirtual Machineを使用します。
こちらのOCI Tutorial「[その3 - インスタンスを作成する](https://oracle-japan.github.io/ocitutorials/beginners/creating-compute-instance/)」を参考にVirtual Machineを作成してください。また、追加でPythonをインストールして実行環境を作成してください。
Utuntu22.04, Python 3.11.9の環境で動作確認済みです。
```bash
$ lsb_release -d
Description:    Ubuntu 22.04.4 LTS

$ python --version
Python 3.11.9
```

:::note info
Oracle Linux 8.9でも動作確認済みです。Oracle Linux 8.9を利用する場合は、`ol8_appstream`リポジトリから`libglvnd-devel`のパッケージを追加でインストールしてください。

```bash
$ sudo yum --enablerepo=ol8_appstream install libglvnd-devel
...
  mesa-libglapi-23.1.4-2.el8.x86_64
  xorg-x11-proto-devel-2020.1-3.el8.noarch

Complete!
```
:::

### GitリポジトリのClone
今回使うソースコードはこのGitHubに公開しています。

https://github.com/sh-sho/oci_rag_sample_kit

ご自身の環境にGitリポジトリをCloneします。
```bash
$ git clone https://github.com/sh-sho/oci_rag_sample_kit.git
```
この記事では`src`ディレクトリで作業を行います。`src`に移動します。
```bash
$ cd oci_rag_sample_kit/src/
$ tree -a
.
├── .env.example
├── 01.create_table.py
├── 02.insert_data.py
├── 03.embed_save_texts.py
├── 04.execute_vector_search.py
├── 05.vector_search_api.py
├── 06.front_app_streamlit.py
├── 07.drop_table.py
├── chatclass.py
├── data
│   ├── autonomous-database-self-securing-wp-ja.pdf
│   ├── oracle-cloud-infrastructure-waf-data-sheet-ja.pdf
│   └── oracle-gov-cloud-oci-ja.pdf
├── requirements.txt
├── table_detail.py
└── utils.py

1 directory, 15 files
```

次にPythonのライブラリをインストールします。`requirements.txt`に必要なライブラリが記載されているので以下のコマンドでインストールをします。
```bash
$ pip install -r requirements.txt
```
Vector Searchを行うため`python-oracledb`のバージョンは2.2.1以上のものを使用してください。

### 環境変数の設定
次に環境変数を設定します。
`.env.example`ファイルをコピーして`.env`ファイルを作成します。
```bash
$ cp .env.example .env
```
`.env`の内容をご自身の環境に合わせて書き換えます。
```
UN=username
PW=password
DSN=dsn
OCI_CONFIG_FILE=~/.oci/config
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CSV_DIRECTORY_PATH=/tmp_data
CONFIG_PROFILE=CHICAGO
PDF_DIRECTORY_PATH='./data/'
```
`UN`, `PW`, `DSN`はOracleDatabaseに接続するための環境変数です。本コードはoracleユーザか同等の権限を持ったユーザで実行可能です。対応するユーザ名、パスワード、DSNを設定してください。
`CONFIG_PROFILE`はOCI Generative AIをCallする際に使用するのでChicago (or Frankfurt) RegionのProfile名を指定してください。

以上が事前準備です。

# 1. Tableの作成
`01.create_table.py`を実行し、Base DatabaseにTableを作成します。
`SOURCE_TABLE`, `DOCUMENT_TABLE`, `CHUNK_TABLE`の３つのTableが作成されます。このコードでは、`SOURCE_TABLE`にpdfの名前、`DOCUMENT_TABLE`にその内容が保存されています。そのTableのデータからChunkを作成し、`CHUNK_TABLE`に保尊します。これは既存のDatabaseを想定した場合に、pdfの名前とその中身からChunkとベクトルを作成し、IDで連携するケースが多いためこのようなTable構造にしています。(今回のコードでLangChainのIntegrationのOracleVSを使用していない理由は、このTable構造が作成できないためです。)

`SOURCE_TABLE`

このTableにはpdfの名前が保存されます。

| Column名 | Data Type | Size |
| ---- | -----    | ----     |
| ID   | NUMBER | 9 |
| NAME | VARCHAR2 | 100 BYTE |

`DOCUMENT_TABLE`

このTableにはpdfのTextが保存されます。

| Column名 | Data Type | Size |
| ----| -----| ---- |
| ID | NUMBER | 9 |
| DOCUMENT | CLOB |  |

`CHUNK_TABLE`

このTableにはTextがChunkに分割された値とそのChunk ID、Chunkのベクトルが保存されます。Chunk IDとは１つのドキュメントの中の何番目のChunkかを特定するために使用するIDです。

| Column名 | Data Type | Size |
| ----| -----| ---- |
| ID | NUMBER | 9 |
| CHUNK_ID | NUMBER |  |
| CHUNK | VARCHAR2 | 2000 BYTE |
| VECTOR | VECTOR |  |

コードを実行すると以下のような内容が出力されます。
```bash
$ python 01.create_table.py 
Start Creating Table.
End Creating Table.
```
無事にTableを作成できました。

# 2. pdfデータのInsert
Tableが作成できたので、次にデータを用意します。
今回サンプルとして扱うデータはOCIの技術に関するpdfです。
`Autonomous Database`に関する資料、`OCI WAF`に関する資料、OCIの概要資料の3つのpdfを事前に用意しています。データは`src/data/`の下に保存されており、そこにpdfを追加すると追加したpdfもデータとしてベクトル化してくれます。
```bash
$ tree ./data/
./data/
├── autonomous-database-self-securing-wp-ja.pdf # Autonomous Databaseに関する資料
├── oracle-cloud-infrastructure-waf-data-sheet-ja.pdf # OCI WAFに関する資料
└── oracle-gov-cloud-oci-ja.pdf # OCIの概要資料

0 directories, 3 files
```

ここからpdfデータをベクトルデータにしていきます。
具体的な処理のステップを以下の画像に記載しています。
![splits.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/609af984-9587-74bf-393e-667c85414037.png)

まずpdfのドキュメントデータからTextデータに変換し、Databaseに保存します。
実行するコードは`02.insert_data.py`です。

`doc_loader`という関数内で、`langchain_community.document_loaders`ライブラリの`UnstructuredFileLoader`関数を使いpdfデータをドキュメントデータに変換しています。
```python
def doc_loader(pdf_path: str) -> np.ndarray:
    loader = UnstructuredFileLoader(PDF_DIRECTORY + pdf_path)
    doc = loader.load()
    return doc
```

以下のコードで指定のディレクトリ内（`./src/data/`)のpdfを取得し、ドキュメントデータへ変換しTableにInsertしています。
```python
pdf_files = [f for f in os.listdir(PDF_DIRECTORY) if f.lower().endswith('.pdf')]
for idx, pdf_file in enumerate(pdf_files):
  pdf_data = doc_loader(pdf_path=pdf_file)
  
  insert_table1_sql = f"""
      INSERT INTO {td.table1_name} ({td.table1_index["index_id"]}, {td.table1_index["index_name"]})
      VALUES (:sample_id, :sample_name)
  """

  cursor.execute(insert_table1_sql, sample_id=idx+1, sample_name=pdf_data[0].metadata['source'].replace(PDF_DIRECTORY, ""))
  print(f"Insert data {idx+1} to table1")

  insert_table2_sql = f"""
      INSERT INTO {td.table2_name} ({td.table2_index["index_id"]}, {td.table2_index["index_docs"]})
      VALUES (:sample_id, :sample_doc)
  """

  cursor.execute(insert_table2_sql, sample_id=idx+1, sample_doc=pdf_data[0].page_content)
  print(f"Insert data {idx+1} to table2")
```

`02.insert_data.py`を実行すると、以下の内容が出力されます。
```bash
$ python 02.insert_data.py 
Start Insert data.
Insert data 1 to table1
Insert data 1 to table2
Insert data 2 to table1
Insert data 2 to table2
Insert data 3 to table1
Insert data 3 to table2
End Insert data
```

出力結果を見ると、3つのpdfがTableに保存されていることがわかります。


# 3. Textデータのベクトル化
次に、TextデータをChunkに分割します。コードは`03.embed_save_texts.py`です。
Chunkへ分割する際に、Chunkの文字数を指定する`Chunk Size`やChunkが前後の文とどれだけ重複するかを指定する`Chunk Overlap`の値を決めることができます。これらの値はベクトル検索の精度に影響してきます。
今回は、`Chunk Size`が400文字、`Chunk Overlap`が40文字で指定しています。

```python
CHUNK_SIZE = 400
CHUNK_OVERLAP = 40
```
Chunk分割のコードを説明します。
まず`fetch_data_from_db`関数でドキュメントデータをDatabaseから取得します。

```python
def fetch_data_from_db(connection, table_name, table_index) -> np.ndarray:
    """ Fetch data from Database """
    engine = create_engine("oracle+oracledb://", creator=lambda: connection)
    select_sql = f"""
        SELECT {table_index["index_id"]}, {table_index["index_docs"]}
        FROM {table_name}
        ORDER BY {table_index["index_id"]} DESC
    """
    fetch_data = pd.read_sql(select_sql, engine)
    return fetch_data
```

次に、`split_text_to_chunks`関数でドキュメントデータをChunkに分割しています。

```python
def split_text_to_chunks(df, table_index, chunk_columns) -> np.ndarray:
    """ Text to chunks """
    all_chunks = []
    for _, row in df.iterrows():
        chunks = text_splitter.split_text(row[table_index["index_docs"].lower()])
        for i, chunk in enumerate(chunks):
            all_chunks.append([row[table_index["index_id"].lower()], i + 1, chunk])
    df_chunks = pd.DataFrame(data=all_chunks, columns=chunk_columns)
    return df_chunks
```

最後に、96データずつのBatchにしています。OCI Generative AIのEmbedのモデルでは、最大96セットのデータをまとめてEmbed可能です。Modelの種類や制限は[こちら](https://docs.oracle.com/en-us/iaas/Content/generative-ai/pretrained-models.htm)のドキュメントをご確認ください。

```python
def fetch_batches() -> np.ndarray:
    """ Data fetch and transform to chunks """
    with pool.acquire() as connection:
        df_select = fetch_data_from_db(connection, td.table2_name, td.table2_index)
        
        if df_select.empty:
            print(f"{td.table2_name} doesn't contain any data.")
            exit(1)

        chunk_columns = [td.table3_index["index_id"], td.table3_index["index_chunkid"], td.table3_index["index_chunk"]]
        
        try:
            df_chunks = split_text_to_chunks(df_select, td.table2_index, chunk_columns)
        except Exception as e:
            print("Error chunk loop:", e)
            exit(1)

        df_chunks.reset_index(inplace=True, drop=True)
        batches = np.array_split(df_chunks, len(df_chunks) // BATCH_SIZE + 1)
        print(f"Fetched all data: {len(df_chunks)}")
        return batches
```

次に、96データずつBatchにしたTextデータをベクトル化します。
```python
def embed_text(texts) -> np.ndarray:
    """ Text to Vector """
    text_vector = utils.embed_documents(texts)
    return text_vector
```

実際にベクトル化を行っているのは`utils.py`ファイルの`embed_documents`関数です。OCI Generative AIを使いベクトル化を行っています。
```python
config = oci.config.from_file(file_location=OCI_CONFIG_FILE, profile_name=CONFIG_PROFILE)
generative_ai_inference_client = oci.generative_ai_inference.GenerativeAiInferenceClient(config)

def embed_documents(texts: List[str]) -> List[List[float]]:
    embed_text_response = generative_ai_inference_client.embed_text(
        embed_text_details=oci.generative_ai_inference.models.EmbedTextDetails(
            inputs=texts,
            serving_mode=oci.generative_ai_inference.models.OnDemandServingMode(
                model_id="cohere.embed-multilingual-v3.0"),
            compartment_id=OCI_COMPARTMENT_ID,
            input_type="SEARCH_DOCUMENT"))

    embeddings = embed_text_response.data.embeddings
    return embeddings
```

最後に`bulk_insert`で`CHUNK_TABLE`にChunk ID, Chunk, Vectorの値をInsertしています。

`03.embed_save_texts.py`のコードを実行すると、以下のような出力が得られます。
```bash
$ python 03.embed_save_texts.py 
...
0.0202178955078125, 0.052215576171875, 0.03814697265625, -0.035491943359375]))]
Total Run Time  1.683833360671997
success delete csv directory
```

出力内容を見ると、Chunkに分割されてベクトル化された値が出力されてます。
```bash
    ID  CHUNK_ID                                              CHUNK                                       CHUNK_VECTOR
0    3         1  Oracle Cloud Infrastructure Web Application Fi...  [0.045928955, 0.07409668, -0.026168823, 0.0170...
1    3         2  はもちろん、侵害の緩和、回避、侵害後の修正、クリーンアップのためのコスト\n\n拡大に追従し...  [0.053100586, 0.044433594, -0.056152344, 0.041...
2    3         3  cloud.oracle.com/iaas\n\n@oracleiaas\n\n1ペー\n\...  [0.029891968, 0.048217773, -0.031433105, 0.020...
```

# 4. ベクトル検索
Chunkに分割したpdfデータに対してベクトル検索を行います。
コードは`04.execute_vector_search.py`です。
入力時に検索する文字列を入力します。その文字列をベクトル化して、そのベクトル値に近いChunkをベクトル検索により取得します。
Oracle Database 23ai内の`VECTOR_DISTANCE`関数を使って、文字列のベクトルとChunkのベクトル間の距離を計算しています。

具体的なコードを以下に示しています。

```python
def query_text(text: str):
    embed_text = array.array('f', utils.embed_documents([text])[0])

    print(f"Start Vector Search")
    with oracledb.connect(user=UN, password=PW, dsn=DSN) as connection:
        with connection.cursor() as cursor:
            cursor.setinputsizes(oracledb.DB_TYPE_VECTOR)
            select_sql = f"""
                SELECT
                    ct.{td.table3_index["index_id"]},
                    pt.{td.table1_index["index_name"]},
                    ct.{td.table3_index["index_chunk"]},
                    VECTOR_DISTANCE({td.table3_index["index_vector"]}, :1, COSINE) as distance
                FROM
                    {td.table3_name} ct
                JOIN
                    {td.table1_name} pt ON ct.{td.table3_index["index_id"]} = pt.{td.table1_index["index_id"]}
                ORDER BY distance
                FETCH FIRST 3 ROWS ONLY
            """
            cursor.execute(select_sql, [embed_text])

            print(f"============検索結果============")
            index = 1
            for row in cursor:
                print(f"{index}: {row}")
                index += 1
            print(f"================================")
        connection.commit()
    print(f"End Vector Search")
```


今回はAutonomous Databaseについて質問してみます。
`04.execute_vector_search.py`コードと質問を入力して実行すると以下内容が出力されます。ベクトル検索が行われAutonomous Databaseの可用性に関する情報が取得できています。
```bash
$ python 04.execute_vector_search.py 'Autonomous Databaseの高可用性について教えて'
Search Query：Autonomous Databaseの高可用性について教えて
Start Vector Search
============Result============
1: (2, 'autonomous-database-self-securing-wp-ja.pdf', '自己修復：Autonomous Databaseにより、あらゆる計画外および計画停止時間に対する予防的な保護が提供され、停止時間なしに障害から迅速に自動リカバリできます。\n\nAutonomous Databaseの可用性とパフォーマンスの管理は、AIベースの自律性を使用して次のレベルへと進みます。これによって複数の領域の診断が統合され、実行時に分析して措\n\n置を講じることができるようになり、操作の中断を最小限に抑えるか排除できます。\n\n図1：Oracle CloudのAutonomous Databaseコンポーネント\n\n自己修復の必要性について\n\nあらゆる規模の組織が、停止時間やデータ損失、データ・アクセスに影響を与えるパフォーマンスのボトルネックに関連するリスクについて詳しく知るようになってきています。90 %以上の企業が、', 0.32765958104735404)
2: (2, 'autonomous-database-self-securing-wp-ja.pdf', '9\n\n技術概要 | Oracle Autonomous Database\n\nCopyright © 2020, Oracle and/or its affiliates | 公開\n\nこの実績あるアーキテクチャは、世界中の何千ものインストールをサポートしており、これにはFortune 100企業が運用する世界的にも最もミッション・クリティカルなデータベースも 含まれます。\n\n他の自律機能と連携する自己修復', 0.3282624711042831)
3: (2, 'autonomous-database-self-securing-wp-ja.pdf', 'さまざまな問題を防止して修復します。Oracle Autonomous Databaseが提供するこの自己修復機能のコレクションは、業界の他のどのクラウド（またはオンプレミス）データベースの 追随も許しません。', 0.33745563121903666)
================================
End Vector Search
```

# 5. Fast APIを使ったAPI作成
入力された質問に対して、pdfをもとに回答を生成するチャットアプリを作ります。
今回は、バックエンドはFast API、フロントエンドはStreamlitで作成します。
質問してから回答を出力するまでのチャットのフローを以下に記載しています。

![chatflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/20e6996d-37aa-d03f-e235-4a3b918a3942.png)

FAST APIを使い上記のベクトル検索の処理とOCI Generative AIのCommand R+を使ったチャットの処理を提供するAPI作成します。コードは`05.vector_search_api.py`です。

まずベクトル検索のAPIを提供するPythonコードは以下の通りです。`/vector_search/`をエンドポイントとして、入力に対するベクトル検索の結果を返します。

```python
app = FastAPI()

@app.get("/vector_search/")
async def vector_search(
    input_text: str = 'Hello'
):
    embed_text = array.array('f', utils.embed_documents([input_text])[0])
    with oracledb.connect(user=UN, password=PW, dsn=DSN) as connection:
        with connection.cursor() as cursor:
            cursor.setinputsizes(oracledb.DB_TYPE_VECTOR)
            select_sql = f"""
                SELECT
                    ct.{td.table3_index["index_id"]},
                    pt.{td.table1_index["index_name"]},
                    ct.{td.table3_index["index_chunk"]},
                    VECTOR_DISTANCE({td.table3_index["index_vector"]}, :1, COSINE) as distance
                FROM
                    {td.table3_name} ct
                JOIN
                    {td.table1_name} pt ON ct.{td.table3_index["index_id"]} = pt.{td.table1_index["index_id"]}
                ORDER BY distance
                FETCH FIRST 1 ROWS ONLY
            """
            cursor.execute(select_sql, [embed_text])
            results = cursor.fetchall()
    return {"result": results}
```

次にCommand Rを使ったチャットのAPIを作成します。
`/chat_doc/`をエンドポイントとして、入力した質問に対する回答を返します。
`documents`のパラメータにはpdfのTextを格納しています。Cohereのモデルでは、この`documents`モードを用いることで、pdfに関連する質問に対して、与えたpdfの内容を根拠として回答を作成してくれます。
`documents`に関しての詳細はこちらをご確認ください。

https://docs.cohere.com/docs/documents-and-citations

```python
@app.get('/chat_doc/')
async def chat_command_r_documents(
    input_text: str = 'Hello', 
    pdf_files: str = None
    ) -> str:
    documents = create_documents(pdf_files=[pdf_files])
    chat_detail = ChatDetails(
        chat_request=CohereChatRequest(
            documents=documents,
            message=input_text,
            max_tokens=500
            ),
        compartment_id=OCI_COMPARTMENT_ID,
        serving_mode=OnDemandServingMode(
            model_id="cohere.command-r-16k"
        ))
    chat_response = generative_ai_inference_client.chat(chat_detail)
    return chat_response.data.chat_response.text
```

`05.vector_search_api.py`のコードを実行してAPIを提供させます。
```python
$ python 05.vector_search_api.py 
INFO:     Started server process [81175]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
```

# 6. Streamlitを使ったフロントエンドの作成
Streamlitを使って、簡易的なチャットアプリのフロントエンドを作成します。
StreamlitのドキュメントにLLM chat アプリの作成方法が記載されているので、こちらを参考にしています。

https://docs.streamlit.io/develop/tutorials/llms/build-conversational-apps

以下のコードがStreamlitでフロント部分を作成している箇所です。
ユーザーから質問が入力された際に、`chat_doc`のAPIをCallし回答を作成しています。
質問と回答を`session_state.messages`に一時的に保存し、その内容を表示しています。

```python
st.title("OCI Chat Bot")

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("What is up?"):
    st.session_state.messages.append({"role": "user", "content": prompt})

    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        response = f"Response: {chat_doc_response(prompt)}"
        st.markdown(response)
    st.session_state.messages.append({"role": "assistant", "content": response})
```

別のbashを開き、`06.front_app_streamlit.py`のコードを実行すると以下のような出力とアプリを作成することができます。

```bash
$ streamlit run 06.front_app_streamlit.py

Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.


  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
```

`http://localhost:8501`に接続しすると、以下のようなアプリの画面が作成されます。
”What is up”と書かれている枠が入力のテキストボックスです。左側のパラメータをもとに質問の回答が生成されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/944778c7-a9e9-f369-cbe8-ce24b47bd039.png" width="100%">

実際に質問を投げてやり取りを行ってみました。
まずは”こんにちは”とpdfとは関係のない内容を質問しました。問題なく回答が作成されいています。
次に、pdfをデータソースとしたRAGの処理を確認します。”Autonomous Databaseについて教えて”と質問しました。pdfに書かれている内容をもとに作成された回答が返ってきました。裏側でAutonomous Databaseの資料がベクトル検索されて取得されていることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/894fa427-7928-dfae-180f-8828a0739278.png" width="100%">

次に、Command R plusのパラメータを変更して回答の生成を行ってみます。
以下の画像のように`Max_tokens`のパラメータを1000に変更して、質問を投げてみます。

![max_tokens.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/8ffb5eb9-be8b-b582-87ee-907cf392daf7.png)

以下のような回答が得られます。1000Tokensとまではいきませんが、さきほどより文字数の多い回答が作成されています。質問やユースケースに応じてパラメータを変更して回答を生成させることが可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633103/76656aab-8c53-664b-451b-f1ad15077182.png" width="100%">

# 7. Tableの削除
`05.drop_table.py`を実行し作成したTableを削除します。
実行すると以下の内容が出力されます。

```bash
$ python 05.drop_table.py 
Start Drop Table
Drop Table CHUNK_TABLE
Drop Table DOCUMENT_TABLE
Drop Table SOURCE_TABLE
End Drop Table
```

作成したテーブルが削除できました。
またStreamlit, FastAPIはCtrl+cで停止させることができます。

# おわりに
今回はOCIから日本語対応した生成AIであるCohere社のCommand R+が出たので、OCI上でRAGを使った日本語対応のチャットアプリケーションを作成しました。

本コードでは、LangChainのOracle DatabaseのIntegrationであるOracleVSを使っていません。SQLでTableを作成し、ベクトル検索をしています。理由は、このコードを書いた時点では、OCI Generative AIのCommand R+のIntegrationが出ていなかったことと、Tableの形を自由に決めたかったからです。LangChainのIntegrationを利用するとTable構造は変わりますが、もう少し簡単に作成できます。

また、本コードではPythonを使ってOracle Databaseの操作やベクトル検索をしますが、Oracle Database 23aiの中で行う方法もあります。こちらの記事をご参考ください。

https://qiita.com/kenwatan/items/8435a92fab53a91a7df5

Oracle Database 23aiを情報ソースとしたRAGを考えている方は是非試してみてください。

# 参考

https://qiita.com/shukawam/items/067850711cd0d008bbbd

https://qiita.com/kenwatan/items/8435a92fab53a91a7df5