Closed8

OpenAIのAssistant APIを試してみる

kun432kun432

全然試してないうちにv2が出てた。

https://platform.openai.com/docs/assistants/whats-new

Assistant APIに様々な新機能と改善を発表し、ベータ版を新しいAPIバージョン、OpenAI-Beta: assistants=v2に移行します:

  • file_searchと呼ばれる改良された検索ツールを発表します。このツールは、1アシスタントあたり最大10,000ファイルまで取り込むことができます。より高速で、マルチスレッド検索による並列クエリをサポートし、リランキングとクエリ書き換えが強化されています。
  • file_searchと並んで、APIにvector_storeオブジェクトを導入します。ファイルがベクターストアに追加されると、自動的に解析、チャンク、埋め込みが行われ、検索できるようになります。ベクターストアはアシスタントやThreadにまたがって使用することができ、ファイル管理や課金を簡素化します。
  • Assistants APIで、1回の実行で使用するトークンの最大数を制御できるようになり、トークンの使用コストを管理できるようになりました。また、各実行で使用される前/最近のメッセージの数に制限を設定することもできます。
  • 特定の実行で特定のツール(file_search、code_interpreter、関数など)を強制的に使用するために使用できるtool_choiceパラメータがサポートされました。
  • Threaadsでカスタム会話履歴を作成するために、 assistantロールでメッセージを作成できるようになりました。
  • AssistantオブジェクトとRunオブジェクトが、temperatureresponse_format(JSONモード)、top_pのような一般的なモデル設定パラメータをサポートするようになりました。
  • Assistant APIでファインチューニングされたモデルを使用できるようになりました。現時点では、gpt-3.5-turbo-0125のファインチューン版のみがサポートされています。
  • Assistant APIがストリーミングに対応しました。
  • Nodeと PythonSDKにいくつかのストリーミングとポーリングのヘルパーを追加しました。

ツールの使用方法を最新バージョンのアシストAPIに移行する方法については、移行ガイドをご覧ください。

https://note.com/npaka/n/n4438384c96d0

kun432kun432

まず概念的なところから。

概要

https://platform.openai.com/docs/assistants/overview

Assistants APIを使えば、独自のアプリケーション内にAIアシスタントを構築することができる。アシスタントは指示を持ち、モデル、ツール、ファイルを活用してユーザーの問い合わせに応答することができる。Assistants APIは現在、3種類のツールをサポートしている: Code Interpreter、File Search、Function calling。

Assistants Playgroundが用意されている。

Asssitants APIの典型的なインテグレーションは以下のような流れになる:

  1. カスタムの指示を定義し、モデルを選択して、Assistantを作成する。必要ならば、ファイルを追加し、Code Interpreter、File Search、Function Callingなどのツールを有効にする。
  2. ユーザーが会話を開始したら、Threadを作成する。
  3. ユーザが質問したら、Threadにメッセージを追加する。
  4. Thread上でAssistantを実行し、モデルとツールを呼び出して応答を生成する。

Assitantsの仕組み

https://platform.openai.com/docs/assistants/how-it-works

Assistants APIは、開発者が様々なタスクを実行できる強力なAIアシスタントを構築できるように設計されている。

  1. AssistantはOpenAIのモデルを呼び出し、性格や能力を調整するための具体的な指示を出すことができる。
  2. Assistantは複数のツールに並行してアクセスできる。これらは、OpenAIがホストするツール(code_interpreterやfile_searchなど)、または、あなたが構築/ホストするツール(function_calling経由)の両方にアクセスできる。
  3. Assistantは永続的なThreadsにアクセスできる。Threadsは、メッセージの履歴を保存し、会話がモデルのコンテキストの長さに対して長くなりすぎたときに切り捨てることで、AIアプリケーションの開発を簡素化する。Threadsを一度作成し、ユーザーが返信するたびにメッセージをThreadsに追加するだけだ。
  4. Assistantは、Assistant作成時の一部として、またはアシスタントとユーザー間のスレッドの一部として、複数の形式のファイルにアクセスすることができる、
  5. ファイル作成の一部として、またはAssistantとユーザー間のThreadsの一部として、。ツールを使用する場合、Assistantはファイル(画像、スプレッドシートなど)を作成することもでき、生成したメッセージでファイルへの引用を追加することもできる。

