見出し画像

guidanceのpromptは.handlebarsで保存しておくとコードハイライトが効いて良い

guidanceのプロンプトで使用されているテンプレートエンジンにはHandlebarsぽい文法を独自に拡張したものが利用されています。

Handlebarsはフロントエンドの世界では有名なテンプレートエンジンですが、最近はあんまり利用例を聞かないなぁ・・・という印象です。backbone.jsとかEmber.jsだとかを触っている時代に使っていた印象のあるもので、React全盛の今となってはあんまり馴染みがないのではないでしょうか。

こんな感じのコードですね。

<ul class="people_list">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>

Handlebarsに慣れている人にとっては、guidanceではあくまでHandlebarsぽい文法が利用できるだけで、Handlebarsの文法をフルにサポートしているわけではないことに注意が必要です。guidanceのコードを見てみると、以下のようにParsimoniousライブラリを用いて文法定義していることが分かります。

でもまぁ、大体Handlebarsなので、プロンプト部分にはHandlebarsのコードハイライトを適用して欲しいところです。こんな感じのそこそこ長いプロンプトになってくると、Pythonコードの中に直接HTMLを書き込んでいるような気持ちになっちゃいますよね。

role_simulator = guidance('''{{#system~}}
You are a helpful assistant
{{~/system}}

{{#user~}}
You will answer the user as {{role}} in the following conversation. At every step, I will provide you with the user input, as well as a comment reminding you of your instructions. Never talk about the fact that you are an AI, even if the user asks you. Always answer as {{role}}.
{{#if first_question}}You can also start the conversation.{{/if}}
{{~/user}}

{{! The assistant either starts the conversation or not, depending on if this is the first or second agent }}
{{#assistant~}}
Ok, I will follow these instructions.
{{#if first_question}}Let me start the conversation now:
{{role}}: {{first_question}}{{/if}}
{{~/assistant}}

{{! Then the conversation unrolls }}
{{~#geneach 'conversation' stop=False}}
{{#user~}}
User: {{set 'this.input' (await 'input')}}
Comment: Remember, answer as a {{role}}. Start your utterance with {{role}}:
{{~/user}}

{{#assistant~}}
{{gen 'this.response' temperature=0 max_tokens=300}}
{{~/assistant}}
{{~/geneach}}
''')

試しにプロンプトだけ別ファイルで保存してHandlebarsのハイライトを適用してみると、バッチリコードハイライトを適用できます。

PyCharmで表示した例

利用するときはこんな感じにファイル読み込みするだけ。

with open('prompt.handlebars', 'r') as f:
    role_simulator = guidance(f.read())

guidanceのチャットプロンプトはそこそこ複雑になりがちだと思うので、プロンプトはこんな感じに外出しするのが主流になるのではないでしょうか。


ところで上記のプロンプトはREADMEに載っている対話型エージェントの例なのですが、個人的には対話させている部分のコードがめちゃ読みにくいなぁ・・・と感じます。

こんな感じにクラスでラップしてあげると対話部分のコード(first_question以降のコード)が大分読みやすくなる気がするので、以下のような感じで書いてみるのも良いのではないでしょうか。

from collections import namedtuple
import time
import guidance

Response = namedtuple('Response', 'timestamp role text')


class RoleSimulator:
    TEMPLATE_FILE_NAME = 'prompt.handlebars'

    def __init__(self, role, await_missing=True):
        self.role = role
        self.agent = self.guidance()(role=role, await_missing=await_missing)
        self.conversation = []

    @classmethod
    def guidance(cls):
        try:
            with open(cls.TEMPLATE_FILE_NAME, 'r') as f:
                return guidance(f.read())
        except IOError as e:
            print(f"Error opening file: {e}")
            return None

    def respond_to(self, input_text, first_question=None):
        self.agent = self.agent(input=input_text, first_question=first_question)
        response_text = self.agent["conversation"][-2]["response"]
        response = Response(time.time(), self.role, response_text)
        self.conversation.append(response)

    def get_latest_response(self):
        return self.conversation[-1].text


def print_conversation(conversation):
    for response in conversation:
        print(f"{response.text}\n")


first_question = '''What do you think is the best way to stop inflation?'''

republican = RoleSimulator(role='Republican')
democrat = RoleSimulator(role='Democrat')

republican.respond_to(first_question)
democrat.respond_to(republican.get_latest_response(), first_question)

for _ in range(2):
    republican.respond_to(democrat.get_latest_response())
    democrat.respond_to(republican.get_latest_response())

conversation = republican.conversation + democrat.conversation
conversation.sort()
print_conversation(conversation)

ちなみにこっちが元のサンプルコードです。

republican = role_simulator(role='Republican', await_missing=True)
democrat = role_simulator(role='Democrat', await_missing=True)

first_question = '''What do you think is the best way to stop inflation?'''
republican = republican(input=first_question, first_question=None)
democrat = democrat(input=republican["conversation"][-2]["response"].strip('Republican: '), first_question=first_question)
for i in range(2):
    republican = republican(input=democrat["conversation"][-2]["response"].replace('Democrat: ', ''))
    democrat = democrat(input=republican["conversation"][-2]["response"].replace('Republican: ', ''))
print('Democrat: ' + first_question)
for x in democrat['conversation'][:-1]:
    print('Republican:', x['input'])
    print()
    print(x['response'])

見比べてみて、どうでしょう。好みの領域かなぁ・・・。

現場からは以上です。

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