In [9]:
# by nitta@tsuda.ac.jp
import os
is_colab = 'google.colab' in str(get_ipython())   # for Google Colab

if is_colab:
    from google.colab import drive
    drive.mount('/content/drive')
    SAVE_PATH='/content/drive/MyDrive/DeepLearning'
else:
    SAVE_PATH='.'

# # variable definition in 'account.py' to access HuggingFace Hub
# MyAccount = {
#     'HuggingFace': {
#         'user': 'YourUserName',
#         'token': 'YourTokenWithWritePermission'
#     },
#     'OpenAI': {
#         'api-key': 'YourOpenAI_API_Key'
#     },
# }
ACCOUNT_FILE = os.path.join(SAVE_PATH, 'account.py')
%run {ACCOUNT_FILE}             # set 'MyAccount' variable
os.environ["OPENAI_API_KEY"] = MyAccount['OpenAI']['api-key']   # by nitta
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).

OpenAI Assistants API

File Search

"File Search" とは、いろいろな形式のテキストを使って、アシスタントに外部からの知識を拡張できる機能である。

公式ドキュメント: OpenAI Assistant API: File Search

File Search のアシスタントを作る手順

  1. 文書を用意する。
  2. ベクターストアに追加する
  3. File Search 用アシスタントを作り、質問する。

1. 文書を用意する

  1. なんらかの知識が記述されている文書を用意する。
  2. 今回は、投資信託における特定のファンドの目論見書を使ってみることにする。
  3. Google Drive 上にpdfファイルを配置する。
  4. 考えられる質問
    • "主な投資先はどこですか?"
    • "目的を簡単に教えてください。"
    • "特色を簡単に教えてください。"
    • "運用の手数料は1年間でどのくらいですか?"
    • "組み入れ銘柄のうち、情報技術に関する企業は合計何パーセントですか?"
    • "ここ5年間の平均の収益率は何パーセントですか?"
In [24]:
# pdf file へのパス
pdf_path1 = f'{SAVE_PATH}/openai_assistant/data/nissei_nasdaq100_prospectus.pdf'
pdf_path2 = f'{SAVE_PATH}/openai_assistant/data/emaxis_slim_sp500_prospectus.pdf'

2. 前準備

In [6]:
# パッケージのインストール
!pip install openai
Requirement already satisfied: openai in /usr/local/lib/python3.10/dist-packages (1.35.9)
Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai) (3.7.1)
Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai) (1.7.0)
Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai) (0.27.0)
Requirement already satisfied: pydantic<3,>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from openai) (2.7.4)
Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai) (1.3.1)
Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai) (4.66.4)
Requirement already satisfied: typing-extensions<5,>=4.7 in /usr/local/lib/python3.10/dist-packages (from openai) (4.12.2)
Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai) (3.7)
Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai) (1.2.1)
Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai) (2024.6.2)
Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai) (1.0.5)
Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai) (0.14.0)
Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai) (0.7.0)
Requirement already satisfied: pydantic-core==2.18.4 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai) (2.18.4)
In [7]:
from openai import OpenAI

# クライアントの準備
client = OpenAI()
In [8]:
import json

# JSON出力ヘルパーの準備
def show_json(obj):
    display(json.loads(obj.model_dump_json()))

3. アシスタントを作成する

tools パラメータとして file_search を渡して、アシスタントを作成する。

In [11]:
assistant = client.beta.assistants.create(
    name = "Finanicial Analyst Assistant",
    instructions = "あなたは株式投資の専門家です。知識ベースを利用して、投資信託の目論見書に関する質問に回答してください。",
    model="gpt-4o",
    tools=[{'type': 'file_search'}],
)

4. ファイルをアップロードして、ベクターストアに追加する

追加したファイルにアクセスするために、file_search ツールは VectorStore を使う。

vector_stores.file.batches.upload_and_poll() は、 ファイルをアップロードした後、 すべてのコンテンツの処理が終了するのを待ってから値を返す。

