from typing import Dict, Any, List, Optional
import os
import logging
from dataclasses import dataclass, field
from datetime import datetime
import json
from adalflow.core.generator import GeneratorOutput
from adalflow.core.base_data_class import DataClass
from adalflow.utils import append_to_jsonl, load_jsonl
log = logging.getLogger(__name__)
[docs]
@dataclass
class GeneratorCallRecord(DataClass):
prompt_kwargs: Dict[str, Any] = field(
default_factory=dict, metadata={"desc": "The prompt kwargs"}
)
model_kwargs: Dict[str, Any] = field(
default_factory=dict, metadata={"desc": "The model kwargs"}
)
input: Dict[str, Any] = field(
default_factory=dict,
metadata={"desc": "Everything sent to the model api provider"},
)
output: GeneratorOutput = field(
default_factory=GeneratorOutput, metadata={"desc": "The generator output"}
)
metadata: Dict[str, Any] = field(
default_factory=dict, metadata={"desc": "The metadata"}
)
time_stamp: str = field(default_factory=str, metadata={"desc": "The time stamp"})
[docs]
class GeneratorCallLogger:
__doc__ = r"""Log the generator calls.
Allow multiple generators to be logged, and each with its own jsonl file.
The log files are stored in the ./traces/ directory. If a project_name is provided,
it will be stored in ./traces/{project_name}.
Args:
save_dir (str, optional): The directory to save the log files. Defaults to "./traces/".
project_name (str, optional): The project name. Defaults to None.
"""
_generator_names_to_files: Dict[str, str] = {}
# TODO: a project will share the same metadata file to record each logger's metadata
_metadata_filename = "logger_metadata.json"
def __init__(
self, save_dir: Optional[str] = None, project_name: Optional[str] = None
):
self.filepath = save_dir or "./traces/"
self.project_name = project_name
if project_name:
self.filepath = os.path.join(self.filepath, project_name)
os.makedirs(self.filepath, exist_ok=True)
self._metadata_filepath = os.path.join(self.filepath, self._metadata_filename)
if os.path.exists(self._metadata_filepath):
self.load_meta_data(self._metadata_filepath)
else:
self.save_meta_data(self._metadata_filepath)
[docs]
def reset(self):
self._generator_names_to_files = {}
@property
def generator_names_to_files(self) -> Dict[str, str]:
return self._generator_names_to_files
[docs]
def get_log_location(self, name: str) -> str:
return self._generator_names_to_files.get(name, None)
[docs]
def get_calls(self, name: str) -> List[GeneratorCallRecord]:
r"""Get the generator call records for generator with name."""
return self.load(name)
[docs]
def register_generator(self, name: str, filename: Optional[str] = None):
r"""Register a generator with a name and a jsonl file to log its calls.
Args:
name (str): The name of the generator.
filename (str, optional): The jsonl filename to log the calls. Defaults to {name}_call.jsonl.
"""
if name in self._generator_names_to_files:
log.warning(
f"Generator {name} is already registered with jsonl file at {self._generator_names_to_files[name]}"
)
return
filename = filename or f"{name}_call.jsonl"
self._generator_names_to_files[name] = os.path.join(self.filepath, filename)
self.save_meta_data(self._metadata_filepath)
[docs]
def load(self, name: str) -> List[GeneratorCallRecord]:
r"""Load the generator call records."""
if name not in self._generator_names_to_files:
log.error(f"Generator {name} is not registered.")
raise FileNotFoundError(f"Generator {name} is not registered.")
records: List[Dict] = load_jsonl(self._generator_names_to_files[name])
return [GeneratorCallRecord.from_dict(record) for record in records]
[docs]
def log_call(
self,
name: str,
output: GeneratorOutput,
input: Optional[Dict[str, Any]] = None,
prompt_kwargs: Optional[Dict[str, Any]] = None,
model_kwargs: Optional[Dict[str, Any]] = None,
):
r"""Log the generator call."""
if name not in self._generator_names_to_files:
log.error(f"Generator {name} is not registered.")
return
record = GeneratorCallRecord(
prompt_kwargs=prompt_kwargs,
model_kwargs=model_kwargs,
input=input,
output=output,
time_stamp=datetime.now().isoformat(),
)
append_to_jsonl(self._generator_names_to_files[name], record.to_dict())