Receive llm_usage_recorded events from arcadia-llm-gateway
POST /api/v1/integrations/llm-usage stores priced LLM usage events (idempotent on gateway_request_id) in llm_usage_events. The gateway is the LLM-pricing authority — arcadia-cloud trusts the charge it sends rather than re-pricing. The monthly invoice rollup now appends an llm_usage line per deployment alongside the infra quote lines; the exact decimal charges are summed and rounded to cents once. Closes the gateway→cloud billing loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
lib/arcadia_cloud_web/controllers/integrations_controller.ex
Normal file
69
lib/arcadia_cloud_web/controllers/integrations_controller.ex
Normal file
@@ -0,0 +1,69 @@
|
||||
defmodule ArcadiaCloudWeb.IntegrationsController do
|
||||
@moduledoc """
|
||||
Inbound integration hooks from sibling Sky AI services.
|
||||
|
||||
`llm_usage` receives `llm_usage_recorded` events from
|
||||
arcadia-llm-gateway — one per billable LLM request — and stores them
|
||||
for the monthly invoice rollup. platform-admin only (the gateway
|
||||
authenticates with a service JWT carrying that role).
|
||||
"""
|
||||
|
||||
use ArcadiaCloudWeb, :controller
|
||||
|
||||
alias ArcadiaCloud.LlmUsage
|
||||
|
||||
def llm_usage(conn, params) do
|
||||
with :ok <- require_platform_admin(conn) do
|
||||
case LlmUsage.record_event(translate(params)) do
|
||||
{:ok, :recorded} ->
|
||||
conn |> put_status(:created) |> json(%{status: "recorded"})
|
||||
|
||||
{:ok, :duplicate} ->
|
||||
json(conn, %{status: "duplicate"})
|
||||
|
||||
{:error, changeset} ->
|
||||
conn |> put_status(:unprocessable_entity) |> json(%{error: errors(changeset)})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Maps the gateway's wire payload onto the LlmUsageEvent schema.
|
||||
defp translate(params) do
|
||||
%{
|
||||
gateway_request_id: params["gateway_request_id"],
|
||||
tenant_id: params["tenant_id"],
|
||||
deployment_id: params["deployment_id"],
|
||||
provider: params["provider"],
|
||||
model: params["model"],
|
||||
request_kind: params["request_kind"],
|
||||
input_tokens: params["input_tokens"],
|
||||
output_tokens: params["output_tokens"],
|
||||
cached_input_tokens: params["cached_input_tokens"],
|
||||
total_tokens: params["total_tokens"],
|
||||
upstream_cost: params["upstream_cost_usd"],
|
||||
customer_charge: params["customer_charge_usd"],
|
||||
customer_charge_cents: params["customer_charge_cents"],
|
||||
markup_mode: params["markup_mode"],
|
||||
occurred_at: params["occurred_at"]
|
||||
}
|
||||
end
|
||||
|
||||
defp require_platform_admin(conn) do
|
||||
identity = conn.assigns.current_identity
|
||||
|
||||
if is_list(identity.roles) and "platform-admin" in identity.roles do
|
||||
:ok
|
||||
else
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "platform_admin_required"})
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp errors(changeset) do
|
||||
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
|
||||
Enum.reduce(opts, msg, fn {k, v}, acc -> String.replace(acc, "%{#{k}}", to_string(v)) end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user