In [26]:
! ls -l {pdf_path1}
-rw------- 1 root root 750721 Jul  3 07:55 /content/drive/MyDrive/DeepLearning/openai_assistant/data/nissei_nasdaq100_prospectus.pdf
In [27]:
# pdf file を upload する。
vector_store = client.beta.vector_stores.create(name="Investment Trust Prospectus")

file_paths = [pdf_path1, pdf_path2]
file_streams = [ open(path, "rb") for path in file_paths ]

file_batch = client.beta.vector_stores.file_batches.upload_and_poll(
    vector_store_id=vector_store.id,
    files=file_streams
)
In [31]:
show_json(vector_store)
{'id': 'vs_MZsrj644te0JhiMJDIsxu4p7',
 'created_at': 1720007235,
 'file_counts': {'cancelled': 0,
  'completed': 0,
  'failed': 0,
  'in_progress': 0,
  'total': 0},
 'last_active_at': 1720007235,
 'metadata': {},
 'name': 'Investment Trust Prospectus',
 'object': 'vector_store',
 'status': 'completed',
 'usage_bytes': 0,
 'expires_after': None,
 'expires_at': None}
In [29]:
show_json(file_batch)
{'id': 'vsfb_0bc90e5959174c2e975e16d10e6ac2ec',
 'created_at': 1720007240,
 'file_counts': {'cancelled': 0,
  'completed': 2,
  'failed': 0,
  'in_progress': 0,
  'total': 2},
 'object': 'vector_store.file_batch',
 'status': 'completed',
 'vector_store_id': 'vs_MZsrj644te0JhiMJDIsxu4p7'}
In [30]:
print(file_batch.status)
print(file_batch.file_counts)
completed
FileCounts(cancelled=0, completed=2, failed=0, in_progress=0, total=2)

5. 新しい VectorStore を使うためにアシスタントを更新する

アシスタントが追加したファイルにアクセスできるように、 アシスタントの tool_resources を 新しい VectorStore の ID で更新する。

In [34]:
assistant = client.beta.assistants.update(
    assistant_id = assistant.id,
    tool_resources={ 'file_search': { 'vector_store_ids': [vector_store.id]}}
)

6. スレッドを作成する

スレッドに Message Attatchment としてファイルを添付することもできる。 これを行うと、新しい VectorStore が作成されて、スレッドに添付される。 既にスレッドに VectorStore が添付されている場合は、新しいファイルを既存の スレッド VectorStore に添付する。

このスレッドで Run を作成すると、 file search ツールはアシスタントのVectorStoreとスレッドのVectorStore の両方を探索する。

In [36]:
message_file = client.files.create(
    file=open(pdf_path1, "rb"),
    purpose='assistants',
)
In [38]:
thread = client.beta.threads.create(
    messages = [
        {
            'role': 'user',
            'content': 'nasdaq100 の特色を教えてください。',
            'attachments': [
                {
                    'file_id': message_file.id,
                    'tools': [ { 'type': 'file_search' }]
                }
            ],
        }
    ]
)
In [39]:
print(thread.tool_resources.file_search)
ToolResourcesFileSearch(vector_store_ids=['vs_0n2Z8YzEVZkw9P3u1oIqiwFf'])

7. Run を作成する。

"With Streaming" と "Without Streaming" の2通りがある。

ここでは、簡単のため "Without Streaming" の場合を述べる。

In [40]:
# Run を作成する。
run = client.beta.threads.runs.create_and_poll(
    thread_id = thread.id,
    assistant_id = assistant.id,
)
In [41]:
print(run)
Run(id='run_DzROST0kWMrFSvVzVmGLvivh', assistant_id='asst_Y4FHrpxaGJDHjb8yajurHqiU', cancelled_at=None, completed_at=1720009805, created_at=1720009794, expires_at=None, failed_at=None, incomplete_details=None, instructions='あなたは株式投資の専門家です。知識ベースを利用して、投資信託の目論見書に関する質問に回答してください。', last_error=None, max_completion_tokens=None, max_prompt_tokens=None, metadata={}, model='gpt-4o', object='thread.run', parallel_tool_calls=True, required_action=None, response_format='auto', started_at=1720009794, status='completed', thread_id='thread_eKcaCt68A5Dhxrka2mqCzOwJ', tool_choice='auto', tools=[FileSearchTool(type='file_search', file_search=None)], truncation_strategy=TruncationStrategy(type='auto', last_messages=None), usage=Usage(completion_tokens=371, prompt_tokens=12799, total_tokens=13170), temperature=1.0, top_p=1.0, tool_resources={})
In [42]:
show_json(run)
{'id': 'run_DzROST0kWMrFSvVzVmGLvivh',
 'assistant_id': 'asst_Y4FHrpxaGJDHjb8yajurHqiU',
 'cancelled_at': None,
 'completed_at': 1720009805,
 'created_at': 1720009794,
 'expires_at': None,
 'failed_at': None,
 'incomplete_details': None,
 'instructions': 'あなたは株式投資の専門家です。知識ベースを利用して、投資信託の目論見書に関する質問に回答してください。',
 'last_error': None,
 'max_completion_tokens': None,
 'max_prompt_tokens': None,
 'metadata': {},
 'model': 'gpt-4o',
 'object': 'thread.run',
 'parallel_tool_calls': True,
 'required_action': None,
 'response_format': 'auto',
 'started_at': 1720009794,
 'status': 'completed',
 'thread_id': 'thread_eKcaCt68A5Dhxrka2mqCzOwJ',
 'tool_choice': 'auto',
 'tools': [{'type': 'file_search', 'file_search': None}],
 'truncation_strategy': {'type': 'auto', 'last_messages': None},
 'usage': {'completion_tokens': 371,
  'prompt_tokens': 12799,
  'total_tokens': 13170},
 'temperature': 1.0,
 'top_p': 1.0,
 'tool_resources': {}}

8. 出力を確認する

