Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions langfuse/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
PaginatedSessions,
PatchMediaBody,
PlaceholderMessage,
PricingTier,
PricingTierCondition,
PricingTierInput,
PricingTierOperator,
Project,
ProjectDeletionResponse,
Projects,
Expand Down Expand Up @@ -403,6 +407,10 @@
"PaginatedSessions",
"PatchMediaBody",
"PlaceholderMessage",
"PricingTier",
"PricingTierCondition",
"PricingTierInput",
"PricingTierOperator",
"Project",
"ProjectDeletionResponse",
"Projects",
Expand Down
8 changes: 8 additions & 0 deletions langfuse/api/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
Observation,
ObservationLevel,
ObservationsView,
PricingTier,
PricingTierCondition,
PricingTierInput,
PricingTierOperator,
Score,
ScoreConfig,
ScoreDataType,
Expand Down Expand Up @@ -424,6 +428,10 @@
"PaginatedSessions",
"PatchMediaBody",
"PlaceholderMessage",
"PricingTier",
"PricingTierCondition",
"PricingTierInput",
"PricingTierOperator",
"Project",
"ProjectDeletionResponse",
"Projects",
Expand Down
8 changes: 8 additions & 0 deletions langfuse/api/resources/commons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
Observation,
ObservationLevel,
ObservationsView,
PricingTier,
PricingTierCondition,
PricingTierInput,
PricingTierOperator,
Score,
ScoreConfig,
ScoreDataType,
Expand Down Expand Up @@ -82,6 +86,10 @@
"Observation",
"ObservationLevel",
"ObservationsView",
"PricingTier",
"PricingTierCondition",
"PricingTierInput",
"PricingTierOperator",
"Score",
"ScoreConfig",
"ScoreDataType",
Expand Down
8 changes: 8 additions & 0 deletions langfuse/api/resources/commons/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
from .observation import Observation
from .observation_level import ObservationLevel
from .observations_view import ObservationsView
from .pricing_tier import PricingTier
from .pricing_tier_condition import PricingTierCondition
from .pricing_tier_input import PricingTierInput
from .pricing_tier_operator import PricingTierOperator
from .score import Score, Score_Boolean, Score_Categorical, Score_Numeric
from .score_config import ScoreConfig
from .score_data_type import ScoreDataType
Expand Down Expand Up @@ -63,6 +67,10 @@
"Observation",
"ObservationLevel",
"ObservationsView",
"PricingTier",
"PricingTierCondition",
"PricingTierInput",
"PricingTierOperator",
"Score",
"ScoreConfig",
"ScoreDataType",
Expand Down
32 changes: 31 additions & 1 deletion langfuse/api/resources/commons/types/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
from .model_price import ModelPrice
from .model_usage_unit import ModelUsageUnit
from .pricing_tier import PricingTier


class Model(pydantic_v1.BaseModel):
"""
Model definition used for transforming usage into USD cost and/or tokenization.

Models can have either simple flat pricing or tiered pricing:
- Flat pricing: Single price per usage type (legacy, but still supported)
- Tiered pricing: Multiple pricing tiers with conditional matching based on usage patterns

The pricing tiers approach is recommended for models with usage-based pricing variations.
When using tiered pricing, the flat price fields (inputPrice, outputPrice, prices) are populated
from the default tier for backward compatibility.
"""

id: str
Expand Down Expand Up @@ -73,9 +82,30 @@ class Model(pydantic_v1.BaseModel):
"""

is_langfuse_managed: bool = pydantic_v1.Field(alias="isLangfuseManaged")
created_at: dt.datetime = pydantic_v1.Field(alias="createdAt")
"""
Timestamp when the model was created
"""

prices: typing.Dict[str, ModelPrice] = pydantic_v1.Field()
"""
Price (USD) by usage type
Deprecated. Use 'pricingTiers' instead for models with usage-based pricing variations.

