Closed12

openai.log を使ってLlamaIndexからOpenAIへのAPIリクエストの内容を見てみる

suzuki-navisuzuki-navi
$ pip install langchain llama_index
$ pip show openai langchain llama_index
Name: openai
Version: 0.27.8
Summary: Python client library for the OpenAI API
Home-page: https://github.com/openai/openai-python
Author: OpenAI
Author-email: support@openai.com
License:
Location: /home/ubuntu/.local/lib/python3.11/site-packages
Requires: aiohttp, requests, tqdm
Required-by: llama-index
---
Name: langchain
Version: 0.0.231
Summary: Building applications with LLMs through composability
Home-page: https://www.github.com/hwchase17/langchain
Author:
Author-email:
License: MIT
Location: /home/ubuntu/.local/lib/python3.11/site-packages
Requires: aiohttp, dataclasses-json, langchainplus-sdk, numexpr, numpy, openapi-schema-pydantic, pydantic, PyYAML, requests, SQLAlchemy, tenacity
Required-by: llama-index
---
Name: llama-index
Version: 0.7.6
Summary: Interface between LLMs and your data
Home-page: https://github.com/jerryjliu/llama_index
Author: Jerry Liu
Author-email:
License: MIT
Location: /home/ubuntu/.local/lib/python3.11/site-packages
Requires: beautifulsoup4, dataclasses-json, fsspec, langchain, nest-asyncio, numpy, openai, pandas, sqlalchemy, tenacity, tiktoken, typing-extensions, typing-inspect, urllib3
Required-by:
suzuki-navisuzuki-navi

インデックス作成のPythonコード

indexing.py
import openai
import langchain.embeddings
import llama_index

openai_api_key = 'xxx'
openai_api_base =  'https://xxx.openai.azure.com'
openai_api_type = 'azure'
openai_api_version = '2022-12-01'
openai_embedding_deployment_name = 'xxx' # model: "text-embedding-ada-002"

openai.log = "debug" # OpenAI APIリクエストの詳細を表示

embedding_llm = llama_index.LangchainEmbedding(
    langchain.embeddings.OpenAIEmbeddings(
        deployment=openai_embedding_deployment_name,
        openai_api_key=openai_api_key,
        openai_api_base=openai_api_base,
        openai_api_type=openai_api_type,
        openai_api_version=openai_api_version,
    ),
    embed_batch_size=1,
)

service_context = llama_index.ServiceContext.from_defaults(embed_model=embedding_llm)

document = llama_index.SimpleDirectoryReader(input_files=["sample.txt"]).load_data()

index = llama_index.GPTVectorStoreIndex.from_documents(document, service_context=service_context)

index.storage_context.persist(persist_dir="./indexdata")

sample.txt は以下のページの本文をコピペで移したものである。

https://ja.wikipedia.org/wiki/2022年ロシアのウクライナ侵攻

suzuki-navisuzuki-navi

インデックス作成を実行してみる。

$ python indexing.py

大量にリクエストが飛ぶ。長いテキストを分割して処理していると思われる。

インデックス作成時のAPIリクエストの例

message='Request to OpenAI API' method=post path=https://xxx.openai.azure.com/openai/deployments/xxx/embeddings?api-version=2022-12-01 api_version=2022-12-01 data='{"input": [[12, 10314, ........]], "encoding_format": "base64"}' message='Post details' 

input 要素は長い数列が入っていた。内容はわからなかった。

レスポンスの例

message='OpenAI API response' path=https://xxx.openai.azure.com/openai/deployments/xxx/embeddings?api-version=2022-12-01 processing_ms=41.3346 request_id=7fff3f96-7a31-4b92-ad2e-d51c62c0c099 response_code=200
body='{\n  "object": "list",\n  "data": [\n    {\n      "object": "embedding",\n      "index": 0,\n      "embedding": "wVwIv........"\n    }\n  ],\n  "model": "ada",\n  "usage": {\n    "prompt_tokens": 647,\n    "total_tokens": 647\n  }\n}\n' headers="{'Content-Length': '8399', 'Content-Type': 'application/json', 'apim-request-id': 'c06cd38c-1eb5-495f-9ec0-05b0b818a005', 'x-ms-region': 'East US', 'x-content-type-options': 'nosniff', 'openai-processing-ms': '41.3346', 'access-control-allow-origin': '*', 'x-request-id': '7fff3f96-7a31-4b92-ad2e-d51c62c0c099', 'x-ms-client-request-id': 'c06cd38c-1eb5-495f-9ec0-05b0b818a005', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', 'Date': 'Thu, 13 Jul 2023 05:13:42 GMT'}" message='API response body' 

