記事 Kosaku Ikeda · 10月 26, 2025 14m read

コミュニティの皆さんこんにちは。
 

ベクトル検索関連の処理が完全にノーマークだった私が、一先ず「やってみよう!」との事で、2つの動画のサンプルを実行してみました。
Pythonは初心者なので、アレな箇所があっても目をつぶっていただけると幸いです。

また、間違っている箇所があったら、ご指摘いただけると幸いです。


■参考にした動画

■参考にしたコミュニティ記事

【目的】

本記事では、動画で紹介された内容を実際にIRIS環境上で実行できるよう、具体的な環境構築とコーディングを記載致します。
コミュニティの皆さんが簡単に試せるようになれば幸いです。

またGithubにサンプルソースを配置しているので、必要な方は参考にして下さい。

【準備】

■作業環境

※環境作成方法に問題のない方は、読み飛ばしていただいて構いません。

項目 バージョン情報・他
OS WIndowsServer2019
IRIS IRIS Community 2025.2.0.227.0
Python 3.12.10
開発環境 VS Code 1.105.1
0
0 41
記事 Mihoko Iijima · 10月 9, 2025 1m read

開発者の皆さん、こんにちは!

この記事では、Developer Hub にあるチュートリアルに新しいチュートリアル:InterSystems IRIS ベクトル検索を使用した RAG が追加されましたので内容をご紹介します。(準備不要でブラウザがあれば試せるチュートリアルです!)

このチュートリアルでは、生成 AI アプリケーションの精度向上に向けて、ベクトル検索と検索拡張生成(Retrieval Augmented Generation)の活用を体験できます。

具体的には、InterSystems IRIS のベクトル検索機能を活用し、生成 AI チャットボット向けのナレッジベースをサンプルコードを利用して作成します。

また、Streamlit を使用して作成したチャットボットを動かしながら、ナレッジベースの情報を追加することで生成 AI からの回答が変化していくことを確認していきます。

アカウント作成やログインも不要で  ボタンをクリックするだけで始められます👍

チュートリアルへのリンクは「開発者コミュニティのリソース」からも辿れます!

ぜひ、お試しください!​​​​​​

0
0 33
記事 Toshihiko Minamoto · 10月 1, 2025 6m read

コミュニティの皆さん、こんにちは。
従来のキーワードベースの検索では、ニュアンスのあるドメイン固有のクエリには対応できません。 ベクトル検索であれば、セマンティック認識を利用して、キーワードだけでなくコンテキストにも基づいたレスポンスを AI エージェントで検索して生成することができます。
この記事では、エージェンティック AI RAG(検索拡張生成)アプリケーションを作成手順を紹介します。

実装手順:

  1. エージェントツールを作成する
    • インジェスト機能の追加: ドキュメント(例: InterSystems IRIS 2025.1 リリースノート)を自動的にインジェストしてインデックス作成を行います。
    • ベクトル検索機能の実装
  2. ベクトル検索エージェントを作成する
  3. Triage(メインエージェント)に渡す
  4. エージェントを実行する

1. エージェントツールを作成する
1.1 - 
ドキュメントの取り込みを実装します。ドキュメントの取り込みとインデックス作成を自動化します。

インジェストツールは以下のコードで実装します。

defingestDoc(self):#Check if document is defined, by selecting from table#If not defined then INGEST document, Otherwise back
        embeddings = OpenAIEmbeddings()	
        #Load the document based on the fle type
        loader = TextLoader("/irisdev/app/docs/IRIS2025-1-Release-Notes.txt", encoding='utf-8')      
    documents = loader.load()        
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=<span class="hljs-number">400</span>, chunk_overlap=<span class="hljs-number">0</span>)
    
    texts = text_splitter.split_documents(documents)
                   
    <span class="hljs-comment">#COLLECTION_NAME = "rag_document"</span>
    db = IRISVector.from_documents(
        embedding=embeddings,
        documents=texts,
        collection_name = self.COLLECTION_NAME,
        connection_string=self.CONNECTION_STRING,
    )

    db = IRISVector.from_documents(embedding=embeddings,documents=texts, collection_name = self.COLLECTION_NAME, connection_string=self.CONNECTION_STRING,)</code></pre>

ベクトル検索エージェントは、指定されたリポジトリフォルダから「New in InterSystems IRIS 2025.1」を IRIS Vector Store に自動的に取り込んでインデックスを作成します。この操作はそのデータがまだ存在しない場合にのみ実行されます。


以下のクエリを実行して、ベクトルストアから必要なデータを取得します。

SELECTid, embedding, document, metadata
FROM SQLUser.AgenticAIRAG


1.2 - ベクトル検索機能の実装
以下のコードはエージェントの検索機能を実装します。

defragSearch(self,prompt):#Check if collections are defined or ingested done.# if not then call ingest method
        embeddings = OpenAIEmbeddings()	
        db2 = IRISVector (
            embedding_function=embeddings,    
            collection_name=self.COLLECTION_NAME,
            connection_string=self.CONNECTION_STRING,
        )
        docs_with_score = db2.similarity_search_with_score(prompt)
        relevant_docs = ["".join(str(doc.page_content)) + " "for doc, _ in docs_with_score]
    <span class="hljs-comment">#Generate Template</span>
    template = <span class="hljs-string">f"""
    Prompt: <span class="hljs-subst">{prompt}</span>
    Relevant Docuemnts: <span class="hljs-subst">{relevant_docs}</span>
    """</span>
    <span class="hljs-keyword">return</span> template</code></pre>


Triage エージェントは、受信したユーザークエリを処理し、それを Vector Search Agent に委任します。このエージェントは、セマンティック検索を実行して、最も関連性の高い情報を取得します。


2 - ベクトルストアエージェントを作成する

以下のコードは、以下の要素を含む vector_search_agent を実装します。

  • エージェントを調整するためのカスタム handoff_descriptions
  • 明確な演算 instructions
  • iris_RAG_search ツール(ドキュメントの取り込みとベクトル検索操作に irisRAG.py を使用)
@function_tool    @cl.step(name = "Vector Search Agent (RAG)", type="tool", show_input = False)asyncdefiris_RAG_search():"""Provide IRIS Release Notes details,IRIS 2025.1 Release Notes, IRIS Latest Release Notes, Release Notes"""ifnot ragOprRef.check_VS_Table():
                 #Ingest the document first
                 msg = cl.user_session.get("ragclmsg")
                 msg.content = "Ingesting Vector Data..."await msg.update()
                 ragOprRef.ingestDoc()
        <span class="hljs-keyword">if</span> ragOprRef.check_VS_Table():
             msg = cl.user_session.get(<span class="hljs-string">"ragclmsg"</span>)
             msg.content = <span class="hljs-string">"Searching Vector Data..."</span>
             <span class="hljs-keyword">await</span> msg.update()                 
             <span class="hljs-keyword">return</span> ragOprRef.ragSearch(cl.user_session.get(<span class="hljs-string">"ragmsg"</span>))   
        <span class="hljs-keyword">else</span>:
             <span class="hljs-keyword">return</span> <span class="hljs-string">"Error while getting RAG data"</span>
vector_search_agent = Agent(
        name=<span class="hljs-string">"RAGAgent"</span>,
        handoff_description=<span class="hljs-string">"Specialist agent for Release Notes"</span>,
        instructions=<span class="hljs-string">"You provide assistance with Release Notes. Explain important events and context clearly."</span>,
        tools=[iris_RAG_search]
)</code></pre>


3 - Triage(メインエージェント)に渡す
以下のコードは、処理済みのクエリを Triage エージェント(メインコーディネーター)に渡すためのハンドオフプロトコルを実装します。

 triage_agent = Agent(
        name="Triage agent",
        instructions=(
            "Handoff to appropriate agent based on user query.""if they ask about Release Notes, handoff to the vector_search_agent.""If they ask about production, handoff to the production agent.""If they ask about dashboard, handoff to the dashboard agent.""If they ask about process, handoff to the processes agent.""use the WebSearchAgent tool to find information related to the user's query and do not use this agent is query is about Release Notes.""If they ask about order, handoff to the order_agent."
        ),
        handoffs=[vector_search_agent,production_agent,dashboard_agent,processes_agent,order_agent,web_search_agent]
    )


4 - エージェントを実行する

以下のコードは次の内容を実行します。

  1. ユーザー入力を受け取る
  2. triage_agent  を呼び出す
  3. クエリを Vector_Search_Agent に送信して処理する
@cl.on_message
async def main(message: cl.Message):
    """Process incoming messages and generate responses."""
    # Send a thinking message
    msg = cl.Message(content="Thinking...")
    await msg.send()
agent: Agent = cast(Agent, cl.user_session.get(<span class="hljs-string">"agent"</span>))
config: RunConfig = cast(RunConfig, cl.user_session.get(<span class="hljs-string">"config"</span>))

# Retrieve the chat history from the session.
history = cl.user_session.get(<span class="hljs-string">"chat_history"</span>) or []

# Append the user'<span class="hljs-keyword">s</span> message to the history.
history.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: message.content})

# Used by RAG agent
cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"ragmsg"</span>, message.content)
cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"ragclmsg"</span>, msg)

<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">print</span>(<span class="hljs-string">"\n[CALLING_AGENT_WITH_CONTEXT]\n"</span>, history, <span class="hljs-string">"\n"</span>)
    result = Runner.run_sync(agent, history, run_config=config)
           
    response_content = result.final_output
    
    # Update the thinking message with the actual response
    msg.content = response_content
    await msg.update()

    # Append the assistant'<span class="hljs-keyword">s</span> response to the history.
    history.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"developer"</span>, <span class="hljs-string">"content"</span>: response_content})
    # NOTE: Here we are appending the response to the history <span class="hljs-keyword">as</span> a developer message.
    # This is a BUG in the agents library.
    # The expected behavior is to append the response to the history <span class="hljs-keyword">as</span> an assistant message.

    # Update the session with the <span class="hljs-keyword">new</span> history.
    cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"chat_history"</span>, history)
    
    # Optional: Log the interaction
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"User: {message.content}"</span>)
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"Assistant: {response_content}"</span>)
    
except Exception <span class="hljs-keyword">as</span> e:
    msg.content = f<span class="hljs-string">"Error: {str(e)}"</span>
    await msg.update()
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"Error: {str(e)}"</span>)</code></pre>


実際の動作をご覧ください

詳細については、iris-AgenticAI の Open Excahnge アプリケーションページをご覧ください。
以上です

0
0 17
記事 Toshihiko Minamoto · 9月 30, 2025 6m read

この連載記事を終えていなかったことに気付きました!

GIF de Shame On You Meme | Tenor

今日の記事では、フロントエンドから最適なオプションを選択できるように、テキストに最も類似する ICD-10 診断を抽出するプロダクションプロセスについて説明します。

診断の類似度検索:

アプリケーション内で、HL7 で受け取った診断リクエストを示す画面から、医療従事者が入力したテキストに最も近い ICD-10 診断を検索できます。

