RAGツールの質問で画像も追加してみた

背景や目的


私たちは、社内ナレッジの質問に答えるRAG型チャットボットを開発・運用しています。
次回のバージョンアップでは、ChatGPTのように質問文に加えて画像も入力し、その内容を理解して回答を生成する機能を検討中です。

今回はその第一歩として、画像認識処理をRAGに組み込む実験をColab上で実施しました。
目的は、画像から得た情報をどのようにRAG処理に活かせるかを確認することです。

本記事では、その内容を紹介します。

実験の方針


今回は、以下の方針で実験します。

1.画像データはDBへ直接登録しない。
 既存のRAG用のベクトルデータベースはそのまま使用する。
 ※本番ツールはAWS上で稼働しています(構成は過去記事で紹介)が、
  今回は事前評価用としてcolab上で実験します。

2.画像はLLMを使って要約テキストに変換し、検索クエリとして利用する。
 画像の内容をテキスト化して、既存のベクトルデータベースを対象にして検索を行う。

3.その検索結果を使いLLMで推論処理する
 ここは既存RAG処理のままとなります。

試す質問


以下の質問をもとに、画像あり/なし(既存)の2パターンでRAG処理を比較します。

質問:「AzureVPNへ接続しようとすると、エラーが出ました。どうすれば良いですか?」

画像:

実験環境


実験は、Google Colab上で実施します。
以下が使用環境の概要です。
1. 実行環境:Google Colab (Colab Pro+を利用)
2. ベクトルDB:Pinecone (既存indexを利用)
3. データ:社内ナレッジのテキスト情報(Slack応答内容や他ルール)
4. 回答生成モデル:OpenAI GPT4.1
5. 画像分析用のモデル:OpenAI GPT4.1
6. Embeddingモデル:GLuCoSE-base-ja-v2(既存RAGでも利用)

実験したい処理フローは、
「画像付き質問 → 画像要約 → 質問に結合 → 既存DBへ検索 → 回答生成」です。

実験準備と実行の流れ


以下のように進めます。
1.APIキー情報を取得する
  PineconeとOpenAIキーの準備をします。
2.既存RAG(画像なし)コードを準備する
  私たちが運用中のRAG型チャットボット(以降”既存RAG”と呼びます)から処理コードをcolab内セルにコピー&整形。
3.今回追加で試す”画像を分析するコード”を追記
  colab上でベースのコードに画像処理部を追記します。(以降”新RAG”と呼びます)
4.colab上で前述の”試す質問”をやってみます。

1.APIキー情報を取得する


ベクトルストア:
利用できるベクトルストアのindexやDBがあればそのままでOKです。私はPineconeのindexを使うので、それのAPIキーと「Host URL」を準備します。
未作成の場合は、以前の記事内にPineconeのindex作成手順を記載しているので、必要であれば参照して構築してください。

OpenAI APIキー:
GPTモデルを利用するために「OpenAI APIキー」の情報を取得します。
すでに取得済み、または別のモデルを使う場合はそれのAPIキーの準備をお願いします。

2.ベースのコード(既存RAGで利用中)


既存RAGの検索&回答生成処理部のコードです。

Google Colab1
# 既存RAG(画像無し)

#ライブラリインストール
!pip -q install transformers torch pinecone openai

import torch, textwrap
from sentence_transformers import SentenceTransformer
from openai import OpenAI
from pinecone import Pinecone

# --- APIキー ---
OPENAI_API_KEY   = "YOUR_OPENAI_API_KEY" #上記で取得したOpenAIのキー情報
PINECONE_API_KEY = "YOUR_PINECONE_API_KEY" #上記で取得したPineconeのキー情報
INDEX_HOST       = "YOUR_PINECONE_INDEX_HOST_URL" #https://***.pinecone.io

# --- クライアント初期化 ---
pc = Pinecone(api_key=PINECONE_API_KEY)
index = pc.Index(host=INDEX_HOST)
openai_client = OpenAI(api_key=OPENAI_API_KEY)

# --- 日本語向けEmbeddingモデルロード ---
device = "cuda" if torch.cuda.is_available() else "cpu"
embed_model = SentenceTransformer("pkshatech/GLuCoSE-base-ja-v2", device=device)

def create_embeddings(sentences):
    embs = embed_model.encode(sentences, convert_to_tensor=True, device=device)
    return embs.detach().cpu().numpy().tolist()

