@@ -44,6 +44,7 @@ from cloud.repository import (
|
||||
DeviceEnrollmentConflictError,
|
||||
HostEnrollmentConflictError,
|
||||
)
|
||||
from cloud.governance import TokenBudgetExceededError
|
||||
from core.models import Device, utc_now
|
||||
from runtime.tool_calling_client import ToolCallingClient, ToolCallUnavailable
|
||||
from runtime.tool_specs import ToolSpec
|
||||
@@ -65,11 +66,17 @@ def create_internal_router(
|
||||
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
||||
planner_client_factory: Callable[[], ToolCallingClient] | None = None,
|
||||
scheduler: TaskScheduler | None = None,
|
||||
planner_token_reservation_ceiling: int = 4096,
|
||||
planner_token_reservation_ttl_seconds: float = 300.0,
|
||||
) -> APIRouter:
|
||||
if claim_poll_interval_seconds <= 0:
|
||||
raise ValueError("claim_poll_interval_seconds must be greater than zero")
|
||||
if lease_duration_seconds <= 0:
|
||||
raise ValueError("lease_duration_seconds must be greater than zero")
|
||||
if planner_token_reservation_ceiling <= 0:
|
||||
raise ValueError("planner_token_reservation_ceiling must be greater than zero")
|
||||
if planner_token_reservation_ttl_seconds <= 0:
|
||||
raise ValueError("planner_token_reservation_ttl_seconds must be greater than zero")
|
||||
router = APIRouter(prefix=version_prefix, tags=["host-agent"])
|
||||
build_planner_client = planner_client_factory or _default_planner_client_factory
|
||||
|
||||
@@ -352,6 +359,7 @@ def create_internal_router(
|
||||
response_model_exclude_none=True,
|
||||
responses={
|
||||
status.HTTP_502_BAD_GATEWAY: {"model": PlannerDecisionError},
|
||||
status.HTTP_429_TOO_MANY_REQUESTS: {"model": PlannerDecisionError},
|
||||
},
|
||||
)
|
||||
def decide_planner_call(
|
||||
@@ -385,6 +393,25 @@ def create_internal_router(
|
||||
for tool in payload.tools
|
||||
]
|
||||
|
||||
now = utc_now()
|
||||
reservation = None
|
||||
try:
|
||||
reservation = pool.store.reserve_host_token_budget(
|
||||
reservation_id=uuid4().hex,
|
||||
host_id=host_id,
|
||||
usage_day=now.date().isoformat(),
|
||||
reserved_tokens=planner_token_reservation_ceiling,
|
||||
task_id=None,
|
||||
attempt=None,
|
||||
created_at=now,
|
||||
expires_at=now + timedelta(seconds=planner_token_reservation_ttl_seconds),
|
||||
)
|
||||
except TokenBudgetExceededError as exc:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
content=PlannerDecisionError(detail=str(exc)).model_dump(),
|
||||
)
|
||||
|
||||
started_at = monotonic()
|
||||
client = build_planner_client()
|
||||
try:
|
||||
@@ -408,6 +435,19 @@ def create_internal_router(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
content=PlannerDecisionError(detail=str(exc)).model_dump(),
|
||||
)
|
||||
usage = decision.usage
|
||||
if reservation is not None and usage is not None and usage.total_tokens is not None:
|
||||
planner_config = load_cloud_planner_config()
|
||||
pool.store.settle_host_token_reservation(
|
||||
reservation_id=reservation.id,
|
||||
event_id=uuid4().hex,
|
||||
provider=planner_config.provider,
|
||||
model=planner_config.resolved_model(),
|
||||
input_tokens=usage.input_tokens,
|
||||
output_tokens=usage.output_tokens,
|
||||
total_tokens=usage.total_tokens,
|
||||
occurred_at=utc_now(),
|
||||
)
|
||||
logger.info(
|
||||
"planner-decision request resolved",
|
||||
extra={
|
||||
|
||||
Reference in New Issue
Block a user