検索プロセスを高速化するために、HL7 メッセージを取得する際に受信した診断をベクトル化したテキストをデータベースに保存しました。 これを行うために、メッセージから診断コードを抽出し、ベクトルを生成するメソッドにそれを送信する単純な BPL を実装しました。

受信した診断をベクトル化するコードは以下のようになります。

ClassMethod GetEncoding(sentence As %String) As %String [ Language = python ]
{
        import sentence_transformers
        # create the model and form the embeddings
        model = sentence_transformers.SentenceTransformer('/iris-shared/model/')
        embeddings = model.encode(sentence, normalize_embeddings=True).tolist() # Convert search phrase into a vector# convert the embeddings to a stringreturn str(embeddings)
}

こうすることで、診断をベクトル化し、検索するたびにもう一度ベクトル化する必要がなくなります。 このとおり、ダウンロードしたモデルでベクトルを生成するために  sentence_transformer  ライブラリを使用しています。

受信した診断がすべてベクトル化されてデータベースに保存されているため、SELECT クエリを実行するだけで、受信した診断に最も近い ICD-10 診断を抽出できます。

では、ウェブサービスに公開されている、最も類似する 25 件の診断を返すメソッドのコードを見てみましょう。

ClassMethod GetCodeOptions(idRequest As%String) As%Status
{
	set ret = $$$OKtry {
        set sql = 
            "SELECT TOP 25 * FROM (SELECT C.CodeId, C.Description, VECTOR_DOT_PRODUCT(C.VectorDescription, R.VectorDescription) AS Similarity FROM ENCODER_Object.Codes C, ENCODER_Object.CodeRequests R WHERE R.ID = ?) WHERE Similarity > 0.5 ORDER BY Similarity DESC"set statement = ##class(%SQL.Statement).%New()
		$$$ThrowOnError(statement.%Prepare(sql))
        set rs = statement.%Execute(idRequest)

        set array = []
        while rs.%Next() {
            do array.%Push({
                    "CodeId": (rs.%Get("CodeId")),
                    "Description": (rs.%Get("Description")),
                    "Similarity": (rs.%Get("Similarity"))
                })
        }
        set%response.Status = ..#HTTP200OK
        write array.%ToJSON()

    } catch ex {
        set%response.Status = ..#HTTP400BADREQUEST
        return ex.DisplayString()
    }
    quit ret
}

クエリを見てみると、検索を 25 件に制限し、類似度が 0.5 を超える診断に限定しているのがわかります。 類似度計算には、VECTOR_COSINE を使用することも可能ですが、VECTOR_DOT_PRODUCT  メソッドを選択しました。 この場合、今のところこれらに大きな違いは見つかっていません。

検索結果を以下に示します。

LLM を使って診断を特定する

ここまでは、完璧に特定される診断の単純な検索しか行っていませんが、 自由記述形式のテキストから直接診断を特定することは可能でしょうか?

では試してみましょう!

この機能では、選択した LLM モデルに質問を送信する API を提供する Ollama を使用します。 docker-compose.yml ファイルを見ると、Ollama コンテナーの宣言があります。

## llm locally installed  ollama:    build:      context:.      dockerfile:ollama/Dockerfile    container_name:ollama    volumes:    -./ollama/shared:/ollama-shared    ports:      -"11434:11434"

まず、コンテナーデプロイに LLM llama3.2 がダウンロードされることが定義されています。 その理由は? テストでは最も良いパフォーマンスを見せると思ったからです。

これが、コンテナーがデプロイされたときに実行する entrypoint.sh  ファイルのコンテンツとなります。

#!/bin/bash
echo "Starting Ollama server..."
ollama serve &
SERVE_PID=$!

echo "Waiting for Ollama server to be active..."
while ! ollama list | grep -q 'NAME'; do
  sleep 1
done

ollama pull llama3.2

wait $SERVE_PID

Ollama の機能を活用するために、自由形式のテキストを分析する画面を、LLM を使って入力されたテキストから診断を抽出するように変更しました。

また、llama3.2 が診断を直接抽出するために必要なプロンプトを作成するメソッドを追加して、ビジネスプロセスを変更しました。

Method AnalyzeText(text As %String, analysisId As %String, language As %String) As %String [ Language = python ]
{
    import sentence_transformers
    import iris
    import requests
<span class="hljs-keyword">try</span>:
    url = <span class="hljs-string">"http://ollama:11434/api/generate"</span>
    data = {
        <span class="hljs-string">"model"</span>: <span class="hljs-string">"llama3.2"</span>,
        <span class="hljs-string">"prompt"</span>: <span class="hljs-string">"Extrae únicamente los diagnósticos del siguiente texto separándolos por , y sin añadir interpretaciones: "</span>+text,
        <span class="hljs-string">"stream"</span>: <span class="hljs-keyword">False</span>
    }
    response = requests.post(url, json=data)
    analyzedText = response.json()
    
    model = sentence_transformers.SentenceTransformer(<span class="hljs-string">'/iris-shared/model/'</span>)
    phrases = analyzedText[<span class="hljs-string">'response'</span>].split(<span class="hljs-string">","</span>)
    sqlsentence = <span class="hljs-string">""</span>
    <span class="hljs-comment"># iris.cls("Ens.Util.Log").LogInfo("ENCODER.BP.AnalyzeTextProcess", "AnalyzeText", "Starting process")</span>
    <span class="hljs-keyword">for</span> phraseToAnalyze <span class="hljs-keyword">in</span> phrases :
        <span class="hljs-keyword">if</span> phraseToAnalyze != <span class="hljs-string">""</span>:
            embedding = model.encode(phraseToAnalyze, normalize_embeddings=<span class="hljs-keyword">True</span>).tolist()
            sqlsentence = <span class="hljs-string">"INSERT INTO ENCODER_Object.TextMatches (CodeId, Description, Similarity, AnalysisId, RawText) SELECT TOP 50 * FROM (SELECT CodeId, Description, VECTOR_DOT_PRODUCT(VectorDescription, TO_VECTOR('"</span>+str(embedding)+<span class="hljs-string">"', DECIMAL)) AS Similarity, '"</span>+analysisId+<span class="hljs-string">"', '"</span>+phraseToAnalyze+<span class="hljs-string">"' FROM ENCODER_Object.Codes) ORDER BY Similarity DESC"</span>
            stmt = iris.sql.prepare(<span class="hljs-string">"INSERT INTO ENCODER_Object.TextMatches (CodeId, Description, Similarity, AnalysisId, RawText) SELECT TOP 50 * FROM (SELECT CodeId, Description, VECTOR_DOT_PRODUCT(VectorDescription, TO_VECTOR(?, DECIMAL)) AS Similarity, ?, ? FROM ENCODER_Object.Codes) WHERE Similarity &gt; 0.65 ORDER BY Similarity DESC"</span>)                    
            rs = stmt.execute(str(embedding), analysisId, phraseToAnalyze)        
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
    iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"ENCODER.BP.AnalyzeTextProcess"</span>, <span class="hljs-string">"AnalyzeText"</span>, repr(err))
    <span class="hljs-keyword">return</span> repr(err)

<span class="hljs-keyword">return</span> <span class="hljs-string">"Success"</span>

}

このプロンプトは非常に単純なものであるため、必要に応じて改善し、調整することも可能ですが、その選択はユーザーにお任せします。 このメソッドはカンマ区切りで LLM レスポンスを取得し、見つかった診断をベクトル化してデータベースに保存します。 以下は結果の例です。

右下の方に、左下のテキストに関して LLM が見つけたものがすべて表示されています。

これで、診断をコーディングするのに役立つ、LLM モデルを使ったアプリケーションが完成しました。 ここで見たように、実装はまったく複雑ではなく、より複雑で包括的なソリューションを構築するための十分な基礎となります。

お読みいただきありがとうございました!

0
0 20
記事 Hisa Unoura · 9月 4, 2025 9m read

開発者の皆様こんにちは。先日のWebinar「ベクトルであそぼう!」では、以下の内容でデータをベクトル化することの可能性をご紹介しました。

写真から魚の名前をあててみる

  • マルチモーダルモデル CLIP を利用して画像によるテキストの検索

ベクトルを「見える化」する

  • ベクトルを次元削減して 3 次元ベクトルに変換し、可視化

データの集まりを見る

  • K-Means によるデータのクラスタリング

変なデータ (=アノマリ) を見つける

  • K-Means による教師なしアノマリ検知や半教師ありアノマリ検知


一番お伝えしたかったのは 「データをベクトルに変換することで、データ利活用の幅が大きく広がる」 ということです。
本記事ではマルチモーダルAIおよびCLIPについておさらいし、Webinarでは時間の都合で触れきれなかったTips  - モダリティギャップというマルチモーダルモデル特有の現象についてお伝えします。

なお筆者は AI/機械学習の専門家ではありませんが、機械学習を利用したプロダクト・プロジェクトに携わり親しんでまいりました。ご質問・ご指摘などありましたらお気軽にコメント欄からお願いします。

マルチモーダル AI

近年、AI 分野では マルチモーダル AI が大きな注目を集めています。
「モーダル」とはデータの種類のことを指します。

0
0 45
InterSystems公式 Masahito Miura · 3月 27, 2025

インターシステムズは、InterSystems IRIS®data platformInterSystems IRIS® for HealthTM、および HealthShare® Health Connect の 2025.1 リリースを一般提供 (GA) したことを発表しました。2025.1 は、拡張メンテナンス(EM)リリースです。
リリースハイライト
今回のリリースには、以下のような数々の興味深いアップデートが含まれます:

0
0 120
お知らせ Mihoko Iijima · 2月 28, 2025

開発者の皆さん、こんにちは!

今年最初のプログラミング・コンテスト(USコミュニティ)の開催が決定しました!

🏆 InterSystems AI プログラミングコンテスト:ベクトル検索、生成AI、AIエージェント 🏆

期間:2025年3月17日~4月6日

賞品総額:$12,000 + GlobalSummit2025 へご招待!


0
0 91
記事 Toshihiko Minamoto · 12月 10, 2024 9m read

コミュニティメンバーから、Python 2024 コンテストでの出品に対する非常に素晴らしいフィードバックが届きました。 ここで紹介させていただきます。

純粋な IRIS の 5 倍以上のサイズでコンテナーをビルドしているため、時間がかかっています

コンテナーの始動も時間はかかりますが、完了します

バックエンドは説明通りにアクセス可能です

プロダクションは稼動しています

フロントエンドは反応します

何を説明したいのかがよくわかりません

私以外のエキスパート向けに書かれた説明のようです

出品はこちら: https://openexchange.intersystems.com/package/IRIS-RAG-App

このようなフィードバックをいただけて、本当に感謝しています。プロジェクトに関する記事を書く素晴らしいきっかけとなりました。 このプロジェクトにはかなり包括的なドキュメントが含まれてはいますが、ベクトル埋め込み、RAG パイプライン、LLM テキスト生成のほか、Python や LLamaIndex などの人気の Python ライブラリに精通していることが前提です。

