scripts: allow storing logs for release evals
This commit is contained in:
@@ -55,23 +55,29 @@ PIPELINE_IDLE_TIMEOUT_SECS = 30
|
|||||||
|
|
||||||
|
|
||||||
class EvalRunner:
|
class EvalRunner:
|
||||||
def __init__(self, *, pattern: str = "", record_audio: bool = False):
|
def __init__(self, *, pattern: str = "", record_audio: bool = False, log_level: str = "DEBUG"):
|
||||||
self._pattern = f".*{pattern}.*" if pattern else ""
|
self._pattern = f".*{pattern}.*" if pattern else ""
|
||||||
self._record_audio = record_audio
|
self._record_audio = record_audio
|
||||||
|
self._log_level = log_level
|
||||||
self._total_success = 0
|
self._total_success = 0
|
||||||
self._tests: List[EvalResult] = []
|
self._tests: List[EvalResult] = []
|
||||||
self._queue = asyncio.Queue()
|
self._queue = asyncio.Queue()
|
||||||
|
|
||||||
@property
|
# We to save runner files.
|
||||||
def record_audio(self):
|
self._runs_dir = os.path.join(
|
||||||
return self._record_audio
|
SCRIPT_DIR, "test-runs", f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
)
|
||||||
|
self._logs_dir = os.path.join(self._runs_dir, "logs")
|
||||||
|
self._recordings_dir = os.path.join(self._runs_dir, "recordings")
|
||||||
|
os.makedirs(self._logs_dir, exist_ok=True)
|
||||||
|
os.makedirs(self._recordings_dir, exist_ok=True)
|
||||||
|
|
||||||
async def assert_eval(self, params: FunctionCallParams):
|
async def assert_eval(self, params: FunctionCallParams):
|
||||||
reasoning = params.arguments["reasoning"]
|
reasoning = params.arguments["reasoning"]
|
||||||
logger.debug(f"🧠 EVAL REASONING: {reasoning}")
|
logger.debug(f"🧠 EVAL REASONING: {reasoning}")
|
||||||
await self._queue.put(params.arguments["result"])
|
await self._queue.put(params.arguments["result"])
|
||||||
await params.llm.push_frame(EndTaskFrame(), FrameDirection.UPSTREAM)
|
|
||||||
await params.result_callback(None)
|
await params.result_callback(None)
|
||||||
|
await params.llm.push_frame(EndTaskFrame(), FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
async def assert_eval_false(self):
|
async def assert_eval_false(self):
|
||||||
await self._queue.put(False)
|
await self._queue.put(False)
|
||||||
@@ -80,6 +86,10 @@ class EvalRunner:
|
|||||||
if not re.match(self._pattern, example_file):
|
if not re.match(self._pattern, example_file):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Store logs
|
||||||
|
filename = self._log_file_name(example_file)
|
||||||
|
log_file_id = logger.add(filename, level=self._log_level)
|
||||||
|
|
||||||
print_begin_test(example_file)
|
print_begin_test(example_file)
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -111,8 +121,37 @@ class EvalRunner:
|
|||||||
|
|
||||||
print_end_test(example_file, result, eval_time)
|
print_end_test(example_file, result, eval_time)
|
||||||
|
|
||||||
|
logger.remove(log_file_id)
|
||||||
|
|
||||||
def print_results(self):
|
def print_results(self):
|
||||||
print_test_results(self._tests, self._total_success)
|
print_test_results(self._tests, self._total_success, self._runs_dir)
|
||||||
|
|
||||||
|
async def save_audio(self, name: str, audio: bytes, sample_rate: int, num_channels: int):
|
||||||
|
if len(audio) > 0:
|
||||||
|
filename = self._recording_file_name(name)
|
||||||
|
with io.BytesIO() as buffer:
|
||||||
|
with wave.open(buffer, "wb") as wf:
|
||||||
|
wf.setsampwidth(2)
|
||||||
|
wf.setnchannels(num_channels)
|
||||||
|
wf.setframerate(sample_rate)
|
||||||
|
wf.writeframes(audio)
|
||||||
|
async with aiofiles.open(filename, "wb") as file:
|
||||||
|
await file.write(buffer.getvalue())
|
||||||
|
logger.debug(f"Saving {name} audio to {filename}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"There's no audio to save for {name}")
|
||||||
|
|
||||||
|
def _base_file_name(self, example_file: str):
|
||||||
|
base_name = os.path.splitext(example_file)[0]
|
||||||
|
return f"{base_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
|
||||||
|
def _log_file_name(self, example_file: str):
|
||||||
|
base_name = self._base_file_name(example_file)
|
||||||
|
return os.path.join(self._logs_dir, f"{base_name}.log")
|
||||||
|
|
||||||
|
def _recording_file_name(self, example_file: str):
|
||||||
|
base_name = self._base_file_name(example_file)
|
||||||
|
return os.path.join(self._recordings_dir, f"{base_name}.wav")
|
||||||
|
|
||||||
|
|
||||||
async def run_example_pipeline(example_file: str):
|
async def run_example_pipeline(example_file: str):
|
||||||
@@ -136,28 +175,6 @@ async def run_example_pipeline(example_file: str):
|
|||||||
await module.run_example(transport, argparse.Namespace(), True)
|
await module.run_example(transport, argparse.Namespace(), True)
|
||||||
|
|
||||||
|
|
||||||
async def save_audio(audio: bytes, sample_rate: int, num_channels: int, name: str):
|
|
||||||
if len(audio) > 0:
|
|
||||||
recordings_dir = os.path.join(SCRIPT_DIR, "recordings")
|
|
||||||
base_name = os.path.splitext(name)[0]
|
|
||||||
os.makedirs(recordings_dir, exist_ok=True)
|
|
||||||
filename = os.path.join(
|
|
||||||
recordings_dir,
|
|
||||||
f"{base_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.wav",
|
|
||||||
)
|
|
||||||
with io.BytesIO() as buffer:
|
|
||||||
with wave.open(buffer, "wb") as wf:
|
|
||||||
wf.setsampwidth(2)
|
|
||||||
wf.setnchannels(num_channels)
|
|
||||||
wf.setframerate(sample_rate)
|
|
||||||
wf.writeframes(audio)
|
|
||||||
async with aiofiles.open(filename, "wb") as file:
|
|
||||||
await file.write(buffer.getvalue())
|
|
||||||
logger.debug(f"Saving {name} audio to {filename}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"There's no audio to save for {name}")
|
|
||||||
|
|
||||||
|
|
||||||
async def run_eval_pipeline(
|
async def run_eval_pipeline(
|
||||||
eval_runner: EvalRunner, example_file: str, prompt: str, eval: Optional[str]
|
eval_runner: EvalRunner, example_file: str, prompt: str, eval: Optional[str]
|
||||||
):
|
):
|
||||||
@@ -212,7 +229,7 @@ async def run_eval_pipeline(
|
|||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": f"You are an LLM eval, be extremly brief. Your goal is to only ask one question: {prompt}. Tell the user to simply say the answer. Call the eval function only if the user answers the question and check if the answer is correct (words as numbers are valid). {eval_prompt}",
|
"content": f"You are an LLM eval, be extremly brief. Your goal is to only ask one question: {prompt}. Call the eval function only if the user answers the question and check if the answer is correct (words as numbers are valid). {eval_prompt}",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -246,8 +263,7 @@ async def run_eval_pipeline(
|
|||||||
|
|
||||||
@audio_buffer.event_handler("on_audio_data")
|
@audio_buffer.event_handler("on_audio_data")
|
||||||
async def on_audio_data(buffer, audio, sample_rate, num_channels):
|
async def on_audio_data(buffer, audio, sample_rate, num_channels):
|
||||||
if eval_runner.record_audio:
|
await eval_runner.save_audio(example_file, audio, sample_rate, num_channels)
|
||||||
await save_audio(audio, sample_rate, num_channels, example_file)
|
|
||||||
|
|
||||||
@transport.event_handler("on_client_connected")
|
@transport.event_handler("on_client_connected")
|
||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
|
|||||||
@@ -117,7 +117,13 @@ async def main(args: argparse.Namespace):
|
|||||||
if not check_env_variables():
|
if not check_env_variables():
|
||||||
return
|
return
|
||||||
|
|
||||||
runner = EvalRunner(pattern=args.pattern, record_audio=args.audio)
|
# Log level
|
||||||
|
logger.remove(0)
|
||||||
|
log_level = "TRACE" if args.verbose >= 2 else "DEBUG"
|
||||||
|
if args.verbose:
|
||||||
|
logger.add(sys.stderr, level=log_level)
|
||||||
|
|
||||||
|
runner = EvalRunner(pattern=args.pattern, record_audio=args.audio, log_level=log_level)
|
||||||
for test, prompt, eval in TESTS:
|
for test, prompt, eval in TESTS:
|
||||||
await runner.run_eval(test, prompt, eval)
|
await runner.run_eval(test, prompt, eval)
|
||||||
|
|
||||||
@@ -131,9 +137,4 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument("--verbose", "-v", action="count", default=0)
|
parser.add_argument("--verbose", "-v", action="count", default=0)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Log level
|
|
||||||
logger.remove(0)
|
|
||||||
if args.verbose:
|
|
||||||
logger.add(sys.stderr, level="TRACE" if args.verbose >= 2 else "DEBUG")
|
|
||||||
|
|
||||||
asyncio.run(main(args))
|
asyncio.run(main(args))
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def print_end_test(example_file: str, passed: bool, time: float):
|
|||||||
print(f"{example_file:<55} {status} ({time:.2f}s){CLEAR}")
|
print(f"{example_file:<55} {status} ({time:.2f}s){CLEAR}")
|
||||||
|
|
||||||
|
|
||||||
def print_test_results(tests: Sequence[EvalResult], total_success: int):
|
def print_test_results(tests: Sequence[EvalResult], total_success: int, location: str):
|
||||||
total_count = len(tests)
|
total_count = len(tests)
|
||||||
|
|
||||||
bar = "=" * 80
|
bar = "=" * 80
|
||||||
@@ -70,6 +70,8 @@ def print_test_results(tests: Sequence[EvalResult], total_success: int):
|
|||||||
f"{GREEN}SUCCESS{RESET}: {total_success} | {RED}FAIL{RESET}: {total_fail} | TOTAL TIME: {total_time:.2f}s"
|
f"{GREEN}SUCCESS{RESET}: {total_success} | {RED}FAIL{RESET}: {total_fail} | TOTAL TIME: {total_time:.2f}s"
|
||||||
)
|
)
|
||||||
print(f"{GREEN}{bar}{RESET}")
|
print(f"{GREEN}{bar}{RESET}")
|
||||||
|
print()
|
||||||
|
print(f"Tests output: {location}")
|
||||||
|
|
||||||
|
|
||||||
def load_module_from_path(path: str | Path):
|
def load_module_from_path(path: str | Path):
|
||||||
|
|||||||
Reference in New Issue
Block a user