This field shows prices by usage type from the default pricing tier. Maintained for backward compatibility.
If the model uses tiered pricing, this field will be populated from the default tier's prices.
"""

pricing_tiers: typing.List[PricingTier] = pydantic_v1.Field(alias="pricingTiers")
"""
Array of pricing tiers with conditional pricing based on usage thresholds.

Pricing tiers enable accurate cost tracking for models that charge different rates based on usage patterns
(e.g., different rates for high-volume usage, large context windows, or cached tokens).

Each model must have exactly one default tier (isDefault=true, priority=0) that serves as a fallback.
Additional conditional tiers can be defined with specific matching criteria.

If this array is empty, the model uses legacy flat pricing from the inputPrice/outputPrice/totalPrice fields.
"""

def json(self, **kwargs: typing.Any) -> str:
Expand Down
117 changes: 117 additions & 0 deletions langfuse/api/resources/commons/types/pricing_tier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# This file was auto-generated by Fern from our API Definition.

import datetime as dt
import typing

from ....core.datetime_utils import serialize_datetime
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
from .pricing_tier_condition import PricingTierCondition


class PricingTier(pydantic_v1.BaseModel):
"""
Pricing tier definition with conditional pricing based on usage thresholds.

Pricing tiers enable accurate cost tracking for LLM providers that charge different rates based on usage patterns.
For example, some providers charge higher rates when context size exceeds certain thresholds.

How tier matching works:
1. Tiers are evaluated in ascending priority order (priority 1 before priority 2, etc.)
2. The first tier where ALL conditions match is selected
3. If no conditional tiers match, the default tier is used as a fallback
4. The default tier has priority 0 and no conditions

Why priorities matter:
- Lower priority numbers are evaluated first, allowing you to define specific cases before general ones
- Example: Priority 1 for "high usage" (>200K tokens), Priority 2 for "medium usage" (>100K tokens), Priority 0 for default
- Without proper ordering, a less specific condition might match before a more specific one

Every model must have exactly one default tier to ensure cost calculation always succeeds.
"""

id: str = pydantic_v1.Field()
"""
Unique identifier for the pricing tier
"""

name: str = pydantic_v1.Field()
"""
Name of the pricing tier for display and identification purposes.

Examples: "Standard", "High Volume Tier", "Large Context", "Extended Context Tier"
"""

is_default: bool = pydantic_v1.Field(alias="isDefault")
"""
Whether this is the default tier. Every model must have exactly one default tier with priority 0 and no conditions.

The default tier serves as a fallback when no conditional tiers match, ensuring cost calculation always succeeds.
It typically represents the base pricing for standard usage patterns.
"""

priority: int = pydantic_v1.Field()
"""
Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first).

The default tier must always have priority 0. Conditional tiers should have priority 1, 2, 3, etc.

Example ordering:
- Priority 0: Default tier (no conditions, always matches as fallback)
- Priority 1: High usage tier (e.g., >200K tokens)
- Priority 2: Medium usage tier (e.g., >100K tokens)

This ensures more specific conditions are checked before general ones.
"""

conditions: typing.List[PricingTierCondition] = pydantic_v1.Field()
"""
Array of conditions that must ALL be met for this tier to match (AND logic).

The default tier must have an empty conditions array. Conditional tiers should have one or more conditions
that define when this tier's pricing applies.

Multiple conditions enable complex matching scenarios (e.g., "high input tokens AND low output tokens").
"""

prices: typing.Dict[str, float] = pydantic_v1.Field()
"""
Prices (USD) by usage type for this tier.

Common usage types: "input", "output", "total", "request", "image"
Prices are specified in USD per unit (e.g., per token, per request, per second).