コンポーネント


referred from: https://platform.openai.com/docs/assistants/how-it-works

オブジェクト 内容
Assistant 目的に応じて構築されたAI。OpenAIのモデルを使用し、ツールを呼び出す。
Thread アシスタントとユーザー間の会話セッション。Threadsはメッセージを保存し、コンテンツをモデルのコンテキストに合わせるために自動的に切り捨てを処理する。
Message Assistantやユーザーが作成したメッセージ。メッセージにはテキスト、画像、その他のファイルを含めることができる。メッセージはThreadにリストとして保存される。
Run Thread上でAssistantの呼び出し。Assistantはその設定とThreadのメッセージを使用して、モデルやツールを呼び出してタスクを実行する。実行の一部として、AssistantはThreadにメッセージを追加する。
Run Step Assistantが実行の一部として行ったステップの詳細リスト。Assistantは実行中にツールを呼び出したり、メッセージを作成したりできる。Run Stepを調べることで、Assitantが最終結果にどのように到達しているかを内省することができる。

なるほど、会話履歴対応のRAG Agentみたいなものが作れるものと認識した。

kun432kun432

ではOverviewにしたがって実際に試してみる。Colaboratoryで。

https://platform.openai.com/docs/assistants/overview

!pip install --upgrade openai
!pip freeze | grep openai

openai==1.30.1

APIキーをセット

from google.colab import userdata
import os

os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')

まずAssistantを作成。今回は、数学の質問に対して、Code Interpreterでコードを書いて実行する、数学の家庭教師のAssistant。

from openai import OpenAI
client = OpenAI()
  
assistant = client.beta.assistants.create(
  name="数学の家庭教師",
  instructions="あなたは数学の家庭教師です。コードを書いて実行し、数学の質問に答えるために、コードを書いて実行します。",
  tools=[{"type": "code_interpreter"}],
  model="gpt-4o",
)

会話の最初にThreadを作る。

thread = client.beta.threads.create()

ThreadにはIDが振られる。このIDごとに履歴が管理されるっぽい。

thread.id

thread_XXXXXXXXXXXXXXXXXXXXXXXX

Threadにメッセージを追加する。

message = client.beta.threads.messages.create(
  thread_id=thread.id,
  role="user",
  content="方程式 `3x + 11 = 14` を解きたい。手伝ってくれる?"
)

そしてThreadをAssistantで実行(Run)する。まずはストリーミング無しで。

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id,
  instructions="ユーザー名を 山田太郎 とする。このユーザーはプレミアムアカウントを持っている。"
)

Runは非同期になる。create_and_pollを使うと、Runを実行して自動的に完了まで待ってくれる。ステータスを見てRunが完了していたら結果を取りだす。

if run.status == 'completed': 
  messages = client.beta.threads.messages.list(
    thread_id=thread.id
  )
  print(messages)
else:
  print(run.status)

なるほど、実行結果はThreadsに追加されるので、メッセージのリストを取得しているのね。

