OpenInterpreterやその他自律Agent系のOSSの、「足りないパッケージを自分で判断して許可を仰いだうえで、勝手にインストールする機能」を実装する一例を紹介します。
コードの自動生成を駆使する場合、どうしてもライブラリがインストールされていない事態は避けられず、自動でインストールしてくれるとありがたいです
何より、なんかかっこいいです!
(ただし基本的に最新バージョンがインストールされるので、個別の仮想環境での実施が前提となります)。
Google Colab上でこの機能を実現する実装例(あくまで一例)を構築したので記事にしてみます。
実装したGoogle Colabのnotebookはこちらになります。
動作の様子はこちらです
本記事の流れは次の通りです。
手順1: 不足しているパッケージを用意するために、あえて、PILをアンインストール
手順2: 自動生成されたコードをtry-exceptで挟む関数を用意する
手順3: スタックトレースから修正方法をLLM(ChatGPT)に教えてもらう
手順4: メイン関数の実装と実行
手順1: 不足しているパッケージを用意するために、あえて、PILをアンインストール
Google Colabはいろいろとはじめからパッケージが入っていて、不足パッケージを探すのが面倒なので、逆にアンインストールします。
今回は、画像のサイズを出力するコードを自動生成した""、という前提のもとで話を進めます。
import PIL
# エラーなくPILが通るはず
# uninstallします
!pip uninstall pillow -y
import PIL
をすると問題なく通ると思います。
そしてuninstall
をします。
このuninstallを有効にするために、一度ランタイムを再起動してください
そして、再度import PIL
をすると、エラーが発生します。
これでパッケージを不足している状態になりました。
手順2: 自動生成されたコードをtry-exceptで挟む関数を用意する
本記事ではコードの自動生成部分は省略します。
以下のような、PILで画像を読み込んで、その幅と高さを出力するコードが生成されたとします。
# [1] 生成されたコードが例えば以下だったとします。
# 単純に画像を取得して、幅と高さを求めているだけです。
generated_code_txt="""
from PIL import Image
import requests
from io import BytesIO
# 適当な画像のURLを指定
image_url = 'https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F3328834%2Ff8a26020-6ec7-aa41-9ff6-0c4d38cb8319.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=521ce0ce7dc4fe5865bc41b79a4b735f'
# 画像を取得
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
# 画像を取得
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
# 画像の幅と高さを取得
width, height = img.size
# 幅と高さを表示
print("幅:", width)
print("高さ:", height)
"""
このコードを実行したときに、from PIL import Image
でエラーが発生するので、Try-Exceptで実行するコードを囲む関数を用意します。
さらに、例外(Exception)でどのような内容(スタックトレース)が表示されていたのかを取得するために、tracebackを使用します。
import traceback
traceback_str = traceback.format_exc()
エラーメッセージを変数traceback_strに格納します。
このエラーメッセージ(スタックトレース)を解決する方法をLLM(今回はGPT-3.5)に尋ね、その修正のPythonコードを実行するという流れになります。
Try-Exceptで実行するコードを囲む関数(の一例)は次の通りです。
def add_try_except_block(code_str):
"""try-exceptでコードを挟む関数です"""
indented_code = "\n".join([" " + line for line in code_str.splitlines()])
try_except_code = f"""
try:
traceback_str = None
{indented_code}
except Exception as e:
import traceback
traceback_str = traceback.format_exc()"""
return try_except_code
さきほどの生成された(と仮定した)コードにこの関数の引数にして実行すると以下のように変換されます。
# [1] 生成されたコードをtry-exceptで挟む
generated_code_txt_with_try = add_try_except_block(generated_code_txt)
# [2] try-exceptで挟んだ状態を確認
print(generated_code_txt_with_try)
print文の出力結果は次の通りです(実際は文字列です)。
try:
traceback_str = None
from PIL import Image
import requests
from io import BytesIO
# 適当な画像のURLを指定
image_url = 'https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F3328834%2Ff8a26020-6ec7-aa41-9ff6-0c4d38cb8319.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=521ce0ce7dc4fe5865bc41b79a4b735f'
# 画像を取得
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
# 画像を取得
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
# 画像の幅と高さを取得
width, height = img.size
# 幅と高さを表示
print("幅:", width)
print("高さ:", height)
except Exception as e:
import traceback
traceback_str = traceback.format_exc()
手順3: スタックトレースから修正方法をLLM(ChatGPT)に教えてもらう
LLMは何でも良いですが、GPT3.5を使用します。
本記事では簡単のためにAPI_KEYを直書きします(本番ではこんな危険なことは避けましょう)
# パッケージのインストール
!pip install openai==0.28
# 非常に危険。API_KEYを直書き
import os
import re
import openai
KEY = "sk-hogehoge"
openai.api_key = KEY
次に、ChatGPTの回答からPythonのコード部分のみを抜き出す関数を定義します。
コードだけ教えてと伝えても、なんかごちゃごちゃ説明してくれるので、コード以外を無視することにします。
def extract_python_code(text):
"""pythonの実装のみを抽出"""
pattern = r'```python(.*?)```'
result = re.findall(pattern, text, re.DOTALL)
# result リスト内のすべての要素を結合して1つの文字列にする
extracted_code = '\n'.join(result)
return extracted_code
あとは、実行したPythonコード、発生したスタックトレース、そして回答として欲しい修正方法(事前に実行するPythonコード)を埋め込んだ、適切なPromptを用意します。
def get_response_from_chatGPT(generated_code_txt_with_try, traceback_str):
# Promptの作成
user_content = f"""
以下のPythonコードを実行したところ、次のようなスタックトレースが表示されました。
再度このPythonコードを実行し、エラーなく完了させるためには、このスタックトレースの内容を解決するために、
最低限として事前にどのようなPythonコードを実行する必要がありますか?
Pythonのコード形式で教えてください。pipコマンドなどが必要な場合は、subprocess.runを使用し、標準出力をprintするように、
output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
print(output)
を使用してください。
### [Pythonコード]
{generated_code_txt_with_try}
### [スタックトレース]
{traceback_str}
### [事前に実行するPythonコード]
"""
messages = [
{"role": "system","content":"You are an AI assistant that helps people find information."},
{"role": "user", "content":user_content}
]
# responseを得る
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages = messages,
temperature=0.0,
top_p=0.0,
max_tokens=800,
)
# Pythonコードのみを取り出す
ret = response['choices'][0]['message']['content'] # 回答
correction_code = extract_python_code(ret)
return correction_code
手順4: メイン関数の実装と実行
以上で準備は完了です。最後に手順1-3で用意した内容をmain関数に整理し、実行します。
def main(generated_code_txt):
# [1] 何らかコードを生成したとします
# generated_code_txt
# [2] try-exceptで挟み、スタックトレースをとれるようにします
generated_code_txt_with_try = add_try_except_block(generated_code_txt)
# [3] エラーがなくなるまで回します(本当は制限回数を定めるべき)
error_is = True
while error_is:
# [3-1] 空の辞書を作成。exec実行した中身の変数をここに格納します
variables = {}
traceback_str= None
# [3-2] 実行 execを使用してコードを実行し、exec内の変数を辞書で取得します
print("============================================")
print("[1] 生成されたコードを実行します(今回は適当な画像のサイズを出力するコードです)\n")
exec(generated_code_txt_with_try, globals(), variables)
# [3-3] エラーなく終わったかを判定します
if not variables.get("traceback_str"):
error_is = False # loopから抜ける
print("\n[Fin] 実行が完了しました")
break
# [3-4] エラーが発生し、スタックトレースが存在する場合、それを表示します
if variables.get("traceback_str"):
print("[2] エラーが発生しました\n\n")
print(traceback_str, variables.get("traceback_str"))
# [3-5] 修正コマンドの実行
print("\n[3] 修正方法を考えます。少々お待ちください。\n")
correction_code = get_response_from_chatGPT(generated_code_txt_with_try, traceback_str)
# [3-6] 実行して良いかの確認
print("\n[4] 修正のために、以下のコマンドを実行しても良いですか?\n")
print(correction_code)
user_input = input("宜しければyを入力しEnterを押してください。実行を中止する場合はnを入力しEnterを押してください: ")
# [3-7] 修正の実行
if user_input.lower() == 'y':
print("\n[5] それでは修正します。\n")
exec(correction_code, globals())
print("[6] 修正が完了しました\n\n 再実行に入ります・・・\n\n")
else:
print("ユーザーが実行中止を選択しました。")
break
このメイン関数を実行します。
# メインの実行
main(generated_code_txt_with_try)
すると、初回はPILパッケージの不足でエラーになります。
============================================
[1] 生成されたコードを実行します(今回は適当な画像のサイズを出力するコードです)
[2] エラーが発生しました
None Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'PIL'
[3] 修正方法を考えます。少々お待ちください。
そして、修正方法を考えてくれます。
[4] 修正のために、以下のコマンドを実行しても良いですか?
import subprocess
command = 'pip install pillow'
output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
print(output)
宜しければyを入力しEnterを押してください。実行を中止する場合はnを入力しEnterを押してください: y
修正案を提案してくれるので、問題なければ、yを入力し、Enterを押します。
今回は、pip install pillow
を実行したいと言っています。
足りないとスタックトレースに出てきたのはPILですが、きちんとpillowをインストールすると判断してくれています。
[5] それでは修正します。
Collecting pillow
Downloading Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl (3.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.6/3.6 MB 13.7 MB/s eta 0:00:00
Installing collected packages: pillow
Successfully installed pillow-10.0.1
[6] 修正が完了しました
再実行に入ります・・・
修正を実施し、pillowがインストールされ、再実行に入ります。
============================================
[1] 生成されたコードを実行します(今回は適当な画像のサイズを出力するコードです)
幅: 1322
高さ: 715
[Fin] 実行が完了しました
以上で、不足していたパッケージを自動で判断し、許可のもとインストールして、コードを再実行してくれる仕組みが作れました。
今回のものは非常に単純で、もっと注意すべき点やエラーのハンドリングにはいろいろありますが、ミニマムに動かす第一歩としては十分かと思います。
なんか、勝手にやってくれるのかっこいいです。
宜しければぜひお試しください。
(再掲)実装したGoogle Colabのnotebookはこちらになります。
以上、ご一読いただき、ありがとうございました。
小川 雄太郎
【免責】
本記事の内容は執筆者の意見/発信であり、執筆者が属する企業等の公式見解ではございません