LlamaIndexモジュールガイドを試してみる: Models
Models
Modelsには2つのモデルがある。
- LLMs
- Embeddings
あとまだExperimentalだけども、マルチモーダルモデルの対応も徐々に進んでいる模様。
LLMs
- テキスト補完とチャットをサポート
- ストリーミング・ノンストリーミングをサポート
- 同期・非同期をサポート
使い方
OpenAI Completions API(もはやレガシーだけども)を使う場合
from llama_index.llms import OpenAI
llm = OpenAI()
res = llm.complete("富士山の高さは、")
print(res)
3776メートルです。
OpenAI Chat Completions APIの場合は、ChatMessageオブジェクトを配列に入れて渡す
from llama_index.llms import ChatMessage, OpenAI
messages = [
ChatMessage(
role="system", content="あなたはユーザーからの質問に何でも回答するアシスタントです。回答は、平易な言葉遣いでわかりやすく説明します。"
),
ChatMessage(role="user", content="富士山の高さについて教えて。"),
]
llm = OpenAI()
res = llm.chat(messages)
print(res)
assistant: 富士山は、日本の山であり、標高は3,776メートルです。これは、日本で最も高い山です。富士山は、山岳信仰の対象としても知られており、多くの人々が登山や観光に訪れます。
パラメータを変更する場合。
from llama_index.llms import ChatMessage, OpenAI
messages = [
ChatMessage(
role="system", content="あなたはユーザーからの質問に何でも回答するアシスタントです。回答は、平易な言葉遣いでわかりやすく説明します。"
),
ChatMessage(role="user", content="富士山の高さについて教えて。"),
]
llm = OpenAI(model="gpt-4-1106-preview", temperature=0.3)
res = llm.chat(messages)
print(res)
assistant: 富士山の高さは、約3,776メートルです。これは日本で一番高い山の高さで、山梨県と静岡県の県境に位置しています。富士山はその美しい形と雄大さで有名で、世界文化遺産にも登録されています。また、日本のシンボルとしても広く知られており、多くの人々に親しまれています。
ストリーミング
from llama_index.llms import ChatMessage, OpenAI
messages = [
ChatMessage(
role="system", content="あなたはユーザーからの質問に何でも回答するアシスタントです。回答は、平易な言葉遣いでわかりやすく説明します。"
),
ChatMessage(role="user", content="富士山の高さについて教えて。"),
]
llm = OpenAI()
res = llm.stream_chat(messages)
answer = []
for r in res:
answer.append(r.delta)
print(r.delta, end="")
print()
print(answer)
富士山は、日本の山であり、標高は3,776メートルです。これは、日本で最も高い山です。富士山は、山岳信仰の対象としても知られており、多くの人々が登山や観光に訪れます。
['', '富', '士', '山', 'は', '、', '日', '本', 'の', '山', 'で', 'あり', '、', '標', '高', 'は', '3', ',', '776', 'メ', 'ート', 'ル', 'です', '。', 'これ', 'は', '、', '日', '本', 'で', '最', 'も', '高', 'い', '山', 'です', '。', '富', '士', '山', 'は', '、', '山', '岳', '信', '仰', 'の', '対', '象', 'と', 'して', 'も', '知', 'ら', 'れ', 'て', 'お', 'り', '、', '多', 'く', 'の', '人', '々', 'が', '登', '山', 'や', '観', '光', 'に', '訪', 'れ', 'ます', '。', '']
モデルが対応している場合はFunction Callingも使える。
from pydantic import BaseModel
from llama_index.llms.openai_utils import to_openai_tool
from pprint import pprint
class Song(BaseModel):
"""架空の曲名とアーティスト名を生成する"""
song_name: str
artist: str
song_fn = to_openai_tool(Song)
pprint(song_fn)
song_fnはこんな感じで、Function Callingの関数定義が作成される。
{
'function': {
'description': '架空の曲名とアーティスト名を生成する',
'name': 'Song',
'parameters': {
'description': '架空の曲名とアーティスト名を生成する',
'properties': {
'artist': {
'title': 'Artist',
'type': 'string'
},
'song_name': {
'title': 'Song Name',
'type': 'string'
}
},
'required': ['song_name', 'artist'],
'title': 'Song',
'type': 'object'
}
},
'type': 'function'
}
呼んでみる。
from llama_index.llms import OpenAI
llm = OpenAI()
response = llm.complete("曲を生成して", tools=[song_fn])
tool_calls = response.additional_kwargs["tool_calls"]
print(tool_calls)
こんな感じで関数名+引数が返ってくる
[
ChatCompletionMessageToolCall(
id='call_N90wvAVAq3QFCqSFMMowj64a',
function=Function(
arguments='{\n "song_name": "夢の中で会いましょう",\n "artist": "星野源"\n}', name='Song'), type='function'
)
]
異なるモデルを使う場合はそれに合わせて読み込むモジュールを変える。対応しているモデルは以下にある。
たとえば、BedrockのClaude-2.1を使ってみた。
from llama_index.llms import ChatMessage, Bedrock
messages = [
ChatMessage(
role="system", content="あなたはユーザーからの質問に何でも回答するアシスタントです。回答は、平易な言葉遣いでわかりやすく説明します。"
),
ChatMessage(role="user", content="富士山の高さについて教えて。"),
]
llm = Bedrock(
model="anthropic.claude-v2:1",
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
aws_region_name="us-east-1"
)
res = llm.chat(messages)
print(res)
assistant: 富士山の高さは約3,776メートルです。
日本で最も高い山で、世界でも有数の高山です。頂上付近は一年のうち約5か月は雪に覆われています。富士山は完全な円錐形の山で、そのシンメトリーの美しさと神秘性から、日本人にとって特別な存在とされています。
ドキュメントには(まだ)載ってないけど、CohereとかGoogle Geminiとかも使えそう。
Embeddings
いきなりservice contextとかvector indexの話が出てくるけども、まずは普通にembedding apiを叩いてみる。
from llama_index.embeddings import OpenAIEmbedding
embed_model = OpenAIEmbedding()
embeddings = embed_model.get_text_embedding("富士山の高さは?")
print(len(embeddings))
print(embeddings[0:3])
1536
[0.023977849632501602, 0.004216551780700684, -0.01968788169324398]
Service Context
RAGにおけるベクトル化プロセスは一般的に2つある。
- ドキュメントをベクトル化してベクトルDBに入れる。
- ベクトルDBで検索する際にクエリをベクトル化する。
このあたりで都度都度get_text_embeddingするのは手間がかかる。そこでService Contextを使う。
ServiceContextは、LlamaIndexパイプライン/アプリケーションのインデックス作成とクエリ段階で使用される、一般的に使用されるリソースのバンドルです。これを使用して、グローバルな設定や、パイプラインの特定の部分におけるローカルな設定を行うことができます。
以下のような感じで設定しておくと、インデックス作成時とクエリ時の両方でembeddingsにOpenAI Embeddingsが使用される。
from llama_index import ServiceContext, set_global_service_context
from llama_index.embeddings import OpenAIEmbedding
embed_model = OpenAIEmbedding()
service_context = ServiceContext.from_defaults(embed_model=embed_model)
set_global_service_context(service_context)
ドキュメントを準備。ここはnpaka先生の記事にあるスクリプトをまるっと使わせてもらった。
サンプルとして以下のWikipediaの記事を使用してインデックスを作成している。
from pathlib import Path
import requests
# Wikipediaからのデータ読み込み
wiki_titles = ["イクイノックス", "ドウデュース"]
for title in wiki_titles:
response = requests.get(
"https://ja.wikipedia.org/w/api.php",
params={
"action": "query",
"format": "json",
"titles": title,
"prop": "extracts",
# 'exintro': True,
"explaintext": True,
},
).json()
page = next(iter(response["query"]["pages"].values()))
wiki_text = page["extract"]
data_path = Path("data")
if not data_path.exists():
Path.mkdir(data_path)
with open(data_path / f"{title}.txt", "w") as fp:
fp.write(wiki_text)
ではインデックスを作成。
from llama_index import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
クエリを実行
query_engine = index.as_query_engine()
response = query_engine.query("ダービーを勝ったのは?")
print(response)
ドウデュース
response = query_engine.query("ダービーを勝ったのは?")
print(response)
イクイノックス
VectorStoreIndexとかSimpleDirectoryReaderとかは別の所で見ていくので、ここでは深く触れない。