docs(operations): LLM usage-attribution research catalog
Deep-research reference for per-consumer tracking of the fleet endpoints: measured baseline (no identity anywhere; frank-zealot /metrics 501), the seven identity layers, engine-native/gateway/tracing/network catalogs with citations, and a phased adoption plan.
This commit is contained in:
1 parent
a9d45e7012
commit
cf6319aad7
3 files changed
+205
No files matched your search
@@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: LLM Usage Attribution
|
||||||
|
description: Research catalog of every viable way to answer "who is using the fleet LLMs" — engine-native identity, gateways, auth proxies, tracing platforms, network/host attribution — with the measured fleet baseline (2026-10-13) and a phased adoption plan.
|
||||||
|
tags: [llm, observability, auth, gateway, litellm, tracking, identity]
|
||||||
|
timestamp: 2026-10-14T00:00:00-07:00
|
||||||
|
---
|
||||||
|
|
||||||
|
# LLM Usage Attribution
|
||||||
|
|
||||||
|
Deep-research catalog (2026-10-13/14): solutions for tracking WHO uses the
|
||||||
|
fleet endpoints (`frank-archon`, `frank-dragoon`, `frank-zealot`). Full web
|
||||||
|
citations; live-fleet measurements marked **(measured)**.
|
||||||
|
|
||||||
|
## Baseline: what the fleet answers today
|
||||||
|
|
||||||
|
- All three endpoints are `auth: none`, plain HTTP, LAN-only
|
||||||
|
(`models.yml` providers; no vhost on 7770/7771).
|
||||||
|
- **(measured)** Live `/metrics` label census: `frank-archon` exports
|
||||||
|
`vllm:*`+`radiance:*` with labels only `{model_name, engine, le,
|
||||||
|
finished_reason, position, class}` — zero identity labels.
|
||||||
|
`frank-dragoon` exports `halogen:*`+`llamacpp:*`.
|
||||||
|
`frank-zealot` answers **501** — `serve-frank-zealot.sh` never passes
|
||||||
|
`--metrics`, so the Prometheus job is structurally never-green
|
||||||
|
(`LlmEndpointDown` fires). Fix: add `--metrics` to the ARGS array.
|
||||||
|
- **(measured)** frank-zealot verbosity-4 journal lines carry slot/task/timing
|
||||||
|
only — no client IP, no user. Alloy ships journal + podman ctr.log to Loki,
|
||||||
|
so what lands there has no identity either.
|
||||||
|
- Caddy has no `log` directive anywhere → no per-request access logs on the
|
||||||
|
OpenWebUI vhost.
|
||||||
|
- Net: fleet-level token telemetry exists per ENDPOINT; per-CONSUMER
|
||||||
|
identity exists nowhere.
|
||||||
|
|
||||||
|
## The identity layers (pick the layer, get its resolution)
|
||||||
|
|
||||||
|
| # | Layer | Identity | Answers |
|
||||||
|
|---|---|---|---|
|
||||||
|
| L0 | DHCP/ARP | MAC, hostname, lease | which device was on the LAN |
|
||||||
|
| L1 | DNS (Pi-hole v6) | device → queries | which device resolved/used a name |
|
||||||
|
| L2 | L3/L4 (nftables/conntrack, JA3/JA4) | IP, TLS client lib | bytes per client, client software |
|
||||||
|
| L3 | Transport (API keys, mTLS, SPIFFE, Tailscale) | key/cert/node | which credential connected |
|
||||||
|
| L4 | App gateway (virtual keys → user/team/end_user) | named consumer | tokens/spend/cost per who |
|
||||||
|
| L5 | Trace/content (Langfuse, Helicone, halogen records) | user_id, session, prompt text | what they asked, per session |
|
||||||
|
| L6 | Client host (auditd, eBPF, netdata) | pid/uid/comm per connect | which process on which host |
|
||||||
|
|
||||||
|
Keys/headers/`user` fields are CLIENT-DECLARED — they group traffic, they
|
||||||
|
prove nothing about a human (halogen's own docs warn this verbatim:
|
||||||
|
"they group requests, and they prove nothing about who sent them").
|
||||||
|
|
||||||
|
## Catalog
|
||||||
|
|
||||||
|
### A. Engine-native (per-consumer identity inside the inference server)
|
||||||
|
|
||||||
|
| Engine | AuthN | Identity in logs | Per-identity metrics | OTel | Notes |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| llama.cpp | `--api-key` (multi, comma) / `--api-key-file` **(measured in the deployed build's `--help`)** | request `user` field drives slot assignment/eviction; `--log-requests` dumps bodies | `--metrics` exports `llamacpp:*`, NO key/user label | none upstream | [server README](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md) |
|
||||||
|
| vLLM | `--api-key` (multi) | request log; no user persistence | `vllm:*`; NO `api_key` label (cardinality, issue #53926) | native (`--otlp-traces-endpoint`) | [metrics design](https://github.com/vllm-project/vllm/blob/main/docs/design/metrics.md) |
|
||||||
|
| radiance | no public auth docs **[unverified]** | — | **(measured)** no identity labels on live /metrics | none found | closed deployment surface; treat authN as absent until probed |
|
||||||
|
| halogen-flash-server | `HALOGEN_API_KEY` / `HALOGEN_API_KEY_FILE` (multi, comma) | **`HALOGEN_RECORD_DIR` request records**: `turns/*.jsonl` per request — `client_family` (User-Agent), tokens, timings, finish, tool calls, plus HASHES of `user` / `prompt_cache_key` / `safety_identifier` / `metadata.user_id` (`.salt`-keyed); `units/` content-addressed text; caps `HALOGEN_RECORD_GIB`=2, `HALOGEN_RECORD_DAYS`=90; `HALOGEN_RECORD_TEXT=none` for metadata-only; `stats`/`prune` tools in-container | `halogen:*`+`llamacpp:*`, no per-key label | none | [configuration.md](https://github.com/peonist-ai/halogen-flash-server/blob/main/docs/configuration.md) — verbatim-verified 2026-10-13. Best native ledger of the fleet. |
|
||||||
|
| SGLang | `--api-key` (single) | `--log-requests` | `sglang:*`, no key label | native (`--enable-trace`) | for any future swap |
|
||||||
|
| Ollama | single key | request log | `ollama:` no key label | none | not in fleet |
|
||||||
|
|
||||||
|
Wire fields usable from clients today (all client-declared): OpenAI
|
||||||
|
`user`, `safety_identifier`, `prompt_cache_key`, Messages
|
||||||
|
`metadata.user_id`; LiteLLM `end_user_id`; gateway header conventions
|
||||||
|
(`X-Forwarded-User`, `Helicone-User-Id`, `cf-aig-metadata`).
|
||||||
|
|
||||||
|
### B. LLM gateways (front door with virtual keys — the only layer with
|
||||||
|
per-consumer TOKEN counts)
|
||||||
|
|
||||||
|
| Solution | Identity | Usage lands | License | Footprint |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **LiteLLM Proxy** | virtual keys → `user_id`/`team_id`/`end_user`; budgets, rate limits | Postgres spend ledger; `/metrics` labels `api_key_alias,user,team,end_user,model,client_ip,user_agent` (`litellm_*_tokens_metric`, spend, budget gauges); OTel/Langfuse callbacks | MIT core (SSO/RBAC/audit-log enterprise) | ~150–300 MiB + Postgres |
|
||||||
|
| Kong AI Gateway | Consumer + key-auth/JWT/OIDC/mTLS; AI consumer groups | Postgres; `kong_*` per-consumer; file-log/zipkin/otel | OSS core; token-aware AI plugins enterprise | 0.5–1 GiB |
|
||||||
|
| Apache APISIX | Consumer + key-auth/JWT | `apisix_llm_prompt_tokens`/`completion_tokens` with `consumer` label; http-logger/kafka | Apache-2.0 | 200–400 MiB |
|
||||||
|
| Bifrost (maximhq) | virtual keys `sk-bf-*`/`x-bf-vk`, budgets | Prometheus/OTel, config store (SQLite/Postgres) | Apache-2.0, Go | ~150–300 MiB |
|
||||||
|
| Portkey OSS gateway | virtual keys | logs/OTel | MIT | 200–400 MiB |
|
||||||
|
| Higress AI GW | keys (+OIDC ent) | `higress_*`, ai-logging | Apache-2.0 CE | Istio-based, 0.5 GiB+ |
|
||||||
|
| Envoy AI GW / Gateway API Inference Extension / LLM Gatekeeper | delegated auth | Envoy metrics/access logs | Apache-2.0 | K8s-native |
|
||||||
|
| Gloo / KrakenD / Gravitee AI gateways | keys, OIDC | metrics/logs | OSS core, AI features enterprise | heavy |
|
||||||
|
| Cloudflare AI Gateway | Access JWT `cf.user_id`, `cf-aig-metadata` | Cloudflare-managed analytics/logs | SaaS (exotic for LAN; Tunnel possible) | edge |
|
||||||
|
|
||||||
|
([LiteLLM virtual keys](https://docs.litellm.ai/docs/proxy/virtual_keys),
|
||||||
|
[prometheus](https://docs.litellm.ai/docs/proxy/prometheus),
|
||||||
|
[APISIX ai-proxy](https://apisix.apache.org/docs/apisix/plugins/ai-proxy/),
|
||||||
|
[Bifrost](https://github.com/maximhq/bifrost),
|
||||||
|
[Kong AI](https://developer.konghq.com/index/ai-gateway/))
|
||||||
|
|
||||||
|
### C. Auth proxies / identity infrastructure (identity without token counts)
|
||||||
|
|
||||||
|
- **Ory Oathkeeper** (MIT, ~50–100 MiB): per-consumer api-key/JWT/
|
||||||
|
client-credentials → header mutation (`X-User: {{ .Subject }}`) +
|
||||||
|
decision logs.
|
||||||
|
- **oauth2-proxy** (Apache-2.0): OIDC (Authentik) incl. client_credentials,
|
||||||
|
`--pass-user-headers` → `X-Forwarded-User`; ~100 MiB.
|
||||||
|
- **Authentik itself**: proxy-provider custom headers (already injects
|
||||||
|
`X-Authentik-Username`/`-Groups`, spoof-stripped — the /whoami seam proves
|
||||||
|
the pattern on this host); event log + webhook forwarding.
|
||||||
|
- **Caddy `tls client_auth`** (mTLS per client cert) + access-log fields —
|
||||||
|
Caddy already fronts both hosts.
|
||||||
|
- **Smallstep step-ca**: short-lived per-device certs, ACME-style.
|
||||||
|
- **SPIFFE/SPIRE**: per-workload SVIDs (k8s-flavored; heavy for quadlets).
|
||||||
|
- **Tailscale / NetBird**: per-node identity, ACL + network-flow logs,
|
||||||
|
headscale for self-hosted control. Best for off-LAN laptops.
|
||||||
|
- **ApiAuth** (nginx auth_request): the minimal per-consumer apikey sidecar.
|
||||||
|
|
||||||
|
### D. LLM observability / tracing (prompt-level per-user audit)
|
||||||
|
|
||||||
|
| Platform | Identity | Content | Self-host | Shape |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Langfuse** | `user_id`,`session_id`, tags | full input/output, masking hook | ✅ ClickHouse, Apache-2.0 | fed via LiteLLM callback / OTLP / SDK |
|
||||||
|
| **Helicone** | api-key→user, `Helicone-User-Id` | full, tiered redaction | ✅ compose (ClickHouse+PG+Redis+Jawn), Apache-2.0 | PROXY — can be the front door itself |
|
||||||
|
| Arize Phoenix | `user.id`,`session.id` | opt-in `gen_ai.*` | ✅, Apache-2.0 | OTLP collector |
|
||||||
|
| OpenLIT | `user.id`,`session.id` | 3-tier capture | ✅, Apache-2.0 | OTLP+ClickHouse |
|
||||||
|
| SigNoz LLM | OTel attrs | opt-in | ✅, Apache-2.0 | OTLP |
|
||||||
|
| OpenLLMetry/Traceloop | assoc props | opt-in | ✅ Helm | OTel |
|
||||||
|
| Lunary / W&B Weave / LangSmith | user/session | full | enterprise-gated | — |
|
||||||
|
|
||||||
|
OTel GenAI semconv (Development-stability): `gen_ai.operation.name`,
|
||||||
|
`gen_ai.provider.name`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`/
|
||||||
|
`output_tokens`, `gen_ai.conversation.id`, content attrs opt-in; `user.id`
|
||||||
|
from the registry. vLLM and SGLang emit OTLP natively; llama.cpp only via
|
||||||
|
external SDK — **the fleet's engines are OTel-silent at request level**,
|
||||||
|
which is why the gateway callback (LiteLLM→Langfuse) is the practical feed.
|
||||||
|
|
||||||
|
### E. Network/host passive attribution (no client cooperation)
|
||||||
|
|
||||||
|
- **Pi-hole v6** (already on Dankclaw): query log API per device,
|
||||||
|
`pihole6_exporter` + query-logger→Loki (GPLv3 exporter).
|
||||||
|
- **Caddy access logs**: add a `log` directive (JSON) → Alloy→Loki; with the
|
||||||
|
auth chain it carries `X-Authentik-Username` for browser users.
|
||||||
|
- **OpenWebUI admin**: per-user daily token stats, events/logs, analytics API
|
||||||
|
(`/api/v1/analytics/*`) — per-USER identity exists here already (MIT).
|
||||||
|
- **auditd** connect-syscall rules (uid/pid/comm per connect to 7770/7771),
|
||||||
|
ship via audisp→Loki.
|
||||||
|
- **eBPF**: Tetragon (process+socket events, policies), Coroot, DeepFlow,
|
||||||
|
bpftrace `tracepoint:syscalls:sys_enter_connect` one-liners. K8s-shaped
|
||||||
|
for single hosts, but agents.plugin-style tools work.
|
||||||
|
- **nftables named counters** per consumer IP (bytes; reload resets).
|
||||||
|
- **netdata apps.plugin**: per-process network bytes, auto-discovered.
|
||||||
|
- **JA3/JA4 TLS fingerprinting**: client library only, TLS-only; the LAN is
|
||||||
|
plaintext HTTP today → use the HTTP **User-Agent census** instead (free
|
||||||
|
from any access log).
|
||||||
|
- **DHCP/ARP census** (leases + `ip neigh`): device roster.
|
||||||
|
|
||||||
|
## Phased plan for this fleet
|
||||||
|
|
||||||
|
**Phase 0 — repo-only, no new services**
|
||||||
|
1. frank-zealot: add `--metrics` (fixes the measured 501 + the
|
||||||
|
structurally-red Prometheus job + the firing `LlmEndpointDown`).
|
||||||
|
2. frank-dragoon: set `HALOGEN_API_KEY_FILE` (multi-key per consumer) +
|
||||||
|
mount `HALOGEN_RECORD_DIR` (start `HALOGEN_RECORD_TEXT=none`; flip to
|
||||||
|
`all` if prompt audit is wanted) + Alloy ship of `turns/`.
|
||||||
|
3. frank-zealot: `--api-key-file` (multi-key, verified in the deployed
|
||||||
|
build); radiance: probe for `--api-key` support (contract facts are
|
||||||
|
silent — add to `fleet-probes`).
|
||||||
|
4. models.yml: per-consumer bearer keys (`auth: none` → key) — the provider
|
||||||
|
schema supports apiKey (pi-era conversion note).
|
||||||
|
5. Caddy: `log` on `ai.franksdank.cloud` (OpenWebUI browser census, JSON →
|
||||||
|
Loki).
|
||||||
|
Limits: keys gate access, and only the engines' request records (halogen)
|
||||||
|
see them per-request; llama.cpp/radiance/vLLM metrics stay identity-blind.
|
||||||
|
|
||||||
|
**Phase 1 — LiteLLM Proxy quadlet on Dankclaw = the front door.**
|
||||||
|
One virtual key per consumer (omp-<workstation> ×N, openwebui, hermes,
|
||||||
|
agents, bench). Per-key/user/team Prometheus labels into the existing
|
||||||
|
Grafana+Alertmanager; Postgres spend ledger; budgets + rate limits per
|
||||||
|
key. This is the ONLY layer that yields tokens-per-who without engine
|
||||||
|
cooperation. (Bifrost is the lighter Apache-2.0 Go alternative; Helicone
|
||||||
|
the proxy-native-observability alternative.)
|
||||||
|
|
||||||
|
**Phase 2 — prompt-level audit.** Langfuse self-hosted fed by the LiteLLM
|
||||||
|
callback: per-user traces, sessions, cost; masking + retention knobs.
|
||||||
|
|
||||||
|
**Phase 3 — ambitious identity infra.** step-ca + Caddy mTLS per device;
|
||||||
|
Tailscale (+flow logs) for off-LAN; auditd/eBPF process attribution on the
|
||||||
|
agent hosts; Pi-hole query logger; nft counters per consumer IP. Browser
|
||||||
|
users stay owned by OpenWebUI analytics + its per-user API keys; a pipeline
|
||||||
|
can forward `X-Forwarded-User` so the gateway sees them too.
|
||||||
|
|
||||||
|
## Cautions
|
||||||
|
|
||||||
|
- Client-declared identity (keys, `user`, headers) groups traffic; it does
|
||||||
|
not authenticate humans. Authenticated identity = Authentik (browser) /
|
||||||
|
mTLS (device) / Tailscale (node).
|
||||||
|
- Plain-HTTP LAN = bearer keys sniffable by any tenant; treat keys as
|
||||||
|
identity labels on the LAN, not secrets, until TLS/mTLS at the door.
|
||||||
|
- Prompt-content logging stores a plaintext copy of everything — set
|
||||||
|
retention caps (halogen GIB/days; Langfuse masking; Loki retention) and
|
||||||
|
gitignore the record dirs.
|
||||||
|
- Household-scale cardinality is safe for per-key Prometheus labels;
|
||||||
|
the engines' refusal of key labels is a multi-tenant concern, not ours.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
- [[Fleet Audit and Posture]] — monitoring gaps census
|
||||||
|
- [Observability](../Infrastructure/Observability.md) — the Loki/Prometheus
|
||||||
|
pipes every sink above uses
|
||||||
|
- [LLM index](../LLM/index.md) — endpoint runbooks (env trios the Phase-0
|
||||||
|
edits touch)
|
||||||
@@ -23,6 +23,9 @@ Procedures for operating the fleet. The LLM-specific pages live under
|
|||||||
posture, whole-fleet audit, bulk redaction, stash audits
|
posture, whole-fleet audit, bulk redaction, stash audits
|
||||||
- [[SSH and Remote Access]] — passwordless bootstrap without sshpass, fish
|
- [[SSH and Remote Access]] — passwordless bootstrap without sshpass, fish
|
||||||
login shells, GUI-hang diagnosis over ssh
|
login shells, GUI-hang diagnosis over ssh
|
||||||
|
- [[LLM Usage Attribution]] — who-is-using-the-fleet-LLMs research catalog:
|
||||||
|
engine-native identity, gateways, tracing, network/host attribution, and
|
||||||
|
the phased plan
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
- [Observability Provisioning](../Infrastructure/Observability%20Provisioning.md)
|
- [Observability Provisioning](../Infrastructure/Observability%20Provisioning.md)
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ skill-cluster. See [log.md](log.md) for the wave plan and results.
|
|||||||
posture, whole-fleet audit, bulk redaction
|
posture, whole-fleet audit, bulk redaction
|
||||||
- [[SSH and Remote Access]] — passwordless bootstrap, fish login shells,
|
- [[SSH and Remote Access]] — passwordless bootstrap, fish login shells,
|
||||||
GUI-hang diagnosis over ssh
|
GUI-hang diagnosis over ssh
|
||||||
|
- [[LLM Usage Attribution]] — who-is-using-the-fleet-LLMs research catalog:
|
||||||
|
engine-native identity, gateways, tracing, network/host attribution,
|
||||||
|
phased plan
|
||||||
|
|
||||||
**Architecture:**
|
**Architecture:**
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user