promptfooの色々な機能を試す
promptfooはプロンプトの評価をしていくうえで便利そうでしたが、サンプルのコードでは自分のニーズにマッチするか判断がつきませんでした。色々と試してみたので備忘録としてまとめておきます。
基本
下記のようなyamlファイルで実験を記述していきます。
description: 'My first eval'
prompts:
- "Write a tweet about {{topic}}"
- "Write a very concise, funny tweet about {{topic}}"
providers:
- openai:gpt-3.5-turbo-0613
- openai:gpt-4
tests:
- vars:
topic: bananas
- vars:
topic: avocado toast
assert:
# For more information on assertions, see https://promptfoo.dev/docs/configuration/expected-outputs
- type: icontains
value: avocado
- type: javascript
value: 1 / (output.length + 1) # prefer shorter outputs
- vars:
topic: new york city
assert:
# For more information on model-graded evals, see https://promptfoo.dev/docs/configuration/expected-outputs/model-graded
- type: llm-rubric
value: ensure that the output is funny
Provider
カスタムProvider
ClaudeをVertex AI経由で利用したいケースは下記のようにProviderでPythonファイルを指定します。
providers:
- id: 'python:models/vertex_claude.py'
config:
pythonExecutable: "//path/to/python"
Pythonファイルは下記のように書きます。 call_api
関数がエンドポイントになります。
import anthropic
GCP_PROJECT = "your-gcp-project"
HAIKU = "claude-3-haiku@20240307"
def get_client():
return anthropic.AnthropicVertex(region="us-central1", project_id=GCP_PROJECT)
def call_api(prompt, options, context):
output, usage = call_llm(prompt, HAIKU)
result = {
"output": output,
"tokenUsage": {
"total": usage.input_tokens + usage.output_tokens,
"prompt": usage.input_tokens,
"completion": usage.output_tokens,
},
}
return result
def call_llm(prompt: str, model: str) -> tuple[str, anthropic.types.Usage]:
client = get_client()
message = client.messages.create(
model=model,
max_tokens=4096,
temperature=0.1,
messages=[
{"role": "user", "content": prompt},
],
)
return message.content[0].text, message.usage
LLM Chainを評価する場合の選択肢の1つでもあります。
詳細:
- https://promptfoo.dev/docs/providers/python
- https://promptfoo.dev/docs/configuration/testing-llm-chains/#using-a-script-provider
パラメータをチューニングする
Claudeのモデルを変えたり、temperatureやtop_k, top_pをチューニングしたい場合はconfig経由でパラメータを渡せます。ただしlabelを付与しないと、どのProviderで実行した結果なのか見分けがつかなくなるので、合わせて付与しましょう。
providers:
- id: 'python:models/vertex_claude.py'
label: 'Haiku temperature 0.1'
config:
model: 'haiku'
temperature: 0.1
pythonExecutable: "//path/to/python"
- id: 'python:models/vertex_claude.py'
label: 'Sonnet temperature 0.1'
config:
model: 'sonnet'
temperature: 0.1
pythonExecutable: "//path/to/python"
configで指定したパラメータはoptionsに入っています。
...
MODELS = {"haiku": "claude-3-haiku@20240307", "sonnet": "claude-3-sonnet@20240229"}
def call_api(prompt, options, context):
config = options.get("config", None)
model_name = config.get("model", "haiku")
model = MODELS[model_name]
temperature = config.get("temperature", 0.0)
output, usage = call_llm(prompt, temperature, model)
result = {
"output": output,
"tokenUsage": {
"total": usage.input_tokens + usage.output_tokens,
"prompt": usage.input_tokens,
"completion": usage.output_tokens,
},
}
return result
...
詳細:
Prompts
promptを別ファイルで指定する
プロンプトエンジニアリングで試行錯誤する際には、システムプロンプトを指定したり、few-shotのためにユーザープロンプトを作り込むことがあります。その際に構造がわかりやすいようにJSONファイルに切り出したくなります。その場合は1ファイル、もしくは複数ファイルに切り出すこともできます。
prompts:
- file://prompt.json
[
{
"role": "system",
"content": "あなたは翻訳のプロフェッショナルです。入力には日本語か英語を与えます。英語の場合は日本語に変換し、日本語の場合は英語に翻訳してください。"
},
{
"role": "user",
"content": "{{plain_text}}"
}
]
気をつけるべき点として、カスタムProviderとともにを利用する場合は、きちんとAPIリファレンスに沿って設定しないと失敗します。例えばClaudeのMessage APIを使う場合、ユーザープロンプトのroleはuserかassistantのみで、systemを指定すると失敗します。Anthropicのサンプルを見るとシステムプロンプトを設定していますが、これは裏側でうまくシステムプロンプトを分離してAPIに合わせているようです。
JSONファイルを利用すると、実態としては第一引数に文字列として渡ってきます。そこのため、parseしてよしなに変換すると解決できます(無理矢理感があり、ちょっと読みにくくなりますが...)。
def call_api(prompt_json: str, options, context):
...
_prompts = json.loads(prompt_json)
system_prompt, user_prompt = _prompts[0]["content"], list(_prompts[1])
output, usage = call_llm(system_prompt, user_prompt, temperature, model)
...
詳細:
Tests file
testを別ファイルで指定する
長文要約を実行したい、長文から情報抽出したいなど、テストケースが長文になる場合、promptfooconfig.yamlから切り出したくなります。その場合は1ファイル、もしくは複数ファイルに切り出すこともできます。またCSVも利用できます。
tests:
- test.yml
詳細:
model-assisted eval metricsをカスタムProviderで実行する
LLMの出力をLLMで評価したいケースがあります。promptfooではこれをmodel-assisted eval metrics、評価を行うLLMをgraderと呼んでいます。graderのデフォルトはOpenAIのGPT-4です。ここをカスタムProviderで実行する場合は下記のように指定します。
defaultTest:
options:
provider: 'python:vertex_claude.py'
config:
model: 'haiku'
temperature: 0.0
pythonExecutable: "//path/to/python"
tests:
- description: Use LLM to evaluate output
assert:
- type: llm-rubric
value: Is spoken like a pirate
他にもCLIで指定する方法と、テストケースごとにproviderを指定する方法があります。ニーズに応じて設定します。
詳細:
画像ファイルをテストする
Providerによって設定が異なりますが、基本的にはURLだったりbase64を指定します。LLMのAPIリファレンスを読むのが間違いがなく、そこから逆算してテストケースを作成します。
Gemini 1.0 Proの場合はURLにGCSのパスが利用できるので、今回はそちらでやってみます。ついでにtemperatureによる出力の違いも確認してみました。
description: 'My first eval for Gemini Pro vision'
prompts:
- "file://prompt.json"
providers:
- id: vertex:gemini-pro-vision
label: 'Gemini Pro temperature 0.1'
config:
generationConfig:
temperature: 0.1
- id: vertex:gemini-pro-vision
label: 'Gemini Pro temperature 1.0'
config:
generationConfig:
temperature: 1.0
tests:
- vars:
uri: "gs://xxx/yyy/zzz/sample.jpg"
[
{
"role": "user",
"parts": [
{ "text": "What is this image?" },
{
"fileData": {
"mimeType": "image/jpeg",
"fileUri": "{{uri}}"
}
}
]
}
]
詳細:
- ChatGPTのサンプル
- Claudeのサンプル
- https://www.promptfoo.dev/docs/providers/anthropic/#images--vision
- https://www.promptfoo.dev/docs/providers/vertex/
- https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini
promptfooconfig.yaml
実行時にpromptfooconfig.yamlを指定する
複数人で開発したり、複数プロンプトを評価したい場合、1つのpromptfooconfig.yamlを使い回すのではなく、新しいファイルを作りたいケースもあります。その場合はCLIの -c
オプションでyamlファイルを指定できます。
npx promptfoo@latest eval -c experiments/config1.yaml
詳細:
Discussion