Skip to content

Commit

Permalink
feature: better logging (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
siyangqiu authored May 18, 2024
1 parent 7fd7d10 commit fb2852b
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 57 deletions.
13 changes: 11 additions & 2 deletions agentops/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# agentops/__init__.py
import os
import logging
from typing import Optional, List, Union
from .client import Client
from .config import Configuration
from .event import Event, ActionEvent, LLMEvent, ToolEvent, ErrorEvent
from .enums import Models
from .decorators import record_function
from .agent import track_agent
from .log_config import set_logging_level_info, set_logging_level_critial
from .log_config import logger
try:
from .langchain_callback_handler import LangchainCallbackHandler, AsyncLangchainCallbackHandler
except ModuleNotFoundError:
Expand Down Expand Up @@ -46,7 +48,13 @@ def init(api_key: Optional[str] = None,
inherited_session_id (optional, str): Init Agentops with an existing Session
Attributes:
"""
set_logging_level_info()
if os.getenv('AGENTOPS_LOGGING_LEVEL') == 'DEBUG':
logger.setLevel(logging.DEBUG)
elif os.getenv('AGENTOPS_LOGGING_LEVEL') == 'CRITICAL':
logger.setLevel(logging.CRITICAL)
else:
logger.setLevel(logging.INFO)

c = Client(api_key=api_key,
parent_key=parent_key,
endpoint=endpoint,
Expand Down Expand Up @@ -131,6 +139,7 @@ def set_parent_key(parent_key):
"""
Client().set_parent_key(parent_key)


def stop_instrumenting():
Client().stop_instrumenting()

Expand Down
5 changes: 3 additions & 2 deletions agentops/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def new_init(self, *args, **kwargs):
self.agent_ops_agent_id = str(uuid4())
Client().create_agent(name=self.agent_ops_agent_name, agent_id=self.agent_ops_agent_id)
except AttributeError as e:
logger.warning("AgentOps failed to track an agent. This often happens if agentops.init() was not "
logger.warning("Failed to track an agent. This often happens if agentops.init() was not "
"called before initializing an agent with the @track_agent decorator.")
raise e

Expand All @@ -31,7 +31,8 @@ def new_init(self, *args, **kwargs):
Client().create_agent(name=obj.agent_ops_agent_name, agent_id=obj.agent_ops_agent_id)

else:
raise Exception("Invalid input, 'obj' must be a class or a function")
raise Exception(
"Invalid input, 'obj' must be a class or a function")

return obj

Expand Down
65 changes: 35 additions & 30 deletions agentops/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,24 @@
Client: Provides methods to interact with the AgentOps service.
"""
import os
import uuid
import inspect
import atexit
import signal
import sys
import threading
import traceback
import logging
from decimal import Decimal
from uuid import UUID, uuid4
from typing import Optional, List, Union

from .event import ActionEvent, ErrorEvent, Event
from .enums import EndState
from .helpers import get_ISO_time, singleton, check_call_stack_for_agent_id, get_partner_frameworks
from .session import Session
from .worker import Worker
from .host_env import get_host_env
from uuid import uuid4
from typing import Optional, List, Union
import traceback
from .log_config import logger, set_logging_level_info
from decimal import Decimal
import inspect
import atexit
import signal
import sys
import threading


from .log_config import logger
from .meta_client import MetaClient
from .config import Configuration, ConfigurationError
from .llm_tracker import LlmTracker
Expand Down Expand Up @@ -73,7 +71,7 @@ def __init__(self,
):

if override is not None:
logger.warning("🖇 AgentOps: The 'override' parameter is deprecated. Use 'instrument_llm_calls' instead.",
logger.warning("The 'override' parameter is deprecated. Use 'instrument_llm_calls' instead.",
DeprecationWarning, stacklevel=2)
instrument_llm_calls = instrument_llm_calls or override

Expand Down Expand Up @@ -120,9 +118,8 @@ def _check_for_partner_frameworks(self, instrument_llm_calls, auto_start_session
except ImportError:
pass
except Exception as e:
logger.warning("🖇️ AgentOps: Failed to set up autogen logger with AgentOps. Error: " + e)

return partner_frameworks[framework]
logger.warning(
"Failed to set up autogen logger with AgentOps. Error: " + e)

return instrument_llm_calls, auto_start_session

Expand Down Expand Up @@ -173,8 +170,8 @@ def record(self, event: Union[Event, ErrorEvent]):
Args:
event (Event): The event to record.
"""
if self._session is None or self._session.has_ended:
logger.warning("🖇 AgentOps: Cannot record event - no current session")
if self._session is None or self._session.has_ended or self._worker is None:
logger.warning("Cannot record event - no current session")
return

if isinstance(event, Event):
Expand Down Expand Up @@ -274,23 +271,31 @@ def start_session(self, tags: Optional[List[str]] = None, config: Optional[Confi
config: (Configuration, optional): Client configuration object
inherited_session_id (optional, str): assign session id to match existing Session
"""
set_logging_level_info()
if os.getenv('AGENTOPS_LOGGING_LEVEL') == 'DEBUG':
logger.setLevel(logging.DEBUG)
elif os.getenv('AGENTOPS_LOGGING_LEVEL') == 'CRITICAL':
logger.setLevel(logging.CRITICAL)
else:
logger.setLevel(logging.INFO)

if self._session is not None:
return logger.warning("🖇 AgentOps: Cannot start session - session already started")
return logger.warning("Cannot start session - session already started")

if not config and not self.config:
return logger.warning("🖇 AgentOps: Cannot start session - missing configuration")
return logger.warning("Cannot start session - missing configuration")

session_id = UUID(
inherited_session_id) if inherited_session_id is not None else uuid4()

self._session = Session(inherited_session_id or uuid4(),
tags or self._tags_for_future_session, host_env=get_host_env(self._env_data_opt_out))
self._worker = Worker(config or self.config)
start_session_result = self._worker.start_session(self._session)
if not start_session_result:
self._session = None
return logger.warning("🖇 AgentOps: Cannot start session - No server response")
return logger.warning("Cannot start session - No server response")

logger.info('View info on this session at https://app.agentops.ai/drilldown?session_id=%s',
logger.info('\x1b[34mView info on this session at https://app.agentops.ai/drilldown?session_id=%s\x1b[0m',
self._session.session_id)

return self._session.session_id
Expand All @@ -308,23 +313,23 @@ def end_session(self,
video (str, optional): The video screen recording of the session
"""
if self._session is None or self._session.has_ended:
return logger.warning("🖇 AgentOps: Cannot end session - no current session")
return logger.warning("Cannot end session - no current session")

if not any(end_state == state.value for state in EndState):
return logger.warning("🖇 AgentOps: Invalid end_state. Please use one of the EndState enums")
return logger.warning("Invalid end_state. Please use one of the EndState enums")

if self._worker is None or self._worker._session is None:
return logger.warning("🖇 AgentOps: Cannot end session - no current worker or session")
return logger.warning("Cannot end session - no current worker or session")

self._session.video = video
self._session.end_session(end_state, end_state_reason)
token_cost = self._worker.end_session(self._session)

if token_cost == 'unknown':
print('🖇 AgentOps: Could not determine cost of run.')
logger.info('Could not determine cost of run.')
else:
token_cost_d = Decimal(token_cost)
print('\n🖇 AgentOps: This run cost ${}'.format('{:.2f}'.format(
logger.info('This run cost ${}'.format('{:.2f}'.format(
token_cost_d) if token_cost_d == 0 else '{:.6f}'.format(token_cost_d)))
self._session = None
self._worker = None
Expand Down Expand Up @@ -352,7 +357,7 @@ def signal_handler(signum, frame):
frame: The current stack frame.
"""
signal_name = 'SIGINT' if signum == signal.SIGINT else 'SIGTERM'
logger.info('🖇 AgentOps: %s detected. Ending session...', signal_name)
logger.info('%s detected. Ending session...', signal_name)
self.end_session(end_state='Fail',
end_state_reason=f'Signal {signal_name} detected')
sys.exit(0)
Expand Down
6 changes: 4 additions & 2 deletions agentops/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ def __init__(self,
if not api_key:
api_key = environ.get('AGENTOPS_API_KEY', None)
if not api_key:
raise ConfigurationError("🖇 AgentOps: No API key provided - no data will be recorded.")
raise ConfigurationError(
"No API key provided - no data will be recorded.")

if not parent_key:
parent_key = environ.get('AGENTOPS_PARENT_KEY', None)

if not endpoint:
endpoint = environ.get('AGENTOPS_API_ENDPOINT', 'https://api.agentops.ai')
endpoint = environ.get(
'AGENTOPS_API_ENDPOINT', 'https://api.agentops.ai')

self._api_key: str = api_key
self._endpoint = endpoint
Expand Down
8 changes: 4 additions & 4 deletions agentops/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def post(url: str, payload: bytes, api_key: Optional[str] = None, parent_key: Op
result.code = 408
result.status = HttpStatus.TIMEOUT
logger.warning(
'🖇 AgentOps: Could not post data - connection timed out')
'Could not post data - connection timed out')
except requests.exceptions.HTTPError as e:
try:
result.parse(e.response)
Expand All @@ -94,12 +94,12 @@ def post(url: str, payload: bytes, api_key: Optional[str] = None, parent_key: Op
result.body = {'error': str(e)}

if result.code == 401:
logger.warning('🖇 AgentOps: Could not post data - API server rejected your API key: %s',
logger.warning('Could not post data - API server rejected your API key: %s',
api_key)
if result.code == 400:
logger.warning('🖇 AgentOps: Could not post data - %s', result.body)
logger.warning('Could not post data - %s', result.body)
if result.code == 500:
logger.warning(
'🖇 AgentOps: Could not post data - internal server error')
'Could not post data - internal server error')

return result
20 changes: 10 additions & 10 deletions agentops/llm_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def handle_stream_chunk(chunk):
kwargs_str = pprint.pformat(kwargs)
chunk = pprint.pformat(chunk)
logger.warning(
f"🖇 AgentOps: Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"chunk:\n {chunk}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -119,7 +119,7 @@ def generator():
kwargs_str = pprint.pformat(kwargs)
response = pprint.pformat(response)
logger.warning(
f"🖇 AgentOps: Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"response:\n {response}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -175,7 +175,7 @@ def handle_stream_chunk(chunk: ChatCompletionChunk):
kwargs_str = pprint.pformat(kwargs)
chunk = pprint.pformat(chunk)
logger.warning(
f"🖇 AgentOps: Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"chunk:\n {chunk}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -220,7 +220,7 @@ async def async_generator():
kwargs_str = pprint.pformat(kwargs)
response = pprint.pformat(response)
logger.warning(
f"🖇 AgentOps: Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"response:\n {response}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -323,7 +323,7 @@ def handle_stream_chunk(chunk):
kwargs_str = pprint.pformat(kwargs)
chunk = pprint.pformat(chunk)
logger.warning(
f"🖇 AgentOps: Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse a chunk for LLM call. Skipping upload to AgentOps\n"
f"chunk:\n {chunk}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -376,7 +376,7 @@ def generator():
kwargs_str = pprint.pformat(kwargs)
response = pprint.pformat(response)
logger.warning(
f"🖇 AgentOps: Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"Unable to parse response for LLM call. Skipping upload to AgentOps\n"
f"response:\n {response}\n"
f"kwargs:\n {kwargs_str}\n"
)
Expand Down Expand Up @@ -516,13 +516,13 @@ def override_api(self):
if api == 'litellm':
module_version = version(api)
if module_version is None:
logger.warning(f'🖇 AgentOps: Cannot determine LiteLLM version. Only LiteLLM>=1.3.1 supported.')
logger.warning(f'Cannot determine LiteLLM version. Only LiteLLM>=1.3.1 supported.')

if Version(module_version) >= parse('1.3.1'):
self.override_litellm_completion()
self.override_litellm_async_completion()
else:
logger.warning(f'🖇 AgentOps: Only LiteLLM>=1.3.1 supported. v{module_version} found.')
logger.warning(f'Only LiteLLM>=1.3.1 supported. v{module_version} found.')
return # If using an abstraction like litellm, do not patch the underlying LLM APIs

if api == 'openai':
Expand All @@ -541,13 +541,13 @@ def override_api(self):
# Patch cohere v5.4.0+ methods
module_version = version(api)
if module_version is None:
logger.warning(f'🖇 AgentOps: Cannot determine Cohere version. Only Cohere>=5.4.0 supported.')
logger.warning(f'Cannot determine Cohere version. Only Cohere>=5.4.0 supported.')

if Version(module_version) >= parse('5.4.0'):
self.override_cohere_chat()
self.override_cohere_chat_stream()
else:
logger.warning(f'🖇 AgentOps: Only Cohere>=5.4.0 supported. v{module_version} found.')
logger.warning(f'Only Cohere>=5.4.0 supported. v{module_version} found.')

def stop_instrumenting(self):
self.undo_override_openai_v1_async_completion()
Expand Down
30 changes: 25 additions & 5 deletions agentops/log_config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import logging


class AgentOpsFormatter(logging.Formatter):
blue = "\x1b[34m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
format = "🖇 AgentOps: %(message)s"

FORMATS = {
logging.DEBUG: "(DEBUG) " + format,
logging.INFO: format,
logging.WARNING: format,
logging.ERROR: format,
logging.CRITICAL: bold_red + format + reset,
}

def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)


logger = logging.getLogger("agentops")
logger.setLevel(logging.CRITICAL)

def set_logging_level_critial():
logger.setLevel(logging.CRITICAL)

def set_logging_level_info():
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(AgentOpsFormatter())
logger.addHandler(handler)
5 changes: 3 additions & 2 deletions agentops/meta_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ def wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except Exception as e:
logger.warning(f"🖇 AgentOps: Error: {e}")
logger.warning(f"Error: {e}")
config = getattr(self, 'config', None)
if config is not None:
type(self).send_exception_to_server(e, self.config._api_key, self._session)
type(self).send_exception_to_server(
e, self.config._api_key, self._session)
raise e

return wrapper

0 comments on commit fb2852b

Please sign in to comment.