diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 3a98b0646..ebb0bea02 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -115,9 +115,11 @@ PricingTierAttributeCondition, PricingTierAttributeSource, PricingTierCondition, + PricingTierConditionInput, PricingTierInput, PricingTierOperator, PricingTierUsageCondition, + PricingTierUsageConditionInput, Score, ScoreConfig, ScoreConfigDataType, @@ -228,7 +230,7 @@ PatchMediaBody, ) from .metrics import MetricsV2Response - from .models import CreateModelRequest, PaginatedModels + from .models import CreateModelRequest, ModelTokenizerId, PaginatedModels from .observations import ObservationsV2Meta, ObservationsV2Response from .opentelemetry import ( OtelAttribute, @@ -501,6 +503,7 @@ "MetricsV2Response": ".metrics", "Model": ".commons", "ModelPrice": ".commons", + "ModelTokenizerId": ".models", "ModelUsageUnit": ".commons", "NotFoundError": ".commons", "NumericScore": ".commons", @@ -548,9 +551,11 @@ "PricingTierAttributeCondition": ".commons", "PricingTierAttributeSource": ".commons", "PricingTierCondition": ".commons", + "PricingTierConditionInput": ".commons", "PricingTierInput": ".commons", "PricingTierOperator": ".commons", "PricingTierUsageCondition": ".commons", + "PricingTierUsageConditionInput": ".commons", "Project": ".projects", "ProjectDeletionResponse": ".projects", "Projects": ".projects", @@ -845,6 +850,7 @@ def __dir__(): "MetricsV2Response", "Model", "ModelPrice", + "ModelTokenizerId", "ModelUsageUnit", "NotFoundError", "NumericScore", @@ -892,9 +898,11 @@ def __dir__(): "PricingTierAttributeCondition", "PricingTierAttributeSource", "PricingTierCondition", + "PricingTierConditionInput", "PricingTierInput", "PricingTierOperator", "PricingTierUsageCondition", + "PricingTierUsageConditionInput", "Project", "ProjectDeletionResponse", "Projects", diff --git a/langfuse/api/commons/__init__.py b/langfuse/api/commons/__init__.py index 89204bfad..fffe7234d 100644 --- a/langfuse/api/commons/__init__.py +++ b/langfuse/api/commons/__init__.py @@ -43,9 +43,11 @@ PricingTierAttributeCondition, PricingTierAttributeSource, PricingTierCondition, + PricingTierConditionInput, PricingTierInput, PricingTierOperator, PricingTierUsageCondition, + PricingTierUsageConditionInput, Score, ScoreConfig, ScoreConfigDataType, @@ -118,9 +120,11 @@ "PricingTierAttributeCondition": ".types", "PricingTierAttributeSource": ".types", "PricingTierCondition": ".types", + "PricingTierConditionInput": ".types", "PricingTierInput": ".types", "PricingTierOperator": ".types", "PricingTierUsageCondition": ".types", + "PricingTierUsageConditionInput": ".types", "Score": ".types", "ScoreConfig": ".types", "ScoreConfigDataType": ".types", @@ -216,9 +220,11 @@ def __dir__(): "PricingTierAttributeCondition", "PricingTierAttributeSource", "PricingTierCondition", + "PricingTierConditionInput", "PricingTierInput", "PricingTierOperator", "PricingTierUsageCondition", + "PricingTierUsageConditionInput", "Score", "ScoreConfig", "ScoreConfigDataType", diff --git a/langfuse/api/commons/types/__init__.py b/langfuse/api/commons/types/__init__.py index 1bf73059d..6d3303261 100644 --- a/langfuse/api/commons/types/__init__.py +++ b/langfuse/api/commons/types/__init__.py @@ -42,9 +42,11 @@ from .pricing_tier_attribute_condition import PricingTierAttributeCondition from .pricing_tier_attribute_source import PricingTierAttributeSource from .pricing_tier_condition import PricingTierCondition + from .pricing_tier_condition_input import PricingTierConditionInput from .pricing_tier_input import PricingTierInput from .pricing_tier_operator import PricingTierOperator from .pricing_tier_usage_condition import PricingTierUsageCondition + from .pricing_tier_usage_condition_input import PricingTierUsageConditionInput from .score import ( Score, Score_Boolean, @@ -109,9 +111,11 @@ "PricingTierAttributeCondition": ".pricing_tier_attribute_condition", "PricingTierAttributeSource": ".pricing_tier_attribute_source", "PricingTierCondition": ".pricing_tier_condition", + "PricingTierConditionInput": ".pricing_tier_condition_input", "PricingTierInput": ".pricing_tier_input", "PricingTierOperator": ".pricing_tier_operator", "PricingTierUsageCondition": ".pricing_tier_usage_condition", + "PricingTierUsageConditionInput": ".pricing_tier_usage_condition_input", "Score": ".score", "ScoreConfig": ".score_config", "ScoreConfigDataType": ".score_config_data_type", @@ -202,9 +206,11 @@ def __dir__(): "PricingTierAttributeCondition", "PricingTierAttributeSource", "PricingTierCondition", + "PricingTierConditionInput", "PricingTierInput", "PricingTierOperator", "PricingTierUsageCondition", + "PricingTierUsageConditionInput", "Score", "ScoreConfig", "ScoreConfigDataType", diff --git a/langfuse/api/commons/types/pricing_tier_condition_input.py b/langfuse/api/commons/types/pricing_tier_condition_input.py new file mode 100644 index 000000000..d7250be3a --- /dev/null +++ b/langfuse/api/commons/types/pricing_tier_condition_input.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .pricing_tier_attribute_condition import PricingTierAttributeCondition +from .pricing_tier_usage_condition_input import PricingTierUsageConditionInput + +PricingTierConditionInput = typing.Union[ + PricingTierUsageConditionInput, PricingTierAttributeCondition +] diff --git a/langfuse/api/commons/types/pricing_tier_input.py b/langfuse/api/commons/types/pricing_tier_input.py index 40d253ece..6963ed367 100644 --- a/langfuse/api/commons/types/pricing_tier_input.py +++ b/langfuse/api/commons/types/pricing_tier_input.py @@ -6,7 +6,7 @@ import typing_extensions from ...core.pydantic_utilities import UniversalBaseModel from ...core.serialization import FieldMetadata -from .pricing_tier_condition import PricingTierCondition +from .pricing_tier_condition_input import PricingTierConditionInput class PricingTierInput(UniversalBaseModel): @@ -29,9 +29,9 @@ class PricingTierInput(UniversalBaseModel): Must be unique within the model. Common patterns: "Standard", "High Volume Tier", "Extended Context" """ - is_default: typing_extensions.Annotated[bool, FieldMetadata(alias="isDefault")] = ( - pydantic.Field() - ) + is_default: typing_extensions.Annotated[ + typing.Optional[bool], FieldMetadata(alias="isDefault") + ] = pydantic.Field(default=None) """ Whether this is the default tier. Exactly one tier per model must be marked as default. @@ -40,7 +40,7 @@ class PricingTierInput(UniversalBaseModel): - Must have priority=0 - Must have empty conditions array (conditions=[]) - The default tier acts as a fallback when no conditional tiers match. + The default tier acts as a fallback when no conditional tiers match. Defaults to false when omitted. """ priority: int = pydantic.Field() @@ -51,7 +51,7 @@ class PricingTierInput(UniversalBaseModel): Conditional tiers should use priority 1, 2, 3, etc. based on their specificity. """ - conditions: typing.List[PricingTierCondition] = pydantic.Field() + conditions: typing.List[PricingTierConditionInput] = pydantic.Field() """ Array of conditions that must ALL be met for this tier to match (AND logic). diff --git a/langfuse/api/commons/types/pricing_tier_usage_condition_input.py b/langfuse/api/commons/types/pricing_tier_usage_condition_input.py new file mode 100644 index 000000000..07ea86569 --- /dev/null +++ b/langfuse/api/commons/types/pricing_tier_usage_condition_input.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .pricing_tier_operator import PricingTierOperator + + +class PricingTierUsageConditionInput(UniversalBaseModel): + """ + Input condition that sums usage details whose keys match a regex. + """ + + usage_detail_pattern: typing_extensions.Annotated[ + str, FieldMetadata(alias="usageDetailPattern") + ] = pydantic.Field() + """ + Regex pattern matched against usage detail keys. + """ + + operator: PricingTierOperator + value: float = pydantic.Field() + """ + Numeric threshold for the summed matching usage values. + """ + + case_sensitive: typing_extensions.Annotated[ + typing.Optional[bool], FieldMetadata(alias="caseSensitive") + ] = pydantic.Field(default=None) + """ + Whether the usage-detail regex is case-sensitive. Defaults to false. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/models/__init__.py b/langfuse/api/models/__init__.py index 7ebdb7762..a027e8f49 100644 --- a/langfuse/api/models/__init__.py +++ b/langfuse/api/models/__init__.py @@ -6,9 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import CreateModelRequest, PaginatedModels + from .types import CreateModelRequest, ModelTokenizerId, PaginatedModels _dynamic_imports: typing.Dict[str, str] = { "CreateModelRequest": ".types", + "ModelTokenizerId": ".types", "PaginatedModels": ".types", } @@ -40,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["CreateModelRequest", "PaginatedModels"] +__all__ = ["CreateModelRequest", "ModelTokenizerId", "PaginatedModels"] diff --git a/langfuse/api/models/client.py b/langfuse/api/models/client.py index 9f817b8f6..05b245e5b 100644 --- a/langfuse/api/models/client.py +++ b/langfuse/api/models/client.py @@ -9,6 +9,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawModelsClient, RawModelsClient +from .types.model_tokenizer_id import ModelTokenizerId from .types.paginated_models import PaginatedModels # this is used as the default value for optional parameters @@ -35,13 +36,13 @@ def create( *, model_name: str, match_pattern: str, + unit: ModelUsageUnit, start_date: typing.Optional[dt.datetime] = OMIT, - unit: typing.Optional[ModelUsageUnit] = OMIT, input_price: typing.Optional[float] = OMIT, output_price: typing.Optional[float] = OMIT, total_price: typing.Optional[float] = OMIT, pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, - tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, tokenizer_config: typing.Optional[typing.Any] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Model: @@ -56,12 +57,12 @@ def create( match_pattern : str Regex pattern which matches this model definition to generation.model. Useful in case of fine-tuned models. If you want to exact match, use `(?i)^modelname$` + unit : ModelUsageUnit + Unit used by this model. + start_date : typing.Optional[dt.datetime] Apply only to generations which are newer than this ISO date. - unit : typing.Optional[ModelUsageUnit] - Unit used by this model. - input_price : typing.Optional[float] Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. Creates a default tier if pricingTiers not provided. @@ -91,7 +92,7 @@ def create( If omitted, you must provide flat prices instead (inputPrice/outputPrice/totalPrice), which will automatically create a single default tier named "Standard". - tokenizer_id : typing.Optional[str] + tokenizer_id : typing.Optional[ModelTokenizerId] Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. tokenizer_config : typing.Optional[typing.Any] @@ -107,6 +108,7 @@ def create( Examples -------- from langfuse import LangfuseAPI + from langfuse.commons import ModelUsageUnit client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -119,13 +121,127 @@ def create( client.models.create( model_name="modelName", match_pattern="matchPattern", + unit=ModelUsageUnit.CHARACTERS, ) """ _response = self._raw_client.create( model_name=model_name, match_pattern=match_pattern, + unit=unit, start_date=start_date, + input_price=input_price, + output_price=output_price, + total_price=total_price, + pricing_tiers=pricing_tiers, + tokenizer_id=tokenizer_id, + tokenizer_config=tokenizer_config, + request_options=request_options, + ) + return _response.data + + def upsert( + self, + id: str, + *, + model_name: str, + match_pattern: str, + unit: ModelUsageUnit, + start_date: typing.Optional[dt.datetime] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Model: + """ + Create or replace a project-owned model using its id. Built-in models cannot be modified. + + Parameters + ---------- + id : str + + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime Model: @@ -294,12 +410,12 @@ async def create( match_pattern : str Regex pattern which matches this model definition to generation.model. Useful in case of fine-tuned models. If you want to exact match, use `(?i)^modelname$` + unit : ModelUsageUnit + Unit used by this model. + start_date : typing.Optional[dt.datetime] Apply only to generations which are newer than this ISO date. - unit : typing.Optional[ModelUsageUnit] - Unit used by this model. - input_price : typing.Optional[float] Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. Creates a default tier if pricingTiers not provided. @@ -329,7 +445,7 @@ async def create( If omitted, you must provide flat prices instead (inputPrice/outputPrice/totalPrice), which will automatically create a single default tier named "Standard". - tokenizer_id : typing.Optional[str] + tokenizer_id : typing.Optional[ModelTokenizerId] Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. tokenizer_config : typing.Optional[typing.Any] @@ -347,6 +463,7 @@ async def create( import asyncio from langfuse import AsyncLangfuseAPI + from langfuse.commons import ModelUsageUnit client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -362,6 +479,7 @@ async def main() -> None: await client.models.create( model_name="modelName", match_pattern="matchPattern", + unit=ModelUsageUnit.CHARACTERS, ) @@ -370,8 +488,129 @@ async def main() -> None: _response = await self._raw_client.create( model_name=model_name, match_pattern=match_pattern, + unit=unit, start_date=start_date, + input_price=input_price, + output_price=output_price, + total_price=total_price, + pricing_tiers=pricing_tiers, + tokenizer_id=tokenizer_id, + tokenizer_config=tokenizer_config, + request_options=request_options, + ) + return _response.data + + async def upsert( + self, + id: str, + *, + model_name: str, + match_pattern: str, + unit: ModelUsageUnit, + start_date: typing.Optional[dt.datetime] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Model: + """ + Create or replace a project-owned model using its id. Built-in models cannot be modified. + + Parameters + ---------- + id : str + + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime None: + await client.models.upsert( + id="id", + model_name="modelName", + match_pattern="matchPattern", + unit=ModelUsageUnit.CHARACTERS, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upsert( + id, + model_name=model_name, + match_pattern=match_pattern, unit=unit, + start_date=start_date, input_price=input_price, output_price=output_price, total_price=total_price, diff --git a/langfuse/api/models/raw_client.py b/langfuse/api/models/raw_client.py index 0fdc72319..4d645dcab 100644 --- a/langfuse/api/models/raw_client.py +++ b/langfuse/api/models/raw_client.py @@ -19,6 +19,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata +from .types.model_tokenizer_id import ModelTokenizerId from .types.paginated_models import PaginatedModels # this is used as the default value for optional parameters @@ -34,13 +35,13 @@ def create( *, model_name: str, match_pattern: str, + unit: ModelUsageUnit, start_date: typing.Optional[dt.datetime] = OMIT, - unit: typing.Optional[ModelUsageUnit] = OMIT, input_price: typing.Optional[float] = OMIT, output_price: typing.Optional[float] = OMIT, total_price: typing.Optional[float] = OMIT, pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, - tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, tokenizer_config: typing.Optional[typing.Any] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Model]: @@ -55,12 +56,12 @@ def create( match_pattern : str Regex pattern which matches this model definition to generation.model. Useful in case of fine-tuned models. If you want to exact match, use `(?i)^modelname$` + unit : ModelUsageUnit + Unit used by this model. + start_date : typing.Optional[dt.datetime] Apply only to generations which are newer than this ISO date. - unit : typing.Optional[ModelUsageUnit] - Unit used by this model. - input_price : typing.Optional[float] Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. Creates a default tier if pricingTiers not provided. @@ -90,7 +91,7 @@ def create( If omitted, you must provide flat prices instead (inputPrice/outputPrice/totalPrice), which will automatically create a single default tier named "Standard". - tokenizer_id : typing.Optional[str] + tokenizer_id : typing.Optional[ModelTokenizerId] Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. tokenizer_config : typing.Optional[typing.Any] @@ -116,7 +117,184 @@ def create( "totalPrice": total_price, "pricingTiers": convert_and_respect_annotation_metadata( object_=pricing_tiers, - annotation=typing.Sequence[PricingTierInput], + annotation=typing.Optional[typing.Sequence[PricingTierInput]], + direction="write", + ), + "tokenizerId": tokenizer_id, + "tokenizerConfig": tokenizer_config, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Model, + parse_obj_as( + type_=Model, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def upsert( + self, + id: str, + *, + model_name: str, + match_pattern: str, + unit: ModelUsageUnit, + start_date: typing.Optional[dt.datetime] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Model]: + """ + Create or replace a project-owned model using its id. Built-in models cannot be modified. + + Parameters + ---------- + id : str + + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime AsyncHttpResponse[Model]: @@ -539,12 +717,12 @@ async def create( match_pattern : str Regex pattern which matches this model definition to generation.model. Useful in case of fine-tuned models. If you want to exact match, use `(?i)^modelname$` + unit : ModelUsageUnit + Unit used by this model. + start_date : typing.Optional[dt.datetime] Apply only to generations which are newer than this ISO date. - unit : typing.Optional[ModelUsageUnit] - Unit used by this model. - input_price : typing.Optional[float] Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. Creates a default tier if pricingTiers not provided. @@ -574,7 +752,7 @@ async def create( If omitted, you must provide flat prices instead (inputPrice/outputPrice/totalPrice), which will automatically create a single default tier named "Standard". - tokenizer_id : typing.Optional[str] + tokenizer_id : typing.Optional[ModelTokenizerId] Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. tokenizer_config : typing.Optional[typing.Any] @@ -600,7 +778,184 @@ async def create( "totalPrice": total_price, "pricingTiers": convert_and_respect_annotation_metadata( object_=pricing_tiers, - annotation=typing.Sequence[PricingTierInput], + annotation=typing.Optional[typing.Sequence[PricingTierInput]], + direction="write", + ), + "tokenizerId": tokenizer_id, + "tokenizerConfig": tokenizer_config, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Model, + parse_obj_as( + type_=Model, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def upsert( + self, + id: str, + *, + model_name: str, + match_pattern: str, + unit: ModelUsageUnit, + start_date: typing.Optional[dt.datetime] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[ModelTokenizerId] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Model]: + """ + Create or replace a project-owned model using its id. Built-in models cannot be modified. + + Parameters + ---------- + id : str + + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime T_Result: + if self is ModelTokenizerId.OPENAI: + return openai() + if self is ModelTokenizerId.CLAUDE: + return claude()