Source code for llmp.components.example_manager
"""
Manage and store examples for a given job.
"""
import itertools
import random
from pydantic import UUID4
from llmp.integration.example_selector import ExampleManagerSelector
from llmp.components.base import BaseExampleManager
from llmp.components.generator import ExampleGenerator
from llmp.data_model import ExampleRecord, JobRecord
from llmp.data_model.events import Event
from llmp.types import TestSetMode, EventType
# ===============================
# Decisions:
# - How to store examples and their metrics in the database?
# - How to match examples with their metrics and their generation event?
#
# Options:
# 1. inhibit generation log event in major vote generator and move to example generator
# - pro: easier to match examples with their metrics and their generation event
# - con: generation event is not logged for major vote generator
# 2. manually change generation log event after example generation
# - pro: generation event is logged for major vote generator
# - con: more complex to match examples with their metrics and their generation event
# 3. inhibit log event in every sub generator and move it to the outer generator
# 4. Add reference in ADD_EXAMPLES event to the generation event
# - pro: generation event is logged for major vote generator
# - con: more complex to match examples with their metrics and their generation event
[docs]class ExampleManager(BaseExampleManager):
[docs] def __init__(self, job: JobRecord, debug: bool = False):
"""Initialize the ExampleManager with a job.
Args:
job (JobRecord): The job to manage the examples for.
debug (bool, optional): Enable debug mode. Defaults to False.
"""
self.job = job
self.debug = debug
@property
def records(self) -> list[ExampleRecord]:
return self.job.example_records
[docs] def fill_examples(self, total_count: int = 20, **kwargs) -> None:
"""Fill the example records for a given job.
Args:
total_count (int, optional): The total number of examples to create. Defaults to 20.
**kwargs: Additional keyword arguments passed to self.create_examples().
"""
delta = total_count - len(self.records)
while delta > 0:
if self.debug:
print(f"Creating {delta} examples.")
self.create_examples(delta, **kwargs)
delta = total_count - len(self.records)
[docs] def create_examples(self, num_examples: int = 20, **kwargs) -> None:
"""Create a given number of examples for a given job.
Created examples are added to the job.
Args:
num_examples (int, optional): The number of examples to create. Defaults to 20.
**kwargs: Additional keyword arguments passed to ExampleGenerator.generate().
Example:
>>> manager = ExampleManager(job)
>>> manager.create_examples(20)
"""
generator = ExampleGenerator(self.job, **kwargs)
examples = generator.generate(num_examples)
for example in examples:
self.job.add_example(example)
[docs] def add_example(self, record: ExampleRecord, **kwargs) -> None:
"""Add an example to the job.
Args:
record (ExampleRecord): The example to add.
**kwargs: Additional keyword arguments passed to self.job.add_example().
"""
self.job.add_example(record)
[docs] def get_examples(self, job_id: UUID4) -> list[dict]:
"""Return all examples for a given job.
Args:
job_id (UUID4): The job id.
Returns:
list[dict]: A list of example records.
"""
pass
[docs] def delete_example(self, job_id: UUID4, example_idx: int) -> None:
"""Delete an example from a given job.
Args:
job_id (UUID4): The job id.
example_idx (int): The example index.
"""
pass
[docs] def update_example(self, job_id: UUID4, example_idx: int, example: dict) -> None:
"""Update an example for a given job.
Args:
job_id (UUID4): The job id.
example_idx (int): The example index.
example (dict): The example to update.
"""
pass
[docs] def to_engine(self, job_id: UUID4) -> list[dict]:
"""Return a list of examples for a given job.
Args:
job_id (UUID4): The job id.
Returns:
list[dict]: A list of example records.
"""
return ExampleManagerSelector(examples=self.records)
[docs] def get_test_set(self, test_size: int, mode: str = TestSetMode.RANDOM, exclude_ids: list[str] = None) -> list[ExampleRecord]:
"""Return a test set of examples."""
if exclude_ids:
records = [r for r in self.records if r.idx not in exclude_ids]
else:
records = self.records
if mode == TestSetMode.RANDOM:
return random.sample(records, test_size)
# get the examples with the lowest accuracy
elif mode == TestSetMode.ACCURACY:
return sorted(records, key=lambda x: x.reliability_score)[0:test_size]
[docs] def get_possible_sets(self, set_size: int, exclude_ids: list[str] = None) -> list[tuple[ExampleRecord]]:
"""Return all possible sets of examples.
Is used in ExampleOptimizer to test all possible combinations of existing examples for a given set_size.
Args:
set_size (int): The size of the sets.
exclude_ids (list[str], optional): A list of example ids to exclude. Defaults to None.
Returns:
list[tuple[ExampleRecord]]: A list of example records.
"""
if exclude_ids:
records = [r for r in self.records if r.idx not in exclude_ids]
else:
records = self.records
# get all possible combinations sets of size set_size
return itertools.combinations(records, set_size)