はじめに
公式さんもアドカレ参戦♪ 『かがみの孤城』円盤発売まであと7️⃣日 nikkieです。
先日のFunction callingのエントリで積んだ宿題に取り組みました。
目次
- はじめに
- 目次
- Function callingのエントリの宿題
- なぜコードポイントで表示される?
- 寄り道:OpenAIObjectはどう作られる?
- コードポイントの代わりに、人が読める日本語の文字で表示するには
- 終わりに
- P.S. Pythonの__repr__と__str__
Function callingのエントリの宿題
「Function calling」を完全に理解しました。
この中で1つ宿題がありました。
レスポンスの日本語はencodeされていてそのままでは読めない出力になっているのが宿題事項です。
以下のレスポンスのcontent
の部分ですね。
"\u3053"
のように、Unicodeのコードポイントでの表示です。
print
すれば「こ」だと分かりますが、そのままでは私は読めません。
{ "id": "chatcmpl-7SlBqmUItQTBDOQL18bwqZTT8f03z", "object": "chat.completion", "created": 1687088938, "model": "gpt-3.5-turbo-0613", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "\u3053\u3093\u306b\u3061\u306f\uff01\u79c1\u306f\u540d\u524d\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u79c1\u306fAI\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\u3069\u306e\u3088\u3046\u306b\u304a\u624b\u4f1d\u3044\u3067\u304d\u307e\u3059\u304b\uff1f" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 113, "completion_tokens": 40, "total_tokens": 153 } }
こちらについてこのエントリで現時点の解答をアウトプットします。
動作環境は先日のエントリ同様、以下です。
- Python 3.10.9
- openai 0.27.8
なぜコードポイントで表示される?
ステップ・バイ・ステップでみていきましょう。
APIからのレスポンスの型はOpenAIObject
です。
https://github.com/openai/openai-python/blob/v0.27.8/openai/openai_object.py
>>> import openai >>> response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "こんにちは。あなたの名前は?"}], temperature=0) >>> type(response) <class 'openai.openai_object.OpenAIObject'>
対話モードでresponse
が指すOpenAIObject
の具体値を見ましょう。
>>> response <OpenAIObject chat.completion id=chatcmpl-7Tqiupac9RL8tBIDb8EVjlpwWtTYE at 0x102c6d530> JSON: { "id": "chatcmpl-7Tqiupac9RL8tBIDb8EVjlpwWtTYE", "object": "chat.completion", "created": 1687348536, "model": "gpt-3.5-turbo-0301", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "\u3053\u3093\u306b\u3061\u306f\u3002\u79c1\u306fAI\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\u540d\u524d\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u4f55\u304b\u304a\u624b\u4f1d\u3044\u3067\u304d\u307e\u3059\u304b\uff1f" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 18, "completion_tokens": 32, "total_tokens": 50 } }
contentの値はコードポイントですね。
このとき、内部では特殊メソッド1__repr__
が呼ばれています。
https://docs.python.org/ja/3/reference/datamodel.html#object.__repr__
OpenAIObject
の__repr__
の実装は以下です。
https://github.com/openai/openai-python/blob/v0.27.8/openai/openai_object.py#L261-L277
__repr__
を一部抜粋すると
unicode_repr = "<%s at %s> JSON: %s" % ( " ".join(ident_parts), hex(id(self)), str(self), ) return unicode_repr
str
関数の呼び出しがあり、これは内部で__str__
メソッドを呼びます。
https://docs.python.org/ja/3/reference/datamodel.html#object.__str__
OpenAIObject
の__str__
の実装は以下です。
https://github.com/openai/openai-python/blob/v0.27.8/openai/openai_object.py#L279-L281
def __str__(self): obj = self.to_dict_recursive() return json.dumps(obj, indent=2)
OpenAIObject
のインスタンス自身を再帰的に辞書に変換し、json.dumps
でフォーマットしたJSONに変換しているのですね!
https://docs.python.org/ja/3/library/json.html#json.dumps
(略) obj を JSON 形式の str オブジェクトに直列化します。
「なぜコードポイントで表示される?」の答えは__repr__
で(__str__
を経由して)json.dumps
を呼んでいるからです。
寄り道:OpenAIObject
はどう作られる?
openai.ChatCompletion.create
でOpenAIObject
が返されます。
https://github.com/openai/openai-python/blob/v0.27.8/openai/api_resources/chat_completion.py#L12-L30
親クラスEngineAPIResource
のcreate
メソッドが呼ばれています。
https://github.com/openai/openai-python/blob/v0.27.8/openai/api_resources/abstract/engine_api_resource.py#L127-L190
親クラスのcreate
ではいろいろやっていますが、util.convert_to_openai_object
でOpenAIObject
が返されます。
https://github.com/openai/openai-python/blob/v0.27.8/openai/util.py#L101-L147
再帰的にOpenAIObject
を作っているという理解です。
>>> type(response["choices"][0]) <class 'openai.openai_object.OpenAIObject'>
コードポイントの代わりに、人が読める日本語の文字で表示するには
json.dumps
にensure_ascii=False
を渡します。
https://docs.python.org/ja/3/library/json.html#json.dump
ensure_ascii が (デフォルト値の) true の場合、出力では入力された全ての非 ASCII 文字はエスケープされていることが保証されています。ensure_ascii が false の場合、これらの文字はそのまま出力されます。
ライブラリの実装では、非ASCII文字がエスケープされ(てコードポイントになり)ます。
なので、非ASCII文字がコードポイントにならないように指定するわけです。
>>> import json >>> print(json.dumps(response.to_dict_recursive(), indent=2, ensure_ascii=False)) { "id": "chatcmpl-7Tqiupac9RL8tBIDb8EVjlpwWtTYE", "object": "chat.completion", "created": 1687348536, "model": "gpt-3.5-turbo-0301", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "こんにちは。私はAIアシスタントです。名前はありません。何かお手伝いできますか?" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 18, "completion_tokens": 32, "total_tokens": 50 } }
contentが日本語になりました!🙌
終わりに
Chat Completionエンドポイントのレスポンス全体をprint
したとき、日本語がコードポイントになる事象がなぜ起こるか、どう対処するかを見てきました。
今の私が考えている解決策は、json.dumps
の呼び出しでensure_ascii=False
指定です。
プルリクチャンス!
P.S. Pythonの__repr__
と__str__
今回は言語リファレンスのデータモデルを参照しましたが、もう少し平易な解説が『Effective Python 第2版』にあります。
項目75 出力のデバッグにはrepr文字列を使う