0
0

More than 1 year has passed since last update.

ChatGPTを使ったPython関数エミュレーション

Posted at

やりたいこと

  • プロンプトエンジニアリングの一種として、実装のない関数を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]")

使い道

以前記事にしたような、コード化しにくいデータ処理をしたい時などに使えそうかなと思います。

おまけ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0