liveBook · Manning

Natural language interface

As explained in Machine Learning Platform Engineering

A chat interface is an interaction interface that exchanges conversational messages between a user or client and an AI application.

A natural language interface lets colleagues query an internal documentation system by sending conversational messages instead of calling an API directly. For a retrieval-augmented generation (RAG) application, the interface turns document search and answer generation into a familiar chat workflow. section 12.3 section 12.2.1

Chainlit supplies the frontend for this workflow without requiring separate React, JavaScript, or CSS development. Its chat-oriented features include streaming responses, conversation history, source-document display, file uploads, session management, and feedback collection. Keep retrieval and generation in a reusable core so the same logic can also serve an API or a test harness. section 12.3

A practical message flow is:

  • Initialize the RAG core when the chat starts, and tell the user whether the assistant is starting or ready. section 12.3
  • On each message, check initialization and validate the user’s input before processing the query; return an error message if either check fails. section 12.3
  • Retrieve relevant documents, then stream generated answer chunks to the interface. Pass source information alongside the response so users can inspect its basis. section 12.3
Listing 12.18 Main message handler with streaming and validation
@cl.on_message  #1
async def main(message: cl.Message):
    """Handle user messages with streaming response"""

    if not dakka_bot.initialized:  #2
        await cl.Message(
            content=(
                " Please refresh the page."),
            author="System"
        ).send()
        return

    user_query = message.content

    user_query, is_valid = validate_user_inputs( #3
        user_query)   #3
    if not is_valid:
        await cl.Message(
            content=user_query,  # Contains error message
            author="System"
        ).send()
        return

    thinking_msg = cl.Message(
        content=" Searching DataKrypt documentation...",  #4
        author="DakkaBot"
    )
    await thinking_msg.send()

    try:
        response_msg = cl.Message(  #5
            content="", author="DakkaBot")   #5
        sources_info = None

        async for chunk in 
                dakka_bot.process_query_stream( #6
                 user_query):   #6

            if chunk["type"] == "sources":
                thinking_msg.content = (  #7
                    f" Found {chunk['doc_count']} "  #7
                    "relevant documents. "   #7
                    " Generating response..."
                await thinking_msg.update()
                sources_info = chunk

            elif chunk["type"] == "token":
                if thinking_msg:
                    await thinking_msg.remove()  #8
                    thinking_msg = None

                await response_msg.stream_token( #9
                    chunk["content"])   #9

            elif chunk["type"] == "done":
                await response_msg.send()  #10
                break

            elif chunk["type"] == "error":
                if thinking_msg:
                    await thinking_msg.remove()
                await cl.Message(
                    content=(
                        f" **Error processing your question**: " 
                        f"{chunk['content']}\n\nPlease try "
                        f"rephrasing your question or check "
                        f"if the DataKrypt documentation is "
                        f"properly loaded."
                    ),
                    author="DakkaBot"
                ).send()
                return

    except Exception as e:
        if thinking_msg:
            await thinking_msg.remove()
        await cl.Message(
            content=f" **Unexpected error**: {str(e)}\n\nPlease try again.",
            author="DakkaBot"
        ).send()

The Chainlit message handler shows where readiness checks, input validation, progress messages, and the streamed response connect to the chat UI. section 12.3

The UI is only one layer of the system. Reusable query logic makes it easier to test and maintain, while observability remains important because generated answers can vary and production LLM calls incur token-based costs. section 12.3 section 12.2.1

How Machine Learning Platform Engineering covers it

How deep it goes
Works through Chainlit startup and message handling, validation, reusable RAG logic, and streaming; it does not build a custom frontend.
Examples
Uses an internal documentation chatbot with Chainlit startup and message handlers backed by a reusable Python RAG class.
What it assumes
Assumes Python-based RAG components are available, while avoiding a requirement to build a separate frontend.
What you can do after
Apply it via Chainlit to expose a reusable RAG pipeline as a validated, streaming chat application.
How it connects
Connects chat interfaces to retrieval-augmented generation, input validation, response streaming, source documents, and observability.

Also explained in

Compare how 6 books explain natural language interface →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Machine Learning Platform Engineering. The section links point to the parts of the book it draws on.