# OpenAIのFunction Call

OpenAIの新しいfunction-call APIは、MyScaleを含めてすべてを変えました。

New OpenAI Release

OpenAIは、より安価な16KコンテキストのGPT-3.5 Turboや、より多くのGPT-4モデルなど、多くのアップデートをリリースしました。また、開発者にとって強力なツールとなる新しいAPIインターフェースであるfunction call (opens new window)も提供されました。

Function callは、プロンプトの使い方を変えます。すでに、構造化クエリやベクトル検索を自動化するための優れた試みがいくつかあります。例えば、LangChainのSQLChain (opens new window)self-query retrievers (opens new window)などです。これらのアプローチのほとんどは、大きなプロンプトが必要であり、コストがかかり、調整が難しいという問題があります。OpenAIの新しいfunction callは、プロンプトに対してトークンを節約するため、長い例を複数使用して関数の使用方法を示す必要がありません。

私たちは、新しくリリースされたGPT-3.5 (gpt-3.5-turbo-0613)が新しいfunction callとうまく機能することを見つけました。数回の試行で、LangChainのself-query retrieverに相当するプロンプトを開発することができました。演算子や比較演算子を定義する必要はありません。プロンプトだけで実現できます。詳細は、github (opens new window)で確認できます。

# 開始する前に...

  • いくつかの依存関係をインストールする必要があります:
pip3 install langchain openai clickhouse-connect

方法がわからない場合は、クラスタ管理の公式ドキュメントを参照してください。

# プロンプトを確認する

簡単にするために、LangChainのベクトルストアとself-query retrieversのいくつかのヘルパー関数を再利用しました。

# ベクトルストアを作成し、データを挿入する

8つの論文のメタデータと要約 (opens new window)を提供します(arXiv (opens new window)に感謝します)。より多くのデータは、AWSのストレージ (opens new window)で見つけることができます。

さあ、コードを見てみましょう:

import json
from langchain.schema import Document
from langchain.vectorstores import MyScale
from langchain.embeddings import HuggingFaceEmbeddings
def str2doc(_str):
    j = json.loads(_str)
    return Document(page_content=j['abstract'], metadata=j['metadata'])
with open('func_call_data.jsonl') as f:
    docs = [str2doc(l) for l in f.readlines()]
vectorstore = MyScale.from_documents(
    documents=docs, embedding=HuggingFaceEmbeddings())

# メタデータの列を定義する

ここでは、LangChainから_format_attribute_infoを借りています。これにより、AttributeInfoをプレーンな文字列に変換し、プロンプトに適した形式にすることができます。

これを変更して、さらに多くのデータ型や関数を試してみることをお勧めします。関数を使用して列を定義することもできます。例えば、length(metadata.categories)はそのカテゴリの長さを示します。ClickHouseのドキュメント (opens new window)でサポートされている興味深い関数を見つけてみてください。ベクトル検索とともにネイティブにサポートしています。そして、私たちと一緒にそれらをより良くするために、Discord (opens new window)に参加して、新しい発見や新しい考えを共有してください!

from langchain.chains.query_constructor.base import _format_attribute_info, AttributeInfo
metadata_field_info=[
    AttributeInfo(
        name="metadata.pubdate",
        description="論文が公開された日付。文字列形式のタイムスタンプを比較可能な形式に変換するために`parseDateTime32BestEffort()`を使用する必要があります。",
        type="timestamp",
    ),
    AttributeInfo(
        name="metadata.authors",
        description="著者のリスト",
        type="list[string]",
    ),
    AttributeInfo(
        name="metadata.title",
        description="論文のタイトル",
        type="string",
    ),
    AttributeInfo(
        name="text",
        description="論文の要約",
        type="string",
    ),
    AttributeInfo(
        name="metadata.categories",
        description="この論文のarXivのカテゴリ",
        type="list[string]"
    ),
    AttributeInfo(
        name="length(metadata.categories)",
        description="この論文のarXivのカテゴリの長さ",
        type="int"
    ),
]
formated = _format_attribute_info(metadata_field_info)

# フィルタリングされたベクトル検索を構築するためのFunction Call

