From 2c5f510951797a80f7df4a51f2bf14ca11ed4021 Mon Sep 17 00:00:00 2001 From: rock Date: Sat, 31 Oct 2026 19:43:03 -0700 Subject: [PATCH] feat(llm-serving): give the chat model a stable client-facing name poimen-memory will address `reasoning-long-predictor`, and the plan replaces vLLM with candle-vllm underneath. Without an indirection, that handoff changes a hostname the client is pinned to, and the ingest cutover has a window where the name is wrong. This Service is that indirection. It selects the vLLM reasoning pod today. The handoff becomes a selector edit with no client change, so poimen-memory's LLM_CHAT_BASE stays fixed across both engines. Two ports, both verified through the Service from inside the poimen-memory pod: 80 -> 8080 HTTP 200 in 0.0057s matches every other predictor Service and poimen-memory's LLM_CHAT_BASE, which has no port in the URL 8080 -> 8080 HTTP 200 in 0.0182s the container port, the only port the two NetworkPolicies permit, and the port the handoff readiness check uses Existing predictor Services publish port 80 only, so a curl to :8080 on one of them fails to connect. Publishing both here means neither caller has to know which convention the other follows. Verified the selector resolves to the intended pod and nothing else: one endpoint, 10.244.4.171, which is reasoning-predictor-59b67d5f5d-6mbbb. Residual state, deliberately: /v1/models through this Service returns ['reasoning'], not ['reasoning-long'], because the second served name lands in the previous commit and ArgoCD selfHeal reverts a live patch to a tracked pod spec. Both names appear after this branch merges and syncs. Not deleted and recreated at handoff. A delete drops the ClusterIP, and a client that cached the name across the gap fails until it re-resolves. --- k8s/apps/llm-serving/kustomization.yaml | 3 ++ .../llm-serving/reasoning-long-service.yaml | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 k8s/apps/llm-serving/reasoning-long-service.yaml diff --git a/k8s/apps/llm-serving/kustomization.yaml b/k8s/apps/llm-serving/kustomization.yaml index 5680790..801ae29 100644 --- a/k8s/apps/llm-serving/kustomization.yaml +++ b/k8s/apps/llm-serving/kustomization.yaml @@ -19,6 +19,9 @@ resources: # model-name change. reasoning.yaml below can be dropped once that host change lands. - coder.yaml - reasoning.yaml + # Stable client-facing name for the chat model. poimen-memory points at this Service, + # so the vLLM -> candle-vllm handoff is a selector edit here and no client change. + - reasoning-long-service.yaml # ornith is retired: its InferenceService still reports Ready=True while running zero # replicas, because desired equals available at 0. Its manifest was never committed, # so it is left unmanaged and is not listed here. Delete the CR when convenient. diff --git a/k8s/apps/llm-serving/reasoning-long-service.yaml b/k8s/apps/llm-serving/reasoning-long-service.yaml new file mode 100644 index 0000000..2b05330 --- /dev/null +++ b/k8s/apps/llm-serving/reasoning-long-service.yaml @@ -0,0 +1,43 @@ +# Stable client-facing name for the chat model, deliberately decoupled from the +# process that currently serves it. +# +# poimen-memory addresses this Service, not a pod and not an InferenceService. That +# means the vLLM -> candle-vllm handoff changes only this file's selector. The client +# config never changes again, so the ingest cutover has no window in which the host +# name is wrong. +# +# Two ports on purpose: +# - 80 matches every other predictor Service in this namespace, and matches +# poimen-memory's LLM_CHAT_BASE, which carries no port number. +# - 8080 is the container port. It is also the only port llm-serving-ingress and +# poimen-memory-llm-egress permit, and the port the handoff readiness check +# uses. Publishing it directly avoids a port translation that is easy to get +# wrong in a curl. +# +# The selector names the vLLM reasoning pod today. To hand off, replace the three +# selector labels with candle-vllm's pod labels and nothing else. Do not delete and +# recreate this Service: a delete drops the ClusterIP, and a client that cached the +# name during the gap will fail until it re-resolves. +apiVersion: v1 +kind: Service +metadata: + name: reasoning-long-predictor + namespace: llm-serving + labels: + app.kubernetes.io/part-of: llm-serving + app: reasoning-long-predictor +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: llm-reasoning + component: predictor + serving.kserve.io/inferenceservice: reasoning + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 + - name: http-8080 + protocol: TCP + port: 8080 + targetPort: 8080