Skip to content

Architecture principles

Deliberate design choices behind CAE Copilot. Each principle has direct consequences on how the system is built and operated.


1. State-only communication

Statement: All graph nodes read and write a shared CAEState. Nodes never call each other directly.

Why: In CAE workflows, wrong answers have real cost. A single state object makes every step inspectable — you can replay what the router saw before the report node ran.

Where: src/cae_copilot/state.py, every file in nodes/.


2. Citations required

Statement: RAG-derived answers must reference source chunks. Free-floating claims are rejected when CAEC_REQUIRE_CITATIONS=true.

Why: Engineering knowledge must be auditable. A copilot that invents a safety factor is worse than no copilot.

Where: nodes/rag.py, nodes/report.py, eval faithfulness checks.


3. Pure tools, not LLM math

Statement: Engineering calculations are deterministic Python functions with Pydantic-validated inputs. The LLM decides what should be computed; deterministic tools perform the calculation. The LLM does not perform arithmetic.

Why: Tools are the high-trust surface. Unit tests on estimate_particle_count are stronger evidence than prompt engineering alone.

Where: src/cae_copilot/tools/, tools/registry.py. Full routing rules: Data-First.


4. Data-First split (RAG vs Tools)

Statement: Narrative requirements go to RAG with citations. Tabular numeric facts and catalog lookups should go to structured Tools or APIs (Stage 1 target). Derived results go to deterministic calculation tools.

Why: RAG hallucinates on engineering tables. Separating data types before the agent answers is the core defense against wrong numbers in reports.

Where: data-first.md — target vs implemented routing. Today the graph routes by intent (knowledge → RAG, calculation → tools); structured material lookup is planned, not shipped.


5. Graceful degradation

Statement: The graph runs without an LLM API key and without Qdrant.

Why: CI, local dev, and portfolio reviewers should not need secrets or GPU. Keyword intent, in-memory RAG, and regex tool dispatch provide a baseline path.

Where: nodes/intent.py, rag/retriever.py (in-memory fallback), nodes/tools.py (regex dispatch).


6. UI / API separation

Statement: Streamlit talks to FastAPI over HTTP only. The UI must not import the graph, retriever, or tools.

Why: Mirrors production layout (separate deployables, independent scaling) and matches the SoccerPredictAI pattern.

Where: ui/api_client.py, docker/Dockerfile.streamlit (slim deps).


7. Vector search + metadata filters

Statement: RAG uses vector search plus Qdrant payload filters (doc_type, manifest metadata) and parent-context expansion for child chunks. BM25 + vector fusion is planned but not implemented.

Why: Pure semantic search misses domain constraints. A question about Fluent coupling should not return unrelated module sections.

Where: rag/chunking.py (parent/child), rag/retriever.py (Qdrant payload filters + parent_text expansion).


8. Model-agnostic LLM layer

Statement: The LLM provider is configured via CAEC_LLM_PROVIDER, CAEC_LLM_MODEL, and CAEC_LLM_BASE_URL — not hardcoded to one vendor.

Why: Enterprise deployments need on-prem or regional models (Qwen-class, YandexGPT-class) to avoid vendor lock-in and control data residency. Commodity AI means the orchestration layer is the product, not the model brand.

Where: src/cae_copilot/llm.py, config.py.


9. Execution isolation for external tools (planned)

Statement: Long-running external engineering tools (including solver backends) run in isolated containers, not in the LangGraph process.

Why: A tool crash must not take down the API. Docker provides reproducible execution environments.

Status: Stage 3 — deferred. See roadmap, solver-agent-skill.md.


10. Async for long-running jobs (planned)

Statement: Long-running external tool jobs use a fire-and-forget pattern with polling or webhooks. LangGraph does not block for minutes or hours.

Why: FEA runs are asynchronous by nature. The agent acknowledges the job, returns a task ID, and delivers results when complete.

Status: Stage 3 — deferred. See roadmap, solver-agent-skill.md.