lablup / lablup/backend.ai

RFC: LLM inference gateway with structured trace stream for Endpoints/Deployments

Open
#11,461 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
670
Forks
183
Avg merge
17h 7m
Merged PRs (30d)
358

Description

## Summary

Backend.AI exposes inference workloads through `Endpoint` / `Deployment`, but there is no standardized layer in front of those endpoints to:

- emit structured per-call trace events (model, prompt/completion size, latency, token counts, finish reason, cost),
- route requests across multiple endpoints (and optionally external providers) by policy (cost / latency / quality / SLO),
- enforce pre/post-call validators (PII redaction, schema, jailbreak detection, etc.),
- meter token + accelerator-hour usage per caller / project.

This RFC proposes an **LLM Inference Gateway** as a first-class Backend.AI component sitting between callers and inference targets, with a queryable + streaming trace API for downstream consumers.

## Motivation

Inference traffic is increasingly the dominant workload on Backend.AI clusters. Today, operators have:

- only generic resource metrics (CPU/GPU/memory) — no model-aware metrics
- no canonical place to apply routing, fallback, retries, or cost-aware switching
- no shared format for per-call observability — each user reinvents tracing inside their own client code
- no native budget enforcement at the inference boundary

A single gateway resolves all four concerns and gives every Backend.AI deployment uniform observability and policy without each caller having to instrument.

## Non-goals

- Building a serving runtime — defer to existing `Endpoint` / `Deployment` / `RuntimeVariant`.
- Building a model registry — defer to `ModelCard` / `ArtifactRegistry`.
- Replacing the existing scheduler — gateway is purely on the request data plane.
- Building offline evaluation — that is a workflow concern; the gateway only emits traces that an evaluator can consume.

## Proposed capabilities

### 1. Inference proxy

A managed proxy in front of inference endpoints, OpenAI-compatible at the API surface (chat/completions, embeddings, optionally responses-API style). Internally, it dispatches to:

- one or more Backend.AI `Endpoint`s,
- optionally external providers (OpenAI, Anthropic, etc.) with credentials managed via existing keypair / secret primitives.

Default behavior is transparent passthrough; routing/policy/validators are opt-in.

### 2. Structured trace event schema

For every call, emit a trace event including (at minimum):

- request id, parent span id (for nested tool calls)
- caller identity (keypair / project / domain)
- target route resolved (which endpoint / which provider / which model id)
- prompt and completion token counts, finish reason
- latency (queue, ttft, total)
- request metadata (model, temperature, max tokens, tool calls)
- cost estimate (per resolved route)
- guardrail outcomes (allowed / blocked / redacted, with rule id)
- error class and code if applicable

Bodies (prompt/completion text) should be **opt-in per project**, with redaction hooks.

### 3. Trace delivery API

Two delivery modes for downstream consumers:

- **Streaming**: durable event channel (existing event-dispatcher patterns) for near-real-time consumption.
- **Query**: REST/GraphQL surface for historical lookup by request id, caller, project, time range, model.

Retention configurable per project; raw bodies (when enabled) on a shorter TTL than metadata.

### 4. Routing policies

Declarative routing rules at the project or endpoint-set level:

- weighted split / canary / shadow
- failover order with health/error-rate thresholds
- cost ceiling / latency SLO based selection
- variant routing (e.g., A/B by request attribute)

### 5. Validators (guardrails)

Pluggable pre-request and post-response validators with a common interface:

- input: request or response payload, caller context
- output: allow / block / mutate (with reason and rule id, surfaced in trace)

Initial built-ins: PII redaction, schema validation, basic content filters. Third-party validators registered via the existing plugin system.

### 6. Metering

Per-caller, per-project, per-endpoint counters (tokens, requests, accelerator-seconds, estimated cost) exposed through the existing metrics surface, plus quota-style soft/hard limits configurable per project.

## API surface (sketch)

- `POST /v1/llm/chat/completions` — OpenAI-compatible
- `POST /v1/llm/embeddings`
- `GET /v1/llm/traces?project=...&since=...` — query
- `WS /v1/llm/traces/stream` — streaming
- GraphQL: `llmTrace(id)`, `llmTraces(filter)` connection
- GraphQL: `llmRoutingPolicy`, `llmValidator` CRUD

## Open design questions

1. Do we proxy the raw HTTP path, or also expose a native Backend.AI API form?
2. Where does the gateway run — co-located with `app-proxy`, alongside `manager`, or as a separate component?
3. Trace persistence: piggyback on the existing event store, or a dedicated time-series sink (ClickHouse / Loki / Postgres partitioned)? Volume implications.
4. Cost model: do we ship a default per-token price table per known model, allow operator override per endpoint, or both?
5. Body retention defaults: off, on, or sample-N%?
6. External-provider credential storage: extend keypair, or a new credential entity?
7. Rate limiting / fairness: gateway-local or coordinated through `manager`?
8. Backwards compatibility: do existing `Endpoint` direct callers continue to bypass the gateway, or is the gateway eventually mandatory?

## Phasing suggestion

1. **MVP** — passthrough proxy with trace emission (no routing, no validators); query + stream API.
2. **Routing** — weighted/canary/failover policies.
3. **Validators** — pluggable pre/post hooks with built-ins.
4. **External providers** — OpenAI/Anthropic/etc. behind the same surface.
5. **Quotas** — soft/hard budget enforcement.

## Why this belongs in Backend.AI

- The gateway must see *every* inference call regardless of caller — not only those originating from a specific tool. That requires sitting at the data plane.
- Routing and metering are most naturally expressed next to the existing `Endpoint`/`Deployment` and resource-policy primitives.
- Putting it anywhere else means an extra hop, partial observability, or per-tool reinvention.

Happy to iterate on the design — particularly on the open questions above.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.