Example: {"input": 0.000003, "output": 0.000015} means $3 per million input tokens and $15 per million output tokens.
"""

def json(self, **kwargs: typing.Any) -> str:
kwargs_with_defaults: typing.Any = {
"by_alias": True,
"exclude_unset": True,
**kwargs,
}
return super().json(**kwargs_with_defaults)

def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
kwargs_with_defaults_exclude_unset: typing.Any = {
"by_alias": True,
"exclude_unset": True,
**kwargs,
}
kwargs_with_defaults_exclude_none: typing.Any = {
"by_alias": True,
"exclude_none": True,
**kwargs,
}

return deep_union_pydantic_dicts(
super().dict(**kwargs_with_defaults_exclude_unset),
super().dict(**kwargs_with_defaults_exclude_none),
)

class Config:
frozen = True
smart_union = True
allow_population_by_field_name = True
populate_by_name = True
extra = pydantic_v1.Extra.allow
json_encoders = {dt.datetime: serialize_datetime}
92 changes: 92 additions & 0 deletions langfuse/api/resources/commons/types/pricing_tier_condition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# This file was auto-generated by Fern from our API Definition.

import datetime as dt
import typing

from ....core.datetime_utils import serialize_datetime
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
from .pricing_tier_operator import PricingTierOperator


class PricingTierCondition(pydantic_v1.BaseModel):
"""
Condition for matching a pricing tier based on usage details. Used to implement tiered pricing models where costs vary based on usage thresholds.

How it works:
1. The regex pattern matches against usage detail keys (e.g., "input_tokens", "input_cached")
2. Values of all matching keys are summed together
3. The sum is compared against the threshold value using the specified operator
4. All conditions in a tier must be met (AND logic) for the tier to match

Common use cases:
- Threshold-based pricing: Match when accumulated usage exceeds a certain amount
- Usage-type-specific pricing: Different rates for cached vs non-cached tokens, or input vs output
- Volume-based pricing: Different rates based on total request or token count
"""

usage_detail_pattern: str = pydantic_v1.Field(alias="usageDetailPattern")
"""
Regex pattern to match against usage detail keys. All matching keys' values are summed for threshold comparison.

Examples:
- "^input" matches "input", "input_tokens", "input_cached", etc.
- "^(input|prompt)" matches both "input_tokens" and "prompt_tokens"
- "_cache$" matches "input_cache", "output_cache", etc.

The pattern is case-insensitive by default. If no keys match, the sum is treated as zero.
"""

operator: PricingTierOperator = pydantic_v1.Field()
"""
Comparison operator to apply between the summed value and the threshold.

- gt: greater than (sum > threshold)
- gte: greater than or equal (sum >= threshold)
- lt: less than (sum < threshold)
- lte: less than or equal (sum <= threshold)
- eq: equal (sum == threshold)
- neq: not equal (sum != threshold)
"""

value: float = pydantic_v1.Field()
"""
Threshold value for comparison. For token-based pricing, this is typically the token count threshold (e.g., 200000 for a 200K token threshold).
"""

case_sensitive: bool = pydantic_v1.Field(alias="caseSensitive")
"""
Whether the regex pattern matching is case-sensitive. Default is false (case-insensitive matching).
"""

def json(self, **kwargs: typing.Any) -> str:
kwargs_with_defaults: typing.Any = {
"by_alias": True,
"exclude_unset": True,
**kwargs,
}
return super().json(**kwargs_with_defaults)

def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
kwargs_with_defaults_exclude_unset: typing.Any = {
"by_alias": True,
"exclude_unset": True,
**kwargs,
}
kwargs_with_defaults_exclude_none: typing.Any = {
"by_alias": True,
"exclude_none": True,
**kwargs,
}

return deep_union_pydantic_dicts(
super().dict(**kwargs_with_defaults_exclude_unset),
super().dict(**kwargs_with_defaults_exclude_none),
)

class Config:
frozen = True
smart_union = True
allow_population_by_field_name = True
populate_by_name = True
extra = pydantic_v1.Extra.allow
json_encoders = {dt.datetime: serialize_datetime}
Loading
Loading