A chat interface should not own the retrieval and generation logic it displays. Put that logic in a reusable core, then let each interface handle its own interaction with users. The same core can serve a web chat, an API, or batch processing without duplicating the RAG workflow. section 12.3
Listing 12.13 DakkaBotCore async initialization of RAG components
class DakkaBotCore: #1
def __init__(self):
self.retriever = None #2
self.llm = None
self.vector_store = None
self.initialized = False #3
async def initialize(self): #4
try:
embeddings = GeminiEmbeddings() #5
self.vector_store = FAISS.load_local(
"DATA/faiss_index",
embeddings=embeddings,
allow_dangerous_deserialization=True
)
self.retriever = self.vector_store.as_retriever(
search_kwargs={"k": 5} #6
)
self.llm = ChatGoogleGenerativeAI(
model="gemini-2.5-flash", #7
temperature=0.0,
google_api_key=os.environ["GEMINI_API_KEY"],
convert_system_message_to_human=True
)
self.initialized = True
except Exception as e:
raise RuntimeError(f"Failed to initialize DakkaBot: {str(e)}")
The core holds the retriever, vector store, and language model. Its separate async initialization step handles resources that take time to load; an initialized flag records whether queries can safely run. section 12.3
Keep interface-specific startup and message handling outside the core. A Chainlit handler can initialize the core, validate input, and display streaming results while the core performs query processing. This boundary supports testing, maintenance, and alternative deployments. section 12.3





















