Spec — Gateway integration prep (dormant) for ahu-gpu-manager
Date: 2026-07-04
Source: integration prompt https://x056.think.val.id/prompt-ai-ahu-chatbot-filled.md
(filled variant; gateway live at http://192.168.83.20:8200).
Canonical contract: ../ahu-gpu-manager/docs/CONVENTIONS.md v1.0 — on any
conflict, that file wins.
Goal
Prepare every model-egress path in this repo for the central GPU/LLM gateway
while staying backwards-compatible and dormant: with no new env vars set,
behavior is byte-for-byte identical to today (modulo the explicitly sanctioned
new static identity headers, which current vLLM/DashScope ignore).
Requirements
- Agno agent headers —
apps/{internal,public}-agent/dash/agents.py:
models built by_resolve_model()/_resolve_synthesis_model()/
_build_model_from_slot()(OpenAILike) send
X-Tenant-Id: ahu-chatbot,X-Surface(fromSURFACEenv),
X-Priority: interactive. Verified against agno==2.4.7 (the requirements
pin shipped in the images):OpenAILikesupports client-level
default_headers; there is no per-run header hook, so per-run
user_id→X-User-Idis impossible today →# TODO(gateway)note. - Close bare-OpenAI egress —
apps/{internal,public}-agent/app/api/knowledge.py:421
constructsOpenAI()callinggpt-4o(api.openai.com, bypasses everything).
Reroute via gateway-resolution logic; model from new env
KNOWLEDGE_LLM_MODEL(defaultgpt-4o). - Queue-awareness —
apps/public-web/src/lib/orchestrator/llm/client.ts:
- 429/503 → structured error carryingRetry-After/X-Queue-Depthso
callers degrade with a friendly "sistem sedang sibuk" message.
- Opt-inLLM_QUEUE_EVENTS=on→ sendX-Queue-Events: onon streaming
requests; parseevent: queueframes ({"position":N,"eta_ms":M})
arriving before the first chat chunk; surface to the browser SSE
status channel; handle post-commitevent: errorframes; ignore unknown
SSE event types.
- DeterministicIdempotency-Keyon every request (never
random-per-attempt). - Env prep (commented, not active) — commented flip blocks in
infra/env/{internal,public,shared}.env(+ committed*.env.example
per repo mandate), incl. note thatSYNTHESIS_API_KEYbecomes obsolete
after the flip. Canonical model names stay as-is (gateway forwards bodies
verbatim). - Verification — suites green, type-check/lint clean, dormancy confirmed,
optional safe live check against the real gateway.
Decisions (with Efran, 2026-07-04)
- knowledge.py gating:
MODEL_GATEWAY_URLis already set in staging
env files (pointing at plain vLLM), so "reroute when MODEL_GATEWAY_URL set"
is not dormant. Decision: gate the reroute on the new env
KNOWLEDGE_LLM_MODELbeing set (Efran, option 2). Unset → bareOpenAI()
byte-for-byte.KNOWLEDGE_LLM_MODEL=Qwen/Qwen3.6-35B-A3B-FP8goes in the
commented flip block. - Queue status wording: prompt says "Antrian ke-N (±Xs)"; CONVENTIONS §3.5
says "Dalam antrean — posisi N (±X menit)". Conventions win →
Dalam antrean — posisi N (±M menit)(minutes rounded up, min 1). - Queue-event surfacing: implemented as a typed event stream
(LlmClient.streamEvents()), not a bare callback — queue frames arrive
while the synthesis stream is held, and only a yielding generator can reach
the browser SSE channel before the first content chunk.stream()remains
as a delta-only wrapper for compatibility. - X-Request-Id: added to the orchestrator's base headers
(${sessionId}-t${turn}) — required by CONVENTIONS §2/§8; one turn = one
trace. - Legacy
X-Priority: planning|synthesisin orchestrate.ts stays — the
gateway maps legacy values during transition (§2); not part of this change. - Python placement: new light module
dash/gateway.py(no agno/DB
imports); tests load it by file path liketests/test_embedder.pydoes,
becausedash/__init__.pyimports the heavydash.agents.
Constraints
- Engine mandate: changes dormant until env flip; no live env value changes —
commented entries only. Mirrors (internal-agent↔public-agent) stay
byte-identical.pnpm check+ agent pytest suites stay green.