- Removed Portkey from the equation, and defined explicit monitoring using Langchain native code - Optimization of Business Event logging
240 lines
9.8 KiB
Python
240 lines
9.8 KiB
Python
import os
|
|
import uuid
|
|
from contextlib import contextmanager
|
|
from datetime import datetime
|
|
from typing import Dict, Any, Optional
|
|
from datetime import datetime as dt, timezone as tz
|
|
from portkey_ai import Portkey, Config
|
|
import logging
|
|
|
|
from .business_event_context import BusinessEventContext
|
|
from common.models.monitoring import BusinessEventLog
|
|
from common.extensions import db
|
|
|
|
|
|
class BusinessEvent:
|
|
# The BusinessEvent class itself is a context manager, but it doesn't use the @contextmanager decorator.
|
|
# Instead, it defines __enter__ and __exit__ methods explicitly. This is because we're doing something a bit more
|
|
# complex - we're interacting with the BusinessEventContext and the _business_event_stack.
|
|
|
|
def __init__(self, event_type: str, tenant_id: int, **kwargs):
|
|
self.event_type = event_type
|
|
self.tenant_id = tenant_id
|
|
self.trace_id = str(uuid.uuid4())
|
|
self.span_id = None
|
|
self.span_name = None
|
|
self.parent_span_id = None
|
|
self.document_version_id = kwargs.get('document_version_id')
|
|
self.chat_session_id = kwargs.get('chat_session_id')
|
|
self.interaction_id = kwargs.get('interaction_id')
|
|
self.environment = os.environ.get("FLASK_ENV", "development")
|
|
self.span_counter = 0
|
|
self.spans = []
|
|
self.llm_metrics = {
|
|
'total_tokens': 0,
|
|
'prompt_tokens': 0,
|
|
'completion_tokens': 0,
|
|
'total_time': 0,
|
|
'call_count': 0,
|
|
'interaction_type': None
|
|
}
|
|
|
|
def update_attribute(self, attribute: str, value: any):
|
|
if hasattr(self, attribute):
|
|
setattr(self, attribute, value)
|
|
else:
|
|
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attribute}'")
|
|
|
|
def update_llm_metrics(self, metrics: dict):
|
|
self.llm_metrics['total_tokens'] += metrics['total_tokens']
|
|
self.llm_metrics['prompt_tokens'] += metrics['prompt_tokens']
|
|
self.llm_metrics['completion_tokens'] += metrics['completion_tokens']
|
|
self.llm_metrics['total_time'] += metrics['time_elapsed']
|
|
self.llm_metrics['call_count'] += 1
|
|
self.llm_metrics['interaction_type'] = metrics['interaction_type']
|
|
|
|
def reset_llm_metrics(self):
|
|
self.llm_metrics['total_tokens'] = 0
|
|
self.llm_metrics['prompt_tokens'] = 0
|
|
self.llm_metrics['completion_tokens'] = 0
|
|
self.llm_metrics['total_time'] = 0
|
|
self.llm_metrics['call_count'] = 0
|
|
self.llm_metrics['interaction_type'] = None
|
|
|
|
@contextmanager
|
|
def create_span(self, span_name: str):
|
|
# The create_span method is designed to be used as a context manager. We want to perform some actions when
|
|
# entering the span (like setting the span ID and name) and some actions when exiting the span (like removing
|
|
# these temporary attributes). The @contextmanager decorator allows us to write this method in a way that
|
|
# clearly separates the "entry" and "exit" logic, with the yield statement in between.
|
|
|
|
parent_span_id = self.span_id
|
|
self.span_counter += 1
|
|
new_span_id = str(uuid.uuid4())
|
|
|
|
# Save the current span info
|
|
self.spans.append((self.span_id, self.span_name, self.parent_span_id))
|
|
|
|
# Set the new span info
|
|
self.span_id = new_span_id
|
|
self.span_name = span_name
|
|
self.parent_span_id = parent_span_id
|
|
|
|
self.log(f"Starting span {span_name}")
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
if self.llm_metrics['call_count'] > 0:
|
|
self.log_final_metrics()
|
|
self.reset_llm_metrics()
|
|
self.log(f"Ending span {span_name}")
|
|
# Restore the previous span info
|
|
if self.spans:
|
|
self.span_id, self.span_name, self.parent_span_id = self.spans.pop()
|
|
else:
|
|
self.span_id = None
|
|
self.span_name = None
|
|
self.parent_span_id = None
|
|
|
|
def log(self, message: str, level: str = 'info'):
|
|
logger = logging.getLogger('business_events')
|
|
log_data = {
|
|
'event_type': self.event_type,
|
|
'tenant_id': self.tenant_id,
|
|
'trace_id': self.trace_id,
|
|
'span_id': self.span_id,
|
|
'span_name': self.span_name,
|
|
'parent_span_id': self.parent_span_id,
|
|
'document_version_id': self.document_version_id,
|
|
'chat_session_id': self.chat_session_id,
|
|
'interaction_id': self.interaction_id,
|
|
'environment': self.environment,
|
|
}
|
|
# log to Graylog
|
|
getattr(logger, level)(message, extra=log_data)
|
|
|
|
# Log to database
|
|
event_log = BusinessEventLog(
|
|
timestamp=dt.now(tz=tz.utc),
|
|
event_type=self.event_type,
|
|
tenant_id=self.tenant_id,
|
|
trace_id=self.trace_id,
|
|
span_id=self.span_id,
|
|
span_name=self.span_name,
|
|
parent_span_id=self.parent_span_id,
|
|
document_version_id=self.document_version_id,
|
|
chat_session_id=self.chat_session_id,
|
|
interaction_id=self.interaction_id,
|
|
environment=self.environment,
|
|
message=message
|
|
)
|
|
db.session.add(event_log)
|
|
db.session.commit()
|
|
|
|
def log_llm_metrics(self, metrics: dict, level: str = 'info'):
|
|
self.update_llm_metrics(metrics)
|
|
message = "LLM Metrics"
|
|
logger = logging.getLogger('business_events')
|
|
log_data = {
|
|
'event_type': self.event_type,
|
|
'tenant_id': self.tenant_id,
|
|
'trace_id': self.trace_id,
|
|
'span_id': self.span_id,
|
|
'span_name': self.span_name,
|
|
'parent_span_id': self.parent_span_id,
|
|
'document_version_id': self.document_version_id,
|
|
'chat_session_id': self.chat_session_id,
|
|
'interaction_id': self.interaction_id,
|
|
'environment': self.environment,
|
|
'llm_metrics_total_tokens': metrics['total_tokens'],
|
|
'llm_metrics_prompt_tokens': metrics['prompt_tokens'],
|
|
'llm_metrics_completion_tokens': metrics['completion_tokens'],
|
|
'llm_metrics_total_time': metrics['time_elapsed'],
|
|
'llm_interaction_type': metrics['interaction_type'],
|
|
}
|
|
# log to Graylog
|
|
getattr(logger, level)(message, extra=log_data)
|
|
|
|
# Log to database
|
|
event_log = BusinessEventLog(
|
|
timestamp=dt.now(tz=tz.utc),
|
|
event_type=self.event_type,
|
|
tenant_id=self.tenant_id,
|
|
trace_id=self.trace_id,
|
|
span_id=self.span_id,
|
|
span_name=self.span_name,
|
|
parent_span_id=self.parent_span_id,
|
|
document_version_id=self.document_version_id,
|
|
chat_session_id=self.chat_session_id,
|
|
interaction_id=self.interaction_id,
|
|
environment=self.environment,
|
|
llm_metrics_total_tokens=metrics['total_tokens'],
|
|
llm_metrics_prompt_tokens=metrics['prompt_tokens'],
|
|
llm_metrics_completion_tokens=metrics['completion_tokens'],
|
|
llm_metrics_total_time=metrics['time_elapsed'],
|
|
llm_interaction_type=metrics['interaction_type'],
|
|
message=message
|
|
)
|
|
db.session.add(event_log)
|
|
db.session.commit()
|
|
|
|
def log_final_metrics(self, level: str = 'info'):
|
|
logger = logging.getLogger('business_events')
|
|
message = "Final LLM Metrics"
|
|
log_data = {
|
|
'event_type': self.event_type,
|
|
'tenant_id': self.tenant_id,
|
|
'trace_id': self.trace_id,
|
|
'span_id': self.span_id,
|
|
'span_name': self.span_name,
|
|
'parent_span_id': self.parent_span_id,
|
|
'document_version_id': self.document_version_id,
|
|
'chat_session_id': self.chat_session_id,
|
|
'interaction_id': self.interaction_id,
|
|
'environment': self.environment,
|
|
'llm_metrics_total_tokens': self.llm_metrics['total_tokens'],
|
|
'llm_metrics_prompt_tokens': self.llm_metrics['prompt_tokens'],
|
|
'llm_metrics_completion_tokens': self.llm_metrics['completion_tokens'],
|
|
'llm_metrics_total_time': self.llm_metrics['total_time'],
|
|
'llm_metrics_call_count': self.llm_metrics['call_count'],
|
|
'llm_interaction_type': self.llm_metrics['interaction_type'],
|
|
}
|
|
# log to Graylog
|
|
getattr(logger, level)(message, extra=log_data)
|
|
|
|
# Log to database
|
|
event_log = BusinessEventLog(
|
|
timestamp=dt.now(tz=tz.utc),
|
|
event_type=self.event_type,
|
|
tenant_id=self.tenant_id,
|
|
trace_id=self.trace_id,
|
|
span_id=self.span_id,
|
|
span_name=self.span_name,
|
|
parent_span_id=self.parent_span_id,
|
|
document_version_id=self.document_version_id,
|
|
chat_session_id=self.chat_session_id,
|
|
interaction_id=self.interaction_id,
|
|
environment=self.environment,
|
|
llm_metrics_total_tokens=self.llm_metrics['total_tokens'],
|
|
llm_metrics_prompt_tokens=self.llm_metrics['prompt_tokens'],
|
|
llm_metrics_completion_tokens=self.llm_metrics['completion_tokens'],
|
|
llm_metrics_total_time=self.llm_metrics['total_time'],
|
|
llm_metrics_call_count=self.llm_metrics['call_count'],
|
|
llm_interaction_type=self.llm_metrics['interaction_type'],
|
|
message=message
|
|
)
|
|
db.session.add(event_log)
|
|
db.session.commit()
|
|
|
|
def __enter__(self):
|
|
self.log(f'Starting Trace for {self.event_type}')
|
|
return BusinessEventContext(self).__enter__()
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if self.llm_metrics['call_count'] > 0:
|
|
self.log_final_metrics()
|
|
self.reset_llm_metrics()
|
|
self.log(f'Ending Trace for {self.event_type}')
|
|
return BusinessEventContext(self).__exit__(exc_type, exc_val, exc_tb)
|