Amazon BedrockでCommand R/R+とClaude3.5 Sonnetでの検索クエリ生成を比較する

# はじめに
以前の記事では､Command R/R+のsearch_queries_only:trueを利用し､入力テキストを検索クエリに適した表現へと書き換えて検索を行いました｡例えば､｢**SQLインジェクションはどのようにして発生するのか｡その原因と対策を解説してください｡Web開発者がSQLインジェクションを防ぐために取るべき対策は何ですか｡**｣という質問に対して｢**SQLインジェクション 原因 対策**｣という検索クエリが生成されました｡同様の方式(Rewrite-Retrieve-Read)をClaude3.5 Sonnetを利用して実装した場合､実装方法や出力にどのような違いがるか比較してみました｡

https://qiita.com/revsystem/items/64aecab061ae278003c6

https://qiita.com/revsystem/items/571504731d38278408ba

# 参考情報

https://aws.amazon.com/jp/blogs/news/verifying-the-accuracy-contribution-of-advanced-rag-methods-on-rag-systems-built-with-amazon-kendra-and-amazon-bedrock/

https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html

https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html

# 出力結果

比較用コードを実行しCommand RとCloude 3.5 Sonnetのそれぞれで検索クエリを出力してみました｡ 
Claude3.5 Sonnetを利用した処理では､生成する検索クエリの個数やフォーマットを後述のプロンプトで詳細に指定しました｡一方､Command Rではそのような指定が行えなえないようなのでなにも指定していません｡preambleパラメータでプロンプトを指定できるかと考え試しましたが､出力内容に変化がありませんでした｡search_queries_only:Trueの場合､このパラメータは無視されるのかもしれません｡

```console
2024-06-29 16:48:25,021 [INFO] Found credentials in shared credentials file: ~/.aws/credentials
origin_query: 代表的なLLMを列挙し､それぞれの特徴を箇条書きで列挙してください｡

********************************************************************************
Command Rの検索クエリ:
LLMの種類と特徴

********************************************************************************
Cloude 3.5 Sonnetの検索クエリ:
LLM 代表的 特徴 比較
大規模言語モデル 種類 特徴 list
AI language models comparison features GPT-4 PaLM LLaMA
```

## Command R/R+
Command Rを使った検証では､入力テキストに対し常に｢**LLMの種類と特徴**｣という出力でした｡temperature:1でも出力は変化しませんでした｡ また､以下の記事で取り上げられている入力テキスト「**S3とEFSの暗号化方式を比較してください**」で試したところ､記事とまったく同じ検索クエリが生成されました｡

> 今回のユーザー入力テキスト「S3とEFSの暗号化方式を比較してください」からは、「S3 encryption」「EFS encryption」という2つの検索クエリーが生成されました。

https://dev.classmethod.jp/articles/amazon-bedrock-cohere-command-r-rag/

これらのことから､Command Rの検索クエリ生成は､temperatureパラメータの設定ではなく入力テキストの内容に強く依存しているようです｡

Command R+ (cohere.command-r-plus-v1:0) を使用した場合は､入力テキスト｢**代表的なLLMを列挙し､それぞれの特徴を箇条書きで列挙してください｡**｣に対する出力は｢**代表的なLLM**｣でした｡ Command R/R+のいずれにしても､出力が入力テキストに対して固定されてしまっている印象です｡

### Command R/R+を使った検索クエリの出力結果
> LLMの種類と特徴

> 代表的なLLM

