Python code for test code generation from Swagger and PlantUML with Azure OpenAI Service
Python ライブラリから Azure OpenAI Service にリクエストを行い OpenAPI と PlantUML からテストコードを生成・追加する
これまで 3 回にわたって、Azure OpenAI Service を使って OpenAPI 仕様 と PlantUML からテストコードを生成する取り組みについての情報共有を行ってきました。
- Test code generation from Swagger and PlantUML with Azure OpenAI Service
- Test code generation from Swagger and PlantUML with Azure OpenAI Service using PromptGenerator method
- Test code generation from Swagger and PlantUML with Azure OpenAI Service using PromptGenerator method #2
最終的には、PromptGenerator メソッドを用いて生成したプロンプトを使って GPT-4 でテストコードを生成するのが、最も安定的かつ致命的な間違いの無いコードになることが分かりました。
次はこれを実際の開発業務に適用することを考えると思いますが、Playground を使うというのは、プログラマとして格好悪いですし、三大美徳にも反しますよね。
また、前回 GPT-4 を利用することでテストダブルを正しく使えるようになる (確率が高まる) ことがわかりましたが、一方でテストケースが GPT-3 の場合に比べて減少してしまったという問題が発生しました。
そこで、本記事では OpenAI Python ライブラリを使ってテストコードを生成・追加する方法について解説します。
ゴール
- テストコードを生成する。
- チャットの特徴を利用して、テストケースを追加する。
- 上記を OpenAI Python Library を使って実行する。
前提
- API 操作をするプログラミング言語には Python を用います。パッケージ管理には
pip
またはconda
がインストールされていることを想定します。 - 今回、API にはインターネットからアクセスします。(仮想ネットワークは使用しません。)
- 本記事においては、環境変数に以下の値を格納しておきます。いずれの値も Azure Portal における Azure OpenAI リソースから、
[リソース管理] > [キーとエンドポイント]
より取得できます。OPENAI_API_BASE
- Azure OpenAI Service のエンドポイントURL。例えば
https://my-openai-example-endpoint.openai.azure.com/
といった文字列です。
- Azure OpenAI Service のエンドポイントURL。例えば
OPENAI_API_KEY
- Azure OpenAI Service のキー。キー1 または キー2 のいずれかの値を設定します。
免責
- 本記事の内容は、執筆時点のものです。LLM の変化やゆらぎもあるため、再現性は保証されません。
- 本記事の内容は検証レベルのものです。完全な手法に関する情報を提供するものではありません。
- 本記事で使用する PlantUML は、細部まで作り込んでいるわけではありません。細かい部分で間違いがある可能性があります。
- テストケースの追加は、新たなテストクラスを作成する形となっています。本記事執筆時点において、最初に生成されたテストコードのテストクラスの中にテストケースを追加した上ですべてのテストケースを出力する方法については、確立できていません。
準備
ライブラリのインストール
環境に合わせていずれかを実行してください。
1 | pip install openai |
1 | conda install openai |
Azure OpenAI Service への API リクエスト
ステップは以下の通りです。
- 初期化
- OpenAPI 仕様 の定義
- PlantUML シーケンス図の定義
- PlantUML クラス図の定義
- インストラクションの定義
- テスト対象の定義
- 各定義からプロンプトを作成
- メッセージの作成
- リクエストの実行およびレスポンスの取得
初期化
1 | import os |
反復的に実行することを想定し、後で使用する変数も初期化しておきます。(任意)
1 | messages = [] |
OpenAPI 仕様 の定義
これまでと同様、OpenAPI 公式のサンプル “petstore-simple.json“ を使用します。
1 | openapi_spec = '''{ |
PlantUML シーケンス図の定義
これまでと同様、上記の OpenAPI 仕様に基づいて PlantUML で作成した、簡単なシーケンス図を使用します。
描画済みの図に関しては、過去の記事を参照してください。
1 | plantuml_sequence_diagrams = '''@startuml |
PlantUML クラス図の定義
これまでと同様、上記の OpenAPI 仕様に基づいて PlantUML で作成した、簡単なクラス図を使用します。
描画済みの図に関しては、過去の記事を参照してください。
1 | plantuml_class_diagrams = '''@startuml |
インストラクションの定義
PromptGenerator メソッドで作成したものです。詳細は過去の記事を参照ください。
1 | revised_prompt = '''Generate unit test code in Python using the unittest framework and unittest.mock's patch module for given text materials such as OpenAI Specifications (JSON or YAML), PlantUML sequence diagrams (PlantUML format), and PlantUML class diagrams (PlantUML format), following the PEP 8 coding standards. |
テスト対象の定義
1 | test_target = "ペットストアAPI" |
各定義からプロンプトを作成
1 | prompt = f'''/* |
メッセージの作成
1 | messages = [ |
リクエストの実行およびレスポンスの取得
1 | response = openai.ChatCompletion.create( |
以下、パラメータの一覧を作成しましたので、参照してください。
# | 項目 | 型 | 最小値 | 最大値 | 説明 |
---|---|---|---|---|---|
1 | engine | str | - | - | Azure OpenAI Service にデプロイしたモデルに付与した名前 |
2 | messages | list | - | - | ユーザーとAIアシスタントの間で交換されたメッセージのリストです。各メッセージは、{‘role’: ‘system’, ‘content’: ‘…’} 、{‘role’: ‘user’, ‘content’: ‘…’} 、または{‘role’: ‘assistant’, ‘content’: ‘…’} の形式で記述されます。roleは、メッセージの送信者を示し、contentはメッセージの内容を示します。APIは、このメッセージのコンテキストを使用して、適切な応答を生成します。 |
3 | temperature | float | 0 | 1 | 生成されるテキストのランダム性を制御します。 温度を下げることは、モデルがより反復的で決定論的な応答を生成することを意味します。 温度を上げると、より予想外または創造的な反応が得られます。 両方ではなく、温度またはトップ P を調整してみてください。 |
4 | max_tokens | float | 1 | 32768 (※1) | モデル応答ごとのトークン数に制限を設定します。 API は、プロンプト (システム メッセージ、例、メッセージ履歴、およびユーザー クエリを含む) とモデルの応答の間で共有される最大 32768 個のトークンをサポートします。 1 つのトークンは、一般的な英語のテキストで約 4 文字です。この値を低く設定すると、生成されるテキストが短くなります。 (※1) |
5 | top_p | float | 0 | 1 | 生成されるテキストの多様性を制御するために、トークンの選択確率に基づいて使用されるサンプリング手法です。温度と同様に、これはランダム性を制御しますが、別の方法を使用します。 トップ P を下げると、モデルのトークン選択がより可能性の高いトークンに絞り込まれます。 Top P を増やすと、モデルは可能性の高いトークンと低いトークンの両方から選択できるようになります。 両方ではなく、温度またはトップ P を調整してみてください。 |
6 | frequency_penalty | float | 0 | 2 | これまでにテキストに出現した頻度に基づいて、トークンを繰り返す可能性を比例的に減らします。値が高いほど、低頻度のトークンが選択される可能性が低くなります。これにより、応答でまったく同じテキストが繰り返される可能性が低くなります。 |
7 | presence_penalty | float | 0 | 2 | これまでにテキストに登場したトークンを繰り返す可能性を減らします。値が高いほど、繰り返しのペナルティが大きくなります。これにより、応答に新しいトピックが導入される可能性が高くなります。 |
8 | stop | Optional[list] | - | - | 応答の生成を停止する特定の文字列またはシーケンスのリストを指定します。これにより、APIが特定の文字列を検出したときに、その後の生成を停止できます。 |
9 | n | int | 1 | N/A | APIから返される異なる応答の数を指定します。nを2以上に設定すると、同じ入力に対して複数の異なる応答が返されます。これは、応答の多様性を確保するために役立ちます。 |
10 | return_prompt | bool | - | - | 通常はFalseに設定されていますが、これをTrueに設定すると、APIから返されるJSONレスポンスにプロンプトも含まれます。 |
11 | echo | bool | - | - | 通常はFalseに設定されていますが(※2)、これをTrueに設定すると、APIから返されるJSONレスポンスに入力メッセージも含まれます。 |
12 | best_of | int | 1 | 20 | 複数の応答を生成し、すべてのトークンで合計確率が最も高い応答のみを表示します。 未使用の候補には使用コストがかかるため、このパラメーターを慎重に使用し、最大応答長と終了トリガーのパラメーターも設定してください。 ストリーミングは、これが 1 に設定されている場合にのみ機能することに注意してください。best_of の値が大きいほど、APIはより多くの応答を生成して評価しますが、実行時間が長くなる可能性があります。 |
13 | log_level | str | - | - | APIから返されるログのレベルを指定します。これは、デバッグや問題解決に役立つ情報を取得するために使用できます。 例: debug, info |
14 | logprobs | int | 0 | N/A | 生成されたテキストのトークンごとに対数確率を取得するために使用されます。たとえば、「logprobs」が 10 の場合、API は最も可能性の高い 10 個のトークンのリストを返します。 「logprobs」が指定されている場合、API は生成されたトークンの logprob を常に返すため、応答には最大で「logprobs+1」要素が含まれる場合があります。この値は、0から設定でき、値が大きいほど、APIから返される対数確率の詳細が増えます。 |
※1 gpt-4-32k の場合。
※2 余談ですが、CLI では True
がデフォルトになっている点が API と異なります。(ソースコード cli.py L172 参照)
※3 一部に機械翻訳またはGPT-4を使用しています。
※4 もし間違い等あればご指摘ください。
レスポンスの処理
取得したレスポンスを処理し、コードを取り出します。
ステップは以下の通りです。
- 内容の確認 (任意)
- コードの取得
内容の確認 (任意)
1 | print(response) |
1 | { |
コードの取得
正規表現を使うと処理時間が長くなるため、通常の文字列の処理を採用しました。
1 | code = response.choices[0].message.content.split('```')[1].lstrip('python\n') |
1 | import unittest |
追加のリクエスト
先のリクエスト・レスポンスのコンテキストを引き継いだまま、続けてリクエストを行います。
ステップは以下の通りです。
- メッセージの更新
- リクエストの実行およびレスポンスの取得
- レスポンス内容の確認 (任意)
- コードの取得
メッセージの更新
1 | messages.append(response.choices[0].message) |
1 | prompt = "I would like you to add test cases of transaction failure on delete_pet. You should change the test class name to be different from existing test class names." |
リクエストの実行およびレスポンスの取得
1 | response = openai.ChatCompletion.create( |
レスポンス内容の確認 (任意)
1 | print(response) |
1 | { |
コードの取得
1 | code = response.choices[0].message.content.split('```')[1].lstrip('python\n') |
1 | import unittest |
以前のコンテキストを引き継いだまま、追加のリクエスト通りにコードを取得することができました。
まとめ
Azure OpenAI Service について、 Python ライブラリを使ってテストコードを生成・追加する方法について解説しました。
プログラマブルな点、すなわち openai.ChatCompletion.create
で多彩なパラメータが指定できたり、messages
のリストを操作することができる点、また、少なくともリクエストに関しては再現性を持たせられる点が、 Playground に対する大きなメリットです。
また、この取り組みについて反復的な検証をする際に、Jupyter Notebook を利用して効率的に行うことができます。
実装自体は上記の通り簡単にできますので、ぜひトライしてみてください。