SyncCursorPage[
    Message
]
(data=[
    Message(id='msg_AAAAAAAAAAAAAAAAAAAAAAAA',
    assistant_id='asst_XXXXXXXXXXXXXXXXXXXXXXXX',
    attachments=[
    ],
    completed_at=None,
    content=[
        TextContentBlock(text=Text(annotations=[
        ],
        value='もちろんです!方程式 \\(3x + 11 = 14\\) を解きましょう。\n\nまず、方程式から定数項を移動します。\n\n\\[3x + 11 = 14\\]\n\n両辺から11を引きます。\n\n\\[3x + 11 - 11 = 14 - 11\\]\n\nこれにより、次の方程式が得られます。\n\n\\[3x = 3\\]\n\n次に、xの係数である3で両辺を割ります。\n\n\\[\\frac{3x}{3} = \\frac{3}{3}\\]\n\nこれにより、\\(x\\) の値が求められます。\n\n\\[x = 1\\]\n\nですので、方程式 \\(3x + 11 = 14\\) の解は \\(x = 1\\) です。'),
        type='text')
    ],
    created_at=1715833325,
    incomplete_at=None,
    incomplete_details=None,
    metadata={
    },
    object='thread.message',
    role='assistant',
    run_id='run_XXXXXXXXXXXXXXXXXXXXXXXX',
    status=None,
    thread_id='thread_XXXXXXXXXXXXXXXXXXXXXXXX'),
    Message(id='msg_BBBBBBBBBBBBBBBBBBBBBBBB',
    assistant_id=None,
    attachments=[
    ],
    completed_at=None,
    content=[
        TextContentBlock(text=Text(annotations=[
        ],
        value='方程式 `3x + 11 = 14` を解きたい。手伝ってくれる?'),
        type='text')
    ],
    created_at=1715833031,
    incomplete_at=None,
    incomplete_details=None,
    metadata={
    },
    object='thread.message',
    role='user',
    run_id=None,
    status=None,
    thread_id='thread_XXXXXXXXXXXXXXXXXXXXXXXX')
],
object='list',
first_id='msg_AAAAAAAAAAAAAAAAAAAAAAAA',
last_id='msg_BBBBBBBBBBBBBBBBBBBBBBBB',
has_more=False)

Assistantからの回答だけ取り出したいならこんな感じかな

print(messages.data[0].content[0].text.value)

もちろんです!方程式 (3x + 11 = 14) を解きましょう。

まず、方程式から定数項を移動します。

[3x + 11 = 14]

両辺から11を引きます。

[3x + 11 - 11 = 14 - 11]

これにより、次の方程式が得られます。

[3x = 3]

次に、xの係数である3で両辺を割ります。

[\frac{3x}{3} = \frac{3}{3}]

これにより、(x) の値が求められます。

[x = 1]

ですので、方程式 (3x + 11 = 14) の解は (x = 1) です。

ストリーミングの場合は以下のような感じ。結果を処理するためのイベントハンドラを追加してやると。

from typing_extensions import override
from openai import AssistantEventHandler
 
# まず、EventHandlerクラスを作成し、
# レスポンス・ストリームのイベントをどのように処理するかを定義する。
 
class EventHandler(AssistantEventHandler):    
  @override
  def on_text_created(self, text) -> None:
    print(f"\nassistant > ", end="", flush=True)
      
  @override
  def on_text_delta(self, delta, snapshot):
    print(delta.value, end="", flush=True)
      
  def on_tool_call_created(self, tool_call):
    print(f"\nassistant > {tool_call.type}\n", flush=True)
  
  def on_tool_call_delta(self, delta, snapshot):
    if delta.type == 'code_interpreter':
      if delta.code_interpreter.input:
        print(delta.code_interpreter.input, end="", flush=True)
      if delta.code_interpreter.outputs:
        print(f"\n\noutput >", flush=True)
        for output in delta.code_interpreter.outputs:
          if output.type == "logs":
            print(f"\n{output.logs}", flush=True)

# 次に、`Stream` SDKヘルパーと`EventHandler`クラスを使用して、
# Runを作成し、レスポンスをストリームする。 

with client.beta.threads.runs.stream(
  thread_id=thread.id,
  assistant_id=assistant.id,
  instructions="ユーザー名を 山田太郎 とする。このユーザーはプレミアムアカウントを持っている。",
  event_handler=EventHandler(),
) as stream:
  stream.until_done()