def exec_rag(question: str, similarity: float = 0.65):
    # 1) クエリ埋め込み
    q_embeddings = create_embeddings([f"query: {question}"])

    # 2) Pinecone検索(Files/Slackを分けて取得)
    search_f = index.query(
        vector=q_embeddings, top_k=5,
        filter={"is_f": True},
        include_values=False, include_metadata=True
    )
    search_s = index.query(
        vector=q_embeddings, top_k=5,
        filter={"$or":[{"is_f":{"$ne":True}},{"is_f":{"$exists":False}}]},
        include_values=False, include_metadata=True
    )

    # 3) 類似度で絞り込み
    filtered_f = [[m["id"], m["score"], m["metadata"]["doc"]]
                  for m in search_f["matches"] if m["score"] >= similarity]
    filtered_s = [[m["id"], m["score"], m["metadata"]["doc"]]
                  for m in search_s["matches"] if m["score"] >= similarity]
    filtered_s.sort(key=lambda x: int(x[0][1:]) if x[0][1:].isdigit() else 0, reverse=True)

    # 4) LLM用コンテキスト生成
    context_candidates = filtered_f + filtered_s
    llm_context, view_use_id = "", "[refID:"
    for cid, _, doc in context_candidates:
        llm_context += doc + "\n-----------------\n"
        view_use_id += cid + ","
    view_use_id += " ]"

    # 5) プロンプト & 回答生成
    system_prompt = textwrap.dedent("""\
        あなたは質問に回答するテクニカルセンターのチャットbotです。常に日本語で回答してください。
        - お調べした結果に答えがなければ「情報がありません。CTCまでお問い合わせください。」と答えてください。
        - 「検索結果」という表現は使わず「お調べした結果」を使ってください。
        - リンクには別ウィンドウで開くHTMLタグを付けてください。
    """)
    user_prompt = f"## 質問 ##\n{question}\n\n## お調べした結果 ##\n{llm_context}"

    resp = openai_client.chat.completions.create(
        model="gpt-4.1",
        messages=[
            {"role":"system","content":system_prompt},
            {"role":"user","content":user_prompt}
        ]
    )
    answer = resp.choices[0].message.content + view_use_id
    return answer, system_prompt

##既存RAG(画像無し)実験
ans, sys_prompt = exec_rag("AzureVPNへ接続しようとすると、エラーが出ました。どうすれば良いですか?")
print(ans)

実行は試験の時にします。次は画像処理を追記したコードです。

3.画像処理を追記したコード


画像処理を追記したコードです。
参照している画像ファイルは、以下のように左側のFilesの所へドラッグ&ドロップでColabへアップしておきます。

処理コードの前に、必要ライブラリをインストールするセルも用意しておきます。

Google Colab2
!pip -q install transformers torch pinecone openai

既存の RAGに追加・修正する処理は、主に次の2点です。
1) GPTへ画像の要約文生成依頼。
2) 質問文と画像の要約文を合体して検索・回答生成を行う処理に修正。

画像の要約文を質問文と合体して検索クエリとして利用できる、new_exec_rag()を作成します。
今回の実験のコードは以下の通りです。

Google Colab3
def new_exec_rag(question: str, image_path: str, similarity: float = 0.65):
    # 1) 画像→Data URL→要約テキスト
    data_url = file_to_data_url(image_path)
    image_summary = summarize_image_from_url(data_url)

    # 2) 画像要約をクエリに合体(検索に効かせる)
    merged = f"{question}\n【画像要約】{image_summary}"
    q_embeddings = create_embeddings([f"query: {merged}"])

    # 3) Pinecone検索(既存から変更なし)
    search_f = index.query(
        vector=q_embeddings, top_k=5,
        filter={"is_f": True},
        include_values=False, include_metadata=True
    )
    search_s = index.query(
        vector=q_embeddings, top_k=5,
        filter={"$or":[{"is_f":{"$ne":True}},{"is_f":{"$exists":False}}]},
        include_values=False, include_metadata=True
    )

    filtered_f = [[m["id"], m["score"], m["metadata"]["doc"]]
                  for m in search_f["matches"] if m["score"] >= similarity]
    filtered_s = [[m["id"], m["score"], m["metadata"]["doc"]]
                  for m in search_s["matches"] if m["score"] >= similarity]
    filtered_s.sort(key=lambda x: int(x[0][1:]) if x[0][1:].isdigit() else 0, reverse=True)

    context_candidates = filtered_f + filtered_s
    llm_context, view_use_id = "", "[refID:"
    for cid, _, doc in context_candidates:
        llm_context += doc + "\n-----------------\n"
        view_use_id += cid + ","
    view_use_id += " ]"

    # 4) プロンプト & 回答生成(質問+画像要約を提示)
    system_prompt = textwrap.dedent("""\
        あなたは質問に回答するテクニカルセンターのチャットbotです。常に日本語で回答してください。
        - お調べした結果に答えがなければ「情報がありません。CTCまでお問い合わせください。」と答えてください。
        - 「検索結果」という表現は使わず「お調べした結果」を使ってください。
        - リンクには別ウィンドウで開くHTMLタグを付けてください。
    """)
    user_prompt = f"## 質問+画像要約 ##\n{merged}\n\n## お調べした結果 ##\n{llm_context}"

    resp = openai_client.chat.completions.create(
        model="gpt-4.1",
        messages=[
            {"role":"system","content":system_prompt},
            {"role":"user","content":user_prompt}
        ]
    )
    answer = resp.choices[0].message.content + view_use_id
    return answer, system_prompt, image_summary
    
