Skip to content

Configuration

conatus.models.base.ModelConfig dataclass

ModelConfig(
    not_given_sentinel: object = CTUS_NOT_GIVEN,
    api_key: OptionalArg[str] = CTUS_NOT_GIVEN,
    model_name: OptionalArg[str] = CTUS_NOT_GIVEN,
    max_tokens: OptionalArg[int] = CTUS_NOT_GIVEN,
    stdout_mode: OptionalArg[
        Literal["normal", "preview", "silent"]
    ] = CTUS_NOT_GIVEN,
    temperature: OptionalArg[float] = CTUS_NOT_GIVEN,
    computer_use_mode: OptionalArg[bool] = CTUS_NOT_GIVEN,
    use_mock: OptionalArg[bool] = CTUS_NOT_GIVEN,
    only_pass_new_messages: OptionalArg[
        bool
    ] = CTUS_NOT_GIVEN,
    previous_messages_id: OptionalArg[str] = CTUS_NOT_GIVEN,
    truncation: OptionalArg[
        Literal["auto", "disabled"]
    ] = CTUS_NOT_GIVEN,
)

The configuration for a BaseAIModel.

We're only showing some of the available arguments; if you need more, you can always add them after instantiation. Or, even better, you can subclass this class and add them there. See the OpenAIModelConfig implementation for an example.

One method that might be useful is to_kwargs , which returns a dictionary of the configuration. In that method, you can provide a specification, which is a TypedDict that matches some of the arguments expected by the provider, as well as a not_given_sentinel, which is an object that is used to represent a missing argument. See the OpenAIModel implementation for an example.

Another method that might be useful is apply_config , which returns a new instance of the configuration with the new values applied.

In both cases, because we use the not_given_sentinel object to represent missing arguments, this method can be used to ensure that only arguments that are actually provided are included in the new configuration.

not_given_sentinel class-attribute instance-attribute

not_given_sentinel: object = CTUS_NOT_GIVEN

The sentinel object to use for missing arguments.

This is used to represent a missing argument. If we encounter this sentinel object, we will not include it in the returned dictionary.

api_key class-attribute instance-attribute

The API key to use, if any.

If not provided, the API key will be taken from the environment variable specified in the api_key_env_variable attribute of the model.

model_name class-attribute instance-attribute

The name of the model to use.

max_tokens class-attribute instance-attribute

The maximum number of tokens to generate.

stdout_mode class-attribute instance-attribute

stdout_mode: OptionalArg[
    Literal["normal", "preview", "silent"]
] = CTUS_NOT_GIVEN

The mode to use for the standard output.

  • 'normal': Notify the user that we're waiting for a response, and then that we're receiving the response, displaying the number of chunks received so far.
  • 'preview': Preview the response with a fancy output that updates as the response chunks are received. Only works if the response is a stream. If preview is set and the response is not a stream, it will default to 'normal'.
  • 'silent': Do not print anything to the standard output.

Note that if we detect that we are running in a non TTY environment, we will use a special mode called 'non_tty', unless the user asked for 'silent'.

temperature class-attribute instance-attribute

The temperature for the model.

computer_use_mode class-attribute instance-attribute

computer_use_mode: OptionalArg[bool] = CTUS_NOT_GIVEN

Whether to use the computer use mode.

If set to True, the model will be configured to use the computer use mode.

use_mock class-attribute instance-attribute

Whether to use a mock client or not.

This is useful for testing purposes.

only_pass_new_messages class-attribute instance-attribute

only_pass_new_messages: OptionalArg[bool] = CTUS_NOT_GIVEN

Whether to only pass new messages to the model.

If set to True, the model will only pass new messages to the model, and not the entire history. This is useful for "stateful" APIs, where the history is not needed.

previous_messages_id class-attribute instance-attribute

previous_messages_id: OptionalArg[str] = CTUS_NOT_GIVEN

The ID of the previous messages.

This is useful for "stateful" APIs, where the history is not needed. This should only be used if only_pass_new_messages is True.

truncation class-attribute instance-attribute

truncation: OptionalArg[Literal["auto", "disabled"]] = (
    CTUS_NOT_GIVEN
)

