0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAIの結果先行プロンプトをPythonで契約化する

0
Posted at

7月13日、OpenAIの新しいプロンプティングガイドを扱ったニュースが出た。「考え込みすぎず、結果から始める」という話だ。これが意味するのは、長い指示を書く技術より先に、完了の定義を固定するほうが実務では効く、ということだと思う。

自分はリリースノートのような小さな生成でも、背景や注意点から順に書いていた。さっき同じ依頼を「最終成果物」「完了条件」「入力」「制約」の順に組み替えてみたら、確認したいポイントが先に見える。地味だけど、レビューで戻す回数に直結する差だった。

結論から書くと、プロンプトを短くする前に「何が返れば完了か」を文字列として検査できる形にするのがいい。モデルに渡す文章と、呼び出す側の検査を同じ契約から作る。

先に置くのは答えではなく完了条件

たとえば「git diff からリリースノートを書いて」は、実装する側には情報が足りない。JSONなのかMarkdownなのか、変更のない部分を補ってよいのか、分からないときに推測してよいのか。この空白をモデルが埋めると、もっともらしい余計な一文が混ざる。

ここでは release-note.json を最終成果物に決め、キーを3個に限定する。risks は空配列を許可する。リスクがなかったことまで無理に文章にさせないためだ。

Pythonでプロンプト契約を作る

次のコードはPython 3.9で実行した。外部パッケージは使っていない。LLM APIを呼ぶ直前に build_prompt() を呼び、返答を受けた直後に validate_release_note() を呼ぶ想定だ。

from dataclasses import dataclass
from typing import List
import json

BANNED_GOALS = ("いい感じ", "適切に", "よしなに")
ALLOWED_KEYS = {"title", "summary", "risks"}

@dataclass(frozen=True)
class PromptContract:
    artifact: str
    done_when: List[str]
    source: str
    constraints: List[str]

def _required(label: str, value: str) -> str:
    value = value.strip()
    if not value:
        raise ValueError(label + " を空にできません")
    if any(word in value for word in BANNED_GOALS):
        raise ValueError(label + " に曖昧語があります: " + value)
    return value

def build_prompt(contract: PromptContract) -> str:
    artifact = _required("artifact", contract.artifact)
    source = _required("source", contract.source)
    done_when = [_required("done_when", item) for item in contract.done_when]
    constraints = [_required("constraints", item) for item in contract.constraints]
    if not done_when:
        raise ValueError("done_when を1件以上指定してください")

    return "\n".join([
        "# 最終成果物", artifact,
        "", "# 完了条件", *["- " + item for item in done_when],
        "", "# 入力", source,
        "", "# 制約", *["- " + item for item in constraints],
        "", "完了条件を満たす成果物だけを返してください。",
    ])

def validate_release_note(raw: str) -> dict:
    note = json.loads(raw)
    if set(note) != ALLOWED_KEYS:
        raise ValueError("keys は title, summary, risks だけにしてください")
    if not isinstance(note["title"], str) or not isinstance(note["summary"], str):
        raise ValueError("title と summary は文字列にしてください")
    if not isinstance(note["risks"], list) or not all(
        isinstance(item, str) for item in note["risks"]
    ):
        raise ValueError("risks は文字列の配列にしてください")
    return note

contract = PromptContract(
    artifact="release-note.json(JSONオブジェクト1件)",
    done_when=[
        "keys が title, summary, risks の3つだけである",
        "risks は空配列を許可し、推測で項目を増やさない",
    ],
    source="git diff --cached の出力を根拠にする",
    constraints=[
        "変更されていないファイルには触れない",
        "根拠がない事項は unknown と書く",
    ],
)

print(build_prompt(contract))
print(validate_release_note(
    '{"title":"設定を更新","summary":"timeoutを30秒に変更","risks":[]}'
))

try:
    build_prompt(PromptContract("いい感じの要約", ["JSONを返す"], "diff", ["短く"]))
except ValueError as error:
    print("ERROR:", error)

出力はこうなる。

# 最終成果物
release-note.json(JSONオブジェクト1件)

# 完了条件
- keys が title, summary, risks の3つだけである
- risks は空配列を許可し、推測で項目を増やさない
{'title': '設定を更新', 'summary': 'timeoutを30秒に変更', 'risks': []}
ERROR: artifact に曖昧語があります: いい感じの要約

BANNED_GOALS は万能ではない。チームでよく出る曖昧語を、失敗ログを見ながら2、3個だけ足すのが現実的だった。ここを辞書のように膨らませ始めると、今度は依頼そのものが書きにくくなる。

プロンプトと検査を分ける理由

このコードのポイントは、プロンプトが正しいから出力も正しい、と扱わないところにある。build_prompt() は依頼の抜けを呼び出し前に止める。validate_release_note() はモデルが余計なキーを返したときに、保存前で止める。

順番を変えるだけではなく、失敗を止める場所を二つに分けた。前者は人間が依頼を直す場所で、後者は再試行や人の確認へ戻す場所になる。特にエージェントにファイル更新やチケット作成をさせるなら、後段の検査なしで次のツールへ渡すのは危ない。

細かいけれど、set(note) != ALLOWED_KEYS は余計なキーと不足したキーを一度に弾ける。一方で、この例は空文字の summary までは弾かない。要約が空なら失敗にしたい業務では、not note["summary"].strip() の条件も追加する。契約は厳しいほどよいわけではなく、保存先が本当に必要とする条件だけを置く。ここを盛りすぎると、今度は正しい返答まで落とす。

OpenAIのガイドにある「結果から始める」は、結論を一行だけ先頭へ置く小技ではないはずだ。成果物、完了条件、入力の順で決めると、プロンプトは説明文からインターフェースに近づく。自分はまず、毎週繰り返す1本の自動化だけをこの形にして、失敗時に何を追加したくなったかを見るつもりだ。

おわりに

プロンプトを長文化しても、完了条件が曖昧ならレビューの手間は減らない。最終成果物を先に書き、機械で確かめられる条件を二つほど置く。生成後にも同じ条件を検査する。

この3点だけで、モデルの出力を「読んで判断するもの」から「通してよいか判定できるもの」へ寄せられる。LLMを業務フローに入れるとき、最初に育てるべきなのは凝った指示文よりこの境界だと思う。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?