diff --git a/README.md b/README.md index 4d80ada2..1be7f0e9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Build your next agent with benchmarks, observability, and replay analytics. AgentOps is the toolkit for evaluating and developing robust and reliable AI agents. -AgentOps is open beta. You can sign up for AgentOps [here](https://app.agentops.ai). +You can sign up for AgentOps [here](https://app.agentops.ai). [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![PyPI - Version](https://img.shields.io/pypi/v/agentops) @@ -49,16 +49,16 @@ Initialize the AgentOps client, and automatically get analytics on every LLM cal import agentops # Beginning of program's code (i.e. main.py, __init__.py) -ao_client = agentops.Client() +agentops.init() ... # (optional: record specific functions) -@record_function('sample function being record') +@agentops.record_function('sample function being record') def sample_function(...): ... # End of program -ao_client.end_session('Success') +agentops.end_session('Success') # Woohoo You're done 🎉 ``` diff --git a/agentops/__init__.py b/agentops/__init__.py index 6d021c58..6cd37a41 100755 --- a/agentops/__init__.py +++ b/agentops/__init__.py @@ -8,6 +8,7 @@ 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 def init(api_key: Optional[str] = None, @@ -18,7 +19,9 @@ def init(api_key: Optional[str] = None, tags: Optional[List[str]] = None, override: Optional[bool] = None, # Deprecated instrument_llm_calls=True, - auto_start_session=True): + auto_start_session=True, + inherited_session_id: Optional[str] = None + ): """ Initializes the AgentOps singleton pattern. @@ -38,18 +41,23 @@ def init(api_key: Optional[str] = None, override (bool, optional): [Deprecated] Use `instrument_llm_calls` instead. Whether to instrument LLM calls and emit LLMEvents.. instrument_llm_calls (bool): Whether to instrument LLM calls and emit LLMEvents.. auto_start_session (bool): Whether to start a session automatically when the client is created. + inherited_session_id (optional, str): Init Agentops with an existing Session Attributes: """ - - Client(api_key=api_key, - parent_key=parent_key, - endpoint=endpoint, - max_wait_time=max_wait_time, - max_queue_size=max_queue_size, - tags=tags, - override=override, - instrument_llm_calls=instrument_llm_calls, - auto_start_session=auto_start_session) + set_logging_level_info() + c = Client(api_key=api_key, + parent_key=parent_key, + endpoint=endpoint, + max_wait_time=max_wait_time, + max_queue_size=max_queue_size, + tags=tags, + override=override, + instrument_llm_calls=instrument_llm_calls, + auto_start_session=auto_start_session, + inherited_session_id=inherited_session_id + ) + + return inherited_session_id or c.current_session_id def end_session(end_state: str, @@ -66,7 +74,7 @@ def end_session(end_state: str, Client().end_session(end_state, end_state_reason, video) -def start_session(tags: Optional[List[str]] = None, config: Optional[Configuration] = None): +def start_session(tags: Optional[List[str]] = None, config: Optional[Configuration] = None, inherited_session_id: Optional[str] = None): """ Start a new session for recording events. @@ -75,7 +83,7 @@ def start_session(tags: Optional[List[str]] = None, config: Optional[Configurati e.g. ["test_run"]. config: (Configuration, optional): Client configuration object """ - Client().start_session(tags, config) + return Client().start_session(tags, config, inherited_session_id) def record(event: Event | ErrorEvent): diff --git a/agentops/agent.py b/agentops/agent.py index 2a0a005c..fd3c8df4 100644 --- a/agentops/agent.py +++ b/agentops/agent.py @@ -1,4 +1,4 @@ -import logging +from .log_config import logger from uuid import uuid4 from agentops import Client from inspect import isclass, isfunction @@ -18,7 +18,7 @@ def new_init(self, *args, **kwargs): self.agent_ops_agent_id = uuid4() Client().create_agent(self.agent_ops_agent_id, self.agent_ops_agent_name) except AttributeError as e: - logging.error("AgentOps failed to track an agent. This often happens if agentops.init() was not " + logger.warning("AgentOps 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 diff --git a/agentops/client.py b/agentops/client.py index 5de58b19..d6a4c14a 100644 --- a/agentops/client.py +++ b/agentops/client.py @@ -14,11 +14,12 @@ from uuid import uuid4 from typing import Optional, List import traceback -import logging +from .log_config import logger, set_logging_level_info import inspect import atexit import signal import sys +import threading from .meta_client import MetaClient from .config import Configuration, ConfigurationError @@ -46,6 +47,7 @@ class Client(metaclass=MetaClient): override (bool, optional): [Deprecated] Use `instrument_llm_calls` instead. Whether to instrument LLM calls and emit LLMEvents.. instrument_llm_calls (bool): Whether to instrument LLM calls and emit LLMEvents.. auto_start_session (bool): Whether to start a session automatically when the client is created. + inherited_session_id (optional, str): Init Agentops with an existing Session Attributes: _session (Session, optional): A Session is a grouping of events (e.g. a run of your agent). _worker (Worker, optional): A Worker manages the event queue and sends session updates to the AgentOps api server @@ -60,12 +62,13 @@ def __init__(self, tags: Optional[List[str]] = None, override: Optional[bool] = None, # Deprecated instrument_llm_calls=True, - auto_start_session=True + auto_start_session=True, + inherited_session_id: Optional[str] = None ): if override is not None: - logging.warning("🖇 AgentOps: The 'override' parameter is deprecated. Use 'instrument_llm_calls' instead.", - DeprecationWarning, stacklevel=2) + logger.warning("🖇 AgentOps: The 'override' parameter is deprecated. Use 'instrument_llm_calls' instead.", + DeprecationWarning, stacklevel=2) instrument_llm_calls = instrument_llm_calls or override self._session = None @@ -84,7 +87,7 @@ def __init__(self, self._handle_unclean_exits() if auto_start_session: - self.start_session(tags, self.config) + self.start_session(tags, self.config, inherited_session_id) if instrument_llm_calls: self.llm_tracker = LlmTracker(self) @@ -130,7 +133,7 @@ def record(self, event: Event | ErrorEvent): if self._session is not None and not self._session.has_ended: self._worker.add_event(event.__dict__) else: - logging.warning( + logger.warning( "🖇 AgentOps: Cannot record event - no current session") def _record_event_sync(self, func, event_name, *args, **kwargs): @@ -165,8 +168,7 @@ def _record_event_sync(self, func, event_name, *args, **kwargs): self.record(event) except Exception as e: - # TODO: add the stack trace - self.record(ErrorEvent(trigger_event=event, details={f"{type(e).__name__}": str(e)})) + self.record(ErrorEvent(trigger_event=event, exception=e)) # Re-raise the exception raise @@ -205,15 +207,14 @@ async def _record_event_async(self, func, event_name, *args, **kwargs): self.record(event) except Exception as e: - # TODO: add the stack trace - self.record(ErrorEvent(trigger_event=event, details={f"{type(e).__name__}": str(e)})) + self.record(ErrorEvent(trigger_event=event, exception=e)) # Re-raise the exception raise return returns - def start_session(self, tags: Optional[List[str]] = None, config: Optional[Configuration] = None): + def start_session(self, tags: Optional[List[str]] = None, config: Optional[Configuration] = None, inherited_session_id: Optional[str] = None): """ Start a new session for recording events. @@ -221,22 +222,27 @@ def start_session(self, tags: Optional[List[str]] = None, config: Optional[Confi tags (List[str], optional): Tags that can be used for grouping or sorting later. e.g. ["test_run"]. config: (Configuration, optional): Client configuration object + inherited_session_id (optional, str): assign session id to match existing Session """ + set_logging_level_info() + if self._session is not None: - return logging.warning("🖇 AgentOps: Cannot start session - session already started") + return logger.warning("🖇 AgentOps: Cannot start session - session already started") if not config and not self.config: - return logging.warning("🖇 AgentOps: Cannot start session - missing configuration") + return logger.warning("🖇 AgentOps: Cannot start session - missing configuration") - self._session = Session(uuid4(), tags or self._tags, host_env=get_host_env()) + self._session = Session(inherited_session_id or uuid4(), tags or self._tags, host_env=get_host_env()) 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 logging.warning("🖇 AgentOps: Cannot start session") + return logger.warning("🖇 AgentOps: Cannot start session") + + logger.info('View info on this session at https://app.agentops.ai/drilldown?session_id={}' + .format(self._session.session_id)) - logging.info('View info on this session at https://app.agentops.ai/drilldown?session_id={}' - .format(self._session.session_id)) + return self._session.session_id def end_session(self, end_state: str, @@ -251,10 +257,10 @@ 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 logging.warning("🖇 AgentOps: Cannot end session - no current session") + return logger.warning("🖇 AgentOps: Cannot end session - no current session") if not any(end_state == state.value for state in EndState): - return logging.warning("🖇 AgentOps: Invalid end_state. Please use one of the EndState enums") + return logger.warning("🖇 AgentOps: Invalid end_state. Please use one of the EndState enums") self._session.video = video self._session.end_session(end_state, end_state_reason) @@ -286,7 +292,7 @@ def signal_handler(signum, frame): frame: The current stack frame. """ signal_name = 'SIGINT' if signum == signal.SIGINT else 'SIGTERM' - logging.info( + logger.info( f'🖇 AgentOps: {signal_name} detected. Ending session...') self.end_session(end_state='Fail', end_state_reason=f'Signal {signal_name} detected') @@ -311,15 +317,17 @@ def handle_exception(exc_type, exc_value, exc_traceback): # Then call the default excepthook to exit the program sys.__excepthook__(exc_type, exc_value, exc_traceback) - atexit.register(lambda: cleanup(end_state="Indeterminate", - end_state_reason="Process exited without calling end_session()")) - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - sys.excepthook = handle_exception + # if main thread + if isinstance(threading.current_thread(), threading._MainThread): + atexit.register(lambda: cleanup(end_state="Indeterminate", + end_state_reason="Process exited without calling end_session()")) + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + sys.excepthook = handle_exception @property def current_session_id(self): - return self._session.session_id + return self._session.session_id if self._session else None @property def api_key(self): diff --git a/agentops/config.py b/agentops/config.py index f5751b24..6ea2f7db 100644 --- a/agentops/config.py +++ b/agentops/config.py @@ -7,7 +7,7 @@ from typing import Optional from os import environ -import logging +from .log_config import logger class Configuration: @@ -143,4 +143,4 @@ class ConfigurationError(Exception): def __init__(self, message: str): super().__init__(message) - logging.warning(message) + logger.warning(message) diff --git a/agentops/event.py b/agentops/event.py index 76deeafc..e0f017db 100644 --- a/agentops/event.py +++ b/agentops/event.py @@ -5,11 +5,12 @@ Event: Represents discrete events to be recorded. """ -from dataclasses import dataclass, field +from dataclasses import asdict, dataclass, field from typing import List, Optional from .helpers import get_ISO_time, check_call_stack_for_agent_id from .enums import EventType, Models from uuid import UUID, uuid4 +import traceback @dataclass @@ -115,6 +116,7 @@ class ErrorEvent(): For recording any errors e.g. ones related to agent execution trigger_event(Event, optional): The event object that triggered the error if applicable. + exception(BaseException, optional): The thrown exception. We will automatically parse the error_type and details from this. error_type(str, optional): The type of error e.g. "ValueError". code(str, optional): A code that can be used to identify the error e.g. 501. details(str, optional): Detailed information about the error. @@ -123,11 +125,12 @@ class ErrorEvent(): """ - trigger_event: Optional[Event] = None # TODO: remove from serialization? + trigger_event: Optional[Event] = None + exception: Optional[BaseException] = None error_type: Optional[str] = None code: Optional[str] = None details: Optional[str] = None - logs: Optional[str] = None + logs: Optional[str] = field(default_factory=traceback.format_exc) timestamp: str = field(default_factory=get_ISO_time) def __post_init__(self): @@ -135,5 +138,8 @@ def __post_init__(self): if self.trigger_event: self.trigger_event_id = self.trigger_event.id self.trigger_event_type = self.trigger_event.event_type - # TODO: remove trigger_event from serialization - # e.g. field(repr=False, compare=False, hash=False, metadata={'serialize': False}) + self.trigger_event = None # removes trigger_event from serialization + if self.exception: + self.error_type = self.error_type or type(self.exception).__name__ + self.details = self.details or str(self.exception) + self.exception = None # removes exception from serialization diff --git a/agentops/helpers.py b/agentops/helpers.py index 409889f2..71990c4b 100644 --- a/agentops/helpers.py +++ b/agentops/helpers.py @@ -4,7 +4,7 @@ from datetime import datetime import json import inspect -import logging +from .log_config import logger from uuid import UUID import os from importlib.metadata import version @@ -85,7 +85,7 @@ def check_call_stack_for_agent_id() -> str | None: if var == "__main__": return if hasattr(var, 'agent_ops_agent_id') and getattr(var, 'agent_ops_agent_id'): - logging.debug('LLM call from agent named: ' + getattr(var, 'agent_ops_agent_name')) + logger.debug('LLM call from agent named: ' + getattr(var, 'agent_ops_agent_name')) return getattr(var, 'agent_ops_agent_id') return None @@ -95,7 +95,7 @@ def get_agentops_version(): pkg_version = version("agentops") return pkg_version except Exception as e: - logging.warning(f"Error reading package version: {e}") + logger.warning(f"Error reading package version: {e}") return None diff --git a/agentops/http_client.py b/agentops/http_client.py index f1d205f0..2c743e22 100644 --- a/agentops/http_client.py +++ b/agentops/http_client.py @@ -1,6 +1,6 @@ from enum import Enum from typing import Optional -import logging +from .log_config import logger import requests from requests.adapters import Retry, HTTPAdapter @@ -82,7 +82,7 @@ def post(url: str, payload: bytes, api_key: Optional[str] = None, parent_key: Op except requests.exceptions.Timeout: result.code = 408 result.status = HttpStatus.TIMEOUT - logging.warning( + logger.warning( '🖇 AgentOps: Could not post data - connection timed out') except requests.exceptions.HTTPError as e: try: @@ -96,12 +96,12 @@ def post(url: str, payload: bytes, api_key: Optional[str] = None, parent_key: Op result.body = {'error': str(e)} if result.code == 401: - logging.warning( + logger.warning( f'🖇 AgentOps: Could not post data - API server rejected your API key: {api_key}') if result.code == 400: - logging.warning(f'🖇 AgentOps: Could not post data - {result.body}') + logger.warning(f'🖇 AgentOps: Could not post data - {result.body}') if result.code == 500: - logging.warning( + logger.warning( f'🖇 AgentOps: Could not post data - internal server error') return result diff --git a/agentops/langchain_callback_handler.py b/agentops/langchain_callback_handler.py index a79d3ea9..fda660c5 100644 --- a/agentops/langchain_callback_handler.py +++ b/agentops/langchain_callback_handler.py @@ -80,7 +80,7 @@ def on_llm_error( llm_event: LLMEvent = self.events.llm[str(run_id)] self.ao_client.record(llm_event) - error_event = ErrorEvent(trigger_event=llm_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=llm_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -106,8 +106,7 @@ def on_llm_end( if len(response.generations) == 0: # TODO: more descriptive error - error_event = ErrorEvent(trigger_event=self.events.llm[str(run_id)], - details="on_llm_end: No generations", timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=self.events.llm[str(run_id)], error_type="NoGenerations", details="on_llm_end: No generations") self.ao_client.record(error_event) @debug_print_function_params @@ -156,7 +155,7 @@ def on_chain_error( action_event: ActionEvent = self.events.chain[str(run_id)] self.ao_client.record(action_event) - error_event = ErrorEvent(trigger_event=action_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=action_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -199,7 +198,7 @@ def on_tool_end( # Tools are capable of failing `on_tool_end` quietly. # This is a workaround to make sure we can log it as an error. if kwargs.get('name') == '_Exception': - error_event = ErrorEvent(trigger_event=tool_event, details=output, timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=tool_event, error_type="LangchainToolException", details=output) self.ao_client.record(error_event) @debug_print_function_params @@ -214,7 +213,7 @@ def on_tool_error( tool_event: ToolEvent = self.events.tool[str(run_id)] self.ao_client.record(tool_event) - error_event = ErrorEvent(trigger_event=tool_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=tool_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -265,7 +264,7 @@ def on_retriever_error( action_event: ActionEvent = self.events.retriever[str(run_id)] self.ao_client.record(action_event) - error_event = ErrorEvent(trigger_event=action_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=action_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -405,7 +404,7 @@ async def on_llm_error( llm_event: LLMEvent = self.events.llm[str(run_id)] self.ao_client.record(llm_event) - error_event = ErrorEvent(trigger_event=llm_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=llm_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -431,8 +430,7 @@ async def on_llm_end( if len(response.generations) == 0: # TODO: more descriptive error - error_event = ErrorEvent(trigger_event=self.events.llm[str(run_id)], - details="on_llm_end: No generations", timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=self.events.llm[str(run_id)], error_type="NoGenerations", details="on_llm_end: No generations") self.ao_client.record(error_event) @debug_print_function_params @@ -481,7 +479,7 @@ async def on_chain_error( action_event: ActionEvent = self.events.chain[str(run_id)] self.ao_client.record(action_event) - error_event = ErrorEvent(trigger_event=action_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=action_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -524,7 +522,7 @@ async def on_tool_end( # Tools are capable of failing `on_tool_end` quietly. # This is a workaround to make sure we can log it as an error. if kwargs.get('name') == '_Exception': - error_event = ErrorEvent(trigger_event=tool_event, details=output, timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=tool_event, error_type="LangchainToolException", details=output) self.ao_client.record(error_event) @debug_print_function_params @@ -539,7 +537,7 @@ async def on_tool_error( tool_event: ToolEvent = self.events.tool[str(run_id)] self.ao_client.record(tool_event) - error_event = ErrorEvent(trigger_event=tool_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=tool_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params @@ -590,7 +588,7 @@ async def on_retriever_error( action_event: ActionEvent = self.events.retriever[str(run_id)] self.ao_client.record(action_event) - error_event = ErrorEvent(trigger_event=action_event, details=str(error), timestamp=get_ISO_time()) + error_event = ErrorEvent(trigger_event=action_event, exception=error) self.ao_client.record(error_event) @debug_print_function_params diff --git a/agentops/llm_tracker.py b/agentops/llm_tracker.py index 5a67f7a0..539bd56a 100644 --- a/agentops/llm_tracker.py +++ b/agentops/llm_tracker.py @@ -3,7 +3,7 @@ from importlib import import_module from importlib.metadata import version from packaging.version import Version, parse -import logging +from .log_config import logger from .event import LLMEvent, ErrorEvent from .helpers import get_ISO_time, check_call_stack_for_agent_id import inspect @@ -11,7 +11,7 @@ class LlmTracker: SUPPORTED_APIS = { - 'litellm': {'1.3.1': ("openai_chat_completions.completion",)}, # TODO + 'litellm': {'1.3.1': ("openai_chat_completions.completion",)}, 'openai': { '1.0.0': ( "chat.completions.create", @@ -60,9 +60,9 @@ def handle_stream_chunk(chunk): self.client.record(self.llm_event) except Exception as e: - self.client.record(ErrorEvent(trigger_event=self.llm_event, details={f"{type(e).__name__}": str(e)})) - # TODO: This error is specific to only one path of failure. Should be more generic or have different logging for different paths - logging.warning( + self.client.record(ErrorEvent(trigger_event=self.llm_event, exception=e)) + # TODO: This error is specific to only one path of failure. Should be more generic or have different logger for different paths + logger.warning( f"🖇 AgentOps: Unable to parse a chunk for LLM call {kwargs} - skipping upload to AgentOps") # if the response is a generator, decorate the generator @@ -99,9 +99,9 @@ def generator(): self.client.record(self.llm_event) except Exception as e: - self.client.record(ErrorEvent(trigger_event=self.llm_event, details={f"{type(e).__name__}": str(e)})) - # TODO: This error is specific to only one path of failure. Should be more generic or have different logging for different paths - logging.warning( + self.client.record(ErrorEvent(trigger_event=self.llm_event, exception=e)) + # TODO: This error is specific to only one path of failure. Should be more generic or have different logger for different paths + logger.warning( f"🖇 AgentOps: Unable to parse a chunk for LLM call {kwargs} - skipping upload to AgentOps") return response @@ -145,9 +145,9 @@ def handle_stream_chunk(chunk: ChatCompletionChunk): self.client.record(self.llm_event) except Exception as e: - self.client.record(ErrorEvent(trigger_event=self.llm_event, details={f"{type(e).__name__}": str(e)})) - # TODO: This error is specific to only one path of failure. Should be more generic or have different logging for different paths - logging.warning( + self.client.record(ErrorEvent(trigger_event=self.llm_event, exception=e)) + # TODO: This error is specific to only one path of failure. Should be more generic or have different logger for different paths + logger.warning( f"🖇 AgentOps: Unable to parse a chunk for LLM call {kwargs} - skipping upload to AgentOps") # if the response is a generator, decorate the generator @@ -190,9 +190,9 @@ async def async_generator(): self.client.record(self.llm_event) except Exception as e: - self.client.record(ErrorEvent(trigger_event=self.llm_event, details={f"{type(e).__name__}": str(e)})) - # TODO: This error is specific to only one path of failure. Should be more generic or have different logging for different paths - logging.warning( + self.client.record(ErrorEvent(trigger_event=self.llm_event, exception=e)) + # TODO: This error is specific to only one path of failure. Should be more generic or have different logger for different paths + logger.warning( f"🖇 AgentOps: Unable to parse a chunk for LLM call {kwargs} - skipping upload to AgentOps") return response @@ -302,7 +302,7 @@ def override_api(self): self.override_litellm_completion() self.override_litellm_async_completion() else: - logging.warning(f'🖇 AgentOps: Only litellm>=1.3.1 supported. v{module_version} found.') + logger.warning(f'🖇 AgentOps: 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': diff --git a/agentops/log_config.py b/agentops/log_config.py new file mode 100644 index 00000000..86cdecaf --- /dev/null +++ b/agentops/log_config.py @@ -0,0 +1,10 @@ +import logging + +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) \ No newline at end of file diff --git a/agentops/meta_client.py b/agentops/meta_client.py index d90d8035..b95973fa 100644 --- a/agentops/meta_client.py +++ b/agentops/meta_client.py @@ -1,4 +1,4 @@ -import logging +from .log_config import logger import traceback from .host_env import get_host_env @@ -45,7 +45,7 @@ def wrapper(self, *args, **kwargs): try: return method(self, *args, **kwargs) except Exception as e: - logging.warning(f"🖇 AgentOps: Error: {e}") + logger.warning(f"🖇 AgentOps: Error: {e}") config = getattr(self, 'config', None) if config is not None: type(self).send_exception_to_server(e, self.config._api_key) diff --git a/examples/langchain_examples.ipynb b/examples/langchain_examples.ipynb index 2498faf7..e0bfa337 100644 --- a/examples/langchain_examples.ipynb +++ b/examples/langchain_examples.ipynb @@ -107,10 +107,7 @@ { "name": "stderr", "output_type": "stream", - "text": [ - "/Users/howardgil/Desktop/agentops/AgentOps-AI/agentops/env/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n", - " warn_deprecated(\n" - ] + "text": [""] } ], "source": [ diff --git a/pyproject.toml b/pyproject.toml index 5de5b79b..db81f3b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "agentops" -version = "0.1.4" +version = "0.1.6" authors = [ { name="Alex Reibman", email="areibman@gmail.com" }, { name="Shawn Qiu", email="siyangqiu@gmail.com" }, diff --git a/tests/test_session.py b/tests/test_session.py index 339835de..c553e7bd 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -69,9 +69,32 @@ def test_tags(self, mock_req): agentops.end_session(end_state) time.sleep(0.15) - # Assert 3 requets, 1 for session init, 1 for event, 1 for end session + # Assert 3 requests, 1 for session init, 1 for event, 1 for end session assert len(mock_req.request_history) == 3 assert mock_req.last_request.headers['X-Agentops-Auth'] == self.api_key request_json = mock_req.last_request.json() assert request_json['session']['end_state'] == end_state - assert request_json['session']['tags'] == tags \ No newline at end of file + assert request_json['session']['tags'] == tags + + def test_inherit_session_id(self, mock_req): + # Arrange + inherited_id = '4f72e834-ff26-4802-ba2d-62e7613446f1' + agentops.start_session(tags=['test'], config=self.config, inherited_session_id=inherited_id) + + # Act + agentops.record(ActionEvent(self.event_type)) + agentops.record(ActionEvent(self.event_type)) + time.sleep(0.15) + + # event session_id correct + request_json = mock_req.last_request.json() + assert request_json['session_id'] == inherited_id + + # Act + end_state = 'Success' + agentops.end_session(end_state) + time.sleep(0.15) + + # Assert session ended with correct id + request_json = mock_req.last_request.json() + assert request_json['session']['session_id'] == inherited_id