Implement cost calculator for Tuber (#471)

* Adding cost calculation in tuner for BYOK

* fix

* implement cost calculator for Tuner

* Update api/services/integrations/tuner/completion.py

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* feat: expose render_options in node spec

* Update api/services/integrations/registry.py

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: mohamed salem <259547077+mohamedsalem-bot@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
Mohamed-Mamdouh 2026-07-02 08:21:14 +01:00 committed by GitHub
parent 97803b8121
commit 65d46bc313
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 479 additions and 188 deletions

View file

@ -5,9 +5,13 @@ from pydantic import model_validator
from api.services.integrations.base import IntegrationNodeRegistration
from api.services.workflow.node_data import BaseNodeData
from api.services.workflow.node_specs._base import (
DisplayOptions,
GraphConstraints,
NodeCategory,
NodeExample,
NumberInputOptions,
PropertyLayoutOptions,
PropertyRendererOptions,
PropertyType,
)
from api.services.workflow.node_specs.model_spec import (
@ -16,6 +20,13 @@ from api.services.workflow.node_specs.model_spec import (
spec_field,
)
# Cost rate fields are only shown once the user turns on cost calculation.
_COST_FIELDS_VISIBLE = DisplayOptions(show={"cost_calculation_enabled": [True]})
_COST_RATE_RENDERER_OPTIONS = PropertyRendererOptions(
layout=PropertyLayoutOptions(column_span=6),
number_input=NumberInputOptions(fractional=True),
)
@node_spec(
name="tuner",
@ -48,6 +59,13 @@ from api.services.workflow.node_specs.model_spec import (
"tuner_agent_id",
"tuner_workspace_id",
"tuner_api_key",
"cost_calculation_enabled",
"cost_llm_input_rate",
"cost_llm_cached_input_rate",
"cost_llm_output_rate",
"cost_tts_rate",
"cost_stt_rate",
"cost_telephony_rate",
),
field_overrides={
"name": {
@ -103,6 +121,73 @@ class TunerNodeData(BaseNodeData):
description="Bearer token used when posting completed calls to Tuner.",
)
cost_calculation_enabled: bool = spec_field(
default=False,
ui_type=PropertyType.boolean,
display_name="Calculate cost",
description="Send a per-call cost to Tuner, computed from your own provider rates (BYOK). All rates below are optional.",
)
cost_llm_input_rate: float | None = spec_field(
default=None,
ge=0,
le=1000,
ui_type=PropertyType.number,
display_name="LLM input",
description="USD per 1M tokens",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
cost_llm_cached_input_rate: float | None = spec_field(
default=None,
ge=0,
le=1000,
ui_type=PropertyType.number,
display_name="LLM cached input",
description="USD per 1M cached tokens",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
cost_llm_output_rate: float | None = spec_field(
default=None,
ge=0,
le=1000,
ui_type=PropertyType.number,
display_name="LLM output",
description="USD per 1M tokens",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
cost_tts_rate: float | None = spec_field(
default=None,
ge=0,
le=100,
ui_type=PropertyType.number,
display_name="TTS",
description="USD per 1K characters",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
cost_stt_rate: float | None = spec_field(
default=None,
ge=0,
le=100,
ui_type=PropertyType.number,
display_name="STT",
description="USD per minute",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
cost_telephony_rate: float | None = spec_field(
default=None,
ge=0,
le=100,
ui_type=PropertyType.number,
display_name="Telephony",
description="USD per minute",
display_options=_COST_FIELDS_VISIBLE,
renderer_options=_COST_RATE_RENDERER_OPTIONS,
)
@model_validator(mode="after")
def _validate_enabled_config(self):
if not self.tuner_enabled: