Skip to content

Commit 45783aa

Browse files
authored
feat(impacts): support legacy signature of impacts usage (#791)
* feat(impacts): support legacy signature of impacts usage * Update unit coverage badge --------- Co-authored-by: leoguillaume <leoguillaume@users.noreply.github.com>
1 parent ee63715 commit 45783aa

11 files changed

Lines changed: 61 additions & 46 deletions

File tree

.github/badges/coverage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"schemaVersion":1,"label":"coverage","message":"50.82%","color":"red"}
1+
{"schemaVersion":1,"label":"coverage","message":"50.86%","color":"red"}

api/clients/model/_basemodelprovider.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,15 @@ def _get_usage(self, request_content: RequestContent, response_data: dict | list
130130
usage.completion_tokens += completion_tokens
131131
usage.total_tokens += total_tokens
132132
usage.cost += cost
133-
usage.carbon.kgCO2eq += carbon_footprint.kgCO2eq
134-
usage.carbon.kWh += carbon_footprint.kWh
133+
134+
# legacy usage
135+
usage.carbon.kgCO2eq.min += carbon_footprint.kgCO2eq
136+
usage.carbon.kgCO2eq.max += carbon_footprint.kgCO2eq
137+
usage.carbon.kWh.min += carbon_footprint.kWh
138+
usage.carbon.kWh.max += carbon_footprint.kWh
139+
140+
usage.impacts.kgCO2eq += carbon_footprint.kgCO2eq
141+
usage.impacts.kWh += carbon_footprint.kWh
135142
usage.requests += 1
136143

137144
request_context.get().usage = usage

api/helpers/_usagemanager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
from sqlalchemy.future import select
77

88
from api.schemas.me.usage import (
9-
CarbonFootprintUsage,
109
EndpointUsage,
1110
MetricsUsage,
1211
Usage,
1312
UsageDetail,
1413
)
14+
from api.schemas.usage import EnvironmentalImpacts
1515
from api.sql.models import Usage as UsageTable
1616

1717

@@ -79,7 +79,7 @@ async def get_usages(
7979
completion_tokens=row.completion_tokens,
8080
total_tokens=row.total_tokens,
8181
cost=row.cost,
82-
carbon=CarbonFootprintUsage(kWh=row.kwh, kgCO2eq=row.kgco2eq),
82+
impacts=EnvironmentalImpacts(kWh=row.kwh, kgCO2eq=row.kgco2eq),
8383
metrics=MetricsUsage(latency=row.latency, ttft=row.ttft),
8484
),
8585
)

api/schemas/me/usage.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic import Field
55

66
from api.schemas import BaseModel
7+
from api.schemas.usage import EnvironmentalImpacts
78

89

910
class EndpointUsage(Enum):
@@ -20,17 +21,12 @@ class MetricsUsage(BaseModel):
2021
ttft: int | None = None
2122

2223

23-
class CarbonFootprintUsage(BaseModel):
24-
kWh: Annotated[float | None, Field(default=None, description="Carbon footprint in kWh.")]
25-
kgCO2eq: Annotated[float | None, Field(default=None, description="Carbon footprint in kgCO2eq (global warming potential).")]
26-
27-
2824
class UsageDetail(BaseModel):
2925
prompt_tokens: Annotated[int | None, Field(default=None, description="Number of prompt tokens (e.g. input tokens).")]
3026
completion_tokens: Annotated[int | None, Field(default=None, description="Number of completion tokens (e.g. output tokens).")]
3127
total_tokens: Annotated[int | None, Field(default=None, description="Total number of tokens (e.g. input and output tokens).")]
3228
cost: Annotated[float | None, Field(default=None, description="Total cost of the request.")]
33-
carbon: Annotated[CarbonFootprintUsage, Field(default_factory=CarbonFootprintUsage)]
29+
impacts: Annotated[EnvironmentalImpacts, Field(default_factory=EnvironmentalImpacts)]
3430
metrics: Annotated[MetricsUsage, Field(default_factory=MetricsUsage)]
3531

3632

api/schemas/usage.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,31 @@
33
from api.schemas import BaseModel
44

55

6-
class CarbonFootprintUsage(BaseModel):
6+
class EnvironmentalImpacts(BaseModel):
77
kWh: float = Field(default=0.0, description="Carbon footprint in kWh.")
88
kgCO2eq: float = Field(default=0.0, description="Carbon footprint in kgCO2eq (global warming potential).")
99

1010