embedding の中身はBASE64エンコードされていたがデコードしてみてもなんのバイナリなのかがわからなかった。

インデックスデータはJSONファイル4つである。

$ ls indexdata
docstore.json  graph_store.json  index_store.json  vector_store.json
suzuki-navisuzuki-navi

クエリ実行のPythonスクリプト

query.py
import openai
import langchain.llms
import llama_index

openai_api_key = 'xxx'
openai_api_base =  'https://xxx.openai.azure.com'
openai_api_type = 'azure'
openai_api_version = '2022-12-01'
openai_deployment_name = 'xxx' # mode: "text-davinci-003"
openai_embedding_deployment_name = 'xxx' # model: "text-embedding-ada-002"

openai.log = "debug" # OpenAI APIリクエストの詳細を表示

embedding_llm = llama_index.LangchainEmbedding(
    langchain.embeddings.OpenAIEmbeddings(
        deployment=openai_embedding_deployment_name,
        openai_api_key=openai_api_key,
        openai_api_base=openai_api_base,
        openai_api_type=openai_api_type,
        openai_api_version=openai_api_version,
    ),
    embed_batch_size=1,
)

llm_predictor = llama_index.LLMPredictor(
    llm=langchain.llms.AzureOpenAI(
        deployment_name=openai_deployment_name,
        openai_api_key=openai_api_key,
        openai_api_base=openai_api_base,
        openai_api_type=openai_api_type,
        openai_api_version=openai_api_version,
        max_tokens=1000,
        temperature=0.1,
    ),
)
prompt_helper = llama_index.PromptHelper()
service_context = llama_index.ServiceContext.from_defaults(llm_predictor=llm_predictor, embed_model=embedding_llm, prompt_helper=prompt_helper)

storage_context = llama_index.StorageContext.from_defaults(persist_dir="./indexdata")

index = llama_index.load_index_from_storage(storage_context, service_context=service_context)

query_engine = index.as_query_engine()
query = "ウクライナでの戦争の状況を教えてください。"
response = query_engine.query(query)

print(response.get_formatted_sources())

print(str(response))
suzuki-navisuzuki-navi

クエリを実行してみる。

$ python query.py

APIリクエストが2回飛んだ。

suzuki-navisuzuki-navi

1つ目のリクエスト

message='Request to OpenAI API' method=post path=https://xxx.openai.azure.com/openai/deployments/xxx/embeddings?api-version=2022-12-01
api_version=2022-12-01 data='{"input": ["\\u30a6\\u30af\\u30e9\\u30a4\\u30ca\\u3067\\u306e\\u6226\\u4e89\\u306e\\u72b6\\u6cc1\\u3092\\u6559\\u3048\\u3066\\u304f\\u3060\\u3055\\u3044\\u3002"], "encoding_format": "base64"}' message='Post details'

インデックス作成と同じembeddingのAPIを呼び出している。

input 要素はUnicodeエスケープされている文字列が入っており、これをデコードすると、「ウクライナでの戦争の状況を教えてください。」というPythonコードに書いたプロンプトがそのまま入っていた。

レスポンス

message='OpenAI API response' path=https://xxx.openai.azure.com/openai/deployments/xxx/embeddings?api-version=2022-12-01 processing_ms=33.8454 request_id=78933476-008e-418f-a33e-3d8a7546d5e1 response_code=200
body='{\n  "object": "list",\n  "data": [\n    {\n      "object": "embedding",\n      "index": 0,\n      "embedding": "rgGev........"\n    }\n  ],\n  "model": "ada",\n  "usage": {\n    "prompt_tokens": 23,\n    "total_tokens": 23\n  }\n}\n' headers="{'Content-Length': '8397', 'Content-Type': 'application/json', 'apim-request-id': '5c8be209-09a9-410d-98dc-31b62bbe3d83', 'x-ms-region': 'East US', 'x-content-type-options': 'nosniff', 'openai-processing-ms': '33.8454', 'access-control-allow-origin': '*', 'x-request-id': '78933476-008e-418f-a33e-3d8a7546d5e1', 'x-ms-client-request-id': '5c8be209-09a9-410d-98dc-31b62bbe3d83', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', 'Date': 'Thu, 13 Jul 2023 06:08:56 GMT'}" message='API response body'