self-query retrieversと同様に、function-callベースのクエリエも、ベクトルストアのインターフェースを呼び出すために3つの引数を出力します:

  • query:後で埋め込みに変換されるクエリ文字列。
  • where_str:フィルタリングを実行するための一般的なWHERE文字列。ただし、WHEREキーワードは含まれません。
  • limit:上位limitの要素が返されます。デフォルトは4です。

これがOpenAIのfunction callとの連携方法です:

import openai
# 簡単ですね?
query = "ベイジアンネットワークとは何ですか?"
# いくつかの制約を加えましょう...
query += "2013年2月以降に公開された記事を使用してください "
# もっと欲しいですか?キーワードフィルター。
query += "および要約が`artificial`に似ている論文 "
# それはもっと良いですね!クロスモーダルの論文...
query += "2つ以上のカテゴリを持つ論文 "
# CVの人間ですので、CVの論文だけが欲しいです
query += "かつそのカテゴリに`cs.CV`が含まれている必要があります。"
# さあ、構造化プロンプトです:
completion = openai.ChatCompletion.create(
    # 新しいgptモデルを使用しました
    model="gpt-3.5-turbo-0613",
    # 安定した動作を保証するために温度を0に設定
    temperature=0,
    # ここからがfunction callです:
    # 以下が必要です:
    # 1. 関数に名前を付けること
    # 2. 関数の説明を書くこと
    # 3. パラメータの名前と型を定義すること
    functions=[{"name": "to_structued_sql",
                "description":
                ("クエリをクエリ文字列とフィルタリング用のWHERE文字列に変換します。"
                 "リスト内の要素をチェックする場合は、`has(column, element)`を使用してください。"),
                "parameters": {"type": "object",
                               "properties": {
                                   "query": {"type": "string"},
                                   "where_str": {"type": "string", },
                                   "limit": {
                                       "type": "integer",
                                       "description": "デフォルトは4です"}
                               },
                               "required": ["subject", "where_str", "limit"]
                               }
                },],
    # 呼び出す関数の名前。'auto'の場合、モデルが自動的に決定します
    function_call="auto",
    # ここでおなじみのチャット補完のプロンプト...
    # モデルの振る舞いを制限するためのルールを書くことができます。
    messages=[
        {
            "role": "system",
            "content": ("`metadata`を提供する必要があります。"
                        "タイムスタンプを比較可能な形式に変換するために`parseDateTime32BestEffort()`を使用します。"),
        },
        {
            "role": "user",
            "content": f"Metadata: {formated}"
        },
        {
            "role": "system",
            "content": "今、クエリを入力できます",
        },
        {
            "role": "user",
            "content": query
        },
    ],
)

それでは検索しましょう!

これは、LangChainのself-query retrieversとまったく同じです。さらに柔軟です - 任意のSQLを書くことができます... そして、ユーザー定義関数さえも書くことができます。制限はありませんので、実行するためのケースを作成してください!

ret = vectorstore.similarity_search(**search_kwargs)
for r in ret:
    print(r)

# Function Callを使用する際のヒント

以下は、function callの使用時のヒントです:

  1. 読みやすい名前は、LLMにより多くの情報を提供します。これは、この新しいfunction callを使用する場合でも、従来のプロンプトを使用する場合でも同様です。
  2. 列にいくつかのルールを設定したい場合、例えば、タイプ変換など、他の値と比較する場合は、列の定義で行ってください。ルールは関連するデータに近い位置に配置するべきです。
  3. LLMは、それが馴染みのあるデータ間のマッピング関数を模倣する傾向があります。このデモのように、自然言語とSQLの両方がLLMにとっては非常に一般的なので、結果は期待を超えるものになります。

あなたがこれについてもっと発見できると信じています!MyScaleと一緒にDiscord (opens new window)であなたの発見を共有してみませんか?

# 最後に

私たちは、これが人々やインテリジェントシステムがベクトルデータベースと連携する方法の未来であると信じています。私たちは、このようなアップグレードと新しい機能に非常に興奮しており、よりスケーラブルで安定したAGIシステムを開発するための手助けができることを願っています。🚀 MyScaleがあなたの成長と成功に役立てることができるように、それを実現します!今すぐDiscord (opens new window)に参加して、温かい抱擁を受け取りましょう!🤗 🤗