この記事は、IRIS での RAG ワークフローを実証するに当たって、上記の前提事項や、それらが IRIS で RAG ワークフローをこのプロジェクトにどのように適合するかについてを説明する試みです。AI をまったく使用せずに書かれています。

コンテナーが大きいのは、ベクトル埋め込みの作成に関わる Python パッケージに必要なライブラリ依存関係が非常に大きいためです。 より選択的にインポートすることで、サイズを大幅に縮小することが可能です。

コンテナーの初回ビルドには確かに時間がかかりますが、一度ビルドすれば起動時間は短くなります。 とはいえ、起動時間は確かに改善できるかもしれません。 起動にこれほどの時間がかかる主な理由は、アプリケーションのある個所が最後の起動から変更されていることを想定して entrypoint.sh が更新されているためです。これには、データベースの移行、CSS 構成、JavaScript 構成、Python バックエンドコードも含まれており、起動のたびにプロジェクト全体がリコンパイルされます。 これは、このプロジェクトを開発し始めやすくするためで、そうでない場合は、変更が適用されるたびに、フロントエンドとバックエンドのビルドを適切に実行するのが困難になってしまいます。 こうすることで、プロジェクトのコードを変更した場合はコンテナーを再起動し、場合によってはバックエンドのプロダクションを復旧すれば、アプリケーションのインターフェースと操作に変更が反映されます。

バックエンドのプロダクションは、HTTP リクエストを Django アプリケーションに渡すものであり、このパッケージの相互運用性にとって非常に重要であると確信しています。 ただし、私自身は IRIS プラットフォームの初心者であるため、プロダクションについてもっと学ぶ必要があります。

次に、ベクトル埋め込み、LLM、および RAG について、包括的に説明したいと思います。 この内最初に作られたのはベクトル埋め込みです。 まず、ベクトルについて説明します。 ほとんどのコンテキストにおいて、ベクトルは方向です。 空間のどこかを指す矢印です。 より正式には、ベクトルは「大きさだけでなく方向も持つ量」です。 これは、特定の方向へ移動し、空間内の特定の地点で爆発する花火によって例えることができます。 すべての花火が同じ中心点、つまり原点である [0,0,0] から発射され、その原点の周囲に雲となって飛び散るとします。 数学的には、3 つの座標系 [x,y,z] を使用して各花火の爆発の位置を表現することができ、これが花火の爆発の「ベクトル埋め込み」となります。 花火のビデオをたくさん撮影し、花火の爆発をすべてデータセットとして記録すると、花火の一種のベクトル埋め込みデータベース、つまりベクトルストアが作成されることになります。

花火に関する情報を使って何ができるでしょうか? 特定の花火を指して、花火全体の中から同じ点に最も近い位置で爆発した花火について尋ねると、空間の近くの点で爆発した他の花火を検索できます。 最もちかいものを見つけるだけですが、これを行うために数式があります。

花火ごとに、x、y、z の 3 つの数値のみを記録したことに注意してください。3 次元空間において、地上の花火発射台を [0,0,0] としています。

他の特定の花火に対して、距離と時間の両方の観点で最も近く爆発した花火も知りたい場合はどうでしょうか? それを知るには、花火の映像を確認して、各爆発の時間も記録しなければなりません。 これで、4 つの数値を持つ 4 次元ベクトルが取得されました。花火の爆発の 3 次元の位置に爆発の時間を加えたベクトルです。 ベクトル埋め込みにもう 1 つの次元を追加することで、花火の埋め込みがより記述的になりました。

これを機械学習に変換するとどうなるでしょうか? 手短に言えば、大量のテキストデータを処理することで、コンピューター科学者は、フレーズ、文章、段落、またはページなどのテキストを変換し、理論的な高次元空間の点を表現する非常に長い一連の数値に変換できる埋め込みモデルを作成することができました。

4 つの数字ではなく、300、700、さらには 1500 もの数字があります。 これらは、1 つのテキストが互いに「近い」か「遠い」かを 1500 通りまたは1500 次元の意味で表します。 テキストの意味を何らかの方法で表す数字を作成する手段があるというわけですから、多くの人にとって魅力的な概念と言えるでしょう。

数学を使用すると、これらの高次元テキストベクトル埋め込みのうち 2 つを比較して、同じモデルによって作成された場合に、それらが互いにどの程度類似しているか、つまり「近い」かを調べることができます。

このアプリで最初の行われているのが正にこれです。ユーザーはドキュメントを追加して名前を付け、埋め込みのタイプを選択する必要があります。 サーバーはそのドキュメントを受け取り、テキストのチャンクに分割してから、それぞれのチャンクをベクトル埋め込みに変換します。そのチャンクはそのドキュメントの専用のテーブルの行として保存されます。 各ドキュメントは、さまざまなテキスト埋め込みモデルによって作成されるベクトル埋め込みの可変長に対応できるように、それぞれの専用テーブルに保存されます。

ドキュメントがベクトル埋め込みとしてデータベースに保存されると、ユーザーはドキュメントに「尋ねる」クエリを入力できるようになります。 このクエリは 2 つの方法で使用されます。 1 つは、ドキュメントを検索するためです。 従来のテキスト検索は実行せずに、「ベクトル検索」を実行します。 アプリはクエリを受け取り、それをベクトル埋め込みに変換してから、クエリベクトル埋め込みに最も築地する埋め込みのあるドキュメントのセクションを検索します。 各ドキュメントセクションには 0 と 1 の間の類似性スコアが生成され、top_k_similarity と similarity_threshold に基づいて、ベクトルデータベースから複数のセクションが検索されます。 基本的に、取得するドキュメントのセクション数と取得の対象となるためにクエリとどの程度類似している必要があるかを指定することができます。

これが、検索拡張生成における取得です。 次は生成に移りましょう。

コンピューター科学者がテキストを意味的に重要な数値ベクトル埋め込みに変換する方法を見つけると、次に、テキストを生成するモデルの作成に移りました。 これは大きな成功を生み出し、現在では GPT-4、LLama3、Claude 3.5 などの大規模言語モデルとなっています。 これらの LLM はプロンプトまたはクエリを受け取り、補完または回答を提供します。これは LLM が提示されたテキストであるプロンプトから最も続行できる可能性があると考えるテキストです。

LLM は大量のテキストデータに対してトレーニングする必要があり、その回答または補完はそのトレーニングデータに制限されます。 トレーニングセットにないデータを含む可能性のある補完を LLM に提供させる場合、または補完を特定のナレッジセットに基づかせる場合は、1 つの方法として、プロンプトに追加のコンテキストデータを含めることができます。 基本的に、トレーニングされていない内容について LLM から回答を得たい場合、プロンプトに情報を提供する必要があるということです。

多くの人は、ChatGPT やローカルの LLama インストールが自分の個人文書に基づいて回答を提供してくれることを望む状況に陥っていました。 ドキュメント内でその情報を検索し、プロンプトに貼り付けて、質問を入力するだけの単純な操作であり、手作業で行っていました。 それ自体が検索拡張生成です。 RAG は、より正確または利便的な応答を得られるように、ユーザークエリに関連する情報を検索し、LLM にクエリを提供する操作を自動化したに過ぎません。

このアプリでは、ベクトル検索で取得したドキュメントセクションは、インターフェースでモデルとしてラベル付けされている選択された LLM にクエリとともに送信され、回答のコンテキストが提供されます。

このプロジェクト用に制作した動画の例では、シェイクスピアの 2 つの戯曲の全文を含むドキュメント「ハムレット」と「リア王」を使って、「この戯曲の悪役は誰ですか?」と尋ねています。 IRIS データベースには、ハムレットとリア王の 2 つのテーブルがすでに存在します。 各テーブルには、各戯曲のテキストをセクションに分割して作成されたベクトル埋め込みの行が入力されています。 これらの埋め込みは、一連の長い数値によって各ドキュメントセクションの多次元を表現しています。

サーバーは、「この戯曲の悪役は誰ですか」という質問を、リア王のベクトル埋め込みを生成した Text-to-Vector モデルを使用して数値ベクトルに変換し、リア王テーブル内でそれに最も類似するセクションを見つけます。 これらはおそらく悪役という語が言及されたセクションかもしれませんが、悪役が明示的に言及されていない場合でも、裏切り、裏切り、欺瞞などの他の悪役についても言及されている可能性があります。 こういったドキュメントのセクションは、クエリに追加され、合わせてプロンプトとして LLM に送信されます。LLM は提供されたドキュメントのセクションに基づいて質問に回答します。

これはドキュメントごとに個別に実行されるため、クエリの回答はクエリされているドキュメントに応じて異なります。 これにより頭字語が補完されます。ベクトル検索の力を使用して関連するコンテキスト情報を取得することで、LLM からの応答の生成を強化しているためです。

この記事をお読みいただきありがとうございました。このトピックについては今後の記事でも発展させたいと思います。 フィードバックをお待ちしています。

0
0 82
記事 Toshihiko Minamoto · 12月 4, 2024 5m read

前回の記事では、ICD-10 による診断のコーディングをサポートできるように開発された d[IA]gnosis アプリケーションを紹介しました。 この記事では、InterSystems IRIS for Health が、事前トレーニングされた言語モデル、そのストレージ、およびその後の生成されたすべてのベクトルの類似性の検索を通じて ICD-10 コードのリストからベクトルを生成するために必要なツールをどのように提供するかを見ていきます。

はじめに

AI モデルの開発に伴って登場した主な機能の 1 つは、RAG(検索拡張生成)という、コンテキストをモデルに組み込むことで LLM モデルの結果を向上させることができる機能です。 この例では、コンテキストは ICD-10 診断のセットによって提供されており、これらを使用するには、まずこれらをベクトル化する必要があります。

診断リストをベクトル化するにはどうすればよいでしょうか?

SentenceTransformers と Embedded Python

ベクトルを生成するために、トレーニング済みのモデルからの自由テキストのベクトル化を大幅に容易にする SentenceTransformers という Python ライブラリを使用しました。 そのウェブサイトでは以下のように説明されています。

Sentence Transformers(別名: SBERT)は、最先端のテキスト画像埋め込みモデルへのアクセス、使用、およびトレーニングに使用される一般的な Python モジュールです。 SentenceTransformer モデルを使って埋め込みを計算(クイックスタート)するか、Cross-Encoder モデルを使って類似性スコアを計算(クイックスタート)するために使用できます。 これにより、セマンティック検索セマンティックテキスト類似性パラフレーズマイニングなどの広範なアプリケーションが可能になります。

SentenceTransformers コミュニティが開発した全モデルの中で、786 の次元ベクトルを生成する BioLORD-2023-M というトレーニング済みモデルを見つけました。

このモデルは、臨床文章や生物医学的概念の意味のある表現を生成するための新しい事前トレーニング戦略である BioLORD を使用してトレーニングされました。