実際にはストリーミングで出力されるけどこんな感じ。

assistant > もちろんお手伝いします。方程式 \( 3x + 11 = 14 \) を解いてみましょう。

1. まず、方程式を簡単にするために、両辺から11を引きます。
\[ 3x + 11 - 11 = 14 - 11 \]
\[ 3x = 3 \]

2. 次に、xを求めるために、両辺を3で割ります。
\[ \frac{3x}{3} = \frac{3}{3} \]
\[ x = 1 \]

したがって、方程式 \( 3x + 11 = 14 \) の解は \( x = 1 \) です。

これをPythonで確認してみましょう。
assistant > code_interpreter

import sympy as sp

# Define the variable
x = sp.symbols('x')

# Define the equation
equation = sp.Eq(3 * x + 11, 14)

# Solve the equation
solution = sp.solve(equation, x)
solution

output >

[1]

assistant > Pythonの結果でも、方程式 \( 3x + 11 = 14 \) の解は \( x = 1 \) であることが確認できました。何か他に手伝えることはありますか?

なるほど、こうやって見るとストリーミングなしの最初の例はCode Interpreterが使われなかったのかな?という気もするな。

kun432kun432

作成したAssistantやThreadsなどの削除は以下にまとまっていた。

https://nagomi-informal.net/archives/2476

.list()でAssitantの一覧が取れる。

from openai import OpenAI

client = OpenAI()

my_assistants = client.beta.assistants.list()
for a in my_assistants:
    print("{}: {}".format(a.id, a.name))

asst_XXXXXXXXXXXXXXXXXXXXXXXX: 数学の家庭教師
asst_YYYYYYYYYYYYYYYYYYYYYYYY: 数学の家庭教師
asst_ZZZZZZZZZZZZZZZZZZZZZZZZ: None

AssitantのIDを指定して削除

response = client.beta.assistants.delete("asst_XXXXXXXXXXXXXXXXXXXXXXXX")
print(response)

AssistantDeleted(id='asst_XXXXXXXXXXXXXXXXXXXXXXXX', deleted=True, object='assistant.deleted')

Threadsについては.list()みたいなメソッドがなく、ThreadのIDを指定して削除する必要がある。

response = client.beta.threads.delete("thread_XXXXXXXXXXXXXXXXXXXXXXXX")
print(response)

ThreadDeleted(id='thread_XXXXXXXXXXXXXXXXXXXXXXXX', deleted=True, object='thread.deleted')

となると、まあユーザごとにThreadを発行するのが一般的になるだろうから、ユーザIDとThread IDの紐づけをどこかで持っておかないといけないということになりそう。

ただThreadは現状60日間で消える模様、ただしベータなので今後変わるかもしれないとのこと。

https://community.openai.com/t/assistant-api-thread-lifetime/612983/2

https://platform.openai.com/docs/models/how-we-use-your-data

kun432kun432

Assitantsの仕組みについてもう少し深堀りしながら試してみる

https://platform.openai.com/docs/assistants/how-it-works

基本的にはモデルだけを指定すればミニマムでAssistantを作成できる。最初の例だとこう。

from openai import OpenAI
client = OpenAI()
  
assistant = client.beta.assistants.create(
  model="gpt-4o"
)

余談だけど、Threadsも使ってミニマムに全部指定して動かすとこうなる。

from openai import OpenAI
import time

client = OpenAI()

assistant = client.beta.assistants.create(
  model="gpt-4o"
)

thread = client.beta.threads.create()

message = client.beta.threads.messages.create(
  thread_id=thread.id,
  role="user",
  content="方程式 `3x + 11 = 14` を解きたい。手伝ってくれる?"
)

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id,
)

if run.status == 'completed': 
  messages = client.beta.threads.messages.list(
    thread_id=thread.id
  )
    print(messages.data[0].content[0].text.value)
else:
  print(run.status)