11+
class CarbonFootprintUsageKWh(BaseModel):
12+
min: float = Field(default=0.0, description="Minimum carbon footprint in kWh.", deprecated=True)
13+
max: float = Field(default=0.0, description="Maximum carbon footprint in kWh.", deprecated=True)
14+
15+
16+
class CarbonFootprintUsageKgCO2eq(BaseModel):
17+
min: float = Field(default=0.0, description="Minimum carbon footprint in kgCO2eq (global warming potential).", deprecated=True)
18+
max: float = Field(default=0.0, description="Maximum carbon footprint in kgCO2eq (global warming potential).", deprecated=True)
19+
20+
21+
class CarbonFootprintUsage(BaseModel):
22+
kWh: CarbonFootprintUsageKWh = Field(default_factory=CarbonFootprintUsageKWh, deprecated=True)
23+
kgCO2eq: CarbonFootprintUsageKgCO2eq = Field(default_factory=CarbonFootprintUsageKgCO2eq, deprecated=True)
24+
25+
1126
class Usage(BaseModel):
1227
prompt_tokens: int = Field(default=0, description="Number of prompt tokens (e.g. input tokens).")
1328
completion_tokens: int = Field(default=0, description="Number of completion tokens (e.g. output tokens).")
1429
total_tokens: int = Field(default=0, description="Total number of tokens (e.g. input and output tokens).")
1530
cost: float = Field(default=0.0, description="Total cost of the request.")
16-
carbon: CarbonFootprintUsage = Field(default_factory=CarbonFootprintUsage)
31+
carbon: CarbonFootprintUsage = Field(default_factory=CarbonFootprintUsage, deprecated=True)
32+
impacts: EnvironmentalImpacts = Field(default_factory=EnvironmentalImpacts)
1733
requests: int = Field(default=0, description="Number of model requests.")

api/tests/unit/test_utils/test_carbon.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from types import SimpleNamespace
22

33
from api.schemas.admin.providers import ProviderCarbonFootprintZone
4-
from api.schemas.usage import CarbonFootprintUsage
4+
from api.schemas.usage import EnvironmentalImpacts
55
from api.utils.carbon import get_carbon_footprint
66

77