In [44]:
messages = list(client.beta.threads.messages.list(thread_id=thread.id, run_id=run.id))
In [45]:
print(messages)
[Message(id='msg_Uqg4Ky9Ed6TJFR8c7Ywxbws0', assistant_id='asst_Y4FHrpxaGJDHjb8yajurHqiU', attachments=[], completed_at=None, content=[TextContentBlock(text=Text(annotations=[FileCitationAnnotation(end_index=123, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=111, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=135, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=123, text='【4:2†source】', type='file_citation'), FileCitationAnnotation(end_index=212, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=200, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=224, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=212, text='【4:1†source】', type='file_citation'), FileCitationAnnotation(end_index=280, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=268, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=292, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=280, text='【4:2†source】', type='file_citation'), FileCitationAnnotation(end_index=361, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=349, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=373, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=361, text='【4:2†source】', type='file_citation')], value='NASDAQ100の特色について、以下のポイントが挙げられます:\n\n1. **インデックス構成**: \n   NASDAQ100指数は、NASDAQ市場に上場している金融銘柄を除いた時価総額上位100銘柄で構成されています【4:0†source】【4:2†source】。\n\n2. **投資の目標**: \n   NASDAQ100指数(配当込み、円換算ベース)の動きに連動する投資成果を目指しています【4:0†source】【4:1†source】。\n\n3. **為替ヘッジの方針**:\n   原則として、対円での為替ヘッジは行いません【4:0†source】【4:2†source】。\n\n4. **投資対象**:\n   DR(預託証券)や株式等と同等の投資効果が得られる証券および証書等を含みます【4:0†source】【4:2†source】。\n\nNASADAQ100は、特にハイテク企業が多く含まれており、技術革新の動向による影響を受けやすいインデックスです。また、為替リスクや各国の政治・経済情勢などにも影響を受ける点が留意点となります。\n\nこれらの特色は、投資判断をする上で重要な情報となりますので、リスク管理を含めて総合的に考えられることが望ましいです。'), type='text')], created_at=1720009798, incomplete_at=None, incomplete_details=None, metadata={}, object='thread.message', role='assistant', run_id='run_DzROST0kWMrFSvVzVmGLvivh', status=None, thread_id='thread_eKcaCt68A5Dhxrka2mqCzOwJ')]
In [47]:
print(len(messages))
1
In [48]:
print(messages[0])
Message(id='msg_Uqg4Ky9Ed6TJFR8c7Ywxbws0', assistant_id='asst_Y4FHrpxaGJDHjb8yajurHqiU', attachments=[], completed_at=None, content=[TextContentBlock(text=Text(annotations=[FileCitationAnnotation(end_index=123, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=111, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=135, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=123, text='【4:2†source】', type='file_citation'), FileCitationAnnotation(end_index=212, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=200, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=224, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=212, text='【4:1†source】', type='file_citation'), FileCitationAnnotation(end_index=280, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=268, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=292, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=280, text='【4:2†source】', type='file_citation'), FileCitationAnnotation(end_index=361, file_citation=FileCitation(file_id='file-P7iORP5ju1WpgTWTFA8CpL64'), start_index=349, text='【4:0†source】', type='file_citation'), FileCitationAnnotation(end_index=373, file_citation=FileCitation(file_id='file-drSzFl5teC9JRYQ5Et5JpnPc'), start_index=361, text='【4:2†source】', type='file_citation')], value='NASDAQ100の特色について、以下のポイントが挙げられます:\n\n1. **インデックス構成**: \n   NASDAQ100指数は、NASDAQ市場に上場している金融銘柄を除いた時価総額上位100銘柄で構成されています【4:0†source】【4:2†source】。\n\n2. **投資の目標**: \n   NASDAQ100指数(配当込み、円換算ベース)の動きに連動する投資成果を目指しています【4:0†source】【4:1†source】。\n\n3. **為替ヘッジの方針**:\n   原則として、対円での為替ヘッジは行いません【4:0†source】【4:2†source】。\n\n4. **投資対象**:\n   DR(預託証券)や株式等と同等の投資効果が得られる証券および証書等を含みます【4:0†source】【4:2†source】。\n\nNASADAQ100は、特にハイテク企業が多く含まれており、技術革新の動向による影響を受けやすいインデックスです。また、為替リスクや各国の政治・経済情勢などにも影響を受ける点が留意点となります。\n\nこれらの特色は、投資判断をする上で重要な情報となりますので、リスク管理を含めて総合的に考えられることが望ましいです。'), type='text')], created_at=1720009798, incomplete_at=None, incomplete_details=None, metadata={}, object='thread.message', role='assistant', run_id='run_DzROST0kWMrFSvVzVmGLvivh', status=None, thread_id='thread_eKcaCt68A5Dhxrka2mqCzOwJ')
In [49]:
show_json(messages[0])
{'id': 'msg_Uqg4Ky9Ed6TJFR8c7Ywxbws0',
 'assistant_id': 'asst_Y4FHrpxaGJDHjb8yajurHqiU',
 'attachments': [],
 'completed_at': None,
 'content': [{'text': {'annotations': [{'end_index': 123,
      'file_citation': {'file_id': 'file-P7iORP5ju1WpgTWTFA8CpL64'},
      'start_index': 111,
      'text': '【4:0†source】',
      'type': 'file_citation'},
     {'end_index': 135,
      'file_citation': {'file_id': 'file-drSzFl5teC9JRYQ5Et5JpnPc'},
      'start_index': 123,
      'text': '【4:2†source】',
      'type': 'file_citation'},
     {'end_index': 212,
      'file_citation': {'file_id': 'file-P7iORP5ju1WpgTWTFA8CpL64'},
      'start_index': 200,
      'text': '【4:0†source】',
      'type': 'file_citation'},
     {'end_index': 224,
      'file_citation': {'file_id': 'file-drSzFl5teC9JRYQ5Et5JpnPc'},
      'start_index': 212,
      'text': '【4:1†source】',
      'type': 'file_citation'},
     {'end_index': 280,
      'file_citation': {'file_id': 'file-P7iORP5ju1WpgTWTFA8CpL64'},
      'start_index': 268,
      'text': '【4:0†source】',
      'type': 'file_citation'},
     {'end_index': 292,
      'file_citation': {'file_id': 'file-drSzFl5teC9JRYQ5Et5JpnPc'},
      'start_index': 280,
      'text': '【4:2†source】',
      'type': 'file_citation'},
     {'end_index': 361,
      'file_citation': {'file_id': 'file-P7iORP5ju1WpgTWTFA8CpL64'},
      'start_index': 349,
      'text': '【4:0†source】',
      'type': 'file_citation'},
     {'end_index': 373,
      'file_citation': {'file_id': 'file-drSzFl5teC9JRYQ5Et5JpnPc'},
      'start_index': 361,
      'text': '【4:2†source】',
      'type': 'file_citation'}],
    'value': 'NASDAQ100の特色について、以下のポイントが挙げられます:\n\n1. **インデックス構成**: \n   NASDAQ100指数は、NASDAQ市場に上場している金融銘柄を除いた時価総額上位100銘柄で構成されています【4:0†source】【4:2†source】。\n\n2. **投資の目標**: \n   NASDAQ100指数(配当込み、円換算ベース)の動きに連動する投資成果を目指しています【4:0†source】【4:1†source】。\n\n3. **為替ヘッジの方針**:\n   原則として、対円での為替ヘッジは行いません【4:0†source】【4:2†source】。\n\n4. **投資対象**:\n   DR(預託証券)や株式等と同等の投資効果が得られる証券および証書等を含みます【4:0†source】【4:2†source】。\n\nNASADAQ100は、特にハイテク企業が多く含まれており、技術革新の動向による影響を受けやすいインデックスです。また、為替リスクや各国の政治・経済情勢などにも影響を受ける点が留意点となります。\n\nこれらの特色は、投資判断をする上で重要な情報となりますので、リスク管理を含めて総合的に考えられることが望ましいです。'},
   'type': 'text'}],
 'created_at': 1720009798,
 'incomplete_at': None,
 'incomplete_details': None,
 'metadata': {},
 'object': 'thread.message',
 'role': 'assistant',
 'run_id': 'run_DzROST0kWMrFSvVzVmGLvivh',
 'status': None,
 'thread_id': 'thread_eKcaCt68A5Dhxrka2mqCzOwJ'}
In [53]:
message_content = messages[0].content[0].text
annotations = message_content.annotations
citations = []
for index, annotation in enumerate(annotations):
    message_content.value = message_content.value.replace(annotation.text, f"[{index}]")
    if file_citation := getattr(annotation, "file_citation", None):
        cited_file = client.files.retrieve(file_citation.file_id)
        citations.append(f'[{index}] {cited_file.filename}')
In [54]:
print(message_content.value)
print('\n'.join(citations))
NASDAQ100の特色について、以下のポイントが挙げられます:

1. **インデックス構成**: 
   NASDAQ100指数は、NASDAQ市場に上場している金融銘柄を除いた時価総額上位100銘柄で構成されています[0][1]。

2. **投資の目標**: 
   NASDAQ100指数(配当込み、円換算ベース)の動きに連動する投資成果を目指しています[0][3]。

3. **為替ヘッジの方針**:
   原則として、対円での為替ヘッジは行いません[0][1]。

4. **投資対象**:
   DR(預託証券)や株式等と同等の投資効果が得られる証券および証書等を含みます[0][1]。

NASADAQ100は、特にハイテク企業が多く含まれており、技術革新の動向による影響を受けやすいインデックスです。また、為替リスクや各国の政治・経済情勢などにも影響を受ける点が留意点となります。

これらの特色は、投資判断をする上で重要な情報となりますので、リスク管理を含めて総合的に考えられることが望ましいです。
[0] nissei_nasdaq100_prospectus.pdf
[1] nissei_nasdaq100_prospectus.pdf
[2] nissei_nasdaq100_prospectus.pdf
[3] nissei_nasdaq100_prospectus.pdf
[4] nissei_nasdaq100_prospectus.pdf
[5] nissei_nasdaq100_prospectus.pdf
[6] nissei_nasdaq100_prospectus.pdf
[7] nissei_nasdaq100_prospectus.pdf
In [ ]: