Skip to content

otel.otel

Integration with OpenTelemetry

BaseCallResponse

Bases: BaseModel, Generic[ResponseT, BaseToolT], ABC

A base abstract interface for LLM call responses.

Attributes:

Name Type Description
response ResponseT

The original response from whichever model response this wraps.

Source code in mirascope/base/types.py
class BaseCallResponse(BaseModel, Generic[ResponseT, BaseToolT], ABC):
    """A base abstract interface for LLM call responses.

    Attributes:
        response: The original response from whichever model response this wraps.
    """

    response: ResponseT
    user_message_param: Optional[Any] = None
    tool_types: Optional[list[Type[BaseToolT]]] = None
    start_time: float  # The start time of the completion in ms
    end_time: float  # The end time of the completion in ms
    cost: Optional[float] = None  # The cost of the completion in dollars

    model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

    @property
    @abstractmethod
    def message_param(self) -> Any:
        """Returns the assistant's response as a message parameter."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def tools(self) -> Optional[list[BaseToolT]]:
        """Returns the tools for the 0th choice message."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def tool(self) -> Optional[BaseToolT]:
        """Returns the 0th tool for the 0th choice message."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def tool_message_params(
        cls, tools_and_outputs: list[tuple[BaseToolT, Any]]
    ) -> list[Any]:
        """Returns the tool message parameters for tool call results."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def content(self) -> str:
        """Should return the string content of the response.

        If there are multiple choices in a response, this method should select the 0th
        choice and return it's string content.

        If there is no string content (e.g. when using tools), this method must return
        the empty string.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def finish_reasons(self) -> Union[None, list[str]]:
        """Should return the finish reasons of the response.

        If there is no finish reason, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def model(self) -> Optional[str]:
        """Should return the name of the response model."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def id(self) -> Optional[str]:
        """Should return the id of the response."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def usage(self) -> Any:
        """Should return the usage of the response.

        If there is no usage, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def input_tokens(self) -> Optional[Union[int, float]]:
        """Should return the number of input tokens.

        If there is no input_tokens, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def output_tokens(self) -> Optional[Union[int, float]]:
        """Should return the number of output tokens.

        If there is no output_tokens, this method must return None.
        """
        ...  # pragma: no cover

content: str abstractmethod property

Should return the string content of the response.

If there are multiple choices in a response, this method should select the 0th choice and return it's string content.

If there is no string content (e.g. when using tools), this method must return the empty string.

finish_reasons: Union[None, list[str]] abstractmethod property

Should return the finish reasons of the response.

If there is no finish reason, this method must return None.

id: Optional[str] abstractmethod property

Should return the id of the response.

input_tokens: Optional[Union[int, float]] abstractmethod property

Should return the number of input tokens.

If there is no input_tokens, this method must return None.

message_param: Any abstractmethod property

Returns the assistant's response as a message parameter.

model: Optional[str] abstractmethod property

Should return the name of the response model.

output_tokens: Optional[Union[int, float]] abstractmethod property

Should return the number of output tokens.

If there is no output_tokens, this method must return None.

tool: Optional[BaseToolT] abstractmethod property

Returns the 0th tool for the 0th choice message.

tools: Optional[list[BaseToolT]] abstractmethod property

Returns the tools for the 0th choice message.

usage: Any abstractmethod property

Should return the usage of the response.

If there is no usage, this method must return None.

tool_message_params(tools_and_outputs) abstractmethod classmethod

Returns the tool message parameters for tool call results.

Source code in mirascope/base/types.py
@classmethod
@abstractmethod
def tool_message_params(
    cls, tools_and_outputs: list[tuple[BaseToolT, Any]]
) -> list[Any]:
    """Returns the tool message parameters for tool call results."""
    ...  # pragma: no cover

BaseCallResponseChunk

Bases: BaseModel, Generic[ChunkT, BaseToolT], ABC

A base abstract interface for LLM streaming response chunks.

Attributes:

Name Type Description
response

The original response chunk from whichever model response this wraps.

Source code in mirascope/base/types.py
class BaseCallResponseChunk(BaseModel, Generic[ChunkT, BaseToolT], ABC):
    """A base abstract interface for LLM streaming response chunks.

    Attributes:
        response: The original response chunk from whichever model response this wraps.
    """

    chunk: ChunkT
    user_message_param: Optional[Any] = None
    tool_types: Optional[list[Type[BaseToolT]]] = None
    cost: Optional[float] = None  # The cost of the completion in dollars
    model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

    @property
    @abstractmethod
    def content(self) -> str:
        """Should return the string content of the response chunk.

        If there are multiple choices in a chunk, this method should select the 0th
        choice and return it's string content.

        If there is no string content (e.g. when using tools), this method must return
        the empty string.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def model(self) -> Optional[str]:
        """Should return the name of the response model."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def id(self) -> Optional[str]:
        """Should return the id of the response."""
        ...  # pragma: no cover

    @property
    @abstractmethod
    def finish_reasons(self) -> Union[None, list[str]]:
        """Should return the finish reasons of the response.

        If there is no finish reason, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def usage(self) -> Any:
        """Should return the usage of the response.

        If there is no usage, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def input_tokens(self) -> Optional[Union[int, float]]:
        """Should return the number of input tokens.

        If there is no input_tokens, this method must return None.
        """
        ...  # pragma: no cover

    @property
    @abstractmethod
    def output_tokens(self) -> Optional[Union[int, float]]:
        """Should return the number of output tokens.

        If there is no output_tokens, this method must return None.
        """
        ...  # pragma: no cover

content: str abstractmethod property

Should return the string content of the response chunk.

If there are multiple choices in a chunk, this method should select the 0th choice and return it's string content.

If there is no string content (e.g. when using tools), this method must return the empty string.

finish_reasons: Union[None, list[str]] abstractmethod property

Should return the finish reasons of the response.

If there is no finish reason, this method must return None.

id: Optional[str] abstractmethod property

Should return the id of the response.

input_tokens: Optional[Union[int, float]] abstractmethod property

Should return the number of input tokens.

If there is no input_tokens, this method must return None.

model: Optional[str] abstractmethod property

Should return the name of the response model.

output_tokens: Optional[Union[int, float]] abstractmethod property

Should return the number of output tokens.

If there is no output_tokens, this method must return None.

usage: Any abstractmethod property

Should return the usage of the response.

If there is no usage, this method must return None.

BaseTool

Bases: BaseModel, Generic[ToolCallT], ABC

A base class for easy use of tools with prompts.

BaseTool is an abstract class interface and should not be used directly. When implementing a class that extends BaseTool, you must include the original tool_call from which this till was instantiated. Make sure to skip tool_call when generating the schema by annotating it with SkipJsonSchema.

Source code in mirascope/base/tools.py
class BaseTool(BaseModel, Generic[ToolCallT], ABC):
    """A base class for easy use of tools with prompts.

    `BaseTool` is an abstract class interface and should not be used directly. When
    implementing a class that extends `BaseTool`, you must include the original
    `tool_call` from which this till was instantiated. Make sure to skip `tool_call`
    when generating the schema by annotating it with `SkipJsonSchema`.
    """

    tool_call: SkipJsonSchema[ToolCallT]

    model_config = ConfigDict(arbitrary_types_allowed=True)

    @classmethod
    def name(cls) -> str:
        """Returns the name of the tool."""
        return cls.__name__

    @classmethod
    def description(cls) -> str:
        """Returns the description of the tool."""
        return inspect.cleandoc(cls.__doc__) if cls.__doc__ else DEFAULT_TOOL_DOCSTRING

    @property
    def args(self) -> dict[str, Any]:
        """The arguments of the tool as a dictionary."""
        return {
            field: getattr(self, field)
            for field in self.model_fields
            if field != "tool_call"
        }

    @property
    def fn(self) -> Callable[..., str]:
        """Returns the function that the tool describes."""
        raise RuntimeError("Tool does not have an attached function.")

    def call(self) -> str:
        """Calls the tool's `fn` with the tool's `args`."""
        return self.fn(**self.args)

    @classmethod
    def tool_schema(cls) -> Any:
        """Constructs a JSON Schema tool schema from the `BaseModel` schema defined."""
        model_schema = cls.model_json_schema()
        model_schema.pop("title", None)
        model_schema.pop("description", None)

        fn = {"name": cls.name(), "description": cls.description()}
        if model_schema["properties"]:
            fn["parameters"] = model_schema  # type: ignore

        return fn

    @classmethod
    @abstractmethod
    def from_tool_call(cls, tool_call: ToolCallT) -> BaseTool:
        """Extracts an instance of the tool constructed from a tool call response."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_model(cls, model: type[BaseModel]) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a `BaseModel` type."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_fn(cls, fn: Callable) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a function."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_base_type(cls, base_type: type[BaseType]) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a `BaseType` type."""
        ...  # pragma: no cover

args: dict[str, Any] property

The arguments of the tool as a dictionary.

fn: Callable[..., str] property

Returns the function that the tool describes.

call()

Calls the tool's fn with the tool's args.

Source code in mirascope/base/tools.py
def call(self) -> str:
    """Calls the tool's `fn` with the tool's `args`."""
    return self.fn(**self.args)

description() classmethod

Returns the description of the tool.

Source code in mirascope/base/tools.py
@classmethod
def description(cls) -> str:
    """Returns the description of the tool."""
    return inspect.cleandoc(cls.__doc__) if cls.__doc__ else DEFAULT_TOOL_DOCSTRING

from_base_type(base_type) abstractmethod classmethod

Constructs a BaseTool type from a BaseType type.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_base_type(cls, base_type: type[BaseType]) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a `BaseType` type."""
    ...  # pragma: no cover

from_fn(fn) abstractmethod classmethod

Constructs a BaseTool type from a function.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_fn(cls, fn: Callable) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a function."""
    ...  # pragma: no cover

from_model(model) abstractmethod classmethod

Constructs a BaseTool type from a BaseModel type.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_model(cls, model: type[BaseModel]) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a `BaseModel` type."""
    ...  # pragma: no cover

from_tool_call(tool_call) abstractmethod classmethod

Extracts an instance of the tool constructed from a tool call response.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_tool_call(cls, tool_call: ToolCallT) -> BaseTool:
    """Extracts an instance of the tool constructed from a tool call response."""
    ...  # pragma: no cover

name() classmethod

Returns the name of the tool.

Source code in mirascope/base/tools.py
@classmethod
def name(cls) -> str:
    """Returns the name of the tool."""
    return cls.__name__

tool_schema() classmethod

Constructs a JSON Schema tool schema from the BaseModel schema defined.

Source code in mirascope/base/tools.py
@classmethod
def tool_schema(cls) -> Any:
    """Constructs a JSON Schema tool schema from the `BaseModel` schema defined."""
    model_schema = cls.model_json_schema()
    model_schema.pop("title", None)
    model_schema.pop("description", None)

    fn = {"name": cls.name(), "description": cls.description()}
    if model_schema["properties"]:
        fn["parameters"] = model_schema  # type: ignore

    return fn

configure(processors=None)

Configures the OpenTelemetry tracer, this function should only be called once.

Parameters:

Name Type Description Default
processors Optional[Sequence[SpanProcessor]]

Optional[Sequence[SpanProcessor]] The span processors to use, if None, a console exporter will be used.

None

Returns:

Type Description
Tracer

The configured tracer.

Source code in mirascope/otel/otel.py
def configure(
    processors: Optional[Sequence[SpanProcessor]] = None,
) -> Tracer:
    """Configures the OpenTelemetry tracer, this function should only be called once.

    Args:
        processors: Optional[Sequence[SpanProcessor]]
            The span processors to use, if None, a console exporter will be used.

    Returns:
        The configured tracer.
    """
    provider = TracerProvider()
    if processors is None:
        provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
    else:
        for processor in processors:
            provider.add_span_processor(processor)
    # NOTE: Sets the global trace provider, should only be called once
    set_tracer_provider(provider)
    return get_tracer("otel")

custom_encoder(obj)

Custom encoder for the OpenTelemetry span

Source code in mirascope/otel/otel.py
def custom_encoder(obj) -> str:
    """Custom encoder for the OpenTelemetry span"""
    return obj.__name__

get_class_vars(self)

Get the class variables of a BaseModel removing any dangerous variables.

Source code in mirascope/base/ops_utils.py
def get_class_vars(self: BaseModel) -> dict[str, Any]:
    """Get the class variables of a `BaseModel` removing any dangerous variables."""
    class_vars = {}
    for classvars in self.__class_vars__:
        if not classvars == "api_key":
            class_vars[classvars] = getattr(self.__class__, classvars)
    return class_vars

handle_after_call(self, fn, result, span, **kwargs)

Handles after call

Source code in mirascope/otel/otel.py
def handle_after_call(
    self: BaseModel,
    fn,
    result: Union[BaseCallResponse, list[BaseCallResponseChunk]],
    span: Span,
    **kwargs,
) -> None:
    """Handles after call"""
    if isinstance(result, list):
        response = [chunk.model_dump() for chunk in result]
        span.set_attribute("response", str(response))
    else:
        span.set_attribute("response", str(result.model_dump()))

handle_before_call(self, fn, **kwargs)

Handles before call

Source code in mirascope/otel/otel.py
@contextmanager
def handle_before_call(self: BaseModel, fn: Callable, **kwargs):
    """Handles before call"""

    class_vars = get_class_vars(self)
    inputs = self.model_dump()
    tracer = get_tracer("otel")
    attributes: dict[str, AttributeValue] = {**kwargs, **class_vars, **inputs}
    if hasattr(self, "call_params"):
        attributes["call_params"] = json.dumps(
            self.call_params.model_dump(), default=custom_encoder
        )
    if hasattr(self, "configuration"):
        configuration = self.configuration.model_dump()
        attributes["configuration"] = json.dumps(configuration, default=custom_encoder)

    if hasattr(self, "base_url"):
        attributes["base_url"] = self.base_url if self.base_url else ""

    if hasattr(self, "extract_schema"):
        attributes["extract_schema"] = self.extract_schema.__name__
    with tracer.start_as_current_span(
        f"{self.__class__.__name__}.{fn.__name__}"
    ) as span:
        span.set_attributes(attributes)
        yield span

with_otel(cls)

Wraps a pydantic class with a OTel span.

Source code in mirascope/otel/otel.py
def with_otel(cls):
    """Wraps a pydantic class with a OTel span."""

    provider = get_tracer_provider()
    if not isinstance(provider, TracerProvider):
        configure()
    wrap_mirascope_class_functions(
        cls,
        handle_before_call=handle_before_call,
        handle_after_call=handle_after_call,
    )
    if hasattr(cls, "configuration"):
        cls.configuration = cls.configuration.model_copy(
            update={"llm_ops": [*cls.configuration.llm_ops, mirascope_otel(cls)]}
        )
    return cls

wrap_mirascope_class_functions(cls, *, handle_before_call=None, handle_before_call_async=None, handle_after_call=None, handle_after_call_async=None, decorator=None, **custom_kwargs)

Wraps Mirascope class functions with a decorator.

Parameters:

Name Type Description Default
cls type[BaseModel]

The Mirascope class to wrap.

required
handle_before_call Optional[Callable[..., Any]]

A function to call before the call to the wrapped function.

None
handle_after_call Optional[Callable[..., Any]]

A function to call after the call to the wrapped function.

None
custom_kwargs Any

Additional keyword arguments to pass to the decorator.

{}
Source code in mirascope/base/ops_utils.py
def wrap_mirascope_class_functions(
    cls: type[BaseModel],
    *,
    handle_before_call: Optional[Callable[..., Any]] = None,
    handle_before_call_async: Optional[Callable[..., Awaitable[Any]]] = None,
    handle_after_call: Optional[Callable[..., Any]] = None,
    handle_after_call_async: Optional[Callable[..., Awaitable[Any]]] = None,
    decorator: Optional[DecoratorType] = None,
    **custom_kwargs: Any,
):
    """Wraps Mirascope class functions with a decorator.

    Args:
        cls: The Mirascope class to wrap.
        handle_before_call: A function to call before the call to the wrapped function.
        handle_after_call: A function to call after the call to the wrapped function.
        custom_kwargs: Additional keyword arguments to pass to the decorator.
    """

    for name in get_class_functions(cls):
        setattr(
            cls,
            name,
            mirascope_span(
                getattr(cls, name),
                handle_before_call=handle_before_call,
                handle_before_call_async=handle_before_call_async,
                handle_after_call=handle_after_call,
                handle_after_call_async=handle_after_call_async,
                decorator=decorator,
                **custom_kwargs,
            ),
        )
    return cls