新しく出たOpenAIのGPT-4V, Dalle-3, JSONモードAPIを試す
2023 年 OpenAI Dev Day で発表された新しい API を色々と試してみたいと思います。
今回試すのは、
JSON Mode
DALLE-3 での画像生成
GPT-4V の画像認識 API
の3つです。他のも後ほど試したいと思います。
準備
!pip install openai -Uqq
from google.colab import userdata
api_key = userdata.get('OPENAI_API_KEY')
サンプルコードを実行してちゃんとセットアップできたか試す。
from openai import OpenAI
client = OpenAI(api_key=api_key)
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a poetic assistant, skilled in explaining complex programming concepts with creative flair."},
{"role": "user", "content": "Compose a poem that explains the concept of recursion in programming."}
]
)
print(completion.choices[0].message)
ChatCompletionMessage(content="In the realm where code does dance and sing,\nA concept does its praises bring.\nRecursion, the enchantress of programming art,\nUnravels mysteries with a poet's heart.\n\nLike a mirror reflecting its own reflection,\nRecursion invokes a mystical connection.\nIt's a loop that twists upon itself,\nLike a labyrinth, where wonder dwells.\n\nWith a whisper and a gentle call,\nRecursion beckons, inviting all.\nTo solve the riddles, unravel the maze,\nWith elegance, it sets ablaze.\n\nLike a phoenix, reborn from its own fire,\nRecursion lifts programmers higher.\nWith each iteration, it shrinks and repeats,\nEfficiently solving problems, oh so neat.\n\nDown the rabbit hole we dive,\nInto the depths where loops collide.\nWith base cases as our guide,\nRecursion leads us on this cosmic ride.\n\nBehold, a function that calls itself,\nWith elegant grace, it delves.\nA problem broken into smaller parts,\nRecursive magic, it imparts.\n\nThe task at hand, it gets divided,\nInto subproblems, wisely guided.\nThrough repeated calls, they intertwine,\nSolving complexities, one step at a time.\n\nYet, beware the endless spiral,\nA loop without an end or trial.\nForgetting the base case, oh what a plight,\nRecursive dreams turn into perpetual night.\n\nRecursion, a companion strange,\nDemystifies puzzles with a power to change.\nThrough repetition and introspection,\nIt breathes life into every programming session.\n\nSo, embrace this concept, mysterious and grand,\nLet recursion dance within your hands.\nFor in the realm where code does meet,\nRecursion paints a masterpiece so sweet.", role='assistant', function_call=None, tool_calls=None)
JSON Mode
JSON モードを使うと、必ず JSON 形式で返答を得られます。JSON 形式を担保するだけのものなので、例えば function calling で定義した関数通りの変数を返してくれることは保証していませんが、必ず JSON 形式でパースができるようです。
completion = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{"role": "system", "content": "あなたはとても役に立つチャットボットです。"},
{"role": "user", "content": "日本の主要都市を5つ答えなさい。"}
],
response_format={"type":"json_object"}
)
print(completion.choices[0].message)
---------------------------------------------------------------------------
BadRequestError Traceback (most recent call last)
<ipython-input-7-e42260b3bc4e> in <cell line: 1>()
----> 1 completion = client.chat.completions.create(
2 model="gpt-4-1106-preview",
3 messages=[
4 {"role": "system", "content": "あなたはとても役に立つチャットボットです。"},
5 {"role": "user", "content": "日本の主要都市を5つ答えなさい。"}
/usr/local/lib/python3.10/dist-packages/openai/_utils/_utils.py in wrapper(*args, **kwargs)
297 msg = f"Missing required argument: {quote(missing[0])}"
298 raise TypeError(msg)
--> 299 return func(*args, **kwargs)
300
301 return wrapper # type: ignore
/usr/local/lib/python3.10/dist-packages/openai/resources/chat/completions.py in create(self, messages, model, frequency_penalty, function_call, functions, logit_bias, max_tokens, n, presence_penalty, response_format, seed, stop, stream, temperature, tool_choice, tools, top_p, user, extra_headers, extra_query, extra_body, timeout)
554 timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
555 ) -> ChatCompletion | Stream[ChatCompletionChunk]:
--> 556 return self._post(
557 "/chat/completions",
558 body=maybe_transform(
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in post(self, path, cast_to, body, options, files, stream, stream_cls)
1053 method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1054 )
-> 1055 return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
1056
1057 def patch(
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in request(self, cast_to, options, remaining_retries, stream, stream_cls)
832 stream_cls: type[_StreamT] | None = None,
833 ) -> ResponseT | _StreamT:
--> 834 return self._request(
835 cast_to=cast_to,
836 options=options,
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in _request(self, cast_to, options, remaining_retries, stream, stream_cls)
875 # to completion before attempting to access the response text.
876 err.response.read()
--> 877 raise self._make_status_error_from_response(err.response) from None
878 except httpx.TimeoutException as err:
879 if retries > 0:
BadRequestError: Error code: 400 - {'error': {'message': "'messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}
プロンプトに必ず「JSON」の文字を入れる必要があるそうです。
completion = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{"role": "system", "content": "あなたはとても役に立つチャットボットです。"},
{"role": "user", "content": "日本の主要都市を5つJSON形式で答えなさい。"}
],
response_format={"type":"json_object"}
)
print(completion.choices[0].message)
ChatCompletionMessage(content='\n{\n "都市1": {"名前": "東京", "県": "東京都"},\n "都市2": {"名前": "横浜", "県": "神奈川県"},\n "都市3": {"名前": "大阪", "県": "大阪府"},\n "都市4": {"名前": "名古屋", "県": "愛知県"},\n "都市5": {"名前": "札幌", "県": "北海道"}\n}', role='assistant', function_call=None, tool_calls=None)
DALLE-3 での画像生成
API 経由で画像生成ができるので試してみたいと思います。
import requests
from PIL import Image
response = client.images.generate(
model="dall-e-3",
prompt="スタバのコーヒーを飲んでいる新人女性社員",
size="1024x1024",
quality="standard",
n=4,
)
# download 4 images from url and show the images in a 2x2 grid
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 8))
columns = 2
rows = 2
for i in range(1, columns*rows +1):
img = Image.open(requests.get(response.data[i-1].url, stream=True).raw)
fig.add_subplot(rows, columns, i)
plt.imshow(img)
plt.show()
---------------------------------------------------------------------------
BadRequestError Traceback (most recent call last)
<ipython-input-9-b6ef9a1b47f9> in <cell line: 4>()
2 from PIL import Image
3
----> 4 response = client.images.generate(
5 model="dall-e-3",
6 prompt="スタバのコーヒーを飲んでいる新人女性社員",
/usr/local/lib/python3.10/dist-packages/openai/resources/images.py in generate(self, prompt, model, n, quality, response_format, size, style, user, extra_headers, extra_query, extra_body, timeout)
249 timeout: Override the client-level default timeout for this request, in seconds
250 """
--> 251 return self._post(
252 "/images/generations",
253 body=maybe_transform(
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in post(self, path, cast_to, body, options, files, stream, stream_cls)
1053 method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1054 )
-> 1055 return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
1056
1057 def patch(
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in request(self, cast_to, options, remaining_retries, stream, stream_cls)
832 stream_cls: type[_StreamT] | None = None,
833 ) -> ResponseT | _StreamT:
--> 834 return self._request(
835 cast_to=cast_to,
836 options=options,
/usr/local/lib/python3.10/dist-packages/openai/_base_client.py in _request(self, cast_to, options, remaining_retries, stream, stream_cls)
875 # to completion before attempting to access the response text.
876 err.response.read()
--> 877 raise self._make_status_error_from_response(err.response) from None
878 except httpx.TimeoutException as err:
879 if retries > 0:
BadRequestError: Error code: 400 - {'error': {'code': None, 'message': 'You must provide n=1 for this model.', 'param': None, 'type': 'invalid_request_error'}}
4枚生成しようとしましたが、欲張るとだめらしいです。1枚ずつ生成しましょう。
import requests
from PIL import Image
response = client.images.generate(
model="dall-e-3",
prompt="気合を入れたスーツ姿のサラリーマンをやっているベジータ",
size="1024x1024",
quality="standard",
n=1,
)
img = Image.open(requests.get(response.data[0].url, stream=True).raw)
img
ベジータ?
画像をもとに、バリエーションが生成できるそうなので試してみましょう。
import io
img = Image.open(requests.get(response.data[0].url, stream=True).raw)
# フォーマットがPNG、サイズが4MB以下、BytesIOオブジェクトじゃないといけない
img = img.resize((256, 256))
output = io.BytesIO()
img.save(output, format='PNG')
byte_array = output.getvalue()
response = client.images.create_variation(
image=byte_array,
n=1,
size="1024x1024"
)
img2 = Image.open(requests.get(response.data[0].url, stream=True).raw)
img2
よく読むとバリエーション作れるのは DALLE-2 だけっぽいので、品質が下がったのはそのせいと思われる。
https://platform.openai.com/docs/guides/images/variations-dall-e-2-only
GPT-4V の画像認識 API
API 経由で画像認識と画像に対するチャットができるので試してみます。
from google.colab import files
uploaded = files.upload()
filename = list(uploaded.keys())
bytes_data = uploaded[filename[0]]
img = Image.open(io.BytesIO(bytes_data))
img
こちらのスクリーンショットを送ってみたいと思います。
ローカルの画像をアップロードするには、一度 base64 形式に変換して送る必要があります。
# 画像をbase64に変換
# 参考: https://platform.openai.com/docs/guides/vision/uploading-base-64-encoded-images
import base64
# def encode_image(image_path):
# with open(image_path, "rb") as image_file:
# return base64.b64encode(image_file.read()).decode('utf-8')
def encode_image(img):
return base64.b64encode(img).decode('utf-8')
base64_image = encode_image(img)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-41-6768e71177e4> in <cell line: 13>()
11 return base64.b64encode(img).decode('utf-8')
12
---> 13 base64_image = encode_image(img)
<ipython-input-41-6768e71177e4> in encode_image(img)
9
10 def encode_image(img):
---> 11 return base64.b64encode(img).decode('utf-8')
12
13 base64_image = encode_image(img)
/usr/lib/python3.10/base64.py in b64encode(s, altchars)
56 application to e.g. generate url or filesystem safe Base64 strings.
57 """
---> 58 encoded = binascii.b2a_base64(s, newline=False)
59 if altchars is not None:
60 assert len(altchars) == 2, repr(altchars)
TypeError: a bytes-like object is required, not 'PngImageFile'
Image.open で変換する前の bytes オブジェクトが必要でした。
base64_image = encode_image(bytes_data)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": "gpt-4-vision-preview",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "このパソコンが欲しいんですが、流石に高すぎますか?"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
],
"max_tokens": 300
}
response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload)
print(response.json())
{'id': 'chatcmpl-8IRJ8YJJM1brJ57bA9j2wV4EqkpCx', 'object': 'chat.completion', 'created': 1699405566, 'model': 'gpt-4-1106-vision-preview', 'usage': {'prompt_tokens': 1138, 'completion_tokens': 272, 'total_tokens': 1410}, 'choices': [{'message': {'role': 'assistant', 'content': 'この画像には、16インチMacBook Proのスペックと価格が表示されています。価格は784,800円となっており、確かに高額です。しかし、このモデルのスペックは非常に高性能であり、特にプロフェッショナルな用途や要求の高いソフトウェアを利用する方には適しているかもしれません。搭載されているApple M3 Maxチップ、128GBのメモリー、1TBのSSDストレージ、Liquid Retina XDRディスプレイなどの高性能なコンポーネントを見ると、その価格が正当化される理由がわかります。購入するかどうかは、ご自身の予算、用途、そして必要なパフォーマンスレベルによって異なります。高価ではあるものの、それ相応の価値があるかもしれません。'}, 'finish_details': {'type': 'stop', 'stop': '<|fim_suffix|>'}, 'index': 0}]}
ありがとうございます。買います。
おわりに
以上、お読みいただきありがとうございます。少しでも参考になればと思います。
もし似たようなコンテンツに興味があれば、フォローしていただけると嬉しいです:
今回使った Colab:
この記事が気に入ったらサポートをしてみませんか?