ChatGPTのプロンプトを効率的に作成・管理したい!

# プロンプトの作成・管理のためのPythonライブラリ

~~プロンプトのメンテナンス沼から解放されたい~~


Generative Pre-trained Transformer（GPT）モデルの回答"品質"は入力プロンプトに依存しています。

プロンプトは、モデルに何を生成するべきかを指示する重要な手がかりであり、プロンプトの管理は日々の中でも繰り返し発生し、また、業務として複数人でプロンプトを運用するケースなども考えられます。

そこで、今回はプロンプトを効率的に管理するためのPythonライブラリ[Promptex](https://github.com/rinov/promptex)について紹介をさせていただきます。

URL: https://github.com/rinov/promptex

## 主な機能

**Promptex**は以下の機能にフォーカスしています。

1. **プロンプト管理**: ニーズに合わせたプロンプトの設定と取得の方法を提供します。
2. **データ統合**: データパイプラインにシームレスに統合するための多様なファイル形式（JSON, JSON Schema, CSV, XML, YAML, HTML）をサポートします。
3. **分析**: プロンプト内の要素について、テキスト長さやトークン数など詳細な統計情報を収集・提供します。
4. **スケーラビリティ**: 比較的大きいプロジェクトでのプロンプト管理を念頭に設計し、テキストの管理と出力の分離によってチーム管理やコンポーネント管理などを効率的に進めることができるようになります。

分析などについてはまだ改善の余地があり、今後は多言語管理やChatGPT APIなどとシームレスに連携できるようにする予定です。

## プロンプト管理が必要な理由

プロンプトはGPTの回答の精度や一貫性、再現性を確保するために重要なものです。
プロジェクトや研究で使用されるプロンプトを効率的に管理することは、文の意味だけでなく、プロンプトの特性（構成や順序、入力と出力のトークン比率など）を理解することで、GPTモデルのパフォーマンスの最大化に繋がります。

また、実験としてさまざまな種類のプロンプトを生成したり、同じ分でもレイアウト構成や区切り文字などの違いによって解釈精度が異なるため、効率的に実験や評価をする際にはプロンプト自体の管理フレームワークが必要となってきます。

## インストール


```bash
pip install -U pip
pip install promptex
```

## プロンプトの構成要素
Promptexではデフォルトで以下の構成要素が提供されています。
これらを組み合わせることでプロンプトを構造的に管理することができます。

例えば、指示の中に制約がある場合にはINSTRUCTIONにCONSTRAINTがネストされるような形になります。

```
### ROLE
役割を表現する要素

### INSTRUCTION
指示を表現する要素

### CONSTRAINT
制約条件を表現する要素

### CONTEXT
背景を表現する要素

### INPUT_DATA
入力データを表現する要素

### OUTPUT_FORMAT
回答の出力形式や例を表現する要素
```

## Promptexの使用例

使い方は、以下のようにElementという単位でプロンプトを構成します。
Elementはネスト構造にすることができ、文章毎の因果関係を構造化します。


※詳しい使用例については、リポジトリ内の"examples"が参考になります

```python
from promptex.promptex import Promptex
from promptex.elements import *
from promptex.encoding_strategy import *


promptex = Promptex()

promptex.set_elements(
    [
        Role("あなたはプロのゲームデザイナーです"),
        Instruction(
            "新しくファンタジーRPGを着想として画期的なアイデアを掲示してください"
        ).add_elements(
            [
                Constraint(
                    "舞台は西洋にしてください"
                ),
                Constraint(
                    "神話をモチーフにした世界観にしてください"
                ),
            ]
        ),
        OutputFormat("Markdown形式で世界観、ストーリー、キャラクターについて日本語で回答してください"),
    ]
)

strategy = SimpleJsonEncodingStrategy()
prompt = strategy.encode(promptex)
print(prompt)
```

試しにSimpleJsonEncodingStrategyで出力をすると以下のような結果が取得できます。

```json
[
  {
    "type": "role",
    "text": "あなたはプロのゲームデザイナーです"
  },
  {
    "type": "instruction",
    "text": "新しくファンタジーRPGを着想として画期的なアイデアを掲示してください",
    "elements": [
      {
        "type": "constraint",
        "text": "舞台は西洋にしてください"
      },
      {
        "type": "constraint",
        "text": "神話をモチーフにした世界観にしてください"
      }
    ]
  },
  {
    "type": "output_format",
    "text": "Markdown形式で世界観、ストーリー、キャラクターについて日本語で回答してください"
  }
]
```

JSONのテキストをそのままGPTに与えるメリットとして、テキストでは表現構造が明確にすることが難しい指示や制約の関係を解釈性が高い状態で入力として与えることができ、回答の精度が向上することが見込めます。

また、Promptexでは要素毎に優先度の設定も可能なため同じ制約の中でも、なにが最も優先されるかを表現することも可能です。

さらに、出力形式として通常のテキスト出力やCSV, XML, HTML, JSON Schemaなどさまざまな形式をサポートしています。


これにより、プロンプトの可視性・保守性が向上することで複数人での管理・共有が容易になります。

## トークン数を確認したい
ChatGPTなどのGPTモデルには入出力トークンの最大値が存在します。
そのため、入力を多くしすぎると回答できる余力などに影響があり、これらはトレードオフになっています。
そのため、プロンプトがどれくらいのトークン数で構成されているかを知ることは重要です。

以下の例ではプロンプトをテキストにした際に、GPT4のモデルに入力する場合にはどれくらいのトークン数が消費されるかを確認することができます。

内部的にはChatGPTなどに利用しているTokenizeライブラリと同様のものを利用しているため、さまざまなモデルでの正確なトークン数を計算することができ、最適化やAPIで利用する際のDry-runでのコスト見積もりなどにも活用することができます。

```python
from promptex.promptex import Promptex
from promptex.elements import *
from promptex.encoding_strategy import *

promptex = Promptex()

promptex.set_elements(
    [
        Instruction("Create an innovative quest for a new fantasy RPG game."),
        OutputFormat("Markdown text in English"),
    ]
)

text = SimpleTextEncodingStrategy().encode(promptex)
encoding_model_name = "gpt-4" # gpt-3.5-turbo, text-davinci-001, etc..
token_count = promptex.get_token_count(
    text=text, encoding_model_name=encoding_model_name
)
stats = promptex.get_stats(text=text, encoding_model_name=encoding_model_name)

print(f"Token consumption: {token_count}")

print(f"Stats: {json.dumps(stats, indent=2, ensure_ascii=False)}")

"""
Token consumption: 27
Stats: {
  "element_count": {
    "Instruction": 1,
    "OutputFormat": 1,
    "total": 2
  },
  "text_length": {
    "Instruction": {
      "min": 0,
      "max": 54,
      "avg": 54.0
    },
    "OutputFormat": {
      "min": 0,
      "max": 24,
      "avg": 24.0
    },
    "total": 124
  },
  "token_count": {
    "Instruction": {
      "min": 0,
      "max": 11,
      "avg": 11.0
    },
    "OutputFormat": {
      "min": 0,
      "max": 4,
      "avg": 4.0
    },
    "total": 27
  }
}
"""
```

## 独自の要素を作成したい
ElementはPromptexで提供されているもの以外に、独自で追加定義することも可能です。

```python

from promptex.promptex import Promptex
from promptex.elements import *
from promptex.encoding_strategy import *

# 新たなElementを定義する
class Example(Element):
    def __init__(self, text: str):
        super().__init__("example", text)


promptex = Promptex()

promptex.set_elements(
    [
        Instruction("Create an innovative quest for a new fantasy RPG game."),
        OutputFormat("Markdown text in English").add_element(
            Example("# Quest: The Lost Sword of the Kingdom")
        ),
    ]
)
```

## 独自のフォーマットに出力したい
PromptexではEncodingStrategyという単位で出力方式を多様化しています。(JSON, CSV, XML...など)

もしデフォルトで用意されていないものや、独自のフォーマットで出力したい場合はカスタムのEncodingStrategyを以下のように作成することができます。

```python
from promptex.promptex import Promptex
from promptex.elements import *
from promptex.encoding_strategy import *

# バイナリ化して出力するEncodingStrategyを作成してみる
class BinaryEncodingStrategy(BaseEncodingStrategy):
    def __init__(self):
        super().__init__()

    def encode(self, promptex: Promptex) -> bytes:
        prompt = ""
        for element in promptex.elements:
            prompt += element.text
        return prompt.encode()


promptex = Promptex()

promptex.set_elements([Instruction("Create a new fantasy RPG game.")])

strategy = BinaryEncodingStrategy()
prompt = strategy.encode(promptex)
print(prompt)
```

## 保存や読み込み
テキストやJSONなどに出力して保存することも可能ですが、Promptexをそのまま保存・読み込みすることが可能です。

```python
path = "examples/prompt"

# 保存
promptex.save_prompt(path)

# 読み込み
promptex = promptex.load_prompt(path)
```


## まとめ

GPTモデルのプロンプト管理と分析を効率化するためのツールとしてPromptexを紹介しました。

自然言語での入力を解釈できるGPTですが、毎回詳細な指示な制約などケースに応じたテンプレートを管理・運用していくのは大変です。

プロンプトを効率的に管理してより良いGPTの体験を実現しましょう!

もし興味がある方はリポジトリからContributeや今後の課題をIssueとしていただけると嬉しいです。
最後まで読んでいただき、ありがとうございました。