diff --git a/app/api/v1beta/router.py b/app/api/v1beta/router.py index 357560b..06999fe 100644 --- a/app/api/v1beta/router.py +++ b/app/api/v1beta/router.py @@ -141,3 +141,104 @@ def post_estimations( except Exception: raise HTTPException(status_code=500, detail="Failed to Estimate impacts") +@api_router_v1beta.post( + "/expert-estimations", + response_model=dict, + tags=["Estimations"], + summary="Estimate environmental impacts of an LLM request on unsupported models or custom deployements.", + responses=ESTIMATIONS_RESPONSES, +) +def post_expert_estimations( + output_token_count: int = Body( + ..., + embed=True, + examples=[300], + description="Number of tokens generated by the model.", + ), + model_active_params: float = Body( + ..., + embed=True, + examples=[88], + description="Number of active parameters of the model (in billion).", + ), + model_total_params: float = Body( + ..., + embed=True, + examples=[440], + description="Number of total parameters of the model (in billion).", + ), + request_latency: float | None = Body( + default=None, + embed=True, + examples=[1.56], + description="Measured request latency in seconds.", + ), + tps: float | None = Body( + default=None, + embed=True, + examples=[58], + description="Number of tokens generated per second by the model.", + ), + ttft: float | None = Body( + default=None, + embed=True, + examples=[1.63], + description="Time-to-first-token latency in seconds.", + ), + datacenter_pue: float | None = Body( + default=None, + embed=True, + examples=[1.2], + description="Power Usage Effectiveness of the data center.", + ), + datacenter_wue: float | None = Body( + default=None, + embed=True, + examples=[0.6], + description="Water Usage Effectiveness of the data center in L/kWh.", + ), + electricity_mix_adpe: float = Body( + default=1.349e-8, + embed=True, + examples=[1.349e-8], + description="ADPe impact factor of electricity consumption of kgSbeq / kWh (Antimony). (use `GET /v1beta/electricity-mix-zones/{zone}` to get values. Default: world)", + ), + electricity_mix_pe: float = Body( + default=2.5871, + embed=True, + examples=[2.5871], + description="PE impact factor of electricity consumption in MJ / kWh. (use `GET /v1beta/electricity-mix-zones/{zone}` to get values. Default: world)", + ), + electricity_mix_gwp: float = Body( + default=0.45829, + embed=True, + examples=[0.45829], + description="GWP impact factor of electricity consumption in kgCO2eq / kWh. (use `GET /v1beta/electricity-mix-zones/{zone}` to get values. Default: world)", + ), + electricity_mix_wue: float = Body( + default=3.908, + embed=True, + examples=[3.908], + description="WCF impact factor of electricity consumption in L / kWh. (use `GET /v1beta/electricity-mix-zones/{zone}` to get values. Default: world)", + ) +): + try: + impacts = compute_llm_impacts( + output_token_count=output_token_count, + model_active_parameter_count= model_active_params, + model_total_parameter_count = model_total_params, + # TODO: remove the high value when the estimations module in EcoLogits (Python) is ready + request_latency = request_latency if request_latency is not None else 1e6, + tps = tps, + ttft = ttft, + datacenter_pue = datacenter_pue, + datacenter_wue = datacenter_wue, + if_electricity_mix_adpe = electricity_mix_adpe, + if_electricity_mix_pe = electricity_mix_pe, + if_electricity_mix_gwp = electricity_mix_gwp, + if_electricity_mix_wue = electricity_mix_wue, + ) + return {"impacts": impacts} + + except Exception: + raise HTTPException(status_code=500, detail=f"Failed to Estimate impacts") diff --git a/tests/v1beta/test_router.py b/tests/v1beta/test_router.py index a588ea4..9bff28b 100644 --- a/tests/v1beta/test_router.py +++ b/tests/v1beta/test_router.py @@ -133,3 +133,50 @@ def test_post_estimations_missing_required_fields(): response = client.post("/v1beta/estimations", json=payload) assert response.status_code == 422 # Unprocessable Entity for validation errors + + + +def test_post_expert_estimations(): + """Test the POST /expert-estimations endpoint and validate consistency with model results""" + payload = { + "output_token_count": 300, + "model_active_params": 32.3, + "model_total_params": 32.3, + "tps": 26.8, + "ttft": 0.59, + "datacenter_pue": 1.09, + "datacenter_wue": 0.999, + "electricity_mix_adpe": 9.855e-8, + "electricity_mix_pe": 9.6884, + "electricity_mix_gwp": 0.3844, + "electricity_mix_wue": 3.1321 + } + + # Get the expected impacts directly from llm_impacts + expected_impacts = compute_llm_impacts( + output_token_count=payload["output_token_count"], + model_active_parameter_count= payload["model_active_params"], + model_total_parameter_count = payload["model_total_params"], + tps = payload["tps"], + ttft = payload["ttft"], + datacenter_pue = payload["datacenter_pue"], + datacenter_wue = payload["datacenter_wue"], + if_electricity_mix_adpe = payload["electricity_mix_adpe"], + if_electricity_mix_pe = payload["electricity_mix_pe"], + if_electricity_mix_gwp = payload["electricity_mix_gwp"], + if_electricity_mix_wue = payload["electricity_mix_wue"] + ) + + # Call the API endpoint + response = client.post("/v1beta/expert-estimations", json=payload) + assert response.status_code == 200 + + response_data = response.json() + assert "impacts" in response_data + assert response_data["impacts"] is not None + + # Compare the impacts data - convert expected_impacts to dict for comparison + expected_impacts_dict = expected_impacts.model_dump() + assert response_data["impacts"] == expected_impacts_dict + +