EvalHarness/evalharness/model/prompt_renderers.py

185 lines
8.0 KiB
Python

"""Prompt renderer plugins: one registered function per ``prompt_style``.
Before this module the runner's ``assemble()`` grew a chain of
``if spec_style == 'xxx'`` branches; now each style is a plugin:
@register_prompt_renderer('aime_es')
def aime(question, sample, spec, prompt_style):
return {'question': ...}
Renderer contract:
- input: the bare question text + the Sample + the DatasetSpec
- output: dict with any of ``question`` (rewritten), ``system`` (a system
message to prepend), ``few_shot_header``/``few_shot_glue`` (few-shot
layout hints consumed by assemble)
- unregistered styles fall back to assemble's generic MCQ/QA handling.
Adding a benchmark prompt style = dropping a plugin here; the runner
never changes.
"""
from typing import Any, Callable, Dict, Optional
RENDERERS: Dict[str, Callable] = {}
def register_prompt_renderer(*styles: str):
def decorator(fn):
for s in styles:
RENDERERS[s] = fn
return fn
return decorator
def get_renderer(style: str) -> Optional[Callable]:
return RENDERERS.get(style)
def render(style: str, question: str, sample, spec, prompt_style: str = '') -> Dict[str, Any]:
"""Apply the style's renderer; unknown styles pass through untouched."""
fn = RENDERERS.get(style)
if fn is None:
return {}
out = fn(question=question, sample=sample, spec=spec, prompt_style=prompt_style)
return out if isinstance(out, dict) else {}
# ------------------------------ plugins ------------------------------
@register_prompt_renderer('trivia_es')
def trivia_es(question, sample, spec, prompt_style):
# es trivia template, verbatim (open-book with wiki evidence, trailing
# newline included)
return {'question': (
'Read the content and answer the following question.\n\n'
f"Content: {(sample.metadata or {}).get('evidence') or []}\n\n"
f'Question: {question}\n\n'
'The last line of your response should be of the form "ANSWER: [ANSWER]" '
'(without quotes) where [ANSWER] is the answer to the problem.\n')}
@register_prompt_renderer('aime_es')
def aime_es(question, sample, spec, prompt_style):
# es/MathArena template (NOT lstripped -- leading \n kept; reminder tail
# after the question, both verbatim from aime_adapter)
return {'question': (
'\nSolve the following math problem step by step. '
'Put your answer inside \\boxed{}.\n\n' + question
+ '\n\nRemember to put your answer inside \\boxed{}.')}
@register_prompt_renderer('imo_es')
def imo_es(question, sample, spec, prompt_style):
return {'question': (
f'Problem:\n{question}\n\nPlease reason step by step, and put your '
f'final answer within \\boxed{{}}.\n')}
@register_prompt_renderer('simple_qa_es')
def simple_qa_es(question, sample, spec, prompt_style):
return {'question': f'Answer the question:\n\n{question}'}
@register_prompt_renderer('lb2_es')
def lb2_es(question, sample, spec, prompt_style):
# es longbench-v2 template: <text> wrapper + CoT last-line contract
letters = 'ABCD'
opts = '\n'.join(f'{letters[i]}) {c}' for i, c in enumerate(sample.choices or []))
ctx = (sample.metadata or {}).get('context', '')
return {'question': (
'Please read the following text and answer the questions below.\n\n'
f'<text>\n{ctx}\n</text>\n\n'
"Answer the following multiple choice question. The last line of your response should be "
"of the following format: 'ANSWER: [LETTER]' (without quotes) where [LETTER] is one of "
f'{",".join(letters[:len(sample.choices or [])])}. Think step by step before answering.\n\n'
f'{question}\n\n{opts}')}
@register_prompt_renderer('lcb_es')
def lcb_es(question, sample, spec, prompt_style):
# es/official LCB (load_utils + adapter, verbatim): the expert-programmer
# header is a SYSTEM message (injected by the runner); user keeps
# ### Question:/### Format:/### Answer
starter = (sample.metadata or {}).get('starter_code')
if starter:
fmt = ('### Format: You will use the following starter code to write the '
'solution to the problem and enclose your code within delimiters.\n'
f'```python\n{starter}\n```\n\n')
else:
fmt = ('### Format: Read the inputs from stdin solve the problem and write '
'the answer to stdout (do not directly test on the sample inputs). '
'Enclose your code within delimiters as follows.\n'
'```python\n# YOUR CODE HERE\n```\n\n')
return {'question': (f'### Question:\n{question}\n\n{fmt}### Answer: (use the '
'provided format with backticks)\n\n'),
'system': ('You are an expert Python programmer. You will be given a question '
'(problem specification) and will generate a correct Python program '
'that matches the specification and passes all tests. You will NOT '
'return anything except for the program.')}
@register_prompt_renderer('drop_es')
def drop_es(question, sample, spec, prompt_style):
# es drop: question block = bare passage + 'Question:' line (es does NOT
# label the test passage; only exemplars carry labels)
ps = (sample.metadata or {}).get('passage')
return {'question': f'{ps}\nQuestion: {question}' if ps else f'Question: {question}'}
@register_prompt_renderer('bbh_es')
def bbh_es(question, sample, spec, prompt_style):
# es bbh PROMPT_TEMPLATE: the test question is wrapped in the Q:/A:
# contract (the CoT exemplars already follow this pattern)
return {'question': (
'Q: ' + question + '\nA: Let\'s think step by step. Put your final '
'answer in the format of "So the answer is [ANSWER]" (without quotes '
'and markdown) where [ANSWER] is the answer to the problem.\n')}
@register_prompt_renderer('cot_letter_plain')
def cot_letter_plain(question, sample, spec, prompt_style):
# es mmlu-pro USER_PROMPT verbatim: Question:/Options: + 'A) x' --
# NOTE es renders the TEST question options with PARENS while its
# exemplars use 'A x' (space); replicate the inconsistency exactly
if not sample.choices:
return {}
letters = 'ABCDEFGHIJ'
opts = '\n'.join(f'{letters[i]}) {c}' for i, c in enumerate(sample.choices)
if i < len(letters))
return {'question': (
f'Answer the following multiple choice question. The last line of '
f"your response should be of the following format: 'ANSWER: [LETTER]' "
f'(without quotes) where [LETTER] is one of '
f'{",".join(letters[:len(sample.choices)])}. '
f'Think step by step before answering.\n\nQuestion:\n{question}\nOptions:\n{opts}\n')}
@register_prompt_renderer('cot_letter_zh')
def cot_letter_zh(question, sample, spec, prompt_style):
if not sample.choices:
return {}
letters = 'ABCDEFGH'
opts = '\n'.join(f'{letters[i]}) {c}' for i, c in enumerate(sample.choices)
if i < len(letters))
# es cmmlu contract, verbatim (incl. trailing newline)
return {'question': (
f'回答下面的单项选择题,请选出其中的正确答案。你的回答的最后一行应该是这样的格式:'
f'"答案:[LETTER]"(不带引号),其中 [LETTER] 是 {",".join(letters[:len(sample.choices)])} 中的一个。'
f'请在回答前进行一步步思考。\n\n问题:{question}\n选项:\n{opts}\n')}
@register_prompt_renderer('cot_letter')
def cot_letter(question, sample, spec, prompt_style):
if not sample.choices:
return {}
letters = 'ABCDEFGH'
opts = '\n'.join(f'{letters[i]}) {c}' for i, c in enumerate(sample.choices)
if i < len(letters))
return {'question': (
f'Answer the following multiple choice question. The last line of '
f"your response should be of the following format: 'ANSWER: [LETTER]' "
f'(without quotes) where [LETTER] is one of {",".join(letters[:len(sample.choices)])}. '
f'Think step by step before answering.\n\n{question}\n\n{opts}')}