Skip to content

Commit 8ceb236

Browse files
langfuse-botlangfuse-bothassiebp
authored
feat(api): update API spec from langfuse/langfuse ad16fa0 (#1454)
Co-authored-by: langfuse-bot <[email protected]> Co-authored-by: Hassieb Pakzad <[email protected]>
1 parent d233bc4 commit 8ceb236

File tree

10 files changed

+443
-4
lines changed

10 files changed

+443
-4
lines changed

langfuse/api/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@
157157
PaginatedSessions,
158158
PatchMediaBody,
159159
PlaceholderMessage,
160+
PricingTier,
161+
PricingTierCondition,
162+
PricingTierInput,
163+
PricingTierOperator,
160164
Project,
161165
ProjectDeletionResponse,
162166
Projects,
@@ -403,6 +407,10 @@
403407
"PaginatedSessions",
404408
"PatchMediaBody",
405409
"PlaceholderMessage",
410+
"PricingTier",
411+
"PricingTierCondition",
412+
"PricingTierInput",
413+
"PricingTierOperator",
406414
"Project",
407415
"ProjectDeletionResponse",
408416
"Projects",

langfuse/api/resources/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@
8484
Observation,
8585
ObservationLevel,
8686
ObservationsView,
87+
PricingTier,
88+
PricingTierCondition,
89+
PricingTierInput,
90+
PricingTierOperator,
8791
Score,
8892
ScoreConfig,
8993
ScoreDataType,
@@ -424,6 +428,10 @@
424428
"PaginatedSessions",
425429
"PatchMediaBody",
426430
"PlaceholderMessage",
431+
"PricingTier",
432+
"PricingTierCondition",
433+
"PricingTierInput",
434+
"PricingTierOperator",
427435
"Project",
428436
"ProjectDeletionResponse",
429437
"Projects",

langfuse/api/resources/commons/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
Observation,
2727
ObservationLevel,
2828
ObservationsView,
29+
PricingTier,
30+
PricingTierCondition,
31+
PricingTierInput,
32+
PricingTierOperator,
2933
Score,
3034
ScoreConfig,
3135
ScoreDataType,
@@ -82,6 +86,10 @@
8286
"Observation",
8387
"ObservationLevel",
8488
"ObservationsView",
89+
"PricingTier",
90+
"PricingTierCondition",
91+
"PricingTierInput",
92+
"PricingTierOperator",
8593
"Score",
8694
"ScoreConfig",
8795
"ScoreDataType",

langfuse/api/resources/commons/types/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
from .observation import Observation
2626
from .observation_level import ObservationLevel
2727
from .observations_view import ObservationsView
28+
from .pricing_tier import PricingTier
29+
from .pricing_tier_condition import PricingTierCondition
30+
from .pricing_tier_input import PricingTierInput
31+
from .pricing_tier_operator import PricingTierOperator
2832
from .score import Score, Score_Boolean, Score_Categorical, Score_Numeric
2933
from .score_config import ScoreConfig
3034
from .score_data_type import ScoreDataType
@@ -63,6 +67,10 @@
6367
"Observation",
6468
"ObservationLevel",
6569
"ObservationsView",
70+
"PricingTier",
71+
"PricingTierCondition",
72+
"PricingTierInput",
73+
"PricingTierOperator",
6674
"Score",
6775
"ScoreConfig",
6876
"ScoreDataType",

langfuse/api/resources/commons/types/model.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@
77
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
88
from .model_price import ModelPrice
99
from .model_usage_unit import ModelUsageUnit
10+
from .pricing_tier import PricingTier
1011

1112

1213
class Model(pydantic_v1.BaseModel):
1314
"""
1415
Model definition used for transforming usage into USD cost and/or tokenization.
16+
17+
Models can have either simple flat pricing or tiered pricing:
18+
- Flat pricing: Single price per usage type (legacy, but still supported)
19+
- Tiered pricing: Multiple pricing tiers with conditional matching based on usage patterns
20+
21+
The pricing tiers approach is recommended for models with usage-based pricing variations.
22+
When using tiered pricing, the flat price fields (inputPrice, outputPrice, prices) are populated
23+
from the default tier for backward compatibility.
1524
"""
1625

1726
id: str
@@ -73,9 +82,30 @@ class Model(pydantic_v1.BaseModel):
7382
"""
7483

7584
is_langfuse_managed: bool = pydantic_v1.Field(alias="isLangfuseManaged")
85+
created_at: dt.datetime = pydantic_v1.Field(alias="createdAt")
86+
"""
87+
Timestamp when the model was created
88+
"""
89+
7690
prices: typing.Dict[str, ModelPrice] = pydantic_v1.Field()
7791
"""
78-
Price (USD) by usage type
92+
Deprecated. Use 'pricingTiers' instead for models with usage-based pricing variations.
93+
94+
This field shows prices by usage type from the default pricing tier. Maintained for backward compatibility.
95+
If the model uses tiered pricing, this field will be populated from the default tier's prices.
96+
"""
97+
98+
pricing_tiers: typing.List[PricingTier] = pydantic_v1.Field(alias="pricingTiers")
99+
"""
100+
Array of pricing tiers with conditional pricing based on usage thresholds.
101+
102+
Pricing tiers enable accurate cost tracking for models that charge different rates based on usage patterns
103+
(e.g., different rates for high-volume usage, large context windows, or cached tokens).
104+
105+
Each model must have exactly one default tier (isDefault=true, priority=0) that serves as a fallback.
106+
Additional conditional tiers can be defined with specific matching criteria.
107+
108+
If this array is empty, the model uses legacy flat pricing from the inputPrice/outputPrice/totalPrice fields.
79109
"""
80110

81111
def json(self, **kwargs: typing.Any) -> str:
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# This file was auto-generated by Fern from our API Definition.
2+
3+
import datetime as dt
4+
import typing
5+
6+
from ....core.datetime_utils import serialize_datetime
7+
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
8+
from .pricing_tier_condition import PricingTierCondition
9+
10+
11+
class PricingTier(pydantic_v1.BaseModel):
12+
"""
13+
Pricing tier definition with conditional pricing based on usage thresholds.
14+
15+
Pricing tiers enable accurate cost tracking for LLM providers that charge different rates based on usage patterns.
16+
For example, some providers charge higher rates when context size exceeds certain thresholds.
17+
18+
How tier matching works:
19+
1. Tiers are evaluated in ascending priority order (priority 1 before priority 2, etc.)
20+
2. The first tier where ALL conditions match is selected
21+
3. If no conditional tiers match, the default tier is used as a fallback
22+
4. The default tier has priority 0 and no conditions
23+
24+
Why priorities matter:
25+
- Lower priority numbers are evaluated first, allowing you to define specific cases before general ones
26+
- Example: Priority 1 for "high usage" (>200K tokens), Priority 2 for "medium usage" (>100K tokens), Priority 0 for default
27+
- Without proper ordering, a less specific condition might match before a more specific one
28+
29+
Every model must have exactly one default tier to ensure cost calculation always succeeds.
30+
"""
31+
32+
id: str = pydantic_v1.Field()
33+
"""
34+
Unique identifier for the pricing tier
35+
"""
36+
37+
name: str = pydantic_v1.Field()
38+
"""
39+
Name of the pricing tier for display and identification purposes.
40+
41+
Examples: "Standard", "High Volume Tier", "Large Context", "Extended Context Tier"
42+
"""
43+
44+
is_default: bool = pydantic_v1.Field(alias="isDefault")
45+
"""
46+
Whether this is the default tier. Every model must have exactly one default tier with priority 0 and no conditions.
47+
48+
The default tier serves as a fallback when no conditional tiers match, ensuring cost calculation always succeeds.
49+
It typically represents the base pricing for standard usage patterns.
50+
"""
51+
52+
priority: int = pydantic_v1.Field()
53+
"""
54+
Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first).
55+
56+
The default tier must always have priority 0. Conditional tiers should have priority 1, 2, 3, etc.
57+
58+
Example ordering:
59+
- Priority 0: Default tier (no conditions, always matches as fallback)
60+
- Priority 1: High usage tier (e.g., >200K tokens)
61+
- Priority 2: Medium usage tier (e.g., >100K tokens)
62+
63+
This ensures more specific conditions are checked before general ones.
64+
"""
65+
66+
conditions: typing.List[PricingTierCondition] = pydantic_v1.Field()
67+
"""
68+
Array of conditions that must ALL be met for this tier to match (AND logic).
69+
70+
The default tier must have an empty conditions array. Conditional tiers should have one or more conditions
71+
that define when this tier's pricing applies.
72+
73+
Multiple conditions enable complex matching scenarios (e.g., "high input tokens AND low output tokens").
74+
"""
75+
76+
prices: typing.Dict[str, float] = pydantic_v1.Field()
77+
"""
78+
Prices (USD) by usage type for this tier.
79+
80+
Common usage types: "input", "output", "total", "request", "image"
81+
Prices are specified in USD per unit (e.g., per token, per request, per second).
82+
83+
Example: {"input": 0.000003, "output": 0.000015} means $3 per million input tokens and $15 per million output tokens.
84+
"""
85+
86+
def json(self, **kwargs: typing.Any) -> str:
87+
kwargs_with_defaults: typing.Any = {
88+
"by_alias": True,
89+
"exclude_unset": True,
90+
**kwargs,
91+
}
92+
return super().json(**kwargs_with_defaults)
93+
94+
def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
95+
kwargs_with_defaults_exclude_unset: typing.Any = {
96+
"by_alias": True,
97+
"exclude_unset": True,
98+
**kwargs,
99+
}
100+
kwargs_with_defaults_exclude_none: typing.Any = {
101+
"by_alias": True,
102+
"exclude_none": True,
103+
**kwargs,
104+
}
105+
106+
return deep_union_pydantic_dicts(
107+
super().dict(**kwargs_with_defaults_exclude_unset),
108+
super().dict(**kwargs_with_defaults_exclude_none),
109+
)
110+
111+
class Config:
112+
frozen = True
113+
smart_union = True
114+
allow_population_by_field_name = True
115+
populate_by_name = True
116+
extra = pydantic_v1.Extra.allow
117+
json_encoders = {dt.datetime: serialize_datetime}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# This file was auto-generated by Fern from our API Definition.
2+
3+
import datetime as dt
4+
import typing
5+
6+
from ....core.datetime_utils import serialize_datetime
7+
from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
8+
from .pricing_tier_operator import PricingTierOperator
9+
10+
11+
class PricingTierCondition(pydantic_v1.BaseModel):
12+
"""
13+
Condition for matching a pricing tier based on usage details. Used to implement tiered pricing models where costs vary based on usage thresholds.
14+
15+
How it works:
16+
1. The regex pattern matches against usage detail keys (e.g., "input_tokens", "input_cached")
17+
2. Values of all matching keys are summed together
18+
3. The sum is compared against the threshold value using the specified operator
19+
4. All conditions in a tier must be met (AND logic) for the tier to match
20+
21+
Common use cases:
22+
- Threshold-based pricing: Match when accumulated usage exceeds a certain amount
23+
- Usage-type-specific pricing: Different rates for cached vs non-cached tokens, or input vs output
24+
- Volume-based pricing: Different rates based on total request or token count
25+
"""
26+
27+
usage_detail_pattern: str = pydantic_v1.Field(alias="usageDetailPattern")
28+
"""
29+
Regex pattern to match against usage detail keys. All matching keys' values are summed for threshold comparison.
30+
31+
Examples:
32+
- "^input" matches "input", "input_tokens", "input_cached", etc.
33+
- "^(input|prompt)" matches both "input_tokens" and "prompt_tokens"
34+
- "_cache$" matches "input_cache", "output_cache", etc.
35+
36+
The pattern is case-insensitive by default. If no keys match, the sum is treated as zero.
37+
"""
38+
39+
operator: PricingTierOperator = pydantic_v1.Field()
40+
"""
41+
Comparison operator to apply between the summed value and the threshold.
42+
43+
- gt: greater than (sum > threshold)
44+
- gte: greater than or equal (sum >= threshold)
45+
- lt: less than (sum < threshold)
46+
- lte: less than or equal (sum <= threshold)
47+
- eq: equal (sum == threshold)
48+
- neq: not equal (sum != threshold)
49+
"""
50+
51+
value: float = pydantic_v1.Field()
52+
"""
53+
Threshold value for comparison. For token-based pricing, this is typically the token count threshold (e.g., 200000 for a 200K token threshold).
54+
"""
55+
56+
case_sensitive: bool = pydantic_v1.Field(alias="caseSensitive")
57+
"""
58+
Whether the regex pattern matching is case-sensitive. Default is false (case-insensitive matching).
59+
"""
60+
61+
def json(self, **kwargs: typing.Any) -> str:
62+
kwargs_with_defaults: typing.Any = {
63+
"by_alias": True,
64+
"exclude_unset": True,
65+
**kwargs,
66+
}
67+
return super().json(**kwargs_with_defaults)
68+
69+
def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
70+
kwargs_with_defaults_exclude_unset: typing.Any = {
71+
"by_alias": True,
72+
"exclude_unset": True,
73+
**kwargs,
74+
}
75+
kwargs_with_defaults_exclude_none: typing.Any = {
76+
"by_alias": True,
77+
"exclude_none": True,
78+
**kwargs,
79+
}
80+
81+
return deep_union_pydantic_dicts(
82+
super().dict(**kwargs_with_defaults_exclude_unset),
83+
super().dict(**kwargs_with_defaults_exclude_none),
84+
)
85+
86+
class Config:
87+
frozen = True
88+
smart_union = True
89+
allow_population_by_field_name = True
90+
populate_by_name = True
91+
extra = pydantic_v1.Extra.allow
92+
json_encoders = {dt.datetime: serialize_datetime}

0 commit comments

Comments
 (0)