@@ -23,7 +23,7 @@ def test_get_carbon_footprint_return_null_footprint_when_model_params_not_define
2323
model_zone = ProviderCarbonFootprintZone.WOR
2424
token_count = 1
2525
request_latency = 0.01
26-
expected_carbon_footprint = CarbonFootprintUsage(kWh=0.0, kgCO2eq=0.0)
26+
expected_carbon_footprint = EnvironmentalImpacts(kWh=0.0, kgCO2eq=0.0)
2727
# When
2828
result = get_carbon_footprint(active_params, total_params, model_zone, token_count, request_latency)
2929
# Then
@@ -36,7 +36,7 @@ def test_get_carbon_footprint_return_null_footprint_when_token_count_is_null(sel
3636
model_zone = ProviderCarbonFootprintZone.WOR
3737
token_count = 0
3838
request_latency = 0.01
39-
expected_carbon_footprint = CarbonFootprintUsage(kWh=0.0, kgCO2eq=0.0)
39+
expected_carbon_footprint = EnvironmentalImpacts(kWh=0.0, kgCO2eq=0.0)
4040
# When
4141
result = get_carbon_footprint(active_params, total_params, model_zone, token_count, request_latency)
4242
# Then
@@ -47,18 +47,16 @@ def test_get_carbon_footprint_return_footprint(self, mocker):
4747
mocked_electricity_mix = mocker.patch("api.utils.carbon.electricity_mixes.find_electricity_mix")
4848
mocked_electricity_mix.return_value = SimpleNamespace(adpe=1, pe=2, gwp=3, wue=4)
4949
mocked_compute_llm_impacts = mocker.patch("api.utils.carbon.compute_llm_impacts")
50-
mocked_compute_llm_impacts.return_value = dict_to_namespace(
51-
{
52-
"energy": {"value": 1},
53-
"gwp": {"value": 3},
54-
}
55-
)
50+
mocked_compute_llm_impacts.return_value = dict_to_namespace({
51+
"energy": {"value": 1},
52+
"gwp": {"value": 3},
53+
})
5654
active_params = 1
5755
total_params = 1
5856
model_zone = ProviderCarbonFootprintZone.WOR
5957
token_count = 1
6058
request_latency = 10 # 10 milliseconds, will be converted to 0.01 seconds
61-
expected_carbon_footprint = CarbonFootprintUsage(kWh=1.0, kgCO2eq=3.0)
59+
expected_carbon_footprint = EnvironmentalImpacts(kWh=1.0, kgCO2eq=3.0)
6260
expected_compute_llm_impacts_args = {
6361
"if_electricity_mix_adpe": 1,
6462
"if_electricity_mix_pe": 2,

api/utils/carbon.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ecologits.tracers.utils import compute_llm_impacts, electricity_mixes
22

33
from api.schemas.admin.providers import ProviderCarbonFootprintZone
4-
from api.schemas.usage import CarbonFootprintUsage
4+
from api.schemas.usage import EnvironmentalImpacts
55

66

77
def get_carbon_footprint(
@@ -10,7 +10,7 @@ def get_carbon_footprint(
1010
model_zone: ProviderCarbonFootprintZone,
1111
token_count: int,
1212
request_latency: int | None = None,
13-
) -> CarbonFootprintUsage:
13+
) -> EnvironmentalImpacts:
1414
"""Calculate carbon impact of a model inference using direct parameters.
1515
1616
Args:
@@ -28,7 +28,7 @@ def get_carbon_footprint(
2828
raise ValueError(f"Electricity zone {model_zone.value} not found")
2929

3030
if not active_params or not total_params:
31-
return CarbonFootprintUsage(kWh=0, kgCO2eq=0)
31+
return EnvironmentalImpacts(kWh=0, kgCO2eq=0)
3232

3333
impacts = compute_llm_impacts(
3434
model_active_parameter_count=active_params,
@@ -45,6 +45,6 @@ def get_carbon_footprint(
4545
datacenter_wue=1.8,
4646
request_latency=request_latency / 1000, # convert to seconds
4747
)
48-
carbon_footprint = CarbonFootprintUsage(kWh=impacts.energy.value, kgCO2eq=impacts.gwp.value)
48+
impacts = EnvironmentalImpacts(kWh=impacts.energy.value, kgCO2eq=impacts.gwp.value)
4949

50-
return carbon_footprint
50+
return impacts

api/utils/hooks_decorator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ def set_usage_from_context(usage: Usage):
8181
usage.completion_tokens = context.usage.completion_tokens
8282
usage.total_tokens = context.usage.total_tokens
8383
usage.cost = context.usage.cost
84-
usage.kwh = context.usage.carbon.kWh
85-
usage.kgco2eq = context.usage.carbon.kgCO2eq
84+
usage.kwh = context.usage.impacts.kWh
85+
usage.kgco2eq = context.usage.impacts.kgCO2eq
8686
usage.ttft = context.ttft
8787
usage.latency = context.latency
8888

docs/src/content/docs/features/usage/environmental_footprint.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ There are three way to configure environmental impact tracking, by Playground, A
7474

7575
## Environmental impact metrics
7676

77-
For each call to a generative AI model, the API returns environmental impact metrics in the response, in the `usage.carbon` field. The metrics include minimum and maximum estimates to account for variability in model efficiency:
77+
For each call to a generative AI model, the API returns environmental impact metrics in the response, in the `usage.impacts` field. The metrics include minimum and maximum estimates to account for variability in model efficiency:
7878

7979
- **kWh**: Energy consumption in kilowatt-hours (kWh), representing the final electricity consumption.
8080
- **kgCO2eq**: Global Warming Potential (GWP) in kilograms of CO2 equivalent (kgCO2eq), representing greenhouse gas emissions related to climate change.
@@ -95,7 +95,7 @@ For each call to a generative AI model, the API returns environmental impact met
9595
"completion_tokens": 20,
9696
"total_tokens": 30,
9797
"cost": 0.000015,
98-
+ "carbon": {"kWh": 0.0001456, "kgCO2eq": 0.0000672 }
98+
+ "impacts": {"kWh": 0.0001456, "kgCO2eq": 0.0000672 }
9999
}
100100
}
101101
```

playground/app/features/usage/components/lists.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ def usage_table() -> rx.Component:
6464
rx.table.column_header_cell(
6565
rx.tooltip(
6666
rx.hstack(
67-
rx.text("Carbon"),
67+
rx.text("Impacts"),
6868
rx.icon("info", size=ICON_SIZE_TINY),
6969
spacing=SPACING_TINY,
7070
align="center",
7171
),
72-
content="Carbon footprint in kgCO2eq (Global warming potential)",
72+
content="Footprint impacts in kgCO2eq (Global warming potential)",
7373
),
7474
width="150px",
7575
),

0 commit comments

Comments
 (0)