##既存RAG(画像あり)実験
ans2, sys2, img_sum = new_exec_rag(
    "AzureVPNへ接続しようとすると、エラーが出ました。どうすれば良いですか?",
    "/content/azurevpn_policy_error.png"
)
print("画像要約:", img_sum)
print(ans2)

画像の要約は4行目で実行しています。その結果を使った検索&推論は7~52行目で実行しています。

次は試験をしてみます。

4.試してみる


コード準備が上記で完了したので、早速ですが、実験してみましょう!
「AzureVPNへ接続しようとすると、エラーが出ました。どうすれば良いですか?」を聞いてみます。

既存RAG(画像無し):
上記2.内で準備した「Google Colab1」コードセルを「Shift+Enter」で実行してみます。

期待していたエラー番号(エラー13868)の情報はヒットせず、結果はNGでした。
エラー番号が質問で伝えていなかったため、番号依存の対策情報は取れず、「Azure VPN」「エラー」といったキーワードの検索をし、一般論の結果を出してしまったね。

次は画像付きで試します。

新RAG(画像あり):
画像内の特定エラー情報を抽出し、S317(正解の手順が含まれた情報)がヒットされて具体的な回答を期待しています。
上記3.内で準備した「Google Colab2と3」のコードセルを実行します。

画像内エラーの具体的な回答になった!
画像により不足していたエラー番号が補われ意中のS317を使った良い回答文が得られました。
やりたいと思っていた「画像認識を活かして、よりピンポイントな回答を返す」という狙いは、今回の実験でひとまず達成できました。

結果をサマリーすると、

RAG回答文結果 評価
既存
[画像無し]      
### 1. エラーメッセージごとの対処方法 ####
【エラー 798】「この拡張認証プロトコルで使用できる証明書が見つかりませんでした」 – クライアント証明書
【「パラメーターが間違っています」やエラー87など】
【エラー 2221:「ユーザー名が見つかりません」
~割愛~
### 3. それでも解消しない場合 – 手順・ファイル・証明書などに不明点があるときは、CTCまでご連絡ください。 お困りの内容に該当する部分があれば、上記手順でご対応ください。 ご不明点が解消しない場合は改めてエラー詳細をご連絡いただくか、CTCまでお問い合わせください。[refID:F84,F46,F100,F26,F45,S856,S507,S494,S325,S204, ]
質問文の不備で一般論を回答した。    

[画像あり]
お調べした結果、Azure VPNへ接続時に「ポリシーの一致エラー(エラー 13868)」が発生する主な原因は、**社内ネットワークに接続した状態でVPN接続を行うため**です。 このエラーは、Azure VPNの仕様上、社内ネットワークに接続したままVPN接続を試みると発生します。正常に接続できるかどうか確認するためには、以下の手順をお試しください。 **対処方法:** 1. テザリングやモバイルWi-Fiなどを使用し、PCが社内ネットワークに接続していない状態にする。 2. その状態で、もう一度Azure VPNへの接続をお試しください。 多くの事例で、**社外ネットワーク**(テザリングなど)から接続した場合はエラーが解消されています。 なお、PCや証明書の環境に問題がある場合は、下記の関連手順書もご参照ください。 [AzureVPN接続セットアップ手順書(v104)](G:\共有ドライブ\運用ガイド\AzureVPN接続セットアップ\AzureVPN接続セットアップ手順書v104.pdf) もし上記手順で解決しない場合や、他にも不明点がある場合は、**CTCまでお問い合わせください。**[refID:F84,F46,F100,F74,F63,S1194,S1188,S856,S507,S317, ]画像情報によりS317が引けて、具体的な回答になった。

まとめ


今回、ChatGPTと同じように画像内情報を認識するRAG処理実験を行い、成功しました。

本番ツールへ適用するには、フロント側での画像添付処理などが必要ですが、コアとなる処理は良さそうですと感じました。

画像要素を加えることで、質問文の質が上がり、検索結果と回答精度が向上することを確認できました。今後は、この新RAGの仕組みを本番ツールにも組み込みたいと考えています。

また、AIモデルで画像認識が確認できたため、今後は画像利用の機能の幅を広げ、物体検出が可能かどうかも試してみたいと考えています。

AI ವ್ಯವಸ್ಥೆಯನ್ನು ನವೀಕರಿಸುತ್ತಲೇ ಇರೋಣ
(AIシステムをどんどん更新しましょう)

ご連絡フォーム


フィードバックを是非お願いします。
本記事の方法での問題点や、よりよい方法のアイデアを頂けると大変助かります。

この記事に関して

その他のご連絡

DevAIsをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む