見出し画像

[LangChain]分かりやすくカスタムプロンプトテンプレートを解説

公式ドキュメントを参考に解説します。
プロンプトテンプレートの応用であるカスタムテンプレートがテーマです。


・そもそもプロンプトテンプレートって何

例えば、
"{name}さん、こんにちは!"
という使いまわしが効くプロンプトを作りたい場合に使用します。
↓公式サンプル

from langchain import PromptTemplate

prompt_template = PromptTemplate.from_template(
    "Tell me a {adjective} joke about {content}."
)
prompt_template.format(adjective="funny", content="chickens")

こんな感じにスクリプトを書くのですが、要は{}で囲んだ部分と同じ名前で引数指定してあげると、その箇所を置換してくれるわけです。
サンプルだと
"Tell me a funny joke about chickens."
と出力されます。

・カスタムプロンプトテンプレートって何

プロンプトテンプレートを改造して、なんかもっと複雑なことしたいなぁって時に使います。

例えば、「〇〇っていう名前の関数に書いてある実装コードはこれなんだけど、要約して!」というプロンプトをもとに
"{function_name}っていう名前の関数に書いてある実装コードは{source_code}なんだけど、要約して!"
と使い回しが効く形のプロンプトテンプレートを作りたいとします。
で、通常のブロンプトテンプレートだとfunction_nameとsource_codeを引数に渡さなきゃいけないわけです。
ここで、「function_nameだけ渡せばうまいことソースコードを引っ張ってきて、プロンプトに含んでくれないかなぁ」という要望を叶えるのがカスタムプロンプトテンプレートになります。
もうちょっと具体的な感じにすると、
「"function_nameを渡すとソースコードを文字列でくれる関数"を自前で用意したんだけど、プロンプトテンプレートが内部でそれ使ってくれたら、僕は関数名を渡すだけで済むのになぁ」という要望を叶えることができます。

別の使い方もあると思いますけど、僕の理解だと、文字列置換だけじゃなくて内部で色んな関数を使ってもうちょい複雑なことしたい時に使うのかなぁって思いました。

で、どうスクリプト書くの?

公式になんかサンプルあるんですけどぱっと見よく分からないです。とりあえず僕がちょっと変更したのを全文貼って、そのあとに細かく説明します。

from langchain.prompts import StringPromptTemplate
from pydantic import BaseModel, validator
import inspect

PROMPT = """\
関数の名前とソースコードを元に、その関数の英語の説明を生成します。
関数名: {function_name}
ソースコード:
{source_code}
説明:
"""


class FunctionExplainerPromptTemplate(StringPromptTemplate, BaseModel):
    @validator("input_variables")
    def validate_input_variables(cls, v):
        if len(v) != 1 or "function_name" not in v:
            raise ValueError("function_name must be the only input_variable.")
        return v

    def format(self, **kwargs) -> str:
        # Get the source code of the function
        source_code = get_source_code(kwargs["function_name"])

        # Generate the prompt to be sent to the language model
        prompt = PROMPT.format(
            function_name=kwargs["function_name"].__name__, source_code=source_code
        )
        return prompt

    def _prompt_type(self):
        return "function-explainer"


def get_source_code(function_name):
    # Get the source code of the function
    return inspect.getsource(function_name)


def print_hello():
    print("hello")


fn_explainer = FunctionExplainerPromptTemplate(input_variables=["function_name"])

# Generate a prompt for the function "get_source_code"
prompt = fn_explainer.format(function_name=print_hello)
print(prompt)

まずこれが、最終的に作りたいプロンプトです。

PROMPT = """\
関数の名前とソースコードを元に、その関数の英語の説明を生成します。
関数名: {function_name}
ソースコード:
{source_code}
説明:
"""

次にこれ

import inspect


def get_source_code(function_name):
    # Get the source code of the function
    return inspect.getsource(function_name)


def print_hello():
    print("hello")

pythonだとinspectってモジュールが標準であって、関数名を渡すとその関数の実装コードを文字列でくれるっていう便利関数が用意されてます。
例えば、get_source_code(print_hello)って実行すると、

def print_hello():
    print("hello")

が文字列として取得できます。
じゃあ、これをプロンプトテンプレートが使ってくれれば自分は関数名を渡すだけで良さそうですね。

class FunctionExplainerPromptTemplate(StringPromptTemplate, BaseModel):
    @validator("input_variables")
    def validate_input_variables(cls, v):
        if len(v) != 1 or "function_name" not in v:
            raise ValueError("function_name must be the only input_variable.")
        return v

    def format(self, **kwargs) -> str:
        # Get the source code of the function
        source_code = get_source_code(kwargs["function_name"])

        # Generate the prompt to be sent to the language model
        prompt = PROMPT.format(
            function_name=kwargs["function_name"].__name__, source_code=source_code
        )
        return prompt

    def _prompt_type(self):
        return "function-explainer"

のまずここ

    @validator("input_variables")
    def validate_input_variables(cls, v):
        if len(v) != 1 or "function_name" not in v:
            raise ValueError("function_name must be the only input_variable.")
        return v

このカスタムテンプレートのインスタンスを作る時に、"input_variables"っていう引数を使った場合の処理を実装してます。
具体的には、

fn_explainer = FunctionExplainerPromptTemplate(input_variables=["function_name"])

# Generate a prompt for the function "get_source_code"
prompt = fn_explainer.format(function_name=print_hello)
print(prompt)

一行目、実際に自分で作ったカスタムテンプレートのインスタンスを生成する時は、
引数にinput_variables、それと、その中に"function_name"ってありますよね。
ここでinput_variablesが使われた際の内部の処理を書いてるわけです。
内部の処理、validate_input_variables()を見ると、"function_name"のパラメータだけがちゃんと渡されたのかチェックしてます。
要はそれ以外のパラメータは不要なので、弾く処理を書いたわけです。

    def format(self, **kwargs) -> str:
        # Get the source code of the function
        source_code = get_source_code(kwargs["function_name"])

        # Generate the prompt to be sent to the language model
        prompt = PROMPT.format(
            function_name=kwargs["function_name"].__name__, source_code=source_code
        )
        return prompt

本命のコード。プロンプトを作る時の処理の実装です。
まず、引数で渡されたfunction_nameに入っている文字列をさっき自前で用意したget_source_code()に渡しています。もうこれでソースコード取得できました。
あとは、文字列置換の関数 format()を遣えば、最終的に作りたいプロンプト

PROMPT = """\
関数の名前とソースコードを元に、その関数の英語の説明を生成します。
関数名: {function_name}
ソースコード:
{source_code}
説明:
"""

が作れます。

    def _prompt_type(self):
        return "function-explainer"

これはおまけみたいなもの?とりあえず関数を説明する機能を作ったので、
"関数-説明者"という名前を返すようにしてます。

最後、

fn_explainer = FunctionExplainerPromptTemplate(input_variables=["function_name"])

# Generate a prompt for the function "get_source_code"
prompt = fn_explainer.format(function_name=print_hello)
print(prompt)

で、自前で作ったカスタムテンプレートを作って、それを使用して、プロンプトが完成しました。これを実行すると、

関数の名前とソースコードを元に、その関数の英語の説明を生成します。
関数名: print_hello
ソースコード:
def print_hello():
    print("hello")

説明:

というプロンプトが出来ます。
以上です。

何か間違ってたら教えてください。

この記事が気に入ったらサポートをしてみませんか?