diff --git a/llama-index-core/llama_index/core/instrumentation/dispatcher.py b/llama-index-core/llama_index/core/instrumentation/dispatcher.py index 10d8cecb896897bd5c317ad8d100ae130c1acd2e..727d7cba827ce434448bceba5fb6103b4ca60ee6 100644 --- a/llama-index-core/llama_index/core/instrumentation/dispatcher.py +++ b/llama-index-core/llama_index/core/instrumentation/dispatcher.py @@ -1,5 +1,4 @@ from typing import Any, List, Optional, Dict -import functools import inspect import uuid from llama_index.core.bridge.pydantic import BaseModel, Field @@ -9,6 +8,7 @@ from llama_index.core.instrumentation.span_handlers import ( NullSpanHandler, ) from llama_index.core.instrumentation.events.base import BaseEvent +import wrapt class Dispatcher(BaseModel): @@ -58,68 +58,119 @@ class Dispatcher(BaseModel): else: c = c.parent - def span_enter(self, *args, id: str, **kwargs) -> None: - """Send notice to handlers that a span with id has started.""" + def span_enter( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + **kwargs: Any, + ) -> None: + """Send notice to handlers that a span with id_ has started.""" c = self while c: for h in c.span_handlers: - h.span_enter(*args, id=id, **kwargs) + h.span_enter( + *args, + id_=id_, + bound_args=bound_args, + instance=instance, + **kwargs, + ) if not c.propagate: c = None else: c = c.parent - def span_drop(self, *args, id: str, err: Optional[Exception], **kwargs) -> None: - """Send notice to handlers that a span with id is being dropped.""" + def span_drop( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + err: Optional[BaseException] = None, + **kwargs: Any, + ) -> None: + """Send notice to handlers that a span with id_ is being dropped.""" c = self while c: for h in c.span_handlers: - h.span_drop(*args, id=id, err=err, **kwargs) + h.span_drop( + *args, + id_=id_, + bound_args=bound_args, + instance=instance, + err=err, + **kwargs, + ) if not c.propagate: c = None else: c = c.parent - def span_exit(self, *args, id: str, result: Optional[Any] = None, **kwargs) -> None: - """Send notice to handlers that a span with id is exiting.""" + def span_exit( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any, + ) -> None: + """Send notice to handlers that a span with id_ is exiting.""" c = self while c: for h in c.span_handlers: - h.span_exit(*args, id=id, result=result, **kwargs) + h.span_exit( + *args, + id_=id_, + bound_args=bound_args, + instance=instance, + result=result, + **kwargs, + ) if not c.propagate: c = None else: c = c.parent def span(self, func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - id = f"{func.__qualname__}-{uuid.uuid4()}" - self.span_enter(*args, id=id, **kwargs) + @wrapt.decorator + def wrapper(func, instance, args, kwargs): + bound_args = inspect.signature(func).bind(*args, **kwargs) + id_ = f"{func.__qualname__}-{uuid.uuid4()}" + self.span_enter(id_=id_, bound_args=bound_args, instance=instance) try: result = func(*args, **kwargs) - except Exception as e: - self.span_drop(*args, id=id, err=e, **kwargs) + except BaseException as e: + self.span_drop(id_=id_, bound_args=bound_args, instance=instance, err=e) + raise else: - self.span_exit(*args, id=id, result=result, **kwargs) + self.span_exit( + id_=id_, bound_args=bound_args, instance=instance, result=result + ) return result - @functools.wraps(func) - async def async_wrapper(*args, **kwargs): - id = f"{func.__qualname__}-{uuid.uuid4()}" - self.span_enter(*args, id=id, **kwargs) + @wrapt.decorator + async def async_wrapper(func, instance, args, kwargs): + bound_args = inspect.signature(func).bind(*args, **kwargs) + id_ = f"{func.__qualname__}-{uuid.uuid4()}" + self.span_enter(id_=id_, bound_args=bound_args, instance=instance) try: result = await func(*args, **kwargs) - except Exception as e: - self.span_drop(*args, id=id, err=e, **kwargs) + except BaseException as e: + self.span_drop(id_=id_, bound_args=bound_args, instance=instance, err=e) + raise else: - self.span_exit(*args, id=id, result=result, **kwargs) + self.span_exit( + id_=id_, bound_args=bound_args, instance=instance, result=result + ) return result if inspect.iscoroutinefunction(func): - return async_wrapper + return async_wrapper(func) else: - return wrapper + return wrapper(func) @property def log_name(self) -> str: diff --git a/llama-index-core/llama_index/core/instrumentation/span_handlers/base.py b/llama-index-core/llama_index/core/instrumentation/span_handlers/base.py index a54a8d7b30b79906ddcf4834718d0368ff924a68..78beb7c2956b29da9625f771725b3b528dd8483b 100644 --- a/llama-index-core/llama_index/core/instrumentation/span_handlers/base.py +++ b/llama-index-core/llama_index/core/instrumentation/span_handlers/base.py @@ -1,3 +1,4 @@ +import inspect from abc import abstractmethod from typing import Any, Dict, Generic, Optional, TypeVar @@ -22,52 +23,100 @@ class BaseSpanHandler(BaseModel, Generic[T]): """Class name.""" return "BaseSpanHandler" - def span_enter(self, *args, id: str, **kwargs) -> None: + def span_enter( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + **kwargs: Any, + ) -> None: """Logic for entering a span.""" - if id in self.open_spans: + if id_ in self.open_spans: pass # should probably raise an error here else: # TODO: thread safe? span = self.new_span( - *args, id=id, parent_span_id=self.current_span_id, **kwargs + id_=id_, + bound_args=bound_args, + instance=instance, + parent_span_id=self.current_span_id, ) if span: - self.open_spans[id] = span - self.current_span_id = id + self.open_spans[id_] = span + self.current_span_id = id_ - def span_exit(self, *args, id: str, result: Optional[Any] = None, **kwargs) -> None: + def span_exit( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any, + ) -> None: """Logic for exiting a span.""" - span = self.prepare_to_exit_span(*args, id=id, result=result, **kwargs) + span = self.prepare_to_exit_span( + id_=id_, bound_args=bound_args, instance=instance, result=result + ) if span: - if self.current_span_id == id: - self.current_span_id = self.open_spans[id].parent_id - del self.open_spans[id] + if self.current_span_id == id_: + self.current_span_id = self.open_spans[id_].parent_id + del self.open_spans[id_] - def span_drop(self, *args, id: str, err: Optional[Exception], **kwargs) -> None: + def span_drop( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + err: Optional[BaseException] = None, + **kwargs: Any, + ) -> None: """Logic for dropping a span i.e. early exit.""" - span = self.prepare_to_drop_span(*args, id=id, err=err, **kwargs) + span = self.prepare_to_drop_span( + id_=id_, bound_args=bound_args, instance=instance, err=err + ) if span: - if self.current_span_id == id: - self.current_span_id = self.open_spans[id].parent_id - del self.open_spans[id] + if self.current_span_id == id_: + self.current_span_id = self.open_spans[id_].parent_id + del self.open_spans[id_] @abstractmethod def new_span( - self, *args, id: str, parent_span_id: Optional[str], **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + parent_span_id: Optional[str] = None, + **kwargs: Any, ) -> Optional[T]: """Create a span.""" ... @abstractmethod def prepare_to_exit_span( - self, *args, id: str, result: Optional[Any] = None, **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any, ) -> Optional[T]: """Logic for preparing to exit a span.""" ... @abstractmethod def prepare_to_drop_span( - self, *args, id: str, err: Optional[Exception], **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + err: Optional[BaseException] = None, + **kwargs: Any, ) -> Optional[T]: """Logic for preparing to drop a span.""" ... diff --git a/llama-index-core/llama_index/core/instrumentation/span_handlers/null.py b/llama-index-core/llama_index/core/instrumentation/span_handlers/null.py index fccc12c42c943a44567c78796073693964a4d005..a4c6000ebcd0ac271d517759fb566976662002b5 100644 --- a/llama-index-core/llama_index/core/instrumentation/span_handlers/null.py +++ b/llama-index-core/llama_index/core/instrumentation/span_handlers/null.py @@ -1,3 +1,4 @@ +import inspect from typing import Optional, Any from llama_index.core.instrumentation.span_handlers.base import BaseSpanHandler from llama_index.core.instrumentation.span.base import BaseSpan @@ -9,26 +10,61 @@ class NullSpanHandler(BaseSpanHandler[BaseSpan]): """Class name.""" return "NullSpanHandler" - def span_enter(self, *args, id: str, **kwargs) -> None: + def span_enter( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + **kwargs: Any + ) -> None: """Logic for entering a span.""" return - def span_exit(self, *args, id: str, result: Optional[Any], **kwargs) -> None: + def span_exit( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any + ) -> None: """Logic for exiting a span.""" return - def new_span(self, *args, id: str, parent_span_id: Optional[str], **kwargs) -> None: + def new_span( + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + parent_span_id: Optional[str] = None, + **kwargs: Any + ) -> None: """Create a span.""" return def prepare_to_exit_span( - self, *args, id: str, result: Optional[Any] = None, **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any ) -> None: """Logic for exiting a span.""" return def prepare_to_drop_span( - self, *args, id: str, err: Optional[Exception], **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + err: Optional[BaseException] = None, + **kwargs: Any ) -> None: """Logic for droppping a span.""" if err: diff --git a/llama-index-core/llama_index/core/instrumentation/span_handlers/simple.py b/llama-index-core/llama_index/core/instrumentation/span_handlers/simple.py index 96c54b2fb2982f8389b5b0294d9f3868ffee7358..18f9bddc4f27d1254e712728084181718b9969bf 100644 --- a/llama-index-core/llama_index/core/instrumentation/span_handlers/simple.py +++ b/llama-index-core/llama_index/core/instrumentation/span_handlers/simple.py @@ -1,3 +1,4 @@ +import inspect from typing import Any, cast, List, Optional, TYPE_CHECKING from llama_index.core.bridge.pydantic import Field from llama_index.core.instrumentation.span.simple import SimpleSpan @@ -21,16 +22,28 @@ class SimpleSpanHandler(BaseSpanHandler[SimpleSpan]): return "SimpleSpanHandler" def new_span( - self, *args, id: str, parent_span_id: Optional[str], **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + parent_span_id: Optional[str] = None, + **kwargs: Any, ) -> SimpleSpan: """Create a span.""" - return SimpleSpan(id_=id, parent_id=parent_span_id) + return SimpleSpan(id_=id_, parent_id=parent_span_id) def prepare_to_exit_span( - self, *args, id: str, result: Optional[Any] = None, **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + result: Optional[Any] = None, + **kwargs: Any, ) -> SimpleSpan: """Logic for preparing to drop a span.""" - span = self.open_spans[id] + span = self.open_spans[id_] span = cast(SimpleSpan, span) span.end_time = datetime.now() span.duration = (span.end_time - span.start_time).total_seconds() @@ -38,11 +51,17 @@ class SimpleSpanHandler(BaseSpanHandler[SimpleSpan]): return span def prepare_to_drop_span( - self, *args, id: str, err: Optional[Exception], **kwargs + self, + *args: Any, + id_: str, + bound_args: inspect.BoundArguments, + instance: Optional[Any] = None, + err: Optional[BaseException] = None, + **kwargs: Any, ) -> SimpleSpan: """Logic for droppping a span.""" - if id in self.open_spans: - return self.open_spans[id] + if id_ in self.open_spans: + return self.open_spans[id_] return None def _get_trace_trees(self) -> List["Tree"]: diff --git a/llama-index-core/llama_index/core/llms/llm.py b/llama-index-core/llama_index/core/llms/llm.py index 7f3e8ae623fe58844aaac34ce61a9b6e55c42537..678d9dfcea89b500c1c5a1c9a334578d5e95dea3 100644 --- a/llama-index-core/llama_index/core/llms/llm.py +++ b/llama-index-core/llama_index/core/llms/llm.py @@ -286,6 +286,7 @@ class LLM(BaseLLM): # -- Structured outputs -- + @dispatcher.span def structured_predict( self, output_cls: BaseModel, @@ -331,6 +332,7 @@ class LLM(BaseLLM): return program(**prompt_args) + @dispatcher.span async def astructured_predict( self, output_cls: BaseModel, @@ -418,6 +420,7 @@ class LLM(BaseLLM): dispatcher.event(LLMPredictEndEvent()) return self._parse_output(output) + @dispatcher.span def stream( self, prompt: BasePromptTemplate, @@ -500,6 +503,7 @@ class LLM(BaseLLM): dispatcher.event(LLMPredictEndEvent()) return self._parse_output(output) + @dispatcher.span async def astream( self, prompt: BasePromptTemplate, @@ -544,6 +548,7 @@ class LLM(BaseLLM): return stream_tokens + @dispatcher.span def predict_and_call( self, tools: List["BaseTool"], @@ -595,6 +600,7 @@ class LLM(BaseLLM): return output + @dispatcher.span async def apredict_and_call( self, tools: List["BaseTool"], diff --git a/llama-index-core/pyproject.toml b/llama-index-core/pyproject.toml index 50974ee5da5c3249f23b1c57b9cf2104d9dc33f4..e91bf83c830120a5cff0bdd6484edb31154547b3 100644 --- a/llama-index-core/pyproject.toml +++ b/llama-index-core/pyproject.toml @@ -84,6 +84,7 @@ tqdm = "^4.66.1" pillow = ">=9.0.0" PyYAML = ">=6.0.1" llamaindex-py-client = "^0.1.15" +wrapt = "*" [tool.poetry.extras] gradientai = [ diff --git a/llama-index-core/tests/instrumentation/test_dispatcher.py b/llama-index-core/tests/instrumentation/test_dispatcher.py index 4805a959bb09fdc7db9d346c47cb27bc59998f15..c256719b4b1f857cead0b68396510f0bbce76d07 100644 --- a/llama-index-core/tests/instrumentation/test_dispatcher.py +++ b/llama-index-core/tests/instrumentation/test_dispatcher.py @@ -1,3 +1,6 @@ +import inspect +from asyncio import CancelledError + import pytest import llama_index.core.instrumentation as instrument from llama_index.core.instrumentation.dispatcher import Dispatcher @@ -5,6 +8,9 @@ from unittest.mock import patch, MagicMock dispatcher = instrument.get_dispatcher("test") +value_error = ValueError("value error") +cancelled_error = CancelledError("cancelled error") + @dispatcher.span def func(*args, a, b=3, **kwargs): @@ -16,6 +22,34 @@ async def async_func(*args, a, b=3, **kwargs): return a + b +@dispatcher.span +def func_exc(*args, a, b=3, c=4, **kwargs): + raise value_error + + +@dispatcher.span +async def async_func_exc(*args, a, b=3, c=4, **kwargs): + raise cancelled_error + + +class _TestObject: + @dispatcher.span + def func(self, *args, a, b=3, **kwargs): + return a + b + + @dispatcher.span + async def async_func(self, *args, a, b=3, **kwargs): + return a + b + + @dispatcher.span + def func_exc(self, *args, a, b=3, c=4, **kwargs): + raise value_error + + @dispatcher.span + async def async_func_exc(self, *args, a, b=3, c=4, **kwargs): + raise cancelled_error + + @patch.object(Dispatcher, "span_exit") @patch.object(Dispatcher, "span_enter") @patch("llama_index.core.instrumentation.dispatcher.uuid") @@ -29,60 +63,129 @@ def test_dispatcher_span_args(mock_uuid, mock_span_enter, mock_span_exit): # assert # span_enter span_id = f"{func.__qualname__}-mock" + bound_args = inspect.signature(func).bind(1, 2, a=3, c=5) + mock_span_enter.assert_called_once() + args, kwargs = mock_span_enter.call_args + assert args == () + assert kwargs == {"id_": span_id, "bound_args": bound_args, "instance": None} + + # span_exit + args, kwargs = mock_span_exit.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": None, + "result": result, + } + + +@patch.object(Dispatcher, "span_exit") +@patch.object(Dispatcher, "span_enter") +@patch("llama_index.core.instrumentation.dispatcher.uuid") +def test_dispatcher_span_args_with_instance(mock_uuid, mock_span_enter, mock_span_exit): + # arrange + mock_uuid.uuid4.return_value = "mock" + + # act + instance = _TestObject() + result = instance.func(1, 2, a=3, c=5) + + # assert + # span_enter + span_id = f"{instance.func.__qualname__}-mock" + bound_args = inspect.signature(instance.func).bind(1, 2, a=3, c=5) mock_span_enter.assert_called_once() args, kwargs = mock_span_enter.call_args - assert args == (1, 2) - assert kwargs == {"id": span_id, "a": 3, "c": 5} + assert args == () + assert kwargs == {"id_": span_id, "bound_args": bound_args, "instance": instance} # span_exit args, kwargs = mock_span_exit.call_args - assert args == (1, 2) - assert kwargs == {"id": span_id, "a": 3, "c": 5, "result": result} + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": instance, + "result": result, + } @patch.object(Dispatcher, "span_exit") @patch.object(Dispatcher, "span_drop") @patch.object(Dispatcher, "span_enter") @patch("llama_index.core.instrumentation.dispatcher.uuid") -@patch(f"{__name__}.func") def test_dispatcher_span_drop_args( - mock_func: MagicMock, mock_uuid: MagicMock, mock_span_enter: MagicMock, mock_span_drop: MagicMock, mock_span_exit: MagicMock, ): # arrange - class CustomException(Exception): - pass + mock_uuid.uuid4.return_value = "mock" + + with pytest.raises(ValueError): + # act + _ = func_exc(7, a=3, b=5, c=2, d=5) + + # assert + # span_enter + mock_span_enter.assert_called_once() + + # span_drop + mock_span_drop.assert_called_once() + span_id = f"{func_exc.__qualname__}-mock" + bound_args = inspect.signature(func_exc).bind(7, a=3, b=5, c=2, d=5) + args, kwargs = mock_span_drop.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": None, + "err": value_error, + } + + # span_exit + mock_span_exit.assert_not_called() + +@patch.object(Dispatcher, "span_exit") +@patch.object(Dispatcher, "span_drop") +@patch.object(Dispatcher, "span_enter") +@patch("llama_index.core.instrumentation.dispatcher.uuid") +def test_dispatcher_span_drop_args( + mock_uuid: MagicMock, + mock_span_enter: MagicMock, + mock_span_drop: MagicMock, + mock_span_exit: MagicMock, +): + # arrange mock_uuid.uuid4.return_value = "mock" - mock_func.side_effect = CustomException - with pytest.raises(CustomException): + with pytest.raises(ValueError): # act - result = func(7, a=3, b=5, c=2, d=5) - - # assert - # span_enter - mock_span_enter.assert_called_once() - - # span_drop - mock_span_drop.assert_called_once() - span_id = f"{func.__qualname__}-mock" - args, kwargs = mock_span_exit.call_args - assert args == (7,) - assert kwargs == { - "id": span_id, - "a": 3, - "b": 5, - "c": 2, - "d": 2, - "err": CustomException, - } - - # span_exit - mock_span_exit.assert_not_called() + instance = _TestObject() + _ = instance.func_exc(7, a=3, b=5, c=2, d=5) + + # assert + # span_enter + mock_span_enter.assert_called_once() + + # span_drop + mock_span_drop.assert_called_once() + span_id = f"{instance.func_exc.__qualname__}-mock" + bound_args = inspect.signature(instance.func_exc).bind(7, a=3, b=5, c=2, d=5) + args, kwargs = mock_span_drop.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": instance, + "err": value_error, + } + + # span_exit + mock_span_exit.assert_not_called() @pytest.mark.asyncio() @@ -99,15 +202,55 @@ async def test_dispatcher_async_span_args(mock_uuid, mock_span_enter, mock_span_ # assert # span_enter span_id = f"{async_func.__qualname__}-mock" + bound_args = inspect.signature(async_func).bind(1, 2, a=3, c=5) mock_span_enter.assert_called_once() args, kwargs = mock_span_enter.call_args - assert args == (1, 2) - assert kwargs == {"id": span_id, "a": 3, "c": 5} + assert args == () + assert kwargs == {"id_": span_id, "bound_args": bound_args, "instance": None} # span_exit args, kwargs = mock_span_exit.call_args - assert args == (1, 2) - assert kwargs == {"id": span_id, "a": 3, "c": 5, "result": result} + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": None, + "result": result, + } + + +@pytest.mark.asyncio() +@patch.object(Dispatcher, "span_exit") +@patch.object(Dispatcher, "span_enter") +@patch("llama_index.core.instrumentation.dispatcher.uuid") +async def test_dispatcher_async_span_args_with_instance( + mock_uuid, mock_span_enter, mock_span_exit +): + # arrange + mock_uuid.uuid4.return_value = "mock" + + # act + instance = _TestObject() + result = await instance.async_func(1, 2, a=3, c=5) + + # assert + # span_enter + span_id = f"{instance.async_func.__qualname__}-mock" + bound_args = inspect.signature(instance.async_func).bind(1, 2, a=3, c=5) + mock_span_enter.assert_called_once() + args, kwargs = mock_span_enter.call_args + assert args == () + assert kwargs == {"id_": span_id, "bound_args": bound_args, "instance": instance} + + # span_exit + args, kwargs = mock_span_exit.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": instance, + "result": result, + } @pytest.mark.asyncio() @@ -115,42 +258,75 @@ async def test_dispatcher_async_span_args(mock_uuid, mock_span_enter, mock_span_ @patch.object(Dispatcher, "span_drop") @patch.object(Dispatcher, "span_enter") @patch("llama_index.core.instrumentation.dispatcher.uuid") -@patch(f"{__name__}.async_func") -async def test_dispatcher_aysnc_span_drop_args( - mock_func: MagicMock, +async def test_dispatcher_async_span_drop_args( mock_uuid: MagicMock, mock_span_enter: MagicMock, mock_span_drop: MagicMock, mock_span_exit: MagicMock, ): # arrange - class CustomException(Exception): - pass + mock_uuid.uuid4.return_value = "mock" + + with pytest.raises(CancelledError): + # act + _ = await async_func_exc(7, a=3, b=5, c=2, d=5) + # assert + # span_enter + mock_span_enter.assert_called_once() + + # span_drop + mock_span_drop.assert_called_once() + span_id = f"{async_func_exc.__qualname__}-mock" + bound_args = inspect.signature(async_func_exc).bind(7, a=3, b=5, c=2, d=5) + args, kwargs = mock_span_drop.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": None, + "err": cancelled_error, + } + + # span_exit + mock_span_exit.assert_not_called() + + +@pytest.mark.asyncio() +@patch.object(Dispatcher, "span_exit") +@patch.object(Dispatcher, "span_drop") +@patch.object(Dispatcher, "span_enter") +@patch("llama_index.core.instrumentation.dispatcher.uuid") +async def test_dispatcher_async_span_drop_args_with_instance( + mock_uuid: MagicMock, + mock_span_enter: MagicMock, + mock_span_drop: MagicMock, + mock_span_exit: MagicMock, +): + # arrange mock_uuid.uuid4.return_value = "mock" - mock_func.side_effect = CustomException - with pytest.raises(CustomException): + with pytest.raises(CancelledError): # act - result = await async_func(7, a=3, b=5, c=2, d=5) - - # assert - # span_enter - mock_span_enter.assert_called_once() - - # span_drop - mock_span_drop.assert_called_once() - span_id = f"{func.__qualname__}-mock" - args, kwargs = mock_span_exit.call_args - assert args == (7,) - assert kwargs == { - "id": span_id, - "a": 3, - "b": 5, - "c": 2, - "d": 2, - "err": CustomException, - } - - # span_exit - mock_span_exit.assert_not_called() + instance = _TestObject() + _ = await instance.async_func_exc(7, a=3, b=5, c=2, d=5) + + # assert + # span_enter + mock_span_enter.assert_called_once() + + # span_drop + mock_span_drop.assert_called_once() + span_id = f"{instance.async_func_exc.__qualname__}-mock" + bound_args = inspect.signature(instance.async_func_exc).bind(7, a=3, b=5, c=2, d=5) + args, kwargs = mock_span_drop.call_args + assert args == () + assert kwargs == { + "id_": span_id, + "bound_args": bound_args, + "instance": instance, + "err": cancelled_error, + } + + # span_exit + mock_span_exit.assert_not_called()