「ワードエンベディング:より良い回答のためにチャットボットに文脈を与える」
Word Embedding Providing Context to Chatbots for Better Responses
OpenAIのChatGPTは非常に知能が高いことは疑いありません。弁護士の資格試験に合格し、医者と同じような知識を持ち、一部のテストではIQが155と評価されています。ただし、無知を認める代わりに情報を捏造する傾向があります。この傾向は、2021年をもって知識が停止するという事実と相まって、GPT APIを使用して特殊な製品を構築する際に課題を提起します。
これらの障壁をどのように乗り越えることができるのでしょうか? GPT-3のようなモデルに新しい知識をどのように伝えることができるのでしょうか? Python、OpenAI API、およびワード埋め込みを用いた質問応答ボットを構築することで、これらの問いに取り組むことが私の目標です。
私が構築するもの
私は、プロンプトから連続的な統合パイプラインを生成するボットを作成するつもりです。このプロンプトは、Semaphore CI/CDでYAMLで表されます。
以下は、ボットの動作例です:
- BERTopicを使用したクラスごとのトピック
- 「NVIDIAがインドの巨大企業と提携し、世界最大の人口を持つ国でAIを進める」
- 「VAST DataのプラットフォームがAIイノベーションの障壁を取り除く方法」
実行中のプログラムのスクリーンショットです。画面上で、python query.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub"
というコマンドが実行され、リクエストされたアクションを実行するCIパイプラインに対応したYAMLがプログラムから出力されます。
DocsGPT、My AskAI、Librariaなどのプロジェクトの精神に倣い、私はGPT-3モデルにSemaphoreとパイプライン構成ファイルの生成方法を「教える」ことを計画しています。これは、既存のドキュメンテーションを活用することで実現します。
ボットの構築に関する事前知識を必要とせず、クリーンなコードを保ち、必要に応じて自分の要件に適応できるようにします。
前提条件
このチュートリアルに従うためには、ボットのコーディング経験やニューラルネットワークの知識は必要ありません。ただし、以下が必要です:
-
Python 3。
-
Pineconeアカウント(無料のスタータープランに登録)。
-
OpenAI APIキー(有料、クレジットカードが必要)。新規ユーザーは最初の3ヶ月間に$5の無料クレジットを利用できます。
しかし、ChatGPTは学習できないのでしょうか?
ChatGPT、正確にはGPT-3とGPT-4、それらを駆動するLarge Language Models(LLM)は、巨大なデータセットで訓練されており、カットオフ日は2021年9月頃です。
要するに、GPT-3はその日付以降のイベントについてはほとんど知りません。次の単純なプロンプトでこれを確認できます:
一部のOpenAIモデルは微調整を受けることができますが、私たちが興味を持つようなより高度なモデルでは、トレーニングデータを増やすことはできません。
GPT-3からトレーニングデータを超えた回答をどのように得ることができるのでしょうか? 1つの方法は、テキスト理解能力を利用することです。関連するコンテキストをプロンプトに追加することで、正しい回答を得ることができる可能性が高くなります。
以下の例では、FIFA公式サイトからコンテキストを提供し、応答が大きく異なることがわかります:
任意のプロンプトに対して何が関連しているかを知るにはどうすればよいのでしょうか? これに対処するためには、ワード埋め込みについて調査する必要があります。
ワード埋め込みとは何ですか?
言語モデルの文脈で、埋め込みとは単語、文、または文書全体をベクトルまたは数値のリストとして表現する方法です。
埋め込みを計算するには、word2vecやtext-embedding-ada-002などのニューラルネットワークが必要です。これらのネットワークは、大量のテキストで訓練され、特定のパターンがトレーニングデータにどのような頻度で現れるかを分析することで単語間の関係を見つけることができます。
次の単語があるとしましょう:
-
猫
-
犬
-
ボール
-
家
これらの埋め込みネットワークのいずれかを使用して各単語のベクトルを計算するとします。例えば:
各単語のベクトルを取得したら、それらを使用してテキストの意味を表現することができます。例えば、「猫がボールを追いかけた」という文は、ベクトル[0.1、0.2、0.3、0.4、0.5] + [0.2、0.4、0.6、0.8、1.0] = [0.3、0.6、0.9、1.2、1.5]として表現されます。このベクトルは、動物がオブジェクトを追いかけるという文を表しています。
ワードエンベディングは、類似した意味を持つ単語や文章が近くに配置される多次元空間として可視化されます。ベクトル間の「距離」を計算することで、任意の入力テキストに対して類似した意味を見つけることができます。
埋め込みをベクトル空間として3Dで表現すると、実際には数百または数千の次元を持つ場合があります。出典:AIのマルチツール:ベクトルエンベディングに出会う
これに関連する実際の数学は、この記事の範囲外です。ただし、重要なポイントは、ベクトル演算によって数学を使って意味を操作または決定できるということです。言葉「クイーン」を表すベクトルから「女性」のベクトルを引き、その後「男性」のベクトルを加えると、「王」の近くにあるベクトルが得られるはずです。また、「息子」を加えると、「王子」の近くになります。
トークンを使用した埋め込みニューラルネットワーク
これまでは、単語を入力とし、数値を出力とする埋め込みニューラルネットワークについて説明しました。しかし、多くの最新のネットワークでは、単語ではなくトークンを処理するようになっています。
トークンは、モデルによって処理できるテキストの最小単位です。トークンは、単語、文字、句読点、記号、または単語の一部などになります。
テキストをトークンに変換する様子は、OpenAIのオンライントークナイザーを使って実験することで確認できます。このトークナイザーは、テキストをトークンに変換し、それぞれを番号で表します。
トークンと単語の間には通常、1対1の関係があります。ほとんどのトークンには単語と先行するスペースが含まれます。ただし、「embedding」のような特殊な場合では、「embed」と「ding」という2つのトークン、「capabilities」の場合は4つのトークンから構成されます。トークンIDをクリックすると、モデルが各トークンの数値表現を表示します。
埋め込みを使用してスマートなボットを設計する
埋め込みの理解ができたので、次の質問は次のようになります:どのようにして埋め込みがスマートなボットの構築に役立つのか?
まず、GPT-3 APIを直接使用した場合の動作を考えてみましょう。ユーザーがプロンプトを入力すると、モデルは最善の能力で応答します。
しかし、コンテキストを加えると状況は変わります。たとえば、私がコンテキストを提供した上でChatGPTにワールドカップの優勝者について尋ねた場合、結果は大きく異なります。
したがって、スマートなボットを構築するための計画は次のとおりです:
-
ユーザーのプロンプトを取得します。
-
そのプロンプトの埋め込みを計算し、ベクトルを得ます。
-
ベクトルに近いドキュメントをデータベースから検索し、元のプロンプトに意味的に関連するものとします。
-
元のプロンプトをGPT-3に送信し、関連するコンテキストとともに。
-
GPT-3の応答をユーザーに送信します。
まずは、ほとんどのプロジェクトと同様に、データベースの設計から始めましょう。
埋め込みを使用して知識データベースを作成する
コンテキストデータベースには、元のドキュメントとそれに対応するベクトルが含まれている必要があります。原則として、このタスクには任意の種類のデータベースを使用できますが、最適なツールはベクトルデータベースです。
ベクトルデータベースは、高次元ベクトルデータを格納して検索するために設計された特殊なデータベースです。検索にSQLなどのクエリ言語を使用する代わりに、ベクトルを提供し、N個の最も近い隣接データを要求します。
ベクトルを生成するためには、OpenAIのtext-embedding-ada-002を使用します。これは、最速かつ最も費用効果の高いモデルです。このモデルは、入力テキストをトークンに変換し、Transformerと呼ばれるアテンションメカニズムを使用してその関係を学習します。このニューラルネットワークの出力は、テキストの意味を表すベクトルです。
コンテキストデータベースを作成するために、以下の手順を実行します:
-
ソースドキュメントを収集します。
-
関連のないドキュメントをフィルタリングします。
-
各ドキュメントの埋め込みを計算します。
-
ベクトル、元のテキスト、およびその他の関連するメタデータをデータベースに保存します。
ドキュメントをベクトルに変換する
まず、OpenAIのAPIキーを持つ環境ファイルを初期化する必要があります。このファイルはバージョン管理にコミットしないようにし、APIキーはプライベートでアカウントに紐づいているためです。
export OPENAI_API_KEY=YOUR_API_KEY
次に、Pythonアプリケーションのために仮想環境を作成します:
$ virtualenv venv
$ source venv/bin/activate
$ source .env
そして、OpenAIパッケージをインストールします:
```bash
$ pip install openai numpy
文字列「Docker Container」の埋め込みを計算してみましょう。これはPython REPLまたはPythonスクリプトで実行できます:
$ python
>>> import openai
>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")
>>> embeddings
JSON: {
"data": [
{
"embedding": [
-0.00530336843803525,
0.0013223182177171111,
... 1533 件の項目がさらにあります ...,
-0.015645816922187805
],
"index": 0,
"object": "embedding"
}
],
"model": "text-embedding-ada-002-v2",
"object": "list",
"usage": {
"prompt_tokens": 2,
"total_tokens": 2
}
}
ご覧の通り、OpenAIのモデルからは、text-embedding-ada-002のベクトルサイズである1536の項目を含むembedding
リストが返されます。
Pineconeに埋め込みを格納する
Chromaなど、複数のベクトルデータベースエンジンが選択できますが、私はシンプルな管理型データベースであるPineconeを選びました。Starterプランは、必要なすべてのデータを処理するのに十分な能力を持っています。
Pineconeアカウントを作成し、APIキーと環境を取得した後、両方の値を.env
ファイルに追加します。
これで、.env
にはPineconeとOpenAIの秘密が含まれるはずです。
export OPENAI_API_KEY=YOUR_API_KEY
# Pineconeの秘密
export PINECONE_API_KEY=YOUR_API_KEY
export PINECONE_ENVIRONMENT=YOUR_PINECONE_DATACENTER
次に、Python用のPineconeクライアントをインストールします:
$ pip install pinecone-client
データベースを初期化する必要があります。これはdb_create.py
スクリプトの内容です:
# db_create.py
import pinecone
import openai
import os
index_name = "semaphore"
embed_model = "text-embedding-ada-002"
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)
embedding = openai.Embedding.create(
input=[
"ここにサンプルドキュメントテキストを入力します",
"各バッチにはいくつかのフレーズが含まれます"
], engine=embed_model
)
if index_name not in pinecone.list_indexes():
print("Pineconeインデックスを作成中: " + index_name)
pinecone.create_index(
index_name,
dimension=len(embedding['data'][0]['embedding']),
metric='cosine',
metadata_config={'indexed': ['source', 'id']}
)
スクリプトの作成には数分かかる場合があります。
$ python db_create.py
次に、tiktokenパッケージをインストールします。これを使用して、ソースドキュメントに含まれるトークンの数を計算します。これは重要です。なぜなら、埋め込みモデルは最大8191のトークンしか処理できないからです。
$ pip install tiktoken
パッケージのインストール中に、進捗バーを表示するためにtqdm
もインストールしましょう。
$ pip install tqdm
次に、ドキュメントをデータベースにアップロードする必要があります。このためのスクリプトはindex_docs.py
と呼ばれます。まず、必要なモジュールをインポートし、いくつかの定数を定義します:
# index_docs.py
# Pineconeデータベースの名前とアップロードバッチサイズ
index_name = 'semaphore'
upsert_batch_size = 20
# OpenAIの埋め込みとトークナイザーモデル
embed_model = "text-embedding-ada-002"
encoding_model = "cl100k_base"
max_tokens_model = 8191
次に、トークンの数をカウントする関数が必要です。OpenAIのページにトークンカウンターの例があります:
import tiktoken
def num_tokens_from_string(string: str) -> int:
"""テキスト文字列内のトークンの数を返します。"""
encoding = tiktoken.get_encoding(encoding_model)
num_tokens = len(encoding.encode(string))
return num_tokens
最後に、元のドキュメントを使用可能な例に変換するためのいくつかのフィルタリング関数が必要です。ドキュメントのほとんどの例はコードフェンスの間にありますので、すべてのYAMLコードを抽出します:
import re
def extract_yaml(text: str) -> str:
"""テキスト内で見つかったすべてのYAMLコードブロックのリストを返します。"""
matches = [m.group(1) for m in re.finditer("```yaml([\w\W]*?)```", text)]
return matches
関数は終了しました。次に、ファイルをメモリに読み込み、例を抽出します:
from tqdm import tqdm
import sys
import os
import pathlib
repo_path = sys.argv[1]
repo_path = os.path.abspath(repo_path)
repo = pathlib.Path(repo_path)
markdown_files = list(repo.glob("**/*.md")) + list(
repo.glob("**/*.mdx")
)
print(f"{repo_path}内のMarkdownファイルからYAMLを抽出しています")
new_data = []
for i in tqdm(range(0, len(markdown_files))):
markdown_file = markdown_files[i]
with open(markdown_file, "r") as f:
relative_path = markdown_file.relative_to(repo_path)
text = str(f.read())
if text == '':
continue
yamls = extract_yaml(text)
j = 0
for y in yamls:
j = j+1
new_data.append({
"source": str(relative_path),
"text": y,
"id": f"github.com/semaphore/docs/{relative_path}[{j}]"
})
この時点で、すべてのYAMLはnew_data
リストに格納されているはずです。最後のステップは、埋め込みをPineconeにアップロードすることです。
import pinecone
import openai
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, enviroment=env)
index = pinecone.Index(index_name)
print(f"埋め込みを作成し、ベクトルをデータベースにアップロードしています")
for i in tqdm(range(0, len(new_data), upsert_batch_size)):
i_end = min(len(new_data), i+upsert_batch_size)
meta_batch = new_data[i:i_end]
ids_batch = [x['id'] for x in meta_batch]
texts = [x['text'] for x in meta_batch]
embedding = openai.Embedding.create(input=texts, engine=embed_model)
embeds = [record['embedding'] for record in embedding['data']]
# アップロード前にメタデータをクリーンアップする
meta_batch = [{
'id': x['id'],
'text': x['text'],
'source': x['source']
} for x in meta_batch]
to_upsert = list(zip(ids_batch, embeds, meta_batch))
index.upsert(vectors=to_upsert)
参考までに、デモリポジトリのindex_docs.pyファイルを見つけることができます。
データベースのセットアップを完了するために、インデックススクリプトを実行しましょう:
$ git clone https://github.com/semaphoreci/docs.git /tmp/docs
$ source .env
$ python index_docs.py /tmp/docs
データベースのテスト
Pineconeダッシュボードにはデータベース内のベクトルが表示されます。
次のコードを使用してデータベースをクエリできます。スクリプトとして実行するか、Python REPLで直接実行できます:
$ python
>>> import os
>>> import pinecone
>>> import openai
# 文字列「Docker Container」の埋め込みを計算する
>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")
# データベースに接続する
>>> index_name = "semaphore"
>>> api_key = os.getenv("PINECONE_API_KEY")
>>> env = os.getenv("PINECONE_ENVIRONMENT")
>>> pinecone.init(api_key=api_key, environment=env)
>>> index = pinecone.Index(index_name)
# データベースをクエリする
>>> matches = index.query(embeddings['data'][0]['embedding'], top_k=1, include_metadata=True)
>>> matches['matches'][0]
{'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
'metadata': {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
'source': 'docs/ci-cd-environment/docker-authentication.md',
'text': '\n'
'# .semaphore/semaphore.yml\n'
'version: v1.0\n'
'name: Using a Docker image\n'
'agent:\n'
' machine:\n'
' type: e1-standard-2\n'
' os_image: ubuntu1804\n'
'\n'
'blocks:\n'
' - name: Run container from Docker Hub\n'
' task:\n'
' jobs:\n'
' - name: Authenticate docker pull\n'
' commands:\n'
' - checkout\n'
' - echo $DOCKERHUB_PASSWORD | docker login '
'--username "$DOCKERHUB_USERNAME" --password-stdin\n'
' - docker pull /\n'
' - docker images\n'
' - docker run /\n'
' secrets:\n'
' - name: docker-hub\n'},
'score': 0.796259582,
'values': []}
最初のマッチは、Dockerイメージを取得して実行するSemaphoreパイプラインのYAMLです。”Docker Containers”という検索文字列に関連しているため、良い出発点です。
ボットの構築
データを持っており、それをクエリする方法もわかっています。それでは、ボットでそれを活用しましょう。
プロンプトの処理手順は次のとおりです:
-
ユーザーのプロンプトを取得する。
-
ベクトルを計算する。
-
データベースから関連するコンテキストを取得する。
-
ユーザーのプロンプトとコンテキストをGPT-3に送信する。
-
モデルの応答をユーザーに返す。
いつものように、ボットのメインスクリプトであるcomplete.py
にいくつかの定数を定義します:
# complete.py
# Pineconeデータベース名、一致数の取得
# 類似性のカットオフスコア、およびコンテキストとしてのトークンの量
index_name = 'semaphore'
context_cap_per_query = 30
match_min_score = 0.75
context_tokens_per_query = 3000
# OpenAI LLMモデルのパラメータ
chat_engine_model = "gpt-3.5-turbo"
max_tokens_model = 4096
temperature = 0.2
embed_model = "text-embedding-ada-002"
encoding_model_messages = "gpt-3.5-turbo-0301"
encoding_model_strings = "cl100k_base"
import pinecone
import os
# Pineconeデータベースとインデックスへの接続
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)
index = pinecone.Index(index_name)
次に、OpenAIの例に示されているように、トークンをカウントする関数を追加します。最初の関数は文字列内のトークンをカウントし、2番目の関数はメッセージ内のトークンをカウントします。メッセージについては後で詳しく見ていきます。今のところ、会話の状態をメモリに保持する構造であると言っておきましょう。
import tiktoken
def num_tokens_from_string(string: str) -> int:
"""テキスト文字列内のトークンの数を返します。"""
encoding = tiktoken.get_encoding(encoding_model_strings)
num_tokens = len(encoding.encode(string))
return num_tokens
def num_tokens_from_messages(messages):
"""メッセージのリストで使用されたトークンの数を返します。モデルと互換性があります。"""
try:
encoding = tiktoken.encoding_for_model(encoding_model_messages)
except KeyError:
encoding = tiktoken.get_encoding(encoding_model_strings)
num_tokens = 0
for message in messages:
num_tokens += 4 # 各メッセージは{role/name}\n{content}\nに続く
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name": # 名前がある場合は、役割は省略されます
num_tokens += -1 # 役割は常に必要であり、常に1つのトークンです
num_tokens += 2 # 各応答はアシスタントによってプライムされます
return num_tokens
次の関数は元のプロンプトとコンテキストの文字列を取り、GPT-3用の豊かなプロンプトを返します:
def get_prompt(query: str, context: str) -> str:
"""クエリとコンテキストを含むプロンプトを返します。"""
return (
f"要求されたタスクを実行するための継続的な統合パイプラインのYAMLコードを作成してください。\n" +
f"以下には、役に立つかもしれないいくつかのコンテキストがあります。関連性がない場合は無視してください。\n\n" +
f"コンテキスト:\n{context}" +
f"\n\nタスク: {query}\n\nYAMLコード:"
)
get_message
関数は、APIと互換性のある形式でプロンプトをフォーマットします:
def get_message(role: str, content: str) -> dict:
"""OpenAI APIの補完に使用するメッセージを生成します。"""
return {"role": role, "content": content}
モデルの反応に影響を与える3種類の役割があります:
-
User:ユーザーの元のプロンプト用。
-
System:アシスタントの動作を設定するのに役立ちます。その効果については議論がありますが、メッセージリストの最後に送信するとより効果的であるようです。
-
Assistant:モデルの以前の応答を表します。OpenAI APIには「メモリ」がないため、会話を維持するために各インタラクションごとにモデルの以前の応答を送信する必要があります。
これからは、興味深い部分に入ります。 get_context
関数は、プロンプトを取得し、データベースをクエリし、条件のいずれかが満たされるまでコンテキスト文字列を生成します:
-
完全なテキストが
context_tokens_per_query
を超える、つまりコンテキストのために予約されたスペース。 -
検索関数が要求されたすべてのマッチを取得する。
-
類似度スコアが
match_min_score
未満のマッチは無視する。
import openai
def get_context(query: str, max_tokens: int) -> list:
"""OpenAI モデルのメッセージを生成します。 `context_token_limit` の制限に達するまでコンテキストを追加します。プロンプト文字列を返します。"""
embeddings = openai.Embedding.create(
input=[query],
engine=embed_model
)
# データベースを検索する
vectors = embeddings['data'][0]['embedding']
embeddings = index.query(vectors, top_k=context_cap_per_query, include_metadata=True)
matches = embeddings['matches']
# コンテキストをフィルタリングして集約します
usable_context = ""
context_count = 0
for i in range(0, len(matches)):
source = matches[i]['metadata']['source']
if matches[i]['score'] < match_min_score:
# 類似度スコアが低いコンテキストをスキップします
continue
context = matches[i]['metadata']['text']
token_count = num_tokens_from_string(usable_context + '\n---\n' + context)
if token_count < context_tokens_per_query:
usable_context = usable_context + '\n---\n' + context
context_count = context_count + 1
print(f"クエリに対するコンテキストが {context_count} つ見つかりました")
return usable_context
次の最後の関数である complete
は、OpenAI に API リクエストを送信し、モデルの応答を返します。
def complete(messages):
"""OpenAI モデルにクエリを送信します。最初の回答を返します。"""
res = openai.ChatCompletion.create(
model=chat_engine_model,
messages=messages,
temperature=temperature
)
return res.choices[0].message.content.strip()
以上です。これでコマンドライン引数に対処し、正しい順序で関数を呼び出すだけです:
import sys
query = sys.argv[1]
context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)
# OpenAI API に送信するためのメッセージリストを初期化します
messages = []
messages.append(get_message('user', prompt))
messages.append(get_message('system', 'YAML コードを作成し、Semaphore の継続的インテグレーションパイプラインの説明を行う役立つアシスタントです。コードフェンス内の YAML コードを返します。'))
if num_tokens_from_messages(messages) >= max_tokens_model:
raise Exception('モデルのトークンサイズ制限に達しました')
print("クエリを処理中です... ")
answer = complete(messages)
print("回答:\n")
print(answer)
messages.append(get_message('assistant', answer))
スクリプトを実行して結果を確認する時間です:
$ python complete.py "Docker Hub に Docker イメージをビルドしてアップロードする CI パイプラインを作成する"
結果は:
version: v1.0
name: Docker Build and Push
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
blocks:
- name: "Build and Push Docker Image"
task:
jobs:
- name: "Docker Build and Push"
commands:
- checkout
- docker build -t /: .
- echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
- docker push /:
promotions:
- name: Deploy to production
pipeline_file: deploy-production.yml
auto_promote:
when: "result = 'passed' and branch = 'master'"
これは最初の良い結果です。モデルは、提供したコンテキストの例から構文を推測しました。
ボットの機能拡張に関する考え
私の最初の目標は、YAML パイプラインを作成するアシスタントを作成することでした。ベクトルデータベースにより豊かなコンテンツを持たせることで、Semaphore についての質問に答えるボットを一般化することができます(または、ドキュメントを /tmp
にクローンすることを忘れないでください)。
良い回答を得るための鍵は、予想通り、品質の高いコンテキストです。すべてのドキュメントを単純にベクトルデータベースにアップロードするだけでは、良い結果を得るのは難しいでしょう。コンテキストデータベースは、適切なメタデータでタグ付けされ、簡潔であるべきです。さもないと、プロンプトのトークンクォータを無関係なコンテキストで埋めてしまう可能性があります。
要するに、ボットを私たちのニーズに合わせて微調整するためには、芸術的な要素と多くの試行錯誤が必要です。コンテキストの制限を実験したり、品質の低いコンテンツを削除したり、要約したり、類似度スコアを調整することで、関連性のないコンテキストをフィルタリングすることができます。
適切なチャットボットの実装
おそらく気づいたかもしれませんが、私のボットはChatGPTのような実際の会話を可能にしていません。私たちは1つの質問をすると1つの回答を得るだけです。
基本的なチャットボットにボットを変換することは、原則としてそれほど難しくありません。APIリクエストごとに以前の応答をモデルに再送信することで、会話を維持することができます。以前のGPT-3の回答は「assistant」という役割で送り返されます。たとえば:
messages = []
while True:
query = input('質問を入力してください:\n')
context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)
messages.append(get_message('ユーザー', prompt))
messages.append(get_message('システム', 'セマフォの継続的インテグレーションパイプラインのYAMLコードを書き、それを説明する役立つアシスタントです。コードフェンス内のYAMLコードを返します。'))
if num_tokens_from_messages(messages) >= max_tokens_model:
raise Exception('モデルのトークンサイズ制限に達しました')
print("クエリを処理中... ")
answer = complete(messages)
print("回答:\n")
print(answer)
# システムメッセージを削除してモデルの回答を追加
messages.pop()
messages.append(get_message('アシスタント', answer))
残念ながら、この実装はかなり簡易的です。トークン数が各対話ごとに増加するため、拡張された会話をサポートしません。まもなく、GPT-3の4096トークンの制限に達して、さらなる対話ができなくなります。
したがって、リクエストをトークンの制限内に保つ方法を見つける必要があります。いくつかの戦略があります:
-
過去のメッセージを削除する。これは最も簡単な解決策ですが、会話の「メモリ」は最新のメッセージのみに制限されます。
-
以前のメッセージを要約する。以前のメッセージを短縮し、「モデルに尋ねる」として元の質問と回答の代わりに使用することができます。このアプローチはコストとクエリ間の遅延が増加しますが、単に過去のメッセージを削除するよりも優れた結果を生む可能性があります。
-
相互作用の数に厳格な制限を設定する。
-
GPT-4 APIの一般提供が待たれます。これはよりスマートでトークン容量が2倍のものです。
-
16kトークンまで処理できる「gpt-3.5-turbo-16k」のような新しいモデルを使用する。
結論
単語の埋め込みと良質なコンテキストデータベースを使用すれば、ボットの応答を向上させることが可能です。これを実現するためには、良質なドキュメンテーションが必要です。対象内容を把握したように見えるボットを開発するには、多くの試行錯誤が必要です。
私は、単語の埋め込みと大規模言語モデルの詳細な探求が、より効果的なボットを構築し、要件に合わせてカスタマイズするのに役立つことを願っています。
楽しい開発を!
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles