All articles

Layering an LLM Application

AI practiceRead in Chinese

LLM Application Layering

Core Principle

FastAPI should handle protocol concerns, not LLM logic. Every entry point should call the same pipeline.ask() for question answering. Knowledge capture has a separate pipeline.capture() entry point.

Architecture

Telegram bot / AI assistant / other entry points
  -> FastAPI: authentication, routing, serialization
       -> pipeline.ask(question, session_id)
       -> pipeline.capture(content, source_type)

pipeline.ask()
  -> memory.py: session history
  -> router.classify_intent(question)
  -> retriever.search(): kb_query and complex only
  -> router.route(question, context, intent): ModelConfig
  -> robot.ask(question, history, model_config)
  -> LLM API selected by ModelConfig

pipeline.capture()
  -> Step 1: extract a structured summary; skipped for manual content
  -> Step 2: locate related documents with RAG
  -> Step 3: decide update, create, or skip
  -> Step 3.5: Telegram human confirmation
  -> Step 4: merge content and rebuild the index

In the architecture diagram from the source, RAG document location was marked complete, while steps 3 through 4 were marked pending. A later status section in the same document describes further progress. These labels reflect different moments in the design notes.

Layer Responsibilities

LayerFileResponsibility
Protocolmain.pyHTTP routes and authentication; calls only pipeline.ask() or pipeline.capture()
Applicationpipeline.pyOrchestrates question answering and knowledge capture
Configurationconfig.pyCentral settings for providers, models, and retrieval
Memorymemory.pyConversation history by session_id
Retrievalretriever.pyReplaceable search implementation, from keywords to RAG
Routingrouter.pyChooses model and API endpoint based on the request
Modelrobot.pyBuilds prompts from the question, history, and context and calls the chosen LLM

Model Choice

router.py chooses the model internally; callers no longer pass a model argument. A request first uses intent_classify, then calls a model selected for chitchat, knowledge-base questions, or complex questions.

IntentConfiguration keyModel in the notes
Chitchatchitchat_replyminimax-m2.1
Knowledge-base questionkb_query_replyclaude-haiku-4-5-20251001
Complex questioncomplex_replyclaude-sonnet-4-6

The capture pipeline used separate settings: intent_classify, command_reply, kb_extract, kb_locate, and kb_merge. These allow each stage to choose a model appropriate for its task and cost.

Evolution Recorded in the Source

Step 1: Establish the Application Boundary

main.py was changed to call pipeline.ask() rather than import the model-calling module directly. The source marked this complete.

Step 2: Add Conversation Memory

pipeline.py maintained history by session_id and included that history in LLM messages. Redis persistence had a seven-day TTL, with an in-memory fallback. This was marked complete.

Step 3: Make the Model Selectable

As an intermediate stage, pipeline.ask() accepted a model argument and passed it through to robot.ask(). This was marked complete at the time, but was superseded by the router in step 4.

Step 4: Introduce the LLM Router

The model argument was removed. router.py chose the model and Compatible API endpoint from request characteristics. The source marked this complete.

Step 5: Replace Keyword Search with RAG

Only the retriever.py implementation changed: keyword matching became LlamaIndex and ChromaDB vector search. pipeline.py kept the same interface. Embedding settings moved into router.py as EMBED_CONFIG, using an OpenRouter embedding API. Deployment rebuilt the full index at /srv/kb_index with python3 retriever.py. This was marked complete.

Status and Future Options

The source described pipeline.py as the shared application layer, router-driven model selection, LlamaIndex and ChromaDB retrieval, and Redis memory with a seven-day TTL as current. Proposed later work included more RAG or agent capabilities, provider fallback, and colder long-term conversation storage.

The implementation choices listed in the notes were LlamaIndex for Markdown retrieval, ChromaDB for a local file-backed vector store, and LangChain as a possible tool-calling framework for a later agent stage. The last item was a selection note, not an implemented component of this architecture.

Layering an LLM Application | Xuhuan / Gelatoni