Resolve ai-service directly from the RAG routes, never the gateway

This commit is contained in:
2026-08-19 10:16:44 +07:00
parent f50ccfc5f7
commit 24c55d1627
13 changed files with 470 additions and 22 deletions
+139
View File
@@ -0,0 +1,139 @@
"""Turn apiGateway back off on the live Application to restore the chat.
## Why
Enabling apiGateway makes the Helm chart set `API_GATEWAY_URL` on the web pod
(infra/helm/medical-chatbot/templates/web.yaml). Every one of web's BFF routes
prefers that variable over `AI_SERVICE_URL`:
apps/web/app/api/chat/route.ts:7
apps/web/app/api/suggest/route.ts:5
apps/web/app/api/history/route.ts:5
apps/web/app/api/sections/route.ts:5
apps/web/app/api/section-text/route.ts:5
apps/web/app/api/feedback/route.ts:5
process.env.API_GATEWAY_URL ?? process.env.AI_SERVICE_URL ?? ...
but the gateway only proxies `/auth/*` (apps/api-gateway/src/proxy/ has a
single AuthProxyController). So all six start posting to a service with no
such route, and the UI shows "Dịch vụ AI Service đang khởi động hoặc gặp sự
cố tạm thời."
The bug shipped in PR #27 yesterday and sat dormant: the configuration that
triggers it was never actually rendered until the 2026-08-19 repair of the
folded-scalar corruption deployed it for the first time.
## What this does
Flips `enabled` to false under the `apiGateway` key only -- `authService`'s
own `enabled: true` is left alone -- so `API_GATEWAY_URL` stops being set and
all six routes fall back to `AI_SERVICE_URL`, exactly the configuration that
served traffic before today.
Cost: login stops working again (apps/web/app/api/auth/{login,me}/route.ts
read API_GATEWAY_URL with no fallback). That is the deliberate trade -- chat
is the product and affects every visitor; login is a day-old addition that
was not reachable before today anyway. The real fix is to stop routing RAG
through the gateway at all, which needs an image rebuild.
Refuses to act unless it finds `apiGateway:` followed by `enabled: true`, so
it cannot silently do something else.
Required env: ARGOCD_PRACTICE_URL, ARGOCD_PRACTICE_PASSWORD.
"""
from __future__ import annotations
import json
import os
import sys
import time
import urllib.error
import urllib.request
APP_NAME = "medical-chatbot-app"
POLL_SECONDS = 15
POLL_ROUNDS = 8
def call(base: str, method: str, path: str, token: str | None = None, body=None):
req = urllib.request.Request(
f"{base}{path}",
data=json.dumps(body).encode() if body is not None else None,
method=method,
headers={"Content-Type": "application/json"},
)
if token:
req.add_header("Authorization", f"Bearer {token}")
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
def disable_api_gateway(values: str) -> str:
"""Set `enabled: false` under the apiGateway key, and nowhere else."""
lines = values.splitlines()
out: list[str] = []
in_gateway = False
changed = 0
for line in lines:
stripped = line.strip()
# A non-indented key ends whatever block we were in.
if line and not line[0].isspace():
in_gateway = stripped == "apiGateway:"
elif in_gateway and stripped == "enabled: true":
indent = line[: len(line) - len(line.lstrip())]
line = f"{indent}enabled: false"
changed += 1
out.append(line)
if changed != 1:
raise SystemExit(
f"Expected exactly one `enabled: true` under apiGateway, changed {changed}. "
"Refusing to write."
)
return "\n".join(out) + "\n"
def main() -> int:
base = os.environ["ARGOCD_PRACTICE_URL"].rstrip("/")
password = os.environ["ARGOCD_PRACTICE_PASSWORD"]
session = call(base, "POST", "/api/v1/session", body={"username": "admin", "password": password})
token = session["token"]
app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token)
values = app["spec"]["source"]["helm"].get("values", "")
repaired = disable_api_gateway(values)
print("apiGateway.enabled -> false (authService untouched)")
app["spec"]["source"]["helm"]["values"] = repaired
call(base, "PUT", f"/api/v1/applications/{APP_NAME}", token=token, body=app)
print("PUT accepted.")
try:
call(base, "POST", f"/api/v1/applications/{APP_NAME}/sync", token=token, body={})
print("Sync triggered.")
except urllib.error.HTTPError as exc:
if exc.code == 400:
print("Explicit sync raced with autosync -- continuing.")
else:
raise
for i in range(POLL_ROUNDS):
time.sleep(POLL_SECONDS)
app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token)
status = app.get("status", {})
print(
f"poll {i + 1}/{POLL_ROUNDS}: sync={status.get('sync', {}).get('status')} "
f"health={status.get('health', {}).get('status')}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())