最先端の方法は、同じ概念を指す名前の表現の類似性を最大化し、対照学習を通じて崩壊を防ぐことによって機能します。 ただし、生物医学的名前は必ずしも自明ではないため、非意味的な表現になることがあります。

BioLORD は、定義を使用した概念表現と、生物医学オントロジーで構成されるマルチリレーショナルナレッジグラフから得られる短い説明を基礎にすることで、この問題を克服しています。 この基礎により、このモデルは、オントロジーの階層構造により密接に一致する、より意味論的な概念表現を生成します。 BioLORD-2023 は、臨床文章(MedSTS)と生物医学概念(EHR-Rel-B)の両方でテキスト類似性の新たな最先端を確立しています。

この定義でわかるように、このモデルは、ICD-10 コードと自由テキストの両方をベクトル化するときに役立つ医療概念で事前トレーニングされています。

このプロジェクトでは、このモデルをダウンロードして、ベクトルの作成を高速化します。

if not os.path.isdir('/shared/model/'):
    model = sentence_transformers.SentenceTransformer('FremyCompany/BioLORD-2023-M')            
    model.save('/shared/model/')

ダウンロードしたら、ベクトル化するテキストをリストに入力して、プロセスを高速化します。以前に ENCODER.Object.Codes クラスに記録した ICD-10 コードをベクトル化する方法を見てみましょう。

st = iris.sql.prepare("SELECT TOP 50 CodeId, Description FROM ENCODER_Object.Codes WHERE VectorDescription is null ORDER BY ID ASC ")
resultSet = st.execute()
df = resultSet.dataframe()

if (df.size > 0): model = sentence_transformers.SentenceTransformer("/shared/model/") embeddings = model.encode(df['description'].tolist(), normalize_embeddings=True)

df[<span class="hljs-string">'vectordescription'</span>] = embeddings.tolist()

stmt = iris.sql.prepare(<span class="hljs-string">"UPDATE ENCODER_Object.Codes SET VectorDescription = TO_VECTOR(?,DECIMAL) WHERE CodeId = ?"</span>)
<span class="hljs-keyword">for</span> index, row <span class="hljs-keyword">in</span> df.iterrows():
    rs = stmt.execute(str(row[<span class="hljs-string">'vectordescription'</span>]), row[<span class="hljs-string">'codeid'</span>])

else: flagLoop = False

ご覧のとおり、CSV ファイルから抽出した後に前のステップで登録した、まだベクトル化されていないコードを先に抽出し、次に、ベクトル化する記述のリストを抽出します。Python の sentence_transformers ライブラリを使用してモデルを復元し、関連する埋め込みを生成します。

最後に、UPDATE を実行して、ベクトル化された記述で ICD-10 コードを更新します。 ご覧のように、モデルが返した結果をベクトル化するコマンドは、IRIS における SQL コマンドの TO_VECTOR です。

IRIS で使用する

Python コードの準備ができたので、Ens.BusinessProcess を拡張するクラスにラップして、プロダクションに含めましょう。次に、CSV ファイルを取得するビジネスサービスに接続すれば、完成です!

プロダクションでこのコードがどのように見えるかを確認しましょう。

ご覧のように、EnsLib.File.InboundAdapter アダプターを備えたビジネス サービスにより、コードファイルを収集し、それをすべてのベクトル化とストレージ操作を実行するビジネスプロセスにリダイレクトできます。すると、以下のようなレコードセットが得られます。

これで、アプリケーションは、送信するテキストに一致する可能性のある項目を検索できるようになりました!

次回の記事では...

次回の記事では、Angular 17 で開発されたアプリケーションのフロントエンドが IRIS for Health のプロダクションとどのように統合されるか、および IRIS が分析するテキストをどのように受け取り、それらをベクトル化し、ICD-10 コードテーブルで類似性を検索するかを説明します。

お見逃しなく!

0
0 83
InterSystems公式 Seisuke Nakahashi · 11月 28, 2024

インターシステムズは InterSystems IRIS data platform、InterSystems IRIS for Health、HealthShare Health Connect のバージョン 2024.3 をリリースしました。2024.3 は Continuous Delivery(CD)リリースです。

0
0 81
記事 Toshihiko Minamoto · 11月 26, 2024 5m read

ベクトルデータ型と Vector Search 機能が IRIS に導入されたことにより、アプリケーションの開発に多数の可能性が開かれました。こういったアプリケーションの例として、バレンシア保健省が AI モデルを使用した ICD-10 コーディング支援ツールを要求した公募で出品されたアプリケーションが最近私の目に留まりました。

要求されたツールのようなアプリケーションをどのように実装できるでしょうか? 必要なものを確認しましょう。

  1. ICD-10 コードのリスト。自由テキスト内で診断を検索するための RAG アプリケーションのコンテキストとして使用します。
  2. ICD-10 コード内で相当するものを検索するためにテキストをベクトル化するトレーニング済みモデル。
  3. ICD-10 コードとテキストの取り込みとベクトル化を行うために必要な Python ライブラリ。
  4. 可能性のある診断を見つけるためのテキストを受け入れる使いやすいフロントエンド。
  5. フロントエンドから受信するリクエストのオーケストレーション。

これらのニーズに対応するために、IRIS は何を提供できるでしょうか?

  1. CSV インポート。RecordMapper 機能を使うか、Embedded Python を直接使用します。
  2. Embedded Python によって、選択されたモデルを使ってベクトルを生成するために必要な Python コードを実装できます。
  3. フロントエンドアプリケーションから呼び出される REST API の公開。
  4. IRIS 内で情報を追跡できる相互運用性プロダクション。

では、開発済みの例を確認しましょう。

d[IA]gnosis

この記事に関連して、開発済みのアプリケーションにアクセスできます。次の記事では、モデルの使用、ベクトルのストレージ、ベクトル検索の使用に至るまで、各機能をどのように実装するかを詳しく説明します。

アプリケーションを確認しましょう。

ICD-10 コードをインポートする

構成画面に、CSV ファイルのフォーマットはインポートしようとしている ICD-10 コードに準拠している必要があると表示されます。 読み取りとベクトル化のプロセスは多数のリソースを使用し、長時間かかるため、Docker コンテナーのデプロイによって、Docker が使用できる RAM メモリだけでなく、要件が割り当てられた RAM を超過する場合に備えてディスクメモリも構成されます。

# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse    mem_limit:30G    memswap_limit:32G

ICD-10 コードを含むファイルは、プロジェクトパス /shared/cie10/icd10.csv にあります。100% に達すスト、アプリケーションを使用できます。

私たちのアプリケーションでは、診断コーディング用に 2 つの異なる機能を定義しました。1 つはシステムで受信した HL7 メッセージに基づいたもので、もう 1 つは自由テキストに基づいたものです。

HL7 による診断のキャプチャ

プロジェクトにはテスト用に準備された HL7 メッセージが含まれており、/shared/hl7/messagesa01_en.hl7 ファイルを /shared/HL7In フォルダにコピーすれば、関連付けられたプロダクションが診断を抽出してウェブアプリケーションに表示します。

診断リクエスト画面では、HL7 メッセージングで受信したすべての診断を確認できます。 これを ICD-10 にコーディングするには、虫眼鏡をクリックするだけで、受信した診断に最も近い ICD-10 コードのリストを表示することができます。

選択すると、リストに診断とそれに関連付けられた ICD-10 コードが表示されます。 封筒のアイコンが付いたボタンをクリックすると、元のメッセージと、診断セグメント内で選択した新しいメッセージによって、メッセージが生成されます。

MSH|^~\&|HIS|HULP|EMPI||||ADT^A08|592956|P|2.5.1
EVN|A01|
PID|||1556655212^^^SERMAS^SN~922210^^^HULP^PI||GARCÍA PÉREZ^JUAN^^^||20150403|M|||PASEO PEDRO ÁLVAREZ 1951 CENTRO^^LEGANÉS^MADRID^28379^SPAIN||555283055^PRN^^JUAN.GARCIA@YAHOO.COM|||||||||||||||||N|
PV1||N
DG1|1||O10.91^Unspecified pre-existing hypertension complicating pregnancy^CIE10-ES|Gestational hypertension||A||

このメッセージは /shared/HL7Out パスにあります。

自由テキストによる診断のキャプチャ

テキストアナライザーオプションでは、分析プロセスが実行される自由テキストを含めることができます。 アプリケーションは、見出し語化された 3 つの単語のタプルを検索します(冠詞、代名詞、およびその他の関連性の低い単語は削除されます)。 分析が完了すると、システムは関連する下線付きのテキストと、考えられる診断を表示します。

分析の実行が完了したら、その内容は分析履歴からいつでも参照できます。

分析履歴

実行されたすべての分析は記録され、いつでも参照でき、利用可能なすべての ICD-10 コードを表示できます。

次回の記事では...

Embedded Python を使用して、コンテキストとして使用する ICD-10 コードと自由テキストの両方のベクトル化に特定の LLM モデルを使用する方法を見ていきます。

ご質問やご提案がありましたら、ぜひこの記事のコメントでお知らせください。

0
0 63
記事 Toshihiko Minamoto · 11月 21, 2024 6m read

image
コミュニティの皆さん、こんにちは。
この記事では、iris-RAG-Gen という私のアプリケーションをご紹介します。

iris-RAG-Gen は、IRIS Vector Search の機能を使用して、Streamlit ウェブフレームワーク、LangChain、および OpenAI で ChatGPT をパーソナライズするジェネレーティブ AI 検索拡張生成(RAG: Retrieval-Augmented Generation)アプリケーションです。 このアプリケーションは IRIS をベクトルストアとして使用します。
image

アプリケーションの機能

  • ドキュメント(PDF または TXT)を IRIS に取り込む
  • 選択されたドキュメントの取り込みを使ってチャットする
  • ドキュメントの取り込みを削除する
  • OpenAI ChatGPT

ドキュメント(PDF または TXT)を IRIS に取り込む

以下の手順に従って、ドキュメントを取り込みます。

  • OpenAI キーを入力します。
  • ドキュメント(PDF または TXT)を選択します。
  • ドキュメントの説明を入力します。
  • 「Ingest Document」ボタンをクリックします。

image
 

ドキュメントの取り込み機能は、ドキュメントの詳細を rag_documents テーブルに挿入し、ベクトルデータを保存する 'rag_document + id'(rag_documents の ID)テーブルを作成します。

image

以下の Python コードは選択されたドキュメントをベクトルに保存します。

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain_iris import IRISVector
from langchain_openai import OpenAIEmbeddings
from sqlalchemy import create_engine,text