The truncation to use.

to_kwargs

to_kwargs(
    specification: None = None,
    not_given_sentinel: object = CTUS_NOT_GIVEN,
    argument_mapping: dict[str, str] | None = None,
) -> ModelConfigTD
to_kwargs(
    specification: type[TDSpec],
    not_given_sentinel: object = CTUS_NOT_GIVEN,
    argument_mapping: dict[str, str] | None = None,
) -> TDSpec
to_kwargs(
    specification: type[TDSpec] | None = None,
    not_given_sentinel: object = CTUS_NOT_GIVEN,
    argument_mapping: dict[str, str] | None = None,
) -> ModelConfigTD | TDSpec

Return the configuration as a dictionary.

You can provide a specification, which is a dictionary that matches the arguments expected by the provider. If a specification is provided, the method will return a dictionary that matches the specification (i.e. with only the keys that are expected by the provider).

You can also provide a not_given_sentinel, which is an object that is used to represent a missing argument. If we encounter this sentinel object, we will not include it in the returned dictionary.

Example

Using a specification
from conatus.models.open_ai import OpenAIModel, OpenAIModelCCSpec
from openai import NOT_GIVEN

model = OpenAIModel()

args_to_pass = model.config.to_kwargs(
    specification=OpenAIModelCCSpec,
    not_given_sentinel=NOT_GIVEN,
)

assert args_to_pass == {'max_tokens': 4096}

# And now you can do something like:
# response = self.client.chat.completions.create(
#         messages=messages,
#         **args_to_pass
#  )
Using an argument mapping
from conatus.models.open_ai import OpenAIModel, OpenAIModelResponseSpec
from openai import NOT_GIVEN

model = OpenAIModel()

args_to_pass = model.config.to_kwargs(
    specification=OpenAIModelResponseSpec,
    argument_mapping={"max_tokens": "max_output_tokens"},
    not_given_sentinel=NOT_GIVEN,
)

assert args_to_pass == {'max_output_tokens': 4096, 'truncation': 'auto'}
PARAMETER DESCRIPTION
specification

The specification to use. This should be a TypedDict; if it's not, the method will throw a TypeError. If no specification is provided, the method will return all the keys of the configuration.

TYPE: type[TDSpec] | None DEFAULT: None

not_given_sentinel

The sentinel object to use.

TYPE: object DEFAULT: CTUS_NOT_GIVEN

argument_mapping

A dictionary that maps the keys of the configuration to the keys of the provider. The mapping is of the form {original_key: new_key, ...}.

TYPE: dict[str, str] | None DEFAULT: None

RETURNS DESCRIPTION
ModelConfigTD | TDSpec

The configuration as a dictionary.

RAISES DESCRIPTION
TypeError

If the specification is not a TypedDict .

Source code in conatus/models/config.py
def to_kwargs(
    self,
    specification: type[TDSpec] | None = None,
    not_given_sentinel: object = CTUS_NOT_GIVEN,
    argument_mapping: dict[str, str] | None = None,
) -> ModelConfigTD | TDSpec:
    """Return the configuration as a dictionary.

    You can provide a specification, which is a dictionary that
    matches the arguments expected by the provider. If a specification
    is provided, the method will return a dictionary that matches the
    specification (i.e. with only the keys that are expected by the
    provider).

    You can also provide a `not_given_sentinel`, which is an object
    that is used to represent a missing argument. If we encounter this
    sentinel object, we will not include it in the returned dictionary.

    # Example

    ## Using a specification

    ```python
    from conatus.models.open_ai import OpenAIModel, OpenAIModelCCSpec
    from openai import NOT_GIVEN

    model = OpenAIModel()

    args_to_pass = model.config.to_kwargs(
        specification=OpenAIModelCCSpec,
        not_given_sentinel=NOT_GIVEN,
    )

    assert args_to_pass == {'max_tokens': 4096}

    # And now you can do something like:
    # response = self.client.chat.completions.create(
    #         messages=messages,
    #         **args_to_pass
    #  )
    ```

    ## Using an argument mapping

    ```python
    from conatus.models.open_ai import OpenAIModel, OpenAIModelResponseSpec
    from openai import NOT_GIVEN

    model = OpenAIModel()

    args_to_pass = model.config.to_kwargs(
        specification=OpenAIModelResponseSpec,
        argument_mapping={"max_tokens": "max_output_tokens"},
        not_given_sentinel=NOT_GIVEN,
    )

    assert args_to_pass == {'max_output_tokens': 4096, 'truncation': 'auto'}
    ```

    Args:
        specification: The specification to use. This should be a
            [`TypedDict`][typing.TypedDict]; if it's not, the method will
            throw a `TypeError`. If no specification is provided, the
            method will return all the keys of the configuration.
        not_given_sentinel: The sentinel object to use.
        argument_mapping: A dictionary that maps the keys of the
            configuration to the keys of the provider. The mapping is
            of the form `{original_key: new_key, ...}`.

    Returns:
        The configuration as a dictionary.

    Raises:
        TypeError: If the specification is not a [`TypedDict`
            ][typing.TypedDict].
    """
    keys: list[str]
    not_given_sentinels = {
        CTUS_NOT_GIVEN,
        self.not_given_sentinel,
        not_given_sentinel,
    }
    # If a specification exists, we assume it's a TypedDict,
    # and extract the optional and required keys.
    if specification is not None:
        optional_keys = getattr(specification, "__optional_keys__", None)
        required_keys = getattr(specification, "__required_keys__", None)
        if optional_keys is None or required_keys is None:
            msg = "The specification must be a TypedDict."
            raise TypeError(msg)
        keys = [
            *(cast("frozenset[str]", required_keys)),
            *(cast("frozenset[str]", optional_keys)),
        ]
    # Otherwise, we just return all the keys.
    else:
        keys = list[str](self.__dict__.keys())

    config_as_dict = dict(self.__dict__.items())
    if argument_mapping is not None:
        for k, v in argument_mapping.items():
            if k in config_as_dict:
                config_as_dict[v] = config_as_dict[k]
                del config_as_dict[k]

    return cast(
        "ModelConfigTD | TDSpec",
        {
            k: v
            for k, v in config_as_dict.items()  # pyright: ignore[reportAny]
            if v not in not_given_sentinels and k in keys
        },
    )

from_dict classmethod

from_dict(config: ModelConfigTD) -> Self

Create a new instance from a dictionary.

PARAMETER DESCRIPTION
config

The configuration as a dictionary.

TYPE: ModelConfigTD

RETURNS DESCRIPTION
Self

The new instance.

Source code in conatus/models/config.py
@classmethod
def from_dict(cls, config: ModelConfigTD) -> Self:
    """Create a new instance from a dictionary.

    Args:
        config: The configuration as a dictionary.

    Returns:
        The new instance.
    """
    return cls(**config)

from_dict_instance_or_none classmethod

from_dict_instance_or_none(
    config: Self | ModelConfigTD | None,
) -> Self

Create a new instance from a dictionary or an instance.

PARAMETER DESCRIPTION
config

The configuration as a dictionary or an instance.

TYPE: Self | ModelConfigTD | None

RETURNS DESCRIPTION
Self

The new instance.

Source code in conatus/models/config.py
@classmethod
def from_dict_instance_or_none(
    cls, config: Self | ModelConfigTD | None
) -> Self:
    """Create a new instance from a dictionary or an instance.

    Args:
        config: The configuration as a dictionary or an instance.

    Returns:
        The new instance.
    """
    if config is None:
        return cls()
    if isinstance(config, Mapping):
        return cls.from_dict(config)
    return config

apply_config

apply_config(
    new_config: Self | ModelConfigTD | None,
    *,
    inplace: Literal[True]
) -> None
apply_config(
    new_config: Self | ModelConfigTD | None,
    *,
    inplace: Literal[False] = False
) -> Self
apply_config(
    new_config: Self | ModelConfigTD | None,
    *,
    inplace: bool = False
) -> Self | None

Copy the configuration and apply new values to it.

This ensures that you can create a hierarchy of configurations.

PARAMETER DESCRIPTION
new_config

The new configuration.

TYPE: Self | ModelConfigTD | None

inplace

Whether to update the instance in place, or return a new copy

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Self | None

None if the modification happens in place; otherwise, return a new instance with the modified configuration

Source code in conatus/models/config.py
def apply_config(
    self, new_config: Self | ModelConfigTD | None, *, inplace: bool = False
) -> Self | None:
    """Copy the configuration and apply new values to it.

    This ensures that you can create a hierarchy of configurations.

    Args:
        new_config: The new configuration.
        inplace: Whether to update the instance in place, or return a new
            copy

    Returns:
        None if the modification happens in place; otherwise, return a new
            instance with the modified configuration
    """
    if new_config is None:
        return self
    if isinstance(new_config, Mapping):
        new_config_as_dict = new_config
    else:
        new_config_as_dict = new_config.to_kwargs()
    if inplace:
        self.__dict__.update(new_config_as_dict)
        return None
    new_config_as_dict = self.to_kwargs() | new_config_as_dict
    return type(self).from_dict(new_config_as_dict)

conatus.models.base.AIModelCallArgs dataclass

AIModelCallArgs(
    model_config: ModelConfig,
    system_message: MessageType | None,
    messages: Iterable[MessageType],
    tools: Iterable[ToolType] | None,
    output_schema: OutputJSONSchemaType | None,
    output_schema_was_converted_to_item_object: bool = False,
)

The arguments for the call to the AI model.

This class exists to be subclassed by the subclasses of BaseAIModel .

model_config instance-attribute

model_config: ModelConfig

The configuration for the AI model.

system_message instance-attribute

system_message: MessageType | None

The system message for the AI model.

messages instance-attribute

messages: Iterable[MessageType]

The messages for the AI model.

tools instance-attribute

tools: Iterable[ToolType] | None

The tools for the AI model.

output_schema instance-attribute

output_schema: OutputJSONSchemaType | None

The output schema for the AI model.

output_schema_was_converted_to_item_object class-attribute instance-attribute

output_schema_was_converted_to_item_object: bool = False

Whether the output schema was converted to an object type.

This needs to be set to indicate to AIResponse whether it needs to convert the output schema back to the original type.

Retrieve an AI model from a name

We offer a few utilities to make it easier to create an AI model instance from a model name (as a string):

We do this by storing a few common model names in the conatus.models.names module.

conatus.models.names.ModelName module-attribute

ModelName = Literal[
    "openai:gpt-4o",
    "openai:gpt-4.5-preview",
    "openai:gpt-4o-mini",
    "openai:gpt-4-turbo",
    "openai:gpt-4",
    "openai:gpt-4.1",
    "openai:gpt-4.1-mini",
    "openai:gpt-4.1-nano",
    "openai:o1-mini",
    "openai:o1",
    "openai:o1-pro",
    "openai:o3-mini",
    "openai:o3",
    "openai:o4-mini",
    "openai:computer-use-preview",
    "anthropic:claude-3-7-sonnet-latest",
    "anthropic:claude-3-7-sonnet-20250219",
    "anthropic:claude-3-5-haiku-latest",
    "anthropic:claude-3-5-haiku-20241022",
    "anthropic:claude-3-5-sonnet-latest",
    "anthropic:claude-3-5-sonnet-20241022",
    "anthropic:claude-3-5-sonnet-20240620",
    "google:gemini-2.5-pro-preview-03-25",
    "google:gemini-2.5-flash-preview-04-17",
    "google:gemini-2.0-flash-lite",
    "google:gemini-2.0-flash",
]

Known AI model names.

conatus.models.names.ModelType module-attribute

ModelType = Literal[
    "execution", "chat", "computer_use", "reasoning"
]

The types of models that can be used.

This is used to determine which model to use for a given task.

conatus.models.names.ProviderName module-attribute

ProviderName = Literal[
    "openai", "anthropic", "google", "other"
]

Known AI provider names.

This is used to determine which AI provider to use, and should be used by other classes to customize their behavior based on the provider.

conatus.models.names.unclean_model_names_to_model_name module-attribute

unclean_model_names_to_model_name: dict[str, ModelName] = {
    "gpt-4o": "openai:gpt-4o",
    "gpt-4o-mini": "openai:gpt-4o-mini",
    "gpt-4.5-preview": "openai:gpt-4.5-preview",
    "gpt-4-turbo": "openai:gpt-4-turbo",
    "gpt-4": "openai:gpt-4",
    "gpt4": "openai:gpt-4",
    "gpt4o": "openai:gpt-4o",
    "o1-mini": "openai:o1-mini",
    "o1": "openai:o1",
    "gpt-4.1": "openai:gpt-4.1",
    "gpt-4.1-mini": "openai:gpt-4.1-mini",
    "gpt-4.1-nano": "openai:gpt-4.1-nano",
    "o3-mini": "openai:o3-mini",
    "o3": "openai:o3",
    "o4-mini": "openai:o4-mini",
    "o1-pro": "openai:o1-pro",
    "computer-use-preview": "openai:computer-use-preview",
    "claude-3-7-sonnet": "anthropic:claude-3-7-sonnet-latest",
    "claude-3-7-sonnet-20250219": "anthropic:claude-3-7-sonnet-20250219",
    "claude-3-7-sonnet-latest": "anthropic:claude-3-7-sonnet-latest",
    "claude-3-5-sonnet-20241022": "anthropic:claude-3-5-sonnet-20241022",
    "claude-3-5-sonnet-20240620": "anthropic:claude-3-5-sonnet-20240620",
    "claude-3-5-sonnet": "anthropic:claude-3-5-sonnet-latest",
    "claude-3-5-sonnet-latest": "anthropic:claude-3-5-sonnet-latest",
    "claude-3-5-haiku-20241022": "anthropic:claude-3-5-haiku-20241022",
    "claude-3-5-haiku-latest": "anthropic:claude-3-5-haiku-latest",
    "claude-3-5-haiku": "anthropic:claude-3-5-haiku-latest",
    "claude-3-5": "anthropic:claude-3-5-sonnet-latest",
    "claude-3-7": "anthropic:claude-3-7-sonnet-latest",
    "gemini-2.5-pro": "google:gemini-2.5-pro-preview-03-25",
    "gemini-2.5-flash": "google:gemini-2.5-flash-preview-04-17",
    "gemini-2.0-flash-lite": "google:gemini-2.0-flash-lite",
    "gemini-2.0-flash": "google:gemini-2.0-flash",
}

Retrieve an AI model from a name or provider

conatus.models.retrieval.get_model_from_name

get_model_from_name(
    model_name: str,
    model_config: ModelConfig | ModelConfigTD | None = None,
) -> BaseAIModel

Get a model from a name.

Don't call this function often

This function will make some imports after being called. There's no real way around this, since we don't want to call every optional AI provider library just to get a model name.

Use this function sparingly.

PARAMETER DESCRIPTION
model_name

The name of the model.

TYPE: str

model_config

The configuration for the model.

TYPE: ModelConfig | ModelConfigTD | None DEFAULT: None

RETURNS DESCRIPTION
BaseAIModel

The model, with the proper provider class.

RAISES DESCRIPTION
ValueError

If the model name is unknown.

Source code in conatus/models/retrieval.py
def get_model_from_name(
    model_name: str,
    model_config: ModelConfig | ModelConfigTD | None = None,
) -> BaseAIModel:
    """Get a model from a name.

    !!! warning "Don't call this function often"

        This function will make some imports after being called. There's no
        real way around this, since we don't want to call every optional
        AI provider library just to get a model name.

        Use this function sparingly.

    Args:
        model_name: The name of the model.
        model_config: The configuration for the model.

    Returns:
        The model, with the proper provider class.

    Raises:
        ValueError: If the model name is unknown.
    """
    if model_name in unclean_model_names_to_model_name:
        model_name = unclean_model_names_to_model_name[model_name]
    model_name = cast("ModelName", model_name)
    if model_name.startswith("openai:"):
        return get_model_from_provider(
            provider_name="openai",
            model_name=model_name,
            model_config=model_config,
        )
    if model_name.startswith("anthropic:"):
        return get_model_from_provider(
            provider_name="anthropic",
            model_name=model_name,
            model_config=model_config,
        )
    if model_name.startswith("google:"):
        return get_model_from_provider(
            provider_name="google",
            model_name=model_name,
            model_config=model_config,
        )
    msg = f"Unknown model name: {model_name}"
    raise ValueError(msg)

conatus.models.retrieval.get_model_from_provider

get_model_from_provider(
    provider_name: ProviderName,
    model_name: ModelName | None = None,
    model_config: ModelConfig | ModelConfigTD | None = None,
    *,
    model_type: ModelType | None = None
) -> BaseAIModel

Get a model from a provider and a name.

Don't call this function often

This function will make some imports after being called. There's no real way around this, since we don't want to call every optional AI provider library just to get a model name.

Use this function sparingly.

PARAMETER DESCRIPTION
provider_name

The name of the provider.

TYPE: ProviderName

model_name

The name of the model.

TYPE: ModelName | None DEFAULT: None

model_config

The configuration for the model.

TYPE: ModelConfig | ModelConfigTD | None DEFAULT: None

model_type

The type of model to use. Useful if you need to differentiate between different types of models (e.g. chat, computer use, reasoning).

TYPE: ModelType | None DEFAULT: None

RETURNS DESCRIPTION
BaseAIModel

The model, with the proper provider class

RAISES DESCRIPTION
ValueError

If the provider name is unknown.

Source code in conatus/models/retrieval.py
def get_model_from_provider(
    provider_name: ProviderName,
    model_name: ModelName | None = None,
    model_config: ModelConfig | ModelConfigTD | None = None,
    *,
    model_type: ModelType | None = None,
) -> BaseAIModel:
    """Get a model from a provider and a name.

    !!! warning "Don't call this function often"

        This function will make some imports after being called. There's no
        real way around this, since we don't want to call every optional
        AI provider library just to get a model name.

        Use this function sparingly.

    Args:
        provider_name: The name of the provider.
        model_name: The name of the model.
        model_config: The configuration for the model.
        model_type: The type of model to use. Useful if you need to
            differentiate between different types of models (e.g. chat,
            computer use, reasoning).

    Returns:
        The model, with the proper provider class

    Raises:
        ValueError: If the provider name is unknown.
    """
    if provider_name == "openai":
        from conatus.models.open_ai import OpenAIModel

        model_name = model_name or OpenAIModel.default_model_name(model_type)

        return OpenAIModel(
            model_name=model_name.split(":")[1]
            if (model_name and ":" in model_name)
            else model_name,
            model_config=model_config,
        )
    if provider_name == "anthropic":
        from conatus.models.anthropic import AnthropicAIModel

        model_name = model_name or AnthropicAIModel.default_model_name(
            model_type
        )

        return AnthropicAIModel(
            model_name=model_name.split(":")[1]
            if (model_name and ":" in model_name)
            else model_name,
            model_config=model_config,
        )
    if provider_name == "google":
        from conatus.models.google import GoogleAIModel

        model_name = model_name or GoogleAIModel.default_model_name(model_type)

        return GoogleAIModel(
            model_name=model_name.split(":")[1]
            if (model_name and ":" in model_name)
            else model_name,
            model_config=model_config,
        )
    msg = f"Unknown provider name: {provider_name}"
    raise ValueError(msg)

Retrieve model prices

conatus.models.prices.calculate_cost

calculate_cost(usage: CompletionUsage) -> float

Compute the cost of a CompletionUsage.

Drops the provider prefix, normalizes unclean names via unclean_model_names_to_model_name, and delegates to the appropriate PricingStrategy.

PARAMETER DESCRIPTION
usage

The CompletionUsage whose cost must be evaluated.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Price in USD, or -1 when the model is unknown or usage was never reported.

Source code in conatus/models/prices.py
def calculate_cost(usage: CompletionUsage) -> float:
    """Compute the cost of a [`CompletionUsage`][conatus.models.inputs_outputs.usage.CompletionUsage].

    Drops the provider prefix, normalizes unclean names via
    [`unclean_model_names_to_model_name`][conatus.models.names.unclean_model_names_to_model_name],
    and delegates to the appropriate
    [`PricingStrategy`][conatus.models.prices.PricingStrategy].

    Args:
        usage: The [`CompletionUsage`
            ][conatus.models.inputs_outputs.usage.CompletionUsage]
            whose cost must be evaluated.

    Returns:
        Price in USD, or -1 when the model is unknown or usage was never
            reported.
    """  # noqa: E501
    if usage.model_name is None:
        return -1.0

    # Clean "unclean" names if possible
    if usage.model_name in unclean_model_names_to_model_name:
        model_key: str = unclean_model_names_to_model_name[usage.model_name]
    else:
        model_key = usage.model_name

    strategy = REGISTRY.strategy_for(model_key)
    if strategy is None:
        return -1.0
    cost = strategy.cost(usage)
    if cost < 0:
        return -1.0
    if cost == 0 and usage.usage_was_never_given:
        return -1.0
    return cost

conatus.models.prices.PricingStrategy

Bases: Protocol

Defines the interface for pricing strategies.

cost abstractmethod

cost(usage: CompletionUsage) -> float

Compute the price of a single CompletionUsage.

PARAMETER DESCRIPTION
usage

The usage record to evaluate.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Price in USD. Returns -1 when the model is unknown or usage.usage_was_never_given is True.

Source code in conatus/models/prices.py
@abstractmethod
def cost(self, usage: CompletionUsage) -> float:  # pyright: ignore[reportInvalidAbstractMethod]
    """Compute the price of a single CompletionUsage.

    Args:
        usage: The usage record to evaluate.

    Returns:
        Price in USD. Returns -1 when the model is unknown or
            `usage.usage_was_never_given` is True.
    """

conatus.models.prices.FlatPrice dataclass

FlatPrice(prompt_per_m: float, completion_per_m: float)

Bases: PricingStrategy

Flat price (same $/token regardless of volume or cache status).

prompt_per_m instance-attribute

prompt_per_m: float

Price for every million prompt tokens.

completion_per_m instance-attribute

completion_per_m: float

Price for every million completion tokens.

cost

cost(usage: CompletionUsage) -> float

Compute cost using a flat rate per token.

PARAMETER DESCRIPTION
usage

The usage record to evaluate.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Total cost in USD, or -1 if usage was never given.

Source code in conatus/models/prices.py
@override
def cost(self, usage: CompletionUsage) -> float:
    """Compute cost using a flat rate per token.

    Args:
        usage: The usage record to evaluate.

    Returns:
        Total cost in USD, or -1 if usage was never given.
    """
    return (
        self.prompt_per_m * usage.prompt_tokens / 1_000_000
        + self.completion_per_m * usage.completion_tokens / 1_000_000
    )

conatus.models.prices.CachedAwarePrice dataclass

CachedAwarePrice(
    prompt_per_m: float,
    completion_per_m: float,
    cached_per_m: float = 0.0,
)

Bases: PricingStrategy

Flat price plus a single cache price for reads and writes.

prompt_per_m instance-attribute

prompt_per_m: float

Price for every million prompt tokens.

completion_per_m instance-attribute

completion_per_m: float

Price for every million completion tokens.

cached_per_m class-attribute instance-attribute

cached_per_m: float = 0.0

Price for every million cached tokens.

cost

cost(usage: CompletionUsage) -> float

Compute cost including cache usage.

PARAMETER DESCRIPTION
usage

The usage record to evaluate.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Total cost in USD, or -1 if usage was never given.

Source code in conatus/models/prices.py
@override
def cost(self, usage: CompletionUsage) -> float:
    """Compute cost including cache usage.

    Args:
        usage: The usage record to evaluate.

    Returns:
        Total cost in USD, or -1 if usage was never given.
    """
    cached_tokens = (usage.cached_used_tokens or 0) + (
        usage.cached_created_tokens or 0
    )
    return (
        self.prompt_per_m * usage.prompt_tokens / 1_000_000
        + self.completion_per_m * usage.completion_tokens / 1_000_000
        + self.cached_per_m * cached_tokens / 1_000_000
    )

conatus.models.prices.MultiCachedPrice dataclass

MultiCachedPrice(
    prompt_per_m: float,
    completion_per_m: float,
    cache_read_per_m: float,
    cache_write_per_m: float,
)

Bases: PricingStrategy

Flat price plus separate prices for cache reads and writes.

prompt_per_m instance-attribute

prompt_per_m: float

Price for every million prompt tokens.

completion_per_m instance-attribute

completion_per_m: float

Price for every million completion tokens.

cache_read_per_m instance-attribute

cache_read_per_m: float

Price for every million read cached tokens.

cache_write_per_m instance-attribute

cache_write_per_m: float

Price for every million write cached tokens.

cost

cost(usage: CompletionUsage) -> float

Compute cost including separate cache read/write pricing.

PARAMETER DESCRIPTION
usage

The usage record to evaluate.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Total cost in USD, or -1 if usage was never given.

Source code in conatus/models/prices.py
@override
def cost(self, usage: CompletionUsage) -> float:
    """Compute cost including separate cache read/write pricing.

    Args:
        usage: The usage record to evaluate.

    Returns:
        Total cost in USD, or -1 if usage was never given.
    """
    read = usage.cached_used_tokens or 0
    write = usage.cached_created_tokens or 0
    return (
        self.prompt_per_m * usage.prompt_tokens / 1_000_000
        + self.completion_per_m * usage.completion_tokens / 1_000_000
        + self.cache_read_per_m * read / 1_000_000
        + self.cache_write_per_m * write / 1_000_000
    )

conatus.models.prices.TieredPrice dataclass

TieredPrice(tiers: tuple[Tier, ...])

Bases: PricingStrategy

Pricing strategy where rates change after token thresholds.

tiers instance-attribute

tiers: tuple[Tier, ...]

Ordered tuple of Tier from lowest threshold to highest.

The last tier should usually have up_to_tokens=None.

cost

cost(usage: CompletionUsage) -> float

Compute cost across multiple tiers.

PARAMETER DESCRIPTION
usage

The usage record to evaluate.

TYPE: CompletionUsage

RETURNS DESCRIPTION
float

Total cost in USD, or -1 if usage was never given.

Source code in conatus/models/prices.py
@override
def cost(self, usage: CompletionUsage) -> float:
    """Compute cost across multiple tiers.

    Args:
        usage: The usage record to evaluate.

    Returns:
        Total cost in USD, or -1 if usage was never given.
    """
    prompt_tokens_is_zero = usage.prompt_tokens == 0
    completion_tokens_is_zero = usage.completion_tokens == 0
    if usage.usage_was_never_given and (
        prompt_tokens_is_zero and completion_tokens_is_zero
    ):
        return -1.0

    prompt_left: float = usage.prompt_tokens
    completion_left: float = usage.completion_tokens
    total_cost = 0.0

    for tier in self.tiers:
        if prompt_left == 0 and completion_left == 0:
            break
        capacity = (
            tier.up_to_tokens
            if tier.up_to_tokens is not None
            else float("inf")
        )
        use_prompt = min(prompt_left, capacity)
        use_completion = min(completion_left, capacity)
        total_cost += (
            tier.prompt_price_per_m * use_prompt / 1_000_000
            + tier.completion_price_per_m * use_completion / 1_000_000
        )
        prompt_left -= use_prompt
        completion_left -= use_completion

    return total_cost

conatus.models.prices.Tier dataclass

Tier(
    up_to_tokens: int | None,
    prompt_price_per_m: float,
    completion_price_per_m: float,
)

One tier of a TieredPrice.

up_to_tokens instance-attribute

up_to_tokens: int | None

Upper exclusive bound for which the tier is valid.

None means infinity.

prompt_price_per_m instance-attribute

prompt_price_per_m: float

Price for every million prompt tokens.

completion_price_per_m instance-attribute

completion_price_per_m: float

Price for every million completion tokens.

conatus.models.prices.REGISTRY module-attribute

REGISTRY = _PricingRegistry()