## Claude3.5 Sonnet
Claude3.5 Sonnetでは､入力テキストに対する出力は以下のように実行のたびに変化しました｡この程度の単語のばらつきがあればRAGの精度向上が期待できそうです｡また､検索クエリの長さや言語､フォーマットはプロンプトにより制御できるため､後続の検索処理も柔軟に対処できそうです｡
コード内のプロンプトテンプレートは､[Amazon Kendra と Amazon Bedrock で構成した RAG システムに対する Advanced RAG 手法の精度寄与検証](https://aws.amazon.com/jp/blogs/news/verifying-the-accuracy-contribution-of-advanced-rag-methods-on-rag-systems-built-with-amazon-kendra-and-amazon-bedrock/)にあるものを基にしました｡

### Claude3.5 Sonnetを使った検索クエリの出力結果例
> LLM models comparison
> 代表的な大規模言語モデル 特徴 比較
> ChatGPT GPT-4 PaLM BERT LLaMA 機能 性能 違い

> LLM 代表的モデル 特徴 比較
> 大規模言語モデル 種類 特徴 一覧 comparison
> 人工知能 AI 言語モデル GPT BERT LLaMA 特徴 pros cons

# サンプルコード

```python:command-R_vs_claude3.5-sonnet.py
# Command R+とClaude3.5 Sonnetでの検索クエリ生成の比較

import json
import logging
from typing import Any, Dict, List

import boto3

logging.basicConfig(
    format="%(asctime)s [%(levelname)s] %(message)s",
    level=logging.INFO
)

logger = logging.getLogger(__name__)

bedrock_client = boto3.client("bedrock-runtime")

MODEL_ID = "anthropic.claude-3-5-sonnet-20240620-v1:0"
MODEL_ID_SEARCH_QUERY = "cohere.command-r-v1:0"


def create_search_query_commandr(input_text: str):
    request_body =  {
        "message": input_text,
        "search_queries_only": True, # 検索クエリーのみを生成する
        "max_tokens": 500,
        "temperature": 0,
    }
    response = _invoke_bedrock_model(request_body, MODEL_ID_SEARCH_QUERY)
    response_body = json.loads(response["body"].read())
    search_queries = response_body["search_queries"]

    print("\n" + "*" * 80)
    print("Command Rの検索クエリ:")

    for query in search_queries:
        query_text = query.get("text")
        print(query_text)


def create_search_query_claude(input_text: str):
    # 生成する検索クエリの数
    n_queries = 3
    # 検索クエリの生成するためのプロンプト
    system_instruction = _create_system_instruction(n_queries)
    request_body = {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 500,
        "temperature":0,
        "system": system_instruction,
        "messages": [
            {
                "role": "user",
                "content": input_text,
            },
        ],
    }
    response = _invoke_bedrock_model(request_body, MODEL_ID)
    response_body = json.loads(response["body"].read())
    search_queries = response_body["content"][0]['text'].split(',')

    print("\n" + "*" * 80)
    print("Cloude 3.5 Sonnetの検索クエリ:")

    for query_text in search_queries:
        print(query_text)


def _create_system_instruction(n_queries: int) -> str:
    return f'''
    検索エンジンに入力するクエリを最適化し、様々な角度から検索を行うことで、より適切で幅広い検索結果が得られるようにします。
    具体的には、類義語や日本語と英語の表記揺れを考慮し、多角的な視点からクエリを生成します。

    ユーザーの入力した質問文に基づいて、{n_queries}個の検索用クエリを生成してください。
    <example>タグ内にあるように､各検索用クエリは30トークン以内とし、日本語と英語を適切に混ぜて使用することで、広範囲の文書が取得できるようにしてください。

    生成されたクエリは、<format>タグ内のフォーマットに従って出力してください。
    <format>タグの内容を参考にするが、回答に<format>タグを含めないこと。

    <example>
    question: Knowledge Bases for Amazon Bedrock ではどのベクトルデータベースを使えますか？
    query 1: Knowledge Bases for Amazon Bedrock vector databases engine DB
    query 2: Amazon Bedrock ナレッジベース ベクトルエンジン vector databases DB
    query 3: Amazon Bedrock RAG 検索拡張生成 埋め込みベクトル データベース エンジン
    </example>

    <format>
    for文でクエリを取得できるようカンマで区切ってください｡
    クエリには改行コードを含めないでください｡
    クエリ以外の文章を含めないでください｡
    </format>
    '''


def _invoke_bedrock_model(request_body: Dict[str, Any], model_id: str) -> Dict[str, Any]:
    return bedrock_client.invoke_model(
        body=json.dumps(request_body),
        contentType="application/json",
        accept="application/json",
        modelId=model_id,
    )


def main():
    input_text = "代表的なLLMを列挙し､それぞれの特徴を箇条書きで列挙してください｡"
    print(f"origin_query: {input_text}")

    # Command Rで検索クエリーを生成する
    create_search_query_commandr(input_text)
    # Claude 3.5 Sonnet で検索クエリーを生成する
    create_search_query_claude(input_text)


if __name__ == "__main__":
    main()

```

# まとめ
Amazon BedrockでCommand R/R+とClaude3.5 Sonnetで検索クエリ生成を比較しました｡ Command R/R+はsearch_queries_onlyパラメータのみで検索クエリ生成を行える手軽さがあるものの､出力内容が乏しくプロンプトなどで制御できませんでした｡
一方､Claude3.5 Sonnetを使った場合(もちろん他のモデルでも可)は､プロンプトで検索クエリの出力を制御できるため後続の処理を含め柔軟に対応できそうです｡