classRagOpr:#Ingest document. Parametres contains file path, description and file type defingestDoc(self,filePath,fileDesc,fileType): embeddings = OpenAIEmbeddings() #Load the document based on the file typeif fileType == "text/plain": loader = TextLoader(filePath)
elif fileType == "application/pdf": loader = PyPDFLoader(filePath)

    <span class="hljs-comment">#load data into documents</span>
    documents = loader.load()        
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=<span class="hljs-number">400</span>, chunk_overlap=<span class="hljs-number">0</span>)
    <span class="hljs-comment">#Split text into chunks</span>
    texts = text_splitter.split_documents(documents)
    
    <span class="hljs-comment">#Get collection Name from rag_doucments table. </span>
    COLLECTION_NAME = self.get_collection_name(fileDesc,fileType)
           
    <span class="hljs-comment"># function to create collection_name table and store vector data in it.</span>
    db = IRISVector.from_documents(
        embedding=embeddings,
        documents=texts,
        collection_name = COLLECTION_NAME,
        connection_string=self.CONNECTION_STRING,
    )

<span class="hljs-comment">#Get collection name</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_collection_name</span><span class="hljs-params">(self,fileDesc,fileType)</span>:</span>
    <span class="hljs-comment"># check if rag_documents table exists, if not then create it </span>
    <span class="hljs-keyword">with</span> self.engine.connect() <span class="hljs-keyword">as</span> conn:
        <span class="hljs-keyword">with</span> conn.begin():     
            sql = text(<span class="hljs-string">"""
                SELECT *
                FROM INFORMATION_SCHEMA.TABLES
                WHERE TABLE_SCHEMA = 'SQLUser'
                AND TABLE_NAME = 'rag_documents';
                """</span>)
            result = []
            <span class="hljs-keyword">try</span>:
                result = conn.execute(sql).fetchall()
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)               
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
            <span class="hljs-comment">#if table is not created, then create rag_documents table first</span>
            <span class="hljs-keyword">if</span> len(result) == <span class="hljs-number">0</span>:
                sql = text(<span class="hljs-string">"""
                    CREATE TABLE rag_documents (
                    description VARCHAR(255),
                    docType VARCHAR(50) )
                    """</span>)
                <span class="hljs-keyword">try</span>:    
                    result = conn.execute(sql) 
                <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                    print(<span class="hljs-string">"An exception occurred:"</span>, err)                
                    <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
    <span class="hljs-comment">#Insert description value </span>
    <span class="hljs-keyword">with</span> self.engine.connect() <span class="hljs-keyword">as</span> conn:
        <span class="hljs-keyword">with</span> conn.begin():     
            sql = text(<span class="hljs-string">"""
                INSERT INTO rag_documents 
                (description,docType) 
                VALUES (:desc,:ftype)
                """</span>)
            <span class="hljs-keyword">try</span>:    
                result = conn.execute(sql, {<span class="hljs-string">'desc'</span>:fileDesc,<span class="hljs-string">'ftype'</span>:fileType})
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)                
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
            <span class="hljs-comment">#select ID of last inserted record</span>
            sql = text(<span class="hljs-string">"""
                SELECT LAST_IDENTITY()
            """</span>)
            <span class="hljs-keyword">try</span>:
                result = conn.execute(sql).fetchall()
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"rag_document"</span>+str(result[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>])</code></pre>

 

管理ポータルで以下の SQL コマンドを入力し、ベクトルデータを取得します。

SELECT top 5id, embedding, document, metadata
FROM SQLUser.rag_document2

image

 

選択されたドキュメントの取り込みを使ってチャットする

チャットオプションの選択セクションから「Document」を選択して質問を入力します。アプリケーションはベクトルデータを読み取り、関連する回答を返します。
image
以下の Python コードは、選択されたドキュメントをべく鳥に保存します。

from langchain_iris import IRISVector
from langchain_openai import OpenAIEmbeddings,ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI

classRagOpr:defragSearch(self,prompt,id):#Concat document id with rag_doucment to get the collection name COLLECTION_NAME = "rag_document"+str(id) embeddings = OpenAIEmbeddings() #Get vector store reference db2 = IRISVector ( embedding_function=embeddings,
collection_name=COLLECTION_NAME, connection_string=self.CONNECTION_STRING, ) #Similarity search docs_with_score = db2.similarity_search_with_score(prompt) #Prepair the retrieved documents to pass to LLM relevant_docs = ["".join(str(doc.page_content)) + " "for doc, _ in docs_with_score] #init LLM llm = ChatOpenAI( temperature=0,
model_name="gpt-3.5-turbo" ) #manage and handle LangChain multi-turn conversations conversation_sum = ConversationChain( llm=llm, memory= ConversationSummaryMemory(llm=llm), verbose=False ) #Create prompt template = f""" Prompt: {prompt} Relevant Docuemnts: {relevant_docs} """#Return the answer resp = conversation_sum(template) return resp['response']

</code></pre>


詳細については、iris-RAG-Gen の Open Exchange アプリケーションページをご覧ください。

よろしくお願いします。

0
0 66
InterSystems公式 Seisuke Nakahashi · 10月 4, 2024

InterSystems IRIS の新バージョンに、 Hierarchical Navigable Small World (HNSW) インデックス・アルゴリズムに基づく新しい近似最近傍探索 (ANN) インデックスが搭載されました。こちらは、ベクトル検索 早期アクセスプログラム で入手いただけます。これにより、大規模なベクトルデータセットに対して非常に効率の良い近似最近傍探索が可能となり、クエリパフォーマンスとスケーラビリティが大幅に向上しました。

HNSW アルゴリズムは、グラフベース構造を利用して高次元データのベクトル検索を最適化するよう設計されており、大規模なベクトル集合における近似近傍探索を高速化します。HNSW によって、レコメンデーションシステム、自然言語処理、その他の機会学習アプリケーションなどすべてにおいて検索時間が大幅に短縮します。

HNSWの主な利点:

    •    データセットサイズ増加後も、より高速な検索が可能
    •    高精度をたもちながら、メモリ使用量を削減
    •    既存の IRIS ベクトル検索とのシームレスな統合

最新バージョンのトライアル

0
0 186
記事 Mihoko Iijima · 8月 9, 2024 36m read

開発者の皆さん、こんにちは!

この記事は、2024年7月に開催された「InterSystems Pythonプログラミングコンテスト2024」でエキスパート投票、コミュニティ投票の両方で1位を獲得された @Henry Pereira Pereira さん @José Pereira さん @Henrique Dias Dias さんが開発された sqlzilla について、アプリを動かしてみた感想と、中の構造について @José Pereira さんが投稿された「Text to IRIS with LangChain」の翻訳をご紹介します。

第2回 InterSystems Japan 技術文書ライティングコンテスト 開催! では、生成AIに関連する記事を投稿いただくと、ボーナスポイントを4点獲得できます📢 @José Pereira さんの記事を💡ヒント💡に皆様の操作体験談、アイデアなどを共有いただければと思います。

開発されたアプリSQLzilla についての概要ですが、Open Exchangesqlzilla のREADMEに以下のように紹介されています。

「SQLzilla は、Python と AI のパワーを活用して、自然言語の SQL クエリ生成を通じてデータ アクセスを簡素化し、複雑なデータ クエリとプログラミング経験の少ないユーザーとの間のギャップを埋めます。」

「SQLクエリ生成」とありますので、アプリには Aviationスキーマ以下3つのテーブルとサンプルデータが用意されています。

  • Aviation.Aircraft
  • Aviation.Crew
  • Aviation.Event

3つのテーブルには、米国国家運輸安全委員会に報告された航空事故の選択されたサブセットのデータが含まれています。

メモ: サンプルリポジトリ で提供されるデータセットは、http://www.ntsb.gov から入手できる完全な NTSB データセットの小さなサブセットであるためデモ目的のみで提供されており、正確であることを意図または保証するものではありません。(提供元:National Transportation Safety Board

例えば、Aviation.Aircraftには、AircraftCategoryカラムがあり、航空機の種別が登録されています。

[SQL]IRISAPP>>SELECT AircraftCategory FROM Aviation.Aircraft GROUP BY AircraftCategory
1.      SELECT AircraftCategory FROM Aviation.Aircraft GROUP BY AircraftCategory

| AircraftCategory |
| -- |
| AIRPLANE |
| HELICOPTER |
| GYROCRAFT |
| GLIDER |
| BALLOON |
| POWERED PARACHUTE |
| WEIGHT SHIFT |

また、DepartureCity には、出発都市名が登録されています。

SELECT top 10 DepartureCity FROM Aviation.Aircraft WHERE AircraftCategory='AIRPLANE' GROUP BY DepartureCity

| DepartureCity |
| -- |
| WILBUR |
| IRONWOOD |
| STANIEL CAY |
| OAK ISLAND |
| CLEVELAND |
| DECATUR |
| MARSHALLTOWN |
| MARANA |
| TONOPAH |
| MURRIETA/TEMECU |

また、Aviation.Eventテーブルには発生した事故の情報が含まれていて、InjuriesHighestには負傷者数の状況を文字で表現した情報が含まれています。

SELECT InjuriesHighest FROM Aviation.Event GROUP BY InjuriesHighest

| InjuriesHighest |
| -- |
| NONE |
| FATAL |
| SERIOUS |
| MINOR |

さて、これらのテーブルを使って具体的にどのようなことをしてくれるアプリなのか?ですが(説明文より以下抜粋)

「SQLzilla は、ユーザーがデータベースを操作する方法を変革する革新的なプロジェクトです。InterSystems IRIS と統合することで、SQL に詳しくないユーザーでもさまざまなテーブルからデータを簡単に抽出して分析できるツールを作成しました。」

つまり、

SQLに詳しくないユーザでも、自分の欲しい情報に対して質問するとSQLを組み立てて返してきてくれる便利アプリということになります。

以下、アプリケーションを動作させてみたときの図です。(日本語で質問してもしっかりSQLを組み立ててくれています!)

image

入力した質問によって生成されたSQLは以下の通りです。

  • ヘリコプターによる事故の件数を教えてください。
SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftCategory = 'Helicopter'
  • 2002年に発生したヘリコプターによる事故の件数を教えてください。
SELECT COUNT(*) FROM Aviation.Event WHERE YEAR(EventDate) = 2002 AND Type = 'Helicopter Accident'

image

  • ヘリコプターによる事故でInjuriesHighestがFATALの事故数を年別で表示してください。
SELECT YEAR(e.EventDate) AS IncidentYear, COUNT(*) AS FatalIncidentCount FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE a.AircraftCategory = 'Helicopter' AND e.InjuriesHighest = 'FATAL' GROUP BY YEAR(e.EventDate)

アプリ詳細は、ぜひ Open Exchangesqlzilla をご参照ください。

素晴らしいです!!

※ このサンプルを動作させるためには、OpenAIのAPIキーが必要となりますので、お試しになる場合は事前にご準備ください。

Open Exchangesqlzilla のREADMEにも記載がありますが、コンテナを使う場合は以下3コマンドでサンプルを動作させることができます。

まずはソースコードをclone して

git clone https://github.com/musketeers-br/sqlzilla.git

コンテナをビルドし

docker-compose build --no-cache --progress=plain

コンテナを開始するだけ

docker-compose up -d

後は、アプリ画面を起動するだけ!

コンテナ以外でも操作する方法が提供されています。詳しくは、 sqlzilla のREADMEご参照ください。(ぜひREADMEの一番下までご覧ください!)


記事の紹介

アプリの中でどのようにSQLを生成させているか、については、 @José Pereira さんが投稿された「Text to IRIS with LangChain」の翻訳記事でご紹介します。


LangChainフレームワーク、IRIS Vector Search、LLMを使って、ユーザープロンプトからIRIS互換のSQLを生成する方法についての実験をご紹介します。

この記事は このノートブック を元にしています。 OpenExchange の このアプリケーション を使えば、すぐに使える環境で実行できます。

セットアップ

最初に必要なライブラリをインストールします。

!pip install --upgrade --quiet langchain langchain-openai langchain-iris pandas

次に、必要なモジュールをインポートし、環境をセットアップします。

import os
import datetime
import hashlib
from copy import deepcopy
from sqlalchemy import create_engine
import getpass
import pandas as pd
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.docstore.document import Document
from langchain_community.document_loaders import DataFrameLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_core.output_parsers import StrOutputParser
from langchain.globals import set_llm_cache
from langchain.cache import SQLiteCache
from langchain_iris import IRISVector

SQLiteCacheを使ってLLMコールをキャッシュします。

# Cache for LLM calls
set_llm_cache(SQLiteCache(database_path=".langchain.db"))

IRISデータベースへ接続するためのパラメータをセットします。

# IRIS database connection parameters
os.environ["ISC_LOCAL_SQL_HOSTNAME"] = "localhost"
os.environ["ISC_LOCAL_SQL_PORT"] = "1972"
os.environ["ISC_LOCAL_SQL_NAMESPACE"] = "IRISAPP"
os.environ["ISC_LOCAL_SQL_USER"] = "_system"
os.environ["ISC_LOCAL_SQL_PWD"] = "SYS"

OpenAI APIキーが環境変数に設定されていない場合は、ユーザ入力が求められます。

if not "OPENAI_API_KEY" in os.environ:
    os.environ["OPENAI_API_KEY"] = getpass.getpass()

IRISデータベースの接続文字列を作成します。

# IRIS database connection string
args = {
    'hostname': os.getenv("ISC_LOCAL_SQL_HOSTNAME"), 
    'port': os.getenv("ISC_LOCAL_SQL_PORT"), 
    'namespace': os.getenv("ISC_LOCAL_SQL_NAMESPACE"), 
    'username': os.getenv("ISC_LOCAL_SQL_USER"), 
    'password': os.getenv("ISC_LOCAL_SQL_PWD")
}
iris_conn_str = f"iris://{args['username']}:{args['password']}@{args['hostname']}:{args['port']}/{args['namespace']}"

IRISデータベースとの接続を確立します。

# Connection to IRIS database
engine = create_engine(iris_conn_str)
cnx = engine.connect().connection

システムプロンプトのコンテキスト情報を保持するdictionaryを用意します。

# Dict for context information for system prompt
context = {}
context["top_k"] = 3

プロンプトの作成

ユーザー入力をIRISデータベースと互換性のあるSQLクエリに変換するために、言語モデル用の効果的なプロンプトを作成する必要があります。

SQLクエリを生成するための基本的な指示を提供する初期プロンプトから始めます。

このテンプレートはLangChain's default prompts for MSSQL から派生し、IRISデータベース用にカスタマイズされています。

# Basic prompt template with IRIS database SQL instructions
iris_sql_template = """
You are an InterSystems IRIS expert. Given an input question, first create a syntactically correct InterSystems IRIS query to run and return the answer to the input question.
Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the TOP clause as per InterSystems IRIS. You can order the results to return the most informative data in the database.
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in single quotes ('') to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.
Pay attention to use CAST(CURRENT_DATE as date) function to get the current date, if the question involves "today".
Use double quotes to delimit columns identifiers.
Return just plain SQL; don't apply any kind of formatting.
"""

次のような文章をテンプレートに設定しています: あなたは InterSystems IRIS のエキスパートです。 入力された質問に対して、まず、構文的に正しい InterSystems IRIS クエリを作成し、そのクエリを実行し て、入力された質問に対する回答を返します。 ユーザが質問で特定の数の例を取得するように指定しない限り、InterSystems IRIS に従って、TOP 節を使用して最大 {top_k} の結果をクエリします。 テーブルのすべての列に対してクエリを実行してはなりません。 質問に答えるために必要なカラムのみを問い合わせなければなりません。 各カラム名をシングルクォート('')で囲み、区切り識別子にします。 以下の表で確認できるカラム名のみを使用するように注意してください。 存在しないカラムを問い合わせないように注意してください。 また、どのカラムがどのテーブルにあるかに注意すること。 質問内容が "今日 "を含む場合は、CAST(CURRENT_DATE as date)関数を使用して現在の日付を取得することに注意すること。 カラムの識別子を区切るには二重引用符を使用すること。 単なるSQLを返すこと。いかなるフォーマットも適用しないこと。

この基本プロンプトは、言語モデル(LLM)がIRISデータベースに対する特定のガイダンスを持つSQLエキスパートとして機能するように設定しています。

次に、ハルシネーション(幻覚)を避けるために、データベーススキーマに関する情報を補助プロンプトとして提供します。

# SQL template extension for including tables context information
tables_prompt_template = """
Only use the following tables:
{table_info}
"""

LLMの回答の精度を高めるために、私たちはfew-shot プロンプトと呼ばれるテクニックを使いました。 これはLLMにいくつかの例を提示するものです。

# SQL template extension for including few shots
prompt_sql_few_shots_template = """
Below are a number of examples of questions and their corresponding SQL queries.

{examples_value}
"""

私たちは few-shot の例のためにテンプレートを以下のように定義しています。

# Few shots prompt template
example_prompt_template = "User input: {input}\nSQL query: {query}"
example_prompt = PromptTemplate.from_template(example_prompt_template)

私たちは、 few-shot テンプレートを使ってユーザー・プロンプトを作っています。

# User prompt template
user_prompt = "\n" + example_prompt.invoke({"input": "{input}", "query": ""}).to_string()

最後に、すべてのプロンプトを組み合わせて最終的なプロンプトを作成します。

# Complete prompt template
prompt = (
    ChatPromptTemplate.from_messages([("system", iris_sql_template)])
    + ChatPromptTemplate.from_messages([("system", tables_prompt_template)])
    + ChatPromptTemplate.from_messages([("system", prompt_sql_few_shots_template)])
    + ChatPromptTemplate.from_messages([("human", user_prompt)])
)
prompt

このプロンプトは、変数 examples_value, input, table_info, and top_k を想定しています。

プロンプトの構成は以下の通りです。

ChatPromptTemplate(
    input_variables=['examples_value', 'input', 'table_info', 'top_k'], 
    messages=[
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['top_k'], 
                template=iris_sql_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['table_info'], 
                template=tables_prompt_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['examples_value'], 
                template=prompt_sql_few_shots_template
            )
        ), 
        HumanMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['input'], 
                template=user_prompt
            )
        )
    ]
)

プロンプトがどのようにLLMに送られるかを視覚化するために、必要な変数にプレースホルダーの値を使うことができます。

prompt_value = prompt.invoke({
    "top_k": "<top_k>",
    "table_info": "<table_info>",
    "examples_value": "<examples_value>",
    "input": "<input>"
})
print(prompt_value.to_string())
System: 
You are an InterSystems IRIS expert. Given an input question, first create a syntactically correct InterSystems IRIS query to run and return the answer to the input question.
Unless the user specifies in the question a specific number of examples to obtain, query for at most <top_k> results using the TOP clause as per InterSystems IRIS. You can order the results to return the most informative data in the database.
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in single quotes ('') to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.
Pay attention to use CAST(CURRENT_DATE as date) function to get the current date, if the question involves "today".
Use double quotes to delimit columns identifiers.
Return just plain SQL; don't apply any kind of formatting.

System: 
Only use the following tables:
<table_info>

System: 
Below are a number of examples of questions and their corresponding SQL queries.

<examples_value>

Human: 
User input: <input>
SQL query: 

これで、必要な変数を与えることで、このプロンプトをLLMに送る準備ができました。 準備ができたら次のステップに進みましょう。

テーブル情報の提供

正確なSQLクエリを作成するためには、言語モデル(LLM)にデータベース・テーブルに関する詳細な情報を提供する必要があります。

この情報がないと、LLMは一見もっともらしく見えますが、ハルシネーション(幻覚)により正しくないクエリを生成する可能性があります。 そこで、最初のステップとして、IRISデータベースからテーブル定義を取得する関数を作成します。

テーブル定義情報を取得する関数

以下の関数はINFORMATION_SCHEMAに問い合わせ、指定されたスキーマのテーブル定義を取得します。

特定のテーブルが指定された場合は、そのテーブルの定義を取得します。そうでない場合は、スキーマ内の全てのテーブルの定義を取得します。

def get_table_definitions_array(cnx, schema, table=None):
    cursor = cnx.cursor()

    # Base query to get columns information
    query = """
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, PRIMARY_KEY, null EXTRA
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_SCHEMA = %s
    """
    
    # Parameters for the query
    params = [schema]

    # Adding optional filters
    if table:
        query += " AND TABLE_NAME = %s"
        params.append(table)
    
    # Execute the query
    cursor.execute(query, params)

    # Fetch the results
    rows = cursor.fetchall()
    
    # Process the results to generate the table definition(s)
    table_definitions = {}
    for row in rows:
        table_schema, table_name, column_name, column_type, is_nullable, column_default, column_key, extra = row
        if table_name not in table_definitions:
            table_definitions[table_name] = []
        table_definitions[table_name].append({
            "column_name": column_name,
            "column_type": column_type,
            "is_nullable": is_nullable,
            "column_default": column_default,
            "column_key": column_key,
            "extra": extra
        })

    primary_keys = {}
    
    # Build the output string
    result = []
    for table_name, columns in table_definitions.items():
        table_def = f"CREATE TABLE {schema}.{table_name} (\n"
        column_definitions = []
        for column in columns:
            column_def = f"  {column['column_name']} {column['column_type']}"
            if column['is_nullable'] == "NO":
                column_def += " NOT NULL"
            if column['column_default'] is not None:
                column_def += f" DEFAULT {column['column_default']}"
            if column['extra']:
                column_def += f" {column['extra']}"
            column_definitions.append(column_def)
        if table_name in primary_keys:
            pk_def = f"  PRIMARY KEY ({', '.join(primary_keys[table_name])})"
            column_definitions.append(pk_def)
        table_def += ",\n".join(column_definitions)
        table_def += "\n);"
        result.append(table_def)

    return result

スキーマのテーブル定義を取得する

この例では、Aviationスキーマを使用しています。Aviationスキーマ情報一式については こちら から入手できます。

# Retrieve table definitions for the Aviation schema
tables = get_table_definitions_array(cnx, "Aviation")
print(tables)

