やりたいこと
- プロンプトエンジニアリングの一種として、実装のない関数をChatGPTに渡して実装を推測してもらうというやり方がある
- これをもうちょっと使いやすくしたい。具体的には普通の(Python)関数と同じ感じで使えるようにしたい
元ネタ
marvinというライブラリで同じようなことができる
改善したい点
- ChatGPTへ渡す時のテンプレートが固定
- 拡張性の観点で、ここは自分で触れるようにしたい
やってみた
こちらの実装もmarvinの実装を相当程度参考にしています
まずは、ChatGPTに関数実行をエミュレートさせる部分
excute_by_ai.py
from typing import Dict, Any
import json
def excute_by_ai(fn_def: str, arguments: Dict[str, Any]):
function_name = fn_def.lstrip("def").split("(")[0].strip()
kv = ", ".join(["=".join([key, json.dumps(value, ensure_ascii=False)]) for key, value in arguments.items()])
# `の前の\はQiitaのsyntax highlightのためなので、本来は不要
context = f"""
\```python
{fn_def.strip()}
\```
Estimate the return value when it was called with follwing arguments.
Only returns the return value of the function. No explanation is required.
{function_name}({kv})
"""
# print(context)
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a python simulator, which simulates the return value from definition of function."},
{"role": "user", "content": context[1:-1]}
],
max_tokens=1024,
temperature=0
)
result = response.choices[0]['message']['content']
# 戻り値は ->と:の間にある
return_type: str = fn_def.split("->")[1].split(":")[0].strip()
# print(return_type)
### 戻り値の型に応じて、パース可能なように変換する
# 戻り値がstrなら""で囲む
if (return_type == "str" or return_type == "Optional[str]") and result != "None":
if not result.startswith('"'):
result = f'"{result}'
if not result.endswith('"'):
result = f'{result}"'
try:
return eval(result)
except:
print(response.choices)
print(result)
raise AssertionError
この関数を普通の関数っぽく使えるようにするためのデコレータ
ai_decorator.py
import inspect
from typing import Callable, Optional, List
import excute_by_ai
def ai_decorator(fn: Callable):
# 必要に応じてつけてください
# @wraps(fn)
def ai_wrapper(*args, **kwargs):
# 関数定義の取得
# @ai_decoratorの行を消したいので2行目以降だけにしている
fn_def = "\n".join(inspect.getsource(fn).split("\n")[1:])
# 関数の引数を取得
sig = inspect.signature(fn)
bound_args = sig.bind(*args, **kwargs)
return excute_by_ai(fn_def, bound_args.arguments)
return ai_wrapper
利用例
import ai_decorator
# type hintやdocstringはChatGPT実行時のヒントになるので書くことを推奨します
@ai_decorator
def extract_song_title(video_title: str, description: str) -> Optional[str]:
"""動画のタイトルと概要欄から、曲名だけを抽出する。取得できない場合はNoneを返す"""
song_title = extract_song_title(video_title=video_title, description=description)
どう動くの?
ai_decoratorについて
デコレータの動きについては下記記事などを参考にしてください
結局、以下のように変換された結果、ChatGPTを呼び出す関数(excute_by_ai)が呼ばれます
# 元々の記述
@ai_decorator
extract_song_title(video_title=video_title, description=description)
# デコレータを展開する
ai_decorator(extract_song_title)(video_title=video_title, description=description)
# ai_decorator(extract_song_title)を実行してai_wrapperが返ってくる
# ただしai_wrapper内の変数fnはextract_song_titleにbindされている
ai_wrapper(video_title=video_title, description=description)
# 最終的に呼ばれる関数
excute_by_ai(extract_song_titleの定義, extract_song_titleの引数)
excute_by_ai
色々やってますが、最終的に以下のようなプロンプトがChatGPTに渡されます
# 上のコードと同じく、\はQiita用に追加したものなので実際には送られません
\```python
def extract_song_title(video_title: str, description: str) -> Optional[str]:
"""動画のタイトルと概要欄から、曲名だけを抽出する。取得できない場合はNoneを返す"""
\```
Estimate the return value when it was called with follwing arguments.
Only returns the return value of the function. No explanation is required.
extract_song_title(video_title="[video_title]", description="[description]")
使い道
以前記事にしたような、コード化しにくいデータ処理をしたい時などに使えそうかなと思います。
おまけ