もちろん、方程式 3x + 11 = 14 を解く手順をご紹介します。

  1. 方程式の両辺から11を引きます。
    [
    3x + 11 - 11 = 14 - 11
    ]
    これで、方程式が簡略化されます。
    [
    3x = 3
    ]

  2. 方程式の両辺を3で割ります。
    [
    \frac{3x}{3} = \frac{3}{3}
    ]
    これで、xの値が求まります。
    [
    x = 1
    ]

したがって、方程式 3x + 11 = 14 の解は x = 1 です。

Assistant作成時にいろいろカスタマイズできる。

  • instructions
    • システムメッセージと同じように、Assistantの個性や目的などを定義。
  • tools
    • Assistantが利用可能なツールを指定。最大128ツール。
      • OpenAIがホストするツール: code_interpreter / file_seach
      • ユーザー側でホストするツール: 関数およびその関数の定義を作成し、function_callingに指定。
  • tool_resource
    • code_interpreter / file_seachがアクセスできるファイルを定義。
      • Fileアップロードエンドポイントを使ってファイルを事前にアップロードする必要がある。
      • purposeでファイルの利用用途をassistantにしておく必要がある。

ということで、ファイルのデータから解析・可視化をCode Interpreterで行うAssistantを作ってみる。

まずファイルアップロード。TVゲームの売上をまとめたこのデータを使った。

https://www.kaggle.com/datasets/gregorut/videogamesales?resource=download

file = client.files.create(
  file=open("vgsales.csv", "rb"),
  purpose='assistants'
)

このファイルをCodeInterpreterで使うAssistantを作成。

assistant = client.beta.assistants.create(
  name="Data visualizer",
  description="あなたは美しいデータの可視化を作成するのが得意です。あなたの仕事は、.csvファイルに存在するデータを分析し、傾向を理解し、それらの傾向に関連するデータの可視化を考えることです。また、観察されたトレンドの簡単なテキストサマリーも共有します。",
  model="gpt-4o",
  tools=[{"type": "code_interpreter"}],
  tool_resources={
    "code_interpreter": {
      "file_ids": [file.id]
    }
  }
)

なお、以下の制限がある。

  • code_interpreterは20ファイルまで
  • file_searchは10000ファイルまで(vector_storeオブジェクトを使う)
  • 各ファイルサイズは最大512Bで最大50000トークン
  • 組織あたりトータル100GBまで(申請により緩和可能らしい)

ではThreadsを作成。最初の例ではThreadを作ってからメッセージを追加したけども、threads.create()にメッセージを指定すれば、Thread作成にあわせてメッセージを追加できる。

thread = client.beta.threads.create(
  messages=[
    {
      "role": "user",
      "content": "売上上位のゲーム名をリストアップしてください。",
    }
  ]
)

Runを作成して実行。

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id
)

結果を取得。

if run.status == 'completed': 
    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )
    print(messages.data[0].content[0].text.value)
else:
    print(run.status)

もちろんです。以下は、2021年までの世界的な売上上位のビデオゲームの一部リストです。最新の情報については公式のソースや最新の売上データを参照してください。

  1. Minecraft - Mojang
  2. Grand Theft Auto V (GTA V) - Rockstar Games
  3. Tetris - さまざまなプラットフォームで複数のバージョン(最も有名なのはエレクトロニック・アーツや任天堂のもの)
  4. Wii Sports - 任天堂
  5. PlayerUnknown's Battlegrounds (PUBG) - PUBG Corporation
  6. Super Mario Bros. - 任天堂
  7. Pokémon Red/Green/Blue/Yellow - 任天堂
  8. Mario Kart 8 Deluxe - 任天堂
  9. Red Dead Redemption 2 - Rockstar Games
  10. The Legend of Zelda: Breath of the Wild - 任天堂

これらのゲームは、累計売上数や収益に基づいて評価されています。最新の売上ランキングは定期的に変動しますので、最新のデータは公式サイトや業界レポートを確認してください。