この関数は、Aviation スキーマ内のすべてのテーブルの CREATE TABLE ステートメントを返します。

[
    'CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  AccidentExplosion varchar,\n  AccidentFire varchar,\n  AirFrameHours varchar,\n  AirFrameHoursSince varchar,\n  AirFrameHoursSinceLastInspection varchar,\n  AircraftCategory varchar,\n  AircraftCertMaxGrossWeight integer,\n  AircraftHomeBuilt varchar,\n  AircraftKey integer NOT NULL,\n  AircraftManufacturer varchar,\n  AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  AircraftSerialNo varchar,\n  AircraftSeries varchar,\n  Damage varchar,\n  DepartureAirportId varchar,\n  DepartureCity varchar,\n  DepartureCountry varchar,\n  DepartureSameAsEvent varchar,\n  DepartureState varchar,\n  DepartureTime integer,\n  DepartureTimeZone varchar,\n  DestinationAirportId varchar,\n  DestinationCity varchar,\n  DestinationCountry varchar,\n  DestinationSameAsLocal varchar,\n  DestinationState varchar,\n  EngineCount integer,\n  EvacuationOccurred varchar,\n  EventId varchar NOT NULL,\n  FlightMedical varchar,\n  FlightMedicalType varchar,\n  FlightPhase integer,\n  FlightPlan varchar,\n  FlightPlanActivated varchar,\n  FlightSiteSeeing varchar,\n  FlightType varchar,\n  GearType varchar,\n  LastInspectionDate timestamp,\n  LastInspectionType varchar,\n  Missing varchar,\n  OperationDomestic varchar,\n  OperationScheduled varchar,\n  OperationType varchar,\n  OperatorCertificate varchar,\n  OperatorCertificateNum varchar,\n  OperatorCode varchar,\n  OperatorCountry varchar,\n  OperatorIndividual varchar,\n  OperatorName varchar,\n  OperatorState varchar,\n  Owner varchar,\n  OwnerCertified varchar,\n  OwnerCountry varchar,\n  OwnerState varchar,\n  RegistrationNumber varchar,\n  ReportedToICAO varchar,\n  SeatsCabinCrew integer,\n  SeatsFlightCrew integer,\n  SeatsPassengers integer,\n  SeatsTotal integer,\n  SecondPilot varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.EventC("Aircraft"))\n);',
    'CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  Age integer,\n  AircraftKey integer NOT NULL,\n  Category varchar,\n  CrewNumber integer NOT NULL,\n  EventId varchar NOT NULL,\n  Injury varchar,\n  MedicalCertification varchar,\n  MedicalCertificationDate timestamp,\n  MedicalCertificationValid varchar,\n  Seat varchar,\n  SeatbeltUsed varchar,\n  Sex varchar,\n  ShoulderHarnessUsed varchar,\n  ToxicologyTestPerformed varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.AircraftC("Crew"))\n);',
    'CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  AirportDirection integer,\n  AirportDistance varchar,\n  AirportElevation integer,\n  AirportLocation varchar,\n  AirportName varchar,\n  Altimeter varchar,\n  EventDate timestamp,\n  EventId varchar NOT NULL,\n  EventTime integer,\n  FAADistrictOffice varchar,\n  InjuriesGroundFatal integer,\n  InjuriesGroundMinor integer,\n  InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  InjuriesTotal integer,\n  InjuriesTotalFatal integer,\n  InjuriesTotalMinor integer,\n  InjuriesTotalNone integer,\n  InjuriesTotalSerious integer,\n  InvestigatingAgency varchar,\n  LightConditions varchar,\n  LocationCity varchar,\n  LocationCoordsLatitude double,\n  LocationCoordsLongitude double,\n  LocationCountry varchar,\n  LocationSiteZipCode varchar,\n  LocationState varchar,\n  MidAir varchar,\n  NTSBId varchar,\n  NarrativeCause varchar,\n  NarrativeFull varchar,\n  NarrativeSummary varchar,\n  OnGroundCollision varchar,\n  SkyConditionCeiling varchar,\n  SkyConditionCeilingHeight integer,\n  SkyConditionNonCeiling varchar,\n  SkyConditionNonCeilingHeight integer,\n  TimeZone varchar,\n  Type varchar,\n  Visibility varchar,\n  WeatherAirTemperature integer,\n  WeatherPrecipitation varchar,\n  WindDirection integer,\n  WindDirectionIndicator varchar,\n  WindGust integer,\n  WindGustIndicator varchar,\n  WindVelocity integer,\n  WindVelocityIndicator varchar\n);'
]

これらのテーブル定義ができたので、次のステップに進むことができます。

これにより、LLMがSQLクエリを生成する際に、データベース・スキーマに関する正確で包括的な情報が得られるようになります。

最も関連性の高いテーブルを選ぶ

データベース、特に大規模なデータベースを扱う場合、プロンプト内のすべてのテーブルのデータ定義言語(DDL)を送信することは非現実的です。

このアプローチは小規模なデータベースでは有効かもしれませんが、現実のデータベースには数百から数千のテーブルが含まれていることが多く、すべてのテーブルを処理するのは非効率的です。

さらに、SQLクエリを効率的に生成するために、言語モデルがデータベース内のすべてのテーブルを認識する必要があるとは考えにくいです。 この課題に対処するため、セマンティック検索機能を活用し、ユーザーのクエリに基づいて最も関連性の高いテーブルのみを選択することができます。

アプローチ

IRIS Vector Searchでセマンティック検索を使用することでこれを実現します。

この方法は、SQL要素の識別子(テーブル、フィールド、キーなど)に意味のある名前がある場合に最も効果的です。識別子が任意のコードである場合は、代わりにデータ dictionaryの使用を検討してください。

手順

  1. テーブル情報の取得

まず、テーブル定義をpandas DataFrameに取り出します。

# Retrieve table definitions into a pandas DataFrame
table_def = get_table_definitions_array(cnx=cnx, schema='Aviation')
table_df = pd.DataFrame(data=table_def, columns=["col_def"])
table_df["id"] = table_df.index + 1
table_df

DataFrame(table_df)は以下のようになります。

