metrics.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import time
  2. from pydantic import BaseModel, Field
  3. class Cost(BaseModel):
  4. model: str
  5. cost: float
  6. timestamp: float = Field(default_factory=time.time)
  7. class ResponseLatency(BaseModel):
  8. """Metric tracking the round-trip time per completion call."""
  9. model: str
  10. latency: float
  11. response_id: str
  12. class Metrics:
  13. """Metrics class can record various metrics during running and evaluation.
  14. Currently, we define the following metrics:
  15. accumulated_cost: the total cost (USD $) of the current LLM.
  16. response_latency: the time taken for each LLM completion call.
  17. """
  18. def __init__(self, model_name: str = 'default') -> None:
  19. self._accumulated_cost: float = 0.0
  20. self._costs: list[Cost] = []
  21. self._response_latencies: list[ResponseLatency] = []
  22. self.model_name = model_name
  23. @property
  24. def accumulated_cost(self) -> float:
  25. return self._accumulated_cost
  26. @accumulated_cost.setter
  27. def accumulated_cost(self, value: float) -> None:
  28. if value < 0:
  29. raise ValueError('Total cost cannot be negative.')
  30. self._accumulated_cost = value
  31. @property
  32. def costs(self) -> list[Cost]:
  33. return self._costs
  34. @property
  35. def response_latencies(self) -> list[ResponseLatency]:
  36. return self._response_latencies
  37. def add_cost(self, value: float) -> None:
  38. if value < 0:
  39. raise ValueError('Added cost cannot be negative.')
  40. self._accumulated_cost += value
  41. self._costs.append(Cost(cost=value, model=self.model_name))
  42. def add_response_latency(self, value: float, response_id: str) -> None:
  43. if value < 0:
  44. raise ValueError('Response latency cannot be negative.')
  45. self._response_latencies.append(
  46. ResponseLatency(
  47. latency=value, model=self.model_name, response_id=response_id
  48. )
  49. )
  50. def merge(self, other: 'Metrics') -> None:
  51. self._accumulated_cost += other.accumulated_cost
  52. self._costs += other._costs
  53. self._response_latencies += other._response_latencies
  54. def get(self) -> dict:
  55. """Return the metrics in a dictionary."""
  56. return {
  57. 'accumulated_cost': self._accumulated_cost,
  58. 'costs': [cost.model_dump() for cost in self._costs],
  59. 'response_latencies': [
  60. latency.model_dump() for latency in self._response_latencies
  61. ],
  62. }
  63. def reset(self):
  64. self._accumulated_cost = 0.0
  65. self._costs = []
  66. self._response_latencies = []
  67. def log(self):
  68. """Log the metrics."""
  69. metrics = self.get()
  70. logs = ''
  71. for key, value in metrics.items():
  72. logs += f'{key}: {value}\n'
  73. return logs
  74. def __repr__(self):
  75. return f'Metrics({self.get()}'