# INV-10: Ollama Call-Site Inventory

**版本**：v1.0 初稿
**日期**：2026-05-05（台北）
**範圍**：`apps/api/src/`、`apps/api/scripts/`、K8s prod manifests、ADR-110、AwoooP docs
**用途**：AwoooP EffectivePolicy / Provider Gateway 導入前，先盤清所有 Ollama 入口，避免 GCP-B 只存在於 failover manager 而沒有真正被業務路徑使用。

---

## 1. 目前正式拓撲

| Layer | Runtime endpoint | Upstream | Provider name | 建議角色 |
|------|------------------|----------|---------------|----------|
| Primary | `http://192.168.0.110:11435` | GCP-A `34.143.170.20:11434` | `ollama_gcp_a` / `ollama` alias | 即時對話、Hermes、OpenClaw、低延遲診斷 |
| Secondary | `http://192.168.0.110:11436` | GCP-B `34.21.145.224:11434` | `ollama_gcp_b` | 批次分析、RAG/embedding、shadow/canary、新模型驗證 |
| Tertiary | `http://192.168.0.110:11437` | Local `.111` `192.168.0.111:11434` | `ollama_local` | `local_required`、高敏感資料、DR fallback |
| Emergency | provider API | Gemini → Nemotron → Claude | paid/cloud providers | 全 Ollama 不可用時，需 budget hard kill 保護 |

**source of truth**：

- `k8s/awoooi-prod/04-configmap.yaml` 已宣告 110 proxy 三層路由。
- `k8s/awoooi-prod/06-deployment-api.yaml` 必須與 ConfigMap 保持一致；更新 route 時不可整檔 apply placeholder image。
- `infra/ansible/roles/nginx/templates/110-ollama-proxy.conf.j2` 是 110 proxy template。
- `apps/api/src/services/ollama_failover_manager.py` 是目前 runtime failover 決策核心。

---

## 2. 已進 failover-aware / provider-registry 的路徑

| 位置 | 狀態 | 說明 | AwoooP posture |
|------|------|------|----------------|
| `apps/api/src/services/ollama_failover_manager.py` | ✅ 三層 aware | 讀 `OLLAMA_URL` / `OLLAMA_SECONDARY_URL` / `OLLAMA_FALLBACK_URL`，輸出 `ollama_gcp_a` / `ollama_gcp_b` / `ollama_local` | `wrap`，之後 provider health 成為 platform resource |
| `apps/api/src/services/ai_router.py` | ✅ provider alias aware | 註冊 `ollama`、`ollama_gcp_a`、`ollama_gcp_b`、`ollama_local` | `wrap`，EffectivePolicy 先讀後寫 |
| `apps/api/src/services/ai_providers/ollama.py` | ✅ GCP-B provider 已補 | `OllamaGcpBProvider` 使用 `_endpoint_url()` → `OLLAMA_SECONDARY_URL`；已補回歸測試防止選 B 卻打 A | `keep`，短期可作 registry backend |
| `apps/api/src/services/ollama_endpoint_resolver.py` | ✅ workload-aware | `embedding` / `rag` / `code_review` / `batch` / `shadow` / `canary` 優先 GCP-B，interactive 優先 GCP-A，local-required 優先 Local | `wrap`，低風險 active-active slice |
| `apps/api/src/routes/health.py` | ✅ 三端點 health | 同時探 primary/secondary/tertiary | `wrap`，補 provider label 與 OTel span |
| `apps/api/tests/test_ollama_failover_manager.py` | ✅ 測試覆蓋 | GCP-A/B/Local failover matrix | `keep`，後續補 110 proxy defaults |

---

## 3. 仍直接讀 `OLLAMA_URL` 的 production call sites

這些路徑目前只會使用 primary endpoint。當 GCP-A 可用時行為正常，但無法依 intent / project / load 主動使用 GCP-B，也不一定會被 failover manager 的決策覆蓋。

| 類別 | 位置 | 用途 | 風險 | 建議改造 |
|------|------|------|------|----------|
| API / health | `apps/api/src/api/v1/health.py:110` | 單點 `/api/tags` health | 只看 primary | 改讀 provider health snapshot |
| API / ai debug | `apps/api/src/api/v1/ai.py:278` | 回傳 settings endpoint | 顯示層，不危險 | 顯示三層 topology |
| API / RAG endpoint | `apps/api/src/api/v1/rag.py:80` | ad-hoc embedding endpoint | 還會搶 GCP-A | 下一輪改 resolver 或委派 `EmbeddingService` |
| Agent route | `apps/api/src/routes/agent.py:25` | agent module 取 Ollama URL | 只拿 primary | 改為 resolver |
| Hermes | `apps/api/src/hermes/nl_gateway.py:269` | NL gateway model call | Hermes 只打 primary | Hermes 仍需同步，但 provider 來源改 EffectivePolicy read-only |
| OpenClaw | `apps/api/src/services/openclaw.py:448,458,997,1117` | generate / orchestrator context | 高流量路徑卡 primary | 第一批改 resolver，但保持 legacy output |
| Decision | `apps/api/src/services/decision_manager.py:620,713` | decision helper model call | Tier 3 高風險，不直接重寫 | mirror metrics，最後 wrap |
| Decision fusion | `apps/api/src/services/decision_fusion.py:191` / `decision_fusion_adapter.py:257` | fusion/adapter fallback | 舊 comment 仍寫 111 primary | docs + test 先修；runtime 走 strangler |
| Image analysis | `image_analysis_service.py:127` | vision-ish generate | 可能長任務 | 導向 GCP-B 或專用 model policy |
| Intent classifier | `intent_classifier.py:552` | intent classify | latency-sensitive | 留 GCP-A，但經 resolver |
| Chat manager | `chat_manager.py:172,178` | chat model call | 高流量 primary-only | 先 read-only EffectivePolicy compare |
| Nvidia provider shim | `nvidia_provider.py:878,895,991` | OpenAI-compatible local endpoint | 名稱與實際 provider 容易混淆 | 明確標 `ollama_openai_compat` |
| Heartbeat/report | `heartbeat_report_service.py:230` | tags health | health 只看 primary | 改三層 health |
| Log/drift/knowledge extractor | `log_summary_service.py:39`、`drift_narrator_service.py:39`、`knowledge_extractor_service.py:21` | helper 取 URL | helper 會擴散 primary-only | helper 改 resolver 或標 deprecated |

---

## 3.1 已遷到 GCP-B batch lane 的第一批路徑

| 類別 | 位置 | 變更 | 狀態 |
|------|------|------|------|
| Embedding service | `apps/api/src/services/embedding_service.py` | 預設 endpoint 改用 `resolve_ollama_endpoint("embedding")` | ✅ GCP-B preferred |
| Knowledge RAG | `apps/api/src/services/knowledge_rag_service.py` | `_embed()` 走 `embedding` lane，`_generate_answer()` 走 `rag` lane | ✅ GCP-B preferred |
| Playbook RAG | `apps/api/src/services/playbook_rag.py` | `self.ollama_url` 改用 `embedding` lane | ✅ GCP-B preferred |
| Local code review | `apps/api/src/services/local_code_review_service.py` | PR / push review 改用 `code_review` lane | ✅ GCP-B preferred |

這批不包含 `decision_manager.py`、`OpenClaw`、`Hermes`、`chat_manager` 等互動/決策主線。

---

## 4. Script / test / doc drift

| 位置 | 狀態 | 處理 |
|------|------|------|
| `apps/api/scripts/reembed_bge_m3.py` | default 直連 GCP-A | 批次 embedding 應預設 GCP-B 或接收 `OLLAMA_URL=110:11436` |
| `apps/api/tests/test_failover_e2e_dispatch.py` | 仍以 188 fallback 舊語義命名 | 測試命名需配合 ADR-110 更新，避免「188 fallback」誤導 |
| `apps/api/tests/test_model_version_probe.py` | 多處 mock fallback=188 | 不一定影響 runtime，但應在測試 debt 中列入 |
| `docs/runbooks/DEPLOY-GCP-OLLAMA-PROXY.md` | 已更新為 110:11435/11436/11437 | 後續驗證 live env 後補實測時間 |
| `docs/awooop/DETAILED-IMPLEMENTATION-PLAN.md` | 仍描述 direct GCP IP 為拓撲主體 | 已在本 INV 標註；下一輪收斂為 runtime proxy + upstream direct IP |
| live `awoooi-api` Deployment | `OLLAMA_FALLBACK_URL=192.168.0.111:11434`，ConfigMap 已是 `110:11437` | 需用 `kubectl set env` 或下一次安全 rollout 對齊；目前 Local fallback 實際不可用 |
| live NetworkPolicy | Pod → 110 只允許 `11435/11436` | repo manifest 已補 `11437`，但未 live apply |

---

## 5. AwoooP 使用策略

### 5.1 Compute Pool，不是單純 Active/Passive

GCP-A / GCP-B 不應只做「A 掛才用 B」：

- GCP-A：即時 interactive path，OpenClaw/Hermes/intent classify/low-latency diagnose。
- GCP-B：batch/RAG/embedding/reindex/eval/shadow/canary/model warmup。
- Local `.111`：privacy-sensitive/local-required/DR。

### 5.2 EffectivePolicy 必要欄位

後續 Provider Gateway 或 EffectivePolicy 至少要能吃：

- `project_id`
- `agent_id`
- `intent`
- `complexity`
- `privacy_level`
- `workload_type`: `interactive | batch | embedding | shadow | canary | healthcheck`
- `provider_health`
- `queue_depth`
- `budget_state`

### 5.3 Metrics label 規則

允許：

- `provider=ollama_gcp_a|ollama_gcp_b|ollama_local`
- `project_id`
- `agent_id`
- `workload_type`
- `status`

禁止：

- `run_id`
- `trace_id`
- `session_id`
- raw prompt hash 以外的 prompt 內容

---

## 6. 修補順序

| Priority | 工作 | 行為風險 | 備註 |
|----------|------|----------|------|
| P0 | 讓 ConfigMap / Deployment / ADR / Runbook 全部對齊 110 proxy 三層拓撲 | low | docs + manifest source-of-truth，不 live apply；已補 `test_prod_ollama_env_matches_configmap_source_of_truth` |
| P0 | 將 direct call sites 納入 `forbid-new` 規則：新增 Ollama 呼叫必須經 resolver 或 provider registry | low | 已補 `test_no_new_direct_ollama_url_call_sites`，以目前 legacy count 作上限 |
| P1 | RAG / embedding / local code review 批次路徑導向 GCP-B | medium | 第一批 service-level slice 已完成；剩 `api/v1/rag.py` 與 scripts |
| P1 | health/report 路徑改三層 provider health snapshot | low | 提升可觀測性 |
| P2 | OpenClaw / Hermes / chat manager 先做 EffectivePolicy shadow compare | medium | 不改 user-visible output |
| P3 | decision_manager / decision_fusion 進 runtime strangler | high | Tier 3，最後改 |

---

## 7. 驗收標準

- [x] `rg "OLLAMA_URL" apps/api/src` 新增呼叫點必須在本 INV 登記，並由 `test_no_new_direct_ollama_url_call_sites` 防守。
- [x] `k8s/awoooi-prod/04-configmap.yaml` 與 `06-deployment-api.yaml` 的 Ollama 三層 env 必須一致。
- [ ] 所有 batch/RAG/embedding 路徑不再預設搶 GCP-A。（service-level 第一批已完成，API/script 還待改）
- [ ] provider metrics 可分辨 `ollama_gcp_a`、`ollama_gcp_b`、`ollama_local`。
- [ ] AwoooP EffectivePolicy 可以在 shadow mode 輸出「會選哪一台 Ollama」且不影響 legacy call。
- [ ] GCP-A 故障演練時，GCP-B 承接 interactive path；GCP-A/B 同時故障時，Local `.111` 承接 local path；全部失敗才進 paid provider，並受 budget hard kill 保護。

*最後更新：2026-05-05（台北）*
