Rifqi Hafizuddin Claude Opus 4.8 commited on
Commit
636290e
·
1 Parent(s): 0f2b85e

[NOTICKET][AI] feat(chat): wire Orchestrator -> slow path at the endpoint (env-gated)

Browse files

The slow path (Planner -> TaskRunner -> Assembler) was reachable in ChatHandler but
never enabled from the live endpoint. Wire it so the team can test end-to-end from
/chat/stream, behind an env flag so it stays opt-in until the lead's real
BusinessContext lands (it's still a stub).

- settings.enable_slow_path (env ENABLE_SLOW_PATH, default False).
- api/v1/chat.py passes it to the shared ChatHandler.
- ENABLE_SLOW_PATH=true → structured intents route through the slow path and stream
status progress + the assembled answer; default keeps the single-query QueryService
path. Fast/unstructured paths unchanged.

Verified: toggle flips handler._enable_slow_path; wiring tests pass; live run through
ChatHandler.handle confirmed earlier this session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (3) hide show
  1. PROGRESS.md +8 -0
  2. src/api/v1/chat.py +6 -1
  3. src/config/settings.py +8 -0
PROGRESS.md CHANGED
@@ -56,6 +56,14 @@ Verified against code before logging. Severity: **critical** / important / nice-
56
  | T1 | **`input_schema` is presence-only, not type-checked** — `ToolSpec.input_schema` comment said "validates ToolCall.args", but `TaskRunner._validate_args` only enforces `required` presence; the `properties` types are documentation, never validated at runtime. Clarified the contract in `tools/contracts.py` so nobody assumes type-safety (a wrong-typed arg passes validation, surfaces only inside the compute fn). Doc-only, no behavior change (90e80f9). | nice-to-have | TAB | `[x]` |
57
  | T2 | **Dead Python embed path?** — `document_pipeline.process()` → `knowledge_processor` → `vector_store.aadd_documents()` still writes PDF/DOCX/TXT embeddings to `langchain_pg_embedding`, contradicting CLAUDE.md's "Go is sole writer, Python reads only". Verified the Go service (`Orchestrator-Agent-Service/internal/documents`) IS a complete ingestion writer to the same tables for all 5 file types (OCR + chunk + embed) → the Python embed branch is very likely redundant. **Blocked on one operational fact:** does the frontend still upload to `/document/process` (Python) or to Go? Park until confirmed — deleting a live ingestion path would break unstructured RAG. The csv/xlsx parquet branch stays regardless (feeds the catalog/tabular path). | nice-to-have | TAB | `[blocked]` |
58
 
 
 
 
 
 
 
 
 
59
  **Architecture verdict:** fundamentally sound (catalog-driven IR + deterministic compiler
60
  + static plan is the right call). Debt is transitional duplication (two planners/registries/
61
  contract modules — documented, owned) and `ChatHandler` drifting toward a god object
 
56
  | T1 | **`input_schema` is presence-only, not type-checked** — `ToolSpec.input_schema` comment said "validates ToolCall.args", but `TaskRunner._validate_args` only enforces `required` presence; the `properties` types are documentation, never validated at runtime. Clarified the contract in `tools/contracts.py` so nobody assumes type-safety (a wrong-typed arg passes validation, surfaces only inside the compute fn). Doc-only, no behavior change (90e80f9). | nice-to-have | TAB | `[x]` |
57
  | T2 | **Dead Python embed path?** — `document_pipeline.process()` → `knowledge_processor` → `vector_store.aadd_documents()` still writes PDF/DOCX/TXT embeddings to `langchain_pg_embedding`, contradicting CLAUDE.md's "Go is sole writer, Python reads only". Verified the Go service (`Orchestrator-Agent-Service/internal/documents`) IS a complete ingestion writer to the same tables for all 5 file types (OCR + chunk + embed) → the Python embed branch is very likely redundant. **Blocked on one operational fact:** does the frontend still upload to `/document/process` (Python) or to Go? Park until confirmed — deleting a live ingestion path would break unstructured RAG. The csv/xlsx parquet branch stays regardless (feeds the catalog/tabular path). | nice-to-have | TAB | `[blocked]` |
58
 
59
+ **Slow-path endpoint wiring (2026-06-10):** the Orchestrator→slow-path is now wired
60
+ into the live endpoint behind an **env flag**. `settings.enable_slow_path` (env
61
+ `ENABLE_SLOW_PATH`, default **off**) is passed to the shared `ChatHandler` in
62
+ `api/v1/chat.py`. Flip `ENABLE_SLOW_PATH=true` to route `structured` intents through
63
+ Planner→TaskRunner→Assembler and test end-to-end from `/chat/stream` (status progress
64
+ events + answer stream). Stays opt-in because `BusinessContext` is still the stub;
65
+ fast/unstructured paths unchanged. Verified live via `ChatHandler.handle`.
66
+
67
  **Architecture verdict:** fundamentally sound (catalog-driven IR + deterministic compiler
68
  + static plan is the right call). Debt is transitional duplication (two planners/registries/
69
  contract modules — documented, owned) and `ChatHandler` drifting toward a god object
src/api/v1/chat.py CHANGED
@@ -26,7 +26,12 @@ router = APIRouter(prefix="/api/v1", tags=["Chat"])
26
  # is passed into handle()), and lazily builds + caches the Orchestrator/Chatbot
27
  # chains — so reusing it keeps the Azure OpenAI clients (and their httpx/TLS pools)
28
  # warm across requests instead of re-handshaking on the first call of every request.
29
- _chat_handler = ChatHandler(enable_tracing=True)
 
 
 
 
 
30
 
31
  _GREETINGS = frozenset(["hi", "hello", "hey", "halo", "hai", "hei"])
32
  _GOODBYES = frozenset(["bye", "goodbye", "thanks", "thank you", "terima kasih", "sampai jumpa"])
 
26
  # is passed into handle()), and lazily builds + caches the Orchestrator/Chatbot
27
  # chains — so reusing it keeps the Azure OpenAI clients (and their httpx/TLS pools)
28
  # warm across requests instead of re-handshaking on the first call of every request.
29
+ # enable_slow_path is env-gated (ENABLE_SLOW_PATH): when on, structured intents route
30
+ # Orchestrator -> Planner -> TaskRunner -> Assembler so the team can test e2e here.
31
+ _chat_handler = ChatHandler(
32
+ enable_tracing=True,
33
+ enable_slow_path=settings.enable_slow_path,
34
+ )
35
 
36
  _GREETINGS = frozenset(["hi", "hello", "hey", "halo", "hai", "hei"])
37
  _GOODBYES = frozenset(["bye", "goodbye", "thanks", "thank you", "terima kasih", "sampai jumpa"])
src/config/settings.py CHANGED
@@ -16,6 +16,14 @@ class Settings(BaseSettings):
16
  case_sensitive=False,
17
  )
18
 
 
 
 
 
 
 
 
 
19
  # Database
20
  postgres_connstring: str
21
 
 
16
  case_sensitive=False,
17
  )
18
 
19
+ # Feature flags
20
+ # Route `structured` chat intents through the analytical SLOW PATH
21
+ # (Planner -> TaskRunner -> Assembler) instead of the single-query QueryService.
22
+ # Off by default; the team flips ENABLE_SLOW_PATH=true to test end-to-end from
23
+ # the /chat/stream endpoint. BusinessContext is still a stub until the lead's
24
+ # real source lands, so this stays opt-in.
25
+ enable_slow_path: bool = Field(alias="enable_slow_path", default=False)
26
+
27
  # Database
28
  postgres_connstring: str
29