col_defid
0CREATE TABLE Aviation.Aircraft (\n Event bigi...1
1CREATE TABLE Aviation.Crew (\n Aircraft varch...2
2CREATE TABLE Aviation.Event (\n ID bigint NOT...3
  1. 定義をDocumentsに分割する

次に、テーブル定義をLangchain Documentsに分割します。 このステップは、大きなテキストの塊を扱い、テキスト埋め込みを抽出するために非常に重要です。

loader = DataFrameLoader(table_df, page_content_column="col_def")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=20, separator="\n")
tables_docs = text_splitter.split_documents(documents)
tables_docs

その結果、tables_docsリストには、次のようにメタデータ付きの分割ドキュメントが含まれます。

[Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 2}, page_content='CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 3}, page_content='CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  ...')]
  1. 埋め込み(Embedding)の抽出とIRISへの保存

次に,langchain-irisのIRISVectorクラスを使って埋め込みベクトルを抽出し、それをIRISに格納します。

tables_vector_store = IRISVector.from_documents(
    embedding=OpenAIEmbeddings(), 
    documents=tables_docs,
    connection_string=iris_conn_str,
    collection_name="sql_tables",
    pre_delete_collection=True
)

注意: pre_delete_collection フラグは、各テスト実行で新鮮なコレクションを確保するために、デモ用に True に設定されています。 本番環境では、このフラグは通常 False に設定します。

  1. 関連文書の検索

テーブルに埋め込み(Embedding)が保存されたことで、ユーザー入力に基づいて関連するテーブルを検索できるようになりました。

input_query = "List the first 2 manufacturers"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

例えば、manufacturers を検索すると、次のような結果が返ってきます。

[Document(metadata={'id': 1}, page_content='GearType varchar,\n  LastInspectionDate timestamp,\n  ...'),
 Document(metadata={'id': 1}, page_content='AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...')]

メタデータから、テーブルID 1 (Aviation.Aircraft)だけが関連性があり、クエリと一致していることがわかります。

  1. エッジケースへの対応

このアプローチは一般的に効果的ではありますが、常に完璧とは限りません。 たとえば、事故現場のクエリを実行すると、関連性の低いテーブルが返されることもあります。

input_query = "List the top 10 most crash sites"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

結果は以下の通りです。

[Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  ...'),
 Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...')]

正しいAviation.Eventテーブルを2回取得したにもかかわらず、Aviation.Aircraftテーブルも表示されることがあります。これは、この例の範囲を超えているため、将来の実装に委ねられます。

  1. 関連テーブルを取得する関数を定義する

このプロセスを自動化するため、ユーザー入力に基づいて関連するテーブルをフィルタリングして返す関数を定義します。

def get_relevant_tables(user_input, tables_vector_store, table_df):
    relevant_tables_docs = tables_vector_store.similarity_search(user_input)
    relevant_tables_docs_indices = [x.metadata["id"] for x in relevant_tables_docs]
    indices = table_df["id"].isin(relevant_tables_docs_indices)
    relevant_tables_array = [x for x in table_df[indices]["col_def"]]
    return relevant_tables_array

この機能は、LLMに送信する関連テーブルのみを効率的に検索し、プロンプトの長さを短縮し、クエリ全体のパフォーマンスを向上させるのに役立ちます。

最も適切な例を選ぶ(Few-Shotプロンプティング)

言語モデル(LLM)を扱うとき、適切な例を提供することは、正確で文脈的に適切な応答を保証するのに役立ちます。

これらの例は "Few-Shot" 例と呼ばれ、LLMが処理すべきクエリの構造とコンテキストを理解するためのガイドとなります。今回のケースでは、IRISのSQL構文とデータベースで使用可能なテーブルを幅広くカバーする多様なSQLクエリを examples_value 変数に入力する必要があります。これは、LLMが正しくないクエリーや無関係なクエリーを生成するのを防ぐのに役立ちます。

クエリ例の定義

以下は、様々なSQL操作を説明するために作られたクエリ例のリストです。

examples = [
    {"input": "List all aircrafts.", "query": "SELECT * FROM Aviation.Aircraft"},
    {"input": "Find all incidents for the aircraft with ID 'N12345'.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
    {"input": "List all incidents in the 'Commercial' operation type.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE OperationType = 'Commercial')"},
    {"input": "Find the total number of incidents.", "query": "SELECT COUNT(*) FROM Aviation.Event"},
    {"input": "List all incidents that occurred in 'Canada'.", "query": "SELECT * FROM Aviation.Event WHERE LocationCountry = 'Canada'"},
    {"input": "How many incidents are associated with the aircraft with AircraftKey 5?", "query": "SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5"},
    {"input": "Find the total number of distinct aircrafts involved in incidents.", "query": "SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft"},
    {"input": "List all incidents that occurred after 5 PM.", "query": "SELECT * FROM Aviation.Event WHERE EventTime > 1700"},
    {"input": "Who are the top 5 operators by the number of incidents?", "query": "SELECT TOP 5 OperatorName, COUNT(*) AS IncidentCount FROM Aviation.Aircraft GROUP BY OperatorName ORDER BY IncidentCount DESC"},
    {"input": "Which incidents occurred in the year 2020?", "query": "SELECT * FROM Aviation.Event WHERE YEAR(EventDate) = '2020'"},
    {"input": "What was the month with most events in the year 2020?", "query": "SELECT TOP 1 MONTH(EventDate) EventMonth, COUNT(*) EventCount FROM Aviation.Event WHERE YEAR(EventDate) = '2020' GROUP BY MONTH(EventDate) ORDER BY EventCount DESC"},
    {"input": "How many crew members were involved in incidents?", "query": "SELECT COUNT(*) FROM Aviation.Crew"},
    {"input": "List all incidents with detailed aircraft information for incidents that occurred in the year 2012.", "query": "SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012"},
    {"input": "Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.", "query": "SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5"},
    {"input": "List all crew members involved in incidents with serious injuries, along with the incident date and location.", "query": "SELECT c.CrewNumber AS 'Crew Number', c.Age, c.Sex AS Gender, e.EventDate AS 'Event Date', e.LocationCity AS 'Location City', e.LocationState AS 'Location State' FROM Aviation.Crew c JOIN Aviation.Event e ON c.EventId = e.EventId WHERE c.Injury = 'Serious'"}
]

関連する事例の選択

例のリストが増え続けることを考えると、LLMにすべての例を提供することは現実的ではありません。代わりに、IRIS Vector SearchとSemanticSimilarityExampleSelectorクラスを使用して、ユーザーのプロンプトに基づいて最も関連性の高い例を特定します。

Example Selector を定義する

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    IRISVector,
    k=5,
    input_keys=["input"],
    connection_string=iris_conn_str,
    collection_name="sql_samples",
    pre_delete_collection=True
)

注意: pre_delete_collection フラグは、各テスト実行で新鮮なコレクションを確保するためのデモンストレーション目的で使用されています。本番環境では、不要な削除を避けるためにこのフラグを False に設定する必要があります。

Selectorに問い合わせる

与えられた入力に最も関連する例を見つけるには、次のようにSelector を使用します。

input_query = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
relevant_examples = example_selector.select_examples({"input": input_query})

結果は以下のようになります。

[{'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'},
 {'input': "Find all incidents for the aircraft with ID 'N12345'.", 'query': "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all aircrafts.', 'query': 'SELECT * FROM Aviation.Aircraft'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'}]

数量に関連した例が特に必要な場合は、それに応じてSelectorに問い合わせることができます。

input_query = "What is the number of incidents involving Boeing aircraft."
quantity_examples = example_selector.select_examples({"input": input_query})

出力は以下の通りです。

[{'input': 'How many incidents are associated with the aircraft with AircraftKey 5?', 'query': 'SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'},
 {'input': 'How many crew members were involved in incidents?', 'query': 'SELECT COUNT(*) FROM Aviation.Crew'},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'}]

この出力には、特にカウントと量を扱った例が含まれています。

今後の検討事項

SemanticSimilarityExampleSelectorは強力ですが、選択された例がすべて完璧であるとは限らないことに注意することが重要です。

将来的な改良には、フィルターやしきい値を追加して関連性の低い結果を除外し、最も適切な例だけがLLMに提供されるようにすることが含まれるかもしれません。

精度テスト

プロンプトとSQLクエリ生成のパフォーマンスを評価するために、一連のテストを設定し実行する必要があります。その目的は、LLMがユーザーの入力に基づいてSQLクエリを生成する際に、例題に基づいたfew shotを使用する場合と使用しない場合の、その精度を評価することである。

SQLクエリを生成する関数

まず、LLMを使って、提供されたコンテキスト、プロンプト、ユーザー入力、その他のパラメーターに基づいてSQLクエリーを生成する関数を定義します。

def get_sql_from_text(context, prompt, user_input, use_few_shots, tables_vector_store, table_df, example_selector=None, example_prompt=None):
    relevant_tables = get_relevant_tables(user_input, tables_vector_store, table_df)
    context["table_info"] = "\n\n".join(relevant_tables)

    examples = example_selector.select_examples({"input": user_input}) if example_selector else []
    context["examples_value"] = "\n\n".join([
        example_prompt.invoke(x).to_string() for x in examples
    ])
    
    model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
    output_parser = StrOutputParser()
    chain_model = prompt | model | output_parser
    
    response = chain_model.invoke({
        "top_k": context["top_k"],
        "table_info": context["table_info"],
        "examples_value": context["examples_value"],
        "input": user_input
    })
    return response

プロンプトの実行

例文がある場合とない場合のプロンプトをテストします。

# Prompt execution **with** few shots
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=True, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
    example_selector=example_selector, 
    example_prompt=example_prompt,
)
print(response_with_few_shots)
SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.EventId = a.EventId
WHERE Year(e.EventDate) = 2010
# Prompt execution **without** few shots
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_no_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=False, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
)
print(response_with_no_few_shots)
SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.ID = a.Event
WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
Utility Functions for Testing

生成されたSQLクエリをテストするために、いくつかのユーティリティ関数を定義します。

def execute_sql_query(cnx, query):
    try:
        cursor = cnx.cursor()
        cursor.execute(query)
        rows = cursor.fetchall()
        return rows
    except:
        print('Error running query:')
        print(query)
        print('-'*80)
    return None

def sql_result_equals(cnx, query, expected):
    rows = execute_sql_query(cnx, query)
    result = [set(row._asdict().values()) for row in rows or []]
    if result != expected and rows is not None:
        print('Result not as expected for query:')
        print(query)
        print('-'*80)
    return result == expected
# SQL test for prompt **with** few shots
print("SQL is OK" if not execute_sql_query(cnx, response_with_few_shots) is None else "SQL is not OK")
    SQL is OK
# SQL test for prompt **without** few shots
print("SQL is OK" if not execute_sql_query(cnx, response_with_no_few_shots) is None else "SQL is not OK")
    error on running query: 
    SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.ID = a.Event
    WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
    --------------------------------------------------------------------------------
    SQL is not OK

テストの定義と実行

一連のテストケースを定義し、実行します。

tests = [{
    "input": "What were the top 3 years with the most recorded events?",
    "expected": [{128, 2003}, {122, 2007}, {117, 2005}]
},{
    "input": "How many incidents involving Boeing aircraft.",
    "expected": [{5}]
},{
    "input": "How many incidents that resulted in fatalities.",
    "expected": [{237}]
},{
    "input": "List event Id and date and, crew number, age and gender for incidents that occurred in 2013.",
    "expected": [{1, datetime.datetime(2013, 3, 4, 11, 6), '20130305X71252', 59, 'M'},
                 {1, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 32, 'M'},
                 {2, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 35, 'M'},
                 {1, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 25, 'M'},
                 {2, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 34, 'M'},
                 {1, datetime.datetime(2013, 2, 1, 15, 0), '20130203X53401', 29, 'M'},
                 {1, datetime.datetime(2013, 2, 15, 15, 0), '20130218X70747', 27, 'M'},
                 {1, datetime.datetime(2013, 3, 2, 15, 0), '20130303X21011', 49, 'M'},
                 {1, datetime.datetime(2013, 3, 23, 13, 52), '20130326X85150', 'M', None}]
},{
    "input": "Find the total number of incidents that occurred in the United States.",
    "expected": [{1178}]
},{
    "input": "List all incidents latitude and longitude coordinates with more than 5 injuries that occurred in 2010.",
    "expected": [{-78.76833333333333, 43.25277777777778}]
},{
    "input": "Find all incidents in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model.",
    "expected": [
        {datetime.datetime(2010, 5, 20, 13, 43), '20100520X60222', 'CIRRUS DESIGN CORP', 'Farmingdale', 'New York', 'SR22'},
        {datetime.datetime(2010, 4, 11, 15, 0), '20100411X73253', 'CZECH AIRCRAFT WORKS SPOL SRO', 'Millbrook', 'New York', 'SPORTCRUISER'},
        {'108', datetime.datetime(2010, 1, 9, 12, 55), '20100111X41106', 'Bayport', 'New York', 'STINSON'},
        {datetime.datetime(2010, 8, 1, 14, 20), '20100801X85218', 'A185F', 'CESSNA', 'New York', 'Newfane'}
    ]
}]

精度評価

テストを実施し、精度を計算します。

def execute_tests(cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt):
    tests_generated_sql = [(x, get_sql_from_text(
            context, 
            prompt, 
            user_input=x['input'], 
            use_few_shots=use_few_shots, 
            tables_vector_store=tables_vector_store, 
            table_df=table_df,
            example_selector=example_selector if use_few_shots else None, 
            example_prompt=example_prompt if use_few_shots else None,
        )) for x in deepcopy(tests)]
    
    tests_sql_executions = [(x[0], sql_result_equals(cnx, x[1], x[0]['expected'])) 
                            for x in tests_generated_sql]
    
    accuracy = sum(1 for i in tests_sql_executions if i[1] == True) / len(tests_sql_executions)
    print(f'Accuracy: {accuracy}')
    print('-'*80)

結果

# Accuracy tests for prompts executed **without** few shots
use_few_shots = False
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT "EventDate", COUNT("EventId") as "TotalEvents"
    FROM Aviation.Event
    GROUP BY "EventDate"
    ORDER BY "TotalEvents" DESC
    TOP 3;
    --------------------------------------------------------------------------------
    error on running query: 
    SELECT "EventId", "EventDate", "C"."CrewNumber", "C"."Age", "C"."Sex"
    FROM "Aviation.Event" AS "E"
    JOIN "Aviation.Crew" AS "C" ON "E"."ID" = "C"."EventId"
    WHERE "E"."EventDate" >= '2013-01-01' AND "E"."EventDate" < '2014-01-01'
    --------------------------------------------------------------------------------
    result not expected for query: 
    SELECT TOP 3 "e"."EventId", "e"."EventDate", "e"."LocationCity", "e"."LocationState", "a"."AircraftManufacturer", "a"."AircraftModel"
    FROM "Aviation"."Event" AS "e"
    JOIN "Aviation"."Aircraft" AS "a" ON "e"."ID" = "a"."Event"
    WHERE "e"."EventDate" >= '2010-01-01' AND "e"."EventDate" < '2011-01-01'
    --------------------------------------------------------------------------------
    accuracy: 0.5714285714285714
    --------------------------------------------------------------------------------
# Accuracy tests for prompts executed **with** few shots
use_few_shots = True
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.EventId = a.EventId
    WHERE Year(e.EventDate) = 2010 TOP 3
    --------------------------------------------------------------------------------
    accuracy: 0.8571428571428571
    --------------------------------------------------------------------------------

結論

例(few shots)を使って生成されたSQLクエリの精度は、例なしで生成されたもの(85%対57%)に比べて約49%高くなりました。

ご参考

1
0 146