24 KiB
be-ircfa Backend Architecture
This document explains the backend of the memory-enabled i-RCFA application in beginner-friendly terms.
The backend's shape
be-ircfa
│
├── run.py Local launcher; starts src.main:app
├── pyproject.toml Python/Poetry project configuration
├── requirements/ Dependency groups
├── Dockerfile Backend container definition
├── docker-compose.yaml Local/container orchestration
├── docs/ Design notes and reports
├── scratch/ Experiments and diagnostic scripts
├── tests/ Automated and manual tests
├── workspace/ Workspace/tool files
└── src/ Actual Python application package
├── main.py Creates FastAPI and middleware
├── api.py Connects route groups
├── config.py Environment and service configuration
├── auth/ Checks who the user is
├── database/ Creates PostgreSQL sessions
├── ircfa_interaction/ Main chat and AI orchestration
│ ├── router.py HTTP endpoints
│ ├── stream2.py Main AI workflow
│ ├── tools/ Sensor, PFI, reliability tools
│ ├── llm/ Remote model adapters
│ ├── lib/ Context and attachment helpers
│ ├── system_templates/ Instructions and tool guides
│ ├── model.py Database table definitions
│ ├── schema.py Request/response shapes
│ └── service.py Reusable database/business operations
├── rag/ Document retrieval
├── root_cause/ Root-cause datasets and routes
├── failure_mode/ Failure-mode data and routes
├── equipments/ Equipment data and routes
├── fmea/ FMEA data and routes
└── ircfa_alert/ Alert data and routes
An easy analogy:
run.pyis the key that starts the building.src/main.pyis the building entrance.src/api.pyis the directory showing where each department is.router.pyis the receptionist.stream2.pyis the supervisor.tools/are specialist workers.llm/is the phone connection to Gemma.model.pydescribes database tables.service.pycontains reusable business actions.
1. The application starts in run.py
The normal local launcher is run.py. It starts Uvicorn and points it to the FastAPI application in src/main.py:
run.py -> src.main:app
The FastAPI application itself is created in src/main.py.
At startup it:
- creates the FastAPI application;
- creates database tables if needed;
- loads model configurations;
- installs error handling;
- installs rate limiting;
- installs database-session middleware;
- registers all API routes.
The startup function is in main.py.
Your memory-copy backend runs on port 8005.
2. api.py connects the departments
api.py creates the main route collection.
It registers:
/chat
/rag
/equipments
/failure-mode
/fmea
/root-cause
/ircfa-alert
The chat router is connected under /chat and protected by a JWT dependency:
authenticated_api_router.include_router(
rcfarouter,
prefix="/chat",
tags=["chat"],
)
3. Authentication happens before chat logic
Before the chat code runs, the backend checks the login token using auth/service.py.
The token identifies:
- which user is making the request;
- whether the token is valid;
- which private conversations and memories belong to that user.
The basic flow is:
Login token -> JWTBearer -> CurrentUser -> chat request
This is important for Memory Lab because the memory query uses the authenticated user's ID.
4. Database sessions are created for each request
The backend creates database sessions in middleware.
There are two database connections:
Main application database
- conversations
- chat history
- memories
- model configuration
- application data
Collector database
- collector/plant data
These connections are configured in database/core.py.
The request lifecycle is:
Request starts
↓
Open main DB session
↓
Open collector DB session
↓
Run endpoint
↓
Commit or rollback
↓
Close sessions
The database URLs come from environment variables in config.py:
SQLALCHEMY_DATABASE_URIis the main application database.COLLECTOR_URIis the collector database.PFI_API_URLis the PFI HTTP API.RP_API_URLis the reliability HTTP API.
5. Database models and API schemas
Database table definitions are in ircfa_interaction/model.py.
Important tables include:
Conversation: rcfa_conversation
Stores the larger chat container:
- user ID;
- conversation ID;
- conversation title;
- RCFA phase;
- RCFA state.
Chat history: rcfa_chat_history
Stores individual messages and answers:
- user message;
- assistant response;
- timestamps;
- token usage;
- model name;
- location tag;
- references and context information.
Memory: rcfa_memory
Stores private long-term memories:
- user ID;
- conversation ID;
- memory summary;
- source type;
- equipment;
- location tag;
- metadata.
Model configuration: rcfa_model_config
Stores model connection details:
- model name;
- remote server URL;
- provider;
- API key;
- streaming setting;
- default model flag;
- generation settings.
API request and response shapes are in schema.py.
The difference is:
model.py = database shape
schema.py = API message shape
6. router.py receives the chat request
The main streaming endpoint is in router.py:
POST /chat/stream
It:
- reads the JSON request;
- finds the last user message;
- extracts attachments;
- reads the conversation ID;
- reads the location tag;
- reads the selected model;
- builds a
ChatRequest; - calls
stream_create_v2fromstream2.py.
The important handoff is:
router.py -> stream2.py
router.py mainly receives, validates, and forwards. Most AI orchestration happens elsewhere.
7. stream2.py is the main supervisor
The main orchestration function is stream2.py:
async def stream_create(...)
It coordinates:
- conversation history;
- RCFA state;
- memory retrieval;
- document retrieval;
- model configuration;
- tools;
- streaming;
- database saving.
The flow is:
Receive ChatRequest
↓
Find equipment/location
↓
Load recent conversation
↓
Load relevant memories
↓
Load document context
↓
Prepare system instructions
↓
Create model adapter
↓
Create tool list
↓
Run AI agent
↓
Execute tools when requested
↓
Stream answer
↓
Save chat and memory
8. Context is prepared before Gemma is called
Before Gemma receives the question, stream2.py builds the complete context. It can include:
System instructions
RCFA workflow instructions
Memory privacy instructions
Conversation history
RCFA state
Relevant memories
Document text
Equipment tag
Current question
Gemma therefore receives more than the user's one sentence. The backend prepares the model's working desk first.
9. Technical tools are controlled backend functions
Tools are located under ircfa_interaction/tools/.
Examples include:
current_sensor_tool.py
kondisi_sensor_historis.py
current_reliability_tool.py
history_reliability_tool.py
per_equipment_pfi_tool.py
next_failure_pfi_tool.py
root_cause_spln_tool.py
failure_mode_tool.py
plant_status_tool.py
Each tool generally:
- receives arguments from the AI;
- calls another API or reads data;
- formats the result;
- returns the result to the AI.
The tool loop looks like this:
User asks about sensor data
↓
Gemma decides it needs the sensor tool
↓
i-RCFA backend runs CurrentSensorTool
↓
The tool calls the PFI API
↓
The tool returns sensor data
↓
Gemma explains the result
Gemma does not freely open database connections. It requests a tool, and the backend decides what code runs.
10. PFI integration is usually an API call
The backend configuration contains PFI_API_URL and RP_API_URL in config.py.
For PFI sensor information, the normal relationship is:
Gemma
↓
i-RCFA PFI tool
↓
PFI HTTP API
↓
PFI data source
The i-RCFA backend does not normally query PFI database tables directly.
11. The remote model adapter
The remote model adapter is gemma_llamacpp.py.
It translates the application's internal request into an OpenAI-compatible request:
{
"model": "...",
"messages": [],
"tools": [],
"tool_choice": "auto",
"stream": true
}
The remote completion URL is built from the configured model server:
https://gemma.digitaltwin.my.id/v1/chat/completions
The adapter also:
- adds authentication headers;
- converts messages into OpenAI-compatible format;
- includes tools;
- handles streaming;
- reads tool-call fragments;
- handles multimodal image data;
- logs remote errors.
12. The agent loop
Around the middle of stream2.py, the backend creates a qwen_agent assistant with:
- a system message;
- the selected LLM adapter;
- the allowed tool list.
Conceptually:
bot = Assistant(
system_message=system_prompt,
llm=llm,
function_list=tools,
)
Then it runs the assistant with the prepared messages.
One request can contain multiple rounds:
Round 1: Gemma requests a tool
Round 2: backend executes the tool and returns the result
Round 3: Gemma produces the final answer
13. What happens when Gemma calls a tool
Inside the streaming loop, stream2.py handles:
- assistant messages;
- function/tool messages;
- reasoning content;
- final answer content.
When it sees a tool result, it can update RCFA state.
For example:
PFI tool result
-> save failure prediction state
-> move RCFA phase forward
Current sensor tool result
-> save sensor state
-> move RCFA phase forward
Root-cause tool result
-> save diagnosis state
This is why the UI can show RCFA progress such as:
1. Failure Prediction
2. Sensor Data Mapping
3. Diagnosis
The backend is not only generating text. It is also maintaining an investigation state.
14. Streaming uses Server-Sent Events
stream2.py yields event strings such as:
data: answer text
event: phase
data: 2. Sensor Data Mapping
event: usage_metadata
data: {...}
event: error
data: {...}
The full path is:
Gemma token
↓
Gemma adapter
↓
stream2.py
↓
StreamingResponse
↓
nginx
↓
React chat screen
This is why the answer appears gradually.
15. Saving happens after the answer
After the agent finishes, the backend creates a chat-history record.
It saves things such as:
- user message;
- assistant response;
- timestamps;
- model;
- token counts;
- RCFA state;
- references;
- location tag.
Then it calls the memory-save logic if the user explicitly asked to remember something.
The normal save sequence is:
Save chat history
↓
Check/save explicit memory
↓
Commit transaction
If saving fails, the backend rolls back and logs the error.
16. Memory is a separate backend layer
Memory retrieval happens before Gemma is called:
Database -> memory filter -> relevant memories -> prompt
Memory saving happens after the answer:
Explicit user request
-> policy check
-> lifecycle check
-> database
The memory privacy rules are in memory_policy.py.
The memory lifecycle rules are in memory_lifecycle.py.
The important separation is:
Gemma decides how to answer.
Backend decides what memory is allowed.
Database stores the memory.
17. RAG is another AI-related path
The backend contains a RAG system under rag/.
RAG means:
Retrieve useful documents
↓
Give those documents to the AI
↓
Ask the AI to answer using them
This is different from text generation:
Generation:
Gemma writes the answer.
Retrieval:
Embeddings/vector search find relevant documents.
The text-generation model can work while document retrieval has a separate problem.
18. Legacy model paths
Some older classes in model.py still define Ollama or vLLM-based configurations for older workflows and auxiliary classifiers.
That does not mean the normal i-RCFA chat has switched to local Ollama.
The active chat path is controlled by the model configuration in rcfa_model_config and the selected configuration passed into stream2.py.
Think of it as:
Main chat path:
router.py -> stream2.py -> configured remote model adapter
Other/legacy paths:
may contain older Ollama, vLLM, or auxiliary configurations
19. Complete example
Suppose the user asks:
What is the possible root cause of high vibration on 3DP-FDR711F?
The backend flow is:
1. JWTBearer identifies the user.
2. router.py receives /chat/stream.
3. router.py creates ChatRequest.
4. stream2.py loads:
- recent chat history;
- private memories;
- RCFA state;
- location tag 3DP-FDR711F.
5. stream2.py creates the available tool list.
6. Gemma receives the question and tools.
7. Gemma requests current sensor data.
8. CurrentSensorTool calls the PFI API.
9. The sensor result returns to Gemma.
10. Gemma may request root-cause information.
11. RootCauseSPLNTool searches the root-cause data.
12. Gemma writes the explanation.
13. stream2.py sends answer pieces to the browser.
14. The backend updates RCFA state.
15. The backend saves chat history.
16. If the user explicitly asked to remember something, memory logic handles it.
The most important relationship
router.py
-> stream2.py
-> tools/
-> llm/gemma_llamacpp.py
-> database models/services
-> StreamingResponse
That is the backbone of the backend.
Recommended learning order
Read the backend in this order:
api.py— where routes are connected.router.py— how HTTP chat requests enter.stream2.py— the main AI workflow.- One simple tool, such as
current_sensor_tool.py. gemma_llamacpp.py— how the backend talks to Gemma.model.py— how chat, memory, and model settings are stored.database/core.py— how database connections are created.
20. Files not directly involved in the running backend
The normal backend started with python run.py follows this path:
run.py -> src/main.py -> src/api.py -> route modules -> stream2.py -> tools / database / remote Gemma
The items below are not directly used by that normal running path. This does not automatically mean they are safe to delete: some are useful for maintenance, testing, or an older deployment approach.
| Item | Current role | Keep or remove? |
|---|---|---|
be-ircfa/src/metrics.py |
Old metrics code. The entire file and its import in main.py are commented out. |
A clear cleanup candidate after a normal review. |
be-ircfa/src/ircfa_interaction/schema.py.orig and router.py.orig |
Editor backup copies; Python does not load .orig files. |
Review their differences, then they can normally be removed. |
be-ircfa/scratch/ |
Manual experiments, benchmarks, and diagnostic scripts. | Keep for learning/debugging; it is not part of the live backend. |
be-ircfa/docs/, .vscode/, and tests/ |
Documentation, editor settings, and verification files. | Keep; they do not run as part of the backend service. |
be-ircfa/docker-compose.yaml, docker-kong.yml, kong.yml, config.yaml, docker_build_push.sh, and Jenkinsfile |
Older container, Kong, local-model, build, and CI deployment material. | Not used by the current manual port-8005 setup. Keep until the old deployment is formally retired. |
be-ircfa/requirements/requirements-full.txt, requirements-heavy.txt, and requirements-light.txt |
Alternative dependency lists with no repository references. Docker uses requirements.txt and requirements/requirements-base.txt instead. |
Archive or remove only after confirming no external deployment uses them. |
be-ircfa/src/root_cause/sync.py and cleanup_data.py |
Manual database loading and cleanup scripts. They are not called when the app starts. | Keep if you may need to rebuild or clean root-cause data. |
be-ircfa/src/root_cause/*.json and fmea_data/*.tsv |
Source datasets used by manual data-loading work, not read during ordinary chat requests. | Do not remove unless the data is safely stored elsewhere and no future rebuild is needed. |
be-ircfa/src/ircfa_interaction/stream.py |
Older chat-orchestration implementation. All live chat endpoints call stream2.py. router.py still imports it, however. |
Do not remove it alone; first remove the stale import and test the chat flow. |
be-ircfa/src/ircfa_interaction/llm/n8n.py |
An optional N8N LLM adapter. It is not selected by the current remote Gemma configuration. | Keep as a conditional fallback unless you decide to retire N8N support. |
In simple terms: stream2.py is the current engine. The other items above are mostly old tools, spare parts, instruction books, or data boxes. They do not make the normal chat answer a user request today.
Important caution
The legacy Docker and deployment files should be treated as sensitive configuration. Before sharing, committing, or reusing them, move any credentials and connection values into protected environment variables.
21. Visual code maps
These diagrams show the important driver files, rather than every small helper or data class. Solid arrows show the normal running path. Dashed arrows show optional, legacy, or conditional paths.
21.1 Folder map: startup and HTTP routes
flowchart TD
subgraph ROOT[be-ircfa root]
RUN[run.py]
ENV[Environment variables / .env]
end
subgraph CORE[src/ core files]
MAIN[src/main.py]
API[src/api.py]
CFG[src/config.py]
MID[src/middleware.py]
LOG[src/logging.py]
end
subgraph AUTH[src/auth/]
JWT[service.py: JWTBearer / CurrentUser]
AUTHMODEL[model.py]
end
subgraph INTERACTION[src/ircfa_interaction/]
CHATROUTER[router.py]
STREAM2[stream2.py]
end
subgraph DOMAINS[Other routed feature folders]
RAGROUTER[src/rag/router.py]
ALERTROUTER[src/ircfa_alert/router.py]
EQUIPROUTER[src/equipments/router.py]
FMROUTER[src/failure_mode/router.py]
FMEAROUTER[src/fmea/router.py]
RCROUTER[src/root_cause/router.py]
end
RUN --> MAIN
ENV --> CFG
CFG --> MAIN
MAIN --> LOG
MAIN --> MID
MAIN --> API
API --> JWT
API --> CHATROUTER
API --> RAGROUTER
API --> ALERTROUTER
API --> EQUIPROUTER
API --> FMROUTER
API --> FMEAROUTER
API --> RCROUTER
CHATROUTER --> STREAM2
What this means: run.py turns on FastAPI. main.py prepares the application. api.py is the receptionist that sends each URL request to the correct folder. The chat requests go to ircfa_interaction/router.py, then to stream2.py.
21.2 Folder map: the i-RCFA interaction engine
flowchart LR
subgraph INTERACTION[src/ircfa_interaction/]
ROUTER[router.py\nchat, conversations, Memory Lab]
STREAM[stream2.py\nmain orchestration]
SCHEMA[schema.py\nrequest/response shapes]
SERVICE[service.py\nchat/model persistence]
MODEL[model.py\nchat, memory, LLM tables]
MEMORY[memory_policy.py + memory_lifecycle.py]
LEGACY[stream.py\nolder orchestration]
end
subgraph LLM[src/ircfa_interaction/llm/]
GEMMA[gemma_llamacpp.py\nOpenAI-compatible Gemma adapter]
N8N[n8n.py\noptional adapter]
end
subgraph TOOLS[src/ircfa_interaction/tools/]
SENSOR[Sensor / PFI tools]
RELIABILITY[Reliability tools]
ROOTCAUSE[RootCauseSPLNTool]
VECTOR[VectorSearchTool]
OTHERTOOLS[Other RCFA tools]
end
subgraph SUPPORT[Supporting folders]
RAG[src/rag/\nfiles, vector search, RAG history]
DB[src/database/\nmain and collector sessions]
EQUIP[src/equipments/]
ROOTDATA[src/root_cause/]
end
ROUTER --> SCHEMA
ROUTER --> STREAM
ROUTER --> SERVICE
STREAM --> MEMORY
MEMORY --> MODEL
STREAM --> MODEL
SERVICE --> MODEL
STREAM --> GEMMA
STREAM -. optional configured path .-> N8N
STREAM --> SENSOR
STREAM --> RELIABILITY
STREAM --> ROOTCAUSE
STREAM --> VECTOR
STREAM --> OTHERTOOLS
STREAM --> RAG
SERVICE --> DB
MODEL --> DB
SENSOR --> EQUIP
ROOTCAUSE --> ROOTDATA
VECTOR --> RAG
ROUTER -. stale import; no active endpoint call .-> LEGACY
What this means: stream2.py is the conductor. It gathers chat context, private memory, RCFA data, attachments, and available tools; it then asks the configured LLM to answer. The LLM can request a tool, and stream2.py returns that tool result to the LLM before streaming the final response back to the browser.
21.3 Cross-folder runtime map: one chat request
sequenceDiagram
participant FE as Frontend / nginx
participant MAIN as src/main.py
participant API as src/api.py
participant AUTH as src/auth/service.py
participant ROUTER as ircfa_interaction/router.py
participant STREAM as ircfa_interaction/stream2.py
participant DB as database/ + PostgreSQL
participant TOOLS as tools/ + domain services
participant EXT as PFI / Reliability APIs
participant LLM as Remote Gemma
FE->>MAIN: POST /ircfa/chat/stream
MAIN->>API: send request to registered router
API->>AUTH: verify JWT and identify user
AUTH-->>ROUTER: authenticated user
ROUTER->>STREAM: ChatRequest + database sessions
STREAM->>DB: load chat, RCFA state, model config, private memories
DB-->>STREAM: saved context
STREAM->>LLM: question + system instructions + available tools
opt LLM requests a technical tool
LLM-->>STREAM: tool request
STREAM->>TOOLS: run selected tool
TOOLS->>EXT: request sensor, PFI, or reliability data
EXT-->>TOOLS: technical data
TOOLS-->>STREAM: tool result
STREAM->>LLM: tool result
end
LLM-->>STREAM: streamed answer pieces
STREAM->>DB: save chat, RCFA state, and explicit memory
STREAM-->>FE: streamed answer events
This third diagram is deliberately cross-folder: a real answer passes through authentication, the chat router, orchestration, database storage, tools, external operational APIs, and the remote Gemma service.
21.4 Folder responsibilities at a glance
flowchart TB
API[src/api.py\nconnects routes] --> AUTH[src/auth/\nwho is this user?]
API --> CHAT[src/ircfa_interaction/\nchat, memory, LLM orchestration]
API --> RAG[src/rag/\ndocuments and retrieval]
API --> EQUIP[src/equipments/\nequipment and maintenance data]
API --> FM[src/failure_mode/\nfailure-mode data]
API --> FMEA[src/fmea/\nFMEA records]
API --> RC[src/root_cause/\nroot-cause matrix data]
API --> ALERT[src/ircfa_alert/\nalerts]
CHAT --> DATABASE[src/database/\nPostgreSQL sessions]
RAG --> DATABASE
EQUIP --> DATABASE
FM --> DATABASE
FMEA --> DATABASE
RC --> DATABASE
Use the diagrams together:
- 21.1 answers: “Which folder receives my request?”
- 21.2 answers: “What happens inside the AI/chat engine?”
- 21.3 answers: “How do folders and outside services cooperate for one answer?”
- 21.4 answers: “What is each feature folder responsible for?”