レスポンスの中身にある embedding 要素はインデックス作成時と同様でBASE64エンコードされているが、デコードしてみても内容がよくわからない。

1回目のリクエストではプロンプトの内容をベクトルデータにしているものと思われる。

suzuki-navisuzuki-navi

クエリ時の2つ目のリクエスト

message='Request to OpenAI API' method=post path=https://xxx.openai.azure.com/openai/deployments/xxx/completions?api-version=2022-12-01
api_version=2022-12-01 data='{"prompt": ["Context information is below.\\n---------------------\\n\\u30a6\\u30af\\u30e9\\u30a4\\u30ca\\u306e\\u5404\\u90fd\\u5e02\\u306e\\u5730\\u56f3 ........\\u3057\\u305f[241]\\u3002\\n---------------------\\nGiven the context information and not prior knowledge, answer the question: \\u30a6\\u30af\\u30e9\\u30a4\\u30ca\\u3067\\u306e\\u6226\\u4e89\\u306e\\u72b6\\u6cc1\\u3092\\u6559\\u3048\\u3066\\u304f\\u3060\\u3055\\u3044\\u3002\\n"], "temperature": 0.1, "max_tokens": 1000, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "n": 1, "logit_bias": {}}' message='Post details'

上記の prompt は途中省略してあるが、とても長いテキストをUnicodeエスケープされたものが入っていた。これをデコードすると、次のような内容だった。これも長いので途中を省略しておく。

Context information is below.
---------------------
ウクライナの各都市の地図 ユーロマイダンに........

13][232][233]。プーチンは国民向けのテレビ演説の中で、........
---------------------
Given the context information and not prior knowledge, answer the question: ウクライナでの戦争の状況を教えてください。

13][232][233] という部分は、Wikipediaの注記の記号である。

レスポンス

message='OpenAI API response' path=https://xxx.openai.azure.com/openai/deployments/xxx/completions?api-version=2022-12-01 processing_ms=15355.3169 request_id=ccf25341-0454-458d-bab0-19ae05fd1202 response_code=200
body='{"id":"cmpl-7bjnUfLdvif1AsllEjlxio28l8ZFc","object":"text_completion","created":1689228536,"model":"text-davinci-003","choices":[{"text":"\\n2022年2月になってから、ウクライナではロシア軍による侵攻が行われています。ロシアはウクライナの東部の紛争地域ドネツィク州グラニトノエにおいて、ドローンを使った攻撃を行っています。また、ウクライナ東部の空域では民間航空の飛行が制限されています。オデッサ沖ではトルコが所有する貨物船がロシアからのミサイルにより被害を受け、黒海に面したユズニー港の南でモルドバ船籍の貨物船がロシア軍の発砲を受けました。キーウでは住宅地に航空機が墜落し、黒海沿岸の港湾都市オデッサ周辺ではミサイル攻撃によって少なくとも18人の市民が死亡しました。","index":0,"finish_reason":"stop","logprobs":null}],"usage":{"completion_tokens":366,"prompt_tokens":2009,"total_tokens":2375}}\n' headers="{'Cache-Control': 'no-cache, must-revalidate', 'Content-Length': '1044', 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'apim-request-id': 'ea6e08ea-9812-4914-8b17-0fe1d0cb38f7', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', 'x-ms-region': 'East US', 'x-content-type-options': 'nosniff', 'Openai-Model': 'text-davinci-003', 'Openai-Processing-Ms': '15355.3169', 'X-Accel-Buffering': 'no', 'X-Ms-Client-Request-Id': 'ea6e08ea-9812-4914-8b17-0fe1d0cb38f7', 'X-Request-Id': 'ccf25341-0454-458d-bab0-19ae05fd1202', 'Date': 'Thu, 13 Jul 2023 06:09:11 GMT'}" message='API response body'
suzuki-navisuzuki-navi

ログ以外の実行結果本体は以下の通りだった。

> Source (Doc id: b6f33d99-d9ac-4887-b7ab-9bd1bf2659e8): ウクライナの各都市の地図 ユーロマイダンによるヤヌコーヴィチ解任後、ウクライナの一部地域において親露派による騒乱が勃発した[160]。リトル・グリーンメンはウクライナの戦略的に重要な地域やインフ...

> Source (Doc id: e01076cd-e1d7-4c1f-8202-436171fb8959): 13][232][233]。プーチンは国民向けのテレビ演説の中で、軍事作戦の目的を「ウクライナ政府によって8年間、虐げられてきた人々を保護するため」と述べた[65]。また、ウクライナの領土を占領...

2022年2月になってから、ウクライナではロシア軍による侵攻が行われています。ロシアはウクライナの東部の紛争地域ドネツィク州グラニトノエにおいて、ドローンを使った攻撃を行っています。また、ウクライナ東部の空域では民間航空の飛行が制限されています。オデッサ沖ではトルコが所有する貨物船がロシアからのミサイルにより被害を受け、黒海に面したユズニー港の南でモルドバ船籍の貨物船がロシア軍の発砲を受けました。キーウでは住宅地に航空機が墜落し、黒海沿岸の港湾都市オデッサ周辺ではミサイル攻撃によって少なくとも18人の市民が死亡しました。

Pythonコードに print(response.get_formatted_sources()) と書いているので、実行結果に Source (Doc id:... などと表示されている。

suzuki-navisuzuki-navi

インデックス作成の動作

  • 学習元となるテキストを適当な長さに分割する
    • 分割の箇所は改行などの区切りを考慮されず、自然言語的には不自然な箇所でも容赦なく切る
  • embedding APIでベクトルデータに変換する
  • ベクトルデータをJSONに保存

クエリ実行時の動作

  • プロンプトをembedding APIでベクトルデータに変換する
  • 学習元ベクトルデータのJSONを読み込む
  • 学習元ベクトルデータの中からプロンプトのベクトルデータに近いものを選び出す(上記実行例では2つ選ばれている)
  • 以下のようなフォーマットのプロンプトが作成され、completion APIがリクエストされる

実際に発行されるプロンプトのフォーマット

Context information is below.
---------------------
選ばれた学習元テキスト断片その1

選ばれた学習元テキスト断片その2
---------------------
Given the context information and not prior knowledge, answer the question: もともとのプロンプト

クエリ実行時は、ベクトルデータにしておいたテキストから近いものを2つ程度探し、それを参考情報としてプロンプトに付与される。OpenAIからしたらクエリ時にベクトルデータが来るわけではなく、都度単発のプロンプトが来るので、その範囲で返答するのみである。

suzuki-navisuzuki-navi

ログを見てみるという話からは趣旨が脱線するが、学習元テキストと全然関係ない質問をするとどうなるか見てみた。

同じベクトルデータを使って、プロンプトだけ以下のようにしてみた。

query = "韓国の首都はどこですか?"

実行結果

> Source (Doc id: 67deeed2-b206-47b4-9b2c-239d5aaabc11): ルガンスク州知事のセルヒ・ハイダイ(ウクライナ語版)は「それが空から投下された爆弾なのか、大きな砲弾なのかはわからないが、破片が防弾ガラスを貫通してフランス人記者が死亡した。彼はヘルメットと防弾...

> Source (Doc id: d433ed09-55e4-43d5-9fcc-c88acc80dfba): 背景 詳細は「オレンジ革命」、「ユーロマイダン」、および「2014年ウクライナ騒乱」を参照  2004年11月、キーウの独立広場に集結した群衆(オレンジ革命)...

韓国の首都はソウルです。

学習元テキストが無関係で役に立たなくてもLlamaIndexはいちおう近いものを探してOpenAIにプロンプトとして渡しているが、OpenAIは役に立たないテキストを無視して、もともとわかっている知識から回答している。学習元テキストの sample.txt をいちおう検索したが「ソウル」という単語は含まれていなかった。

このスクラップは2023/07/13にクローズされました