続けて。

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="プラットフォーム別の総売上の割合を円グラフにしてください。",
)

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id
)

if run.status == 'completed': 
    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )
    print(messages.data[0].content[0].text.value)
else:
    print(run.status)

※Zennの引用だとフォントが等幅にならないのはご容赦を・・・

以下に一般的なプラットフォーム別の総売上の割合を示すデータを仮定した円グラフを全てテキストとして表現します。このデータは具体的な年次や調査機関により異なる場合がありますので、参考としてご利用ください。

仮のプラットフォーム別売上割合(2021年までのデータを基に):

  • PlayStation: 35%
  • Xbox: 25%
  • PC: 20%
  • Nintendo: 15%
  • その他 (モバイル、その他のコンソールなど): 5%

以下のように記載します:

PlayStation: 35%
Xbox: 25%
PC: 20%
Nintendo: 15%
その他: 5%

これを仮定した割合で円グラフを表現すると:

                        +--------------------------------------+
                        |                ☆☆☆☆☆☆☆☆☆☆☆☆☆☆        |
                        |            ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆      |
                        |         ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆    |
                        |       ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆  |
                        |     ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ |
                        |    ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆|
                        |   ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆|
                        |   ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆|
                        |  ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ |
                        |  ☆☆☆☆☆☆☆☆☆   日本マイニング      ☆☆☆☆☆☆☆☆ |
                        |  ☆☆☆☆☆☆☆☆☆  ソフトウェア   35%  ☆☆☆☆☆☆☆☆  |
                        |  ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ |
                        |   ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆|
                        |    ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆|
                        |     ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ |
                        |       ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆  |
                        |         ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆    |
                        |            ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆      |
                        |                ☆☆☆☆☆☆☆☆☆☆☆☆☆☆        |
                        +--------------------------------------+

PlayStation: 35% (上部大部分)
Xbox: 25% (左側下方部分)
PC: 20% (右側下方部分)
Nintendo: 15% (右側中間部分)
その他: 5% (左側中間小部分)

実際の最新かつ正確なデータは、ゲーム業界レポートや公式発表の最新資料を基に確認してください。

続けてみる。

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="円グラフは画像にしてほしいな。",
)

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id
)

if run.status == 'completed': 
    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )
    print(messages.data[0].content[0].text.value)
else:
    print(run.status)

申し訳ありませんが、現在の設定では画像を生成する機能がオフになっています。しかし、円グラフを生成するためのPythonコードを提供できます。そのコードを実行すれば、円グラフを画像として表示できます。

import matplotlib.pyplot as plt

# データ
labels = ['PlayStation', 'Xbox', 'PC', 'Nintendo', 'その他']
sizes = [35, 25, 20, 15, 5]

# 円グラフ
fig1, ax1 = plt.subplots()
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
ax1.axis('equal')  # 円を丸くする

# グラフを表示
plt.title('プラットフォーム別総売上の割合')
plt.show()

このコードをPython環境で実行することで、円グラフが表示されます。Pythonがインストールされていて、matplotlibライブラリが利用可能であれば、簡単に実行できます。

んー、画像を生成するにはどうすればいいのかな?とりあえず出力されたコードを実行してみる。

!pip install japanize-matplotlib
import matplotlib.pyplot as plt
import japanize_matplotlib

# データ
labels = ['PlayStation', 'Xbox', 'PC', 'Nintendo', 'その他']
sizes = [35, 25, 20, 15, 5]

# 円グラフ
fig1, ax1 = plt.subplots()
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
ax1.axis('equal')  # 円を丸くする

# グラフを表示
plt.title('プラットフォーム別総売上の割合')
plt.show()

とりあえず実行はできる。あれか、Code Interpreterを使ってくれてないんだよね、おそらく。

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="いや、ツールを使えば生成できるはずですよ。ツールで実行してみてください。",

)

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id
)

if run.status == 'completed': 
    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )
    print(messages.data[0].content[0].text.value)
else:
    print(run.status)

もちろんです。以下に提供するデータを基に、円グラフを生成しました。

仮のプラットフォーム別売上割合:

  • PlayStation: 35%
  • Xbox: 25%
  • PC: 20%
  • Nintendo: 15%
  • その他: 5%

以下がその円グラフです:

import matplotlib.pyplot as plt

# データ
labels = ['PlayStation', 'Xbox', 'PC', 'Nintendo', 'その他']
sizes = [35, 25, 20, 15, 5]
colors = ['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0']

# 円グラフ
fig1, ax1 = plt.subplots()
ax1.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
ax1.axis('equal')  # 円を丸くする

# タイトルを追加
plt.title('プラットフォーム別総売上の割合')

# グラフを表示
plt.savefig("/mnt/data/Platform_Sales_Pie_Chart.png")

plt.show()

以下が生成された円グラフです:

プラットフォーム別総売上の割合

このグラフを元にプラットフォームの売上割合を確認できます。最新のデータに基づいて作成する場合は、最新の統計情報を利用してください。

うーん、このsandboxってのはCode Interpreter実行環境上でのみアクセスできるもので、このままだとアクセスできない。

https://qiita.com/nekoniii3/items/7128e1d0cec06e3ff6a4

を見る限りは、レスポンスに画像を返してくれるっぽいのだけども。。。。

kun432kun432

プロンプトがよくなかったみたい。

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="プラットフォーム別の総売上の割合のグラフを作成してください。",
)

run = client.beta.threads.runs.create_and_poll(
  thread_id=thread.id,
  assistant_id=assistant.id
)

if run.status == 'completed': 
    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )
else:
    print(run.status)

でmessagesの中身を見てみる。

len(messages.data[0].content)

2

for content in messages.data[0].content:
    print(content.type)

image_file
text

上記の記事を参考にしつつColaboratoryのセル内に表示してみる。

from IPython.display import Image

for content in messages.data[0].content:
    if content.type == "image_file":
        file_id = content.image_file.file_id
        file_content = client.files.with_raw_response.retrieve_content(file_id).content
        with open(file_id + ".png", 'wb') as f:
            f.write(file_content)
        display(Image(file_id + ".png"))
    else:
        print(content.text.value)

textの場合のannotationでテキストファイルがつくとかちょっとパターン分けが面倒かも。

で、生成されたファイルはストレージに保存される。

ファイルは削除されないので、自分で削除する必要がある。この辺も管理が必要になりそう。

kun432kun432

とりあえずなんとなく雰囲気だけはわかった気がしたので、一旦ここまで。

  • 会話履歴を持ったRAG Agentみたいなものは簡単に作れる。
    • 会話履歴はちゃんとやろうと思うと結構手間がかかる。それをクライアント側からはステートフルな感じで扱えるのは楽。
    • エージェントやツールなども使える。
    • 非同期で実行して結果を後で受け取れる。バッチ的なものも合いそう。
    • 多分LangChain等を使うよりも簡単そうな気が個人的にはした。
  • 気になるのは以下
    • 当然ながらOpenAIしか使えない、ベンダーロックイン状態にはなる。
    • モデル以外にAssitant API自体にコストが発生する。エージェント的なところも含めてコストはそこそこ発生しそう。
    • 楽なんだけども、それでも手元で管理は必要になりそう。
      • ユーザとスレッドの紐づけ。
      • 生成されたファイルをどうするか。

あとは発表当初に上がっていたこの辺。

https://dev.classmethod.jp/articles/note-for-using-openai-assistants-api/

https://note.com/nike_cha_n/n/n65a6101d59d7

今は改善されているものもあるみたいだけども。

https://note.com/nike_cha_n/n/n21fe47546e8d

まあOpenAIのみで頑張るならばこれでもいいのかもしれない。

このスクラップは2024/05/16にクローズされました