DEV Community

vmodal_ai
vmodal_ai

Posted on

Building a RAG Chatbot with Flutter and FastAPI

Building a RAG Chatbot with Flutter and FastAPI

Retrieval-Augmented Generation (RAG) combines a language model with a searchable knowledge base.

Instead of asking an LLM to answer only from its training knowledge, a RAG system retrieves relevant documents and includes them as context.

A typical architecture is:

             ┌─────────────────┐
             │   Flutter App   │
             └────────┬────────┘
                      │
                    HTTPS
                      │
             ┌────────▼────────┐
             │    FastAPI      │
             └────────┬────────┘
                      │
             ┌────────▼────────┐
             │ Retriever / DB  │
             └────────┬────────┘
                      │
                Relevant chunks
                      │
             ┌────────▼────────┐
             │       LLM       │
             └────────┬────────┘
                      │
                    Answer
                      │
             ┌────────▼────────┐
             │   Flutter UI    │
             └─────────────────┘
Enter fullscreen mode Exit fullscreen mode

What is RAG?

RAG usually contains two pipelines.

Indexing pipeline

Documents
   ↓
Text extraction
   ↓
Chunking
   ↓
Embeddings
   ↓
Vector database
Enter fullscreen mode Exit fullscreen mode

Query pipeline

User question
   ↓
Query embedding
   ↓
Similarity search
   ↓
Relevant chunks
   ↓
Prompt + context
   ↓
LLM
   ↓
Answer
Enter fullscreen mode Exit fullscreen mode

Step 1: Install FastAPI

pip install fastapi uvicorn pydantic
Enter fullscreen mode Exit fullscreen mode

You will also need an embedding model and vector database.

Step 2: Create a FastAPI endpoint

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ChatRequest(BaseModel):
    question: str

@app.post("/chat")
async def chat(request: ChatRequest):
    # 1. Create query embedding
    # 2. Search vector database
    # 3. Build context
    # 4. Call the LLM
    # 5. Return answer

    return {
        "answer": "Example response",
        "sources": []
    }
Enter fullscreen mode Exit fullscreen mode

Step 3: Chunk documents

Large documents should generally be divided into smaller chunks.

Document
   ↓
Chunk 1
Chunk 2
Chunk 3
...
Chunk N
Enter fullscreen mode Exit fullscreen mode

A chunk should be large enough to preserve meaning but small enough to retrieve precisely.

Experiment with:

  • chunk size
  • overlap
  • retrieval count
  • metadata filtering

Step 4: Generate embeddings

An embedding converts text into a vector:

"How do I reset my password?"
             ↓
[0.12, -0.31, 0.87, ...]
Enter fullscreen mode Exit fullscreen mode

Semantically similar sentences should have vectors that are close in the embedding space.

A local model such as nomic-embed-text can be used through an appropriate local inference stack.

Step 5: Store vectors

A vector database stores:

ID
Embedding
Text chunk
Metadata
Enter fullscreen mode Exit fullscreen mode

Metadata might contain:

{
  "document": "employee_handbook.pdf",
  "page": 14,
  "department": "HR"
}
Enter fullscreen mode Exit fullscreen mode

Metadata enables filtered retrieval.

Step 6: Retrieve relevant chunks

For:

What is the company's annual leave policy?
Enter fullscreen mode Exit fullscreen mode

retrieve the most relevant chunks.

Conceptually:

results = vector_store.search(
    query_embedding,
    top_k=5
)
Enter fullscreen mode Exit fullscreen mode

The exact API depends on your vector database.

Step 7: Build the prompt

A RAG prompt might look like:

You are a helpful assistant.

Answer the user's question using only the supplied context.

Context:
{{retrieved_documents}}

Question:
{{user_question}}

If the answer is not present in the context,
say that you do not have enough information.
Enter fullscreen mode Exit fullscreen mode

This reduces unsupported answers.

Step 8: Connect Flutter

Create a simple API client:

import 'dart:convert';
import 'package:http/http.dart' as http;

class RagApi {
  final String baseUrl;

  RagApi(this.baseUrl);

  Future<RagResponse> ask(String question) async {
    final response = await http.post(
      Uri.parse('$baseUrl/chat'),
      headers: {
        'Content-Type': 'application/json',
      },
      body: jsonEncode({
        'question': question,
      }),
    );

    if (response.statusCode != 200) {
      throw Exception('RAG request failed');
    }

    final json = jsonDecode(response.body);

    return RagResponse.fromJson(json);
  }
}

class RagResponse {
  final String answer;
  final List<dynamic> sources;

  RagResponse({
    required this.answer,
    required this.sources,
  });

  factory RagResponse.fromJson(Map<String, dynamic> json) {
    return RagResponse(
      answer: json['answer'] ?? '',
      sources: json['sources'] ?? [],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Display citations

Return source information:

{
  "answer": "Employees receive ...",
  "sources": [
    {
      "document": "employee_handbook.pdf",
      "page": 14
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Flutter can display:

Answer

Employees receive ...

Sources
• employee_handbook.pdf — page 14
Enter fullscreen mode Exit fullscreen mode

This improves user trust and makes answers easier to verify.

Add conversation history

A chatbot normally needs conversation context:

User: What is the refund policy?
AI: The refund period is 30 days.

User: What about digital products?
Enter fullscreen mode Exit fullscreen mode

The second question depends on the first.

A production system can maintain conversation history while still using retrieval for factual context.

RAG quality problems

A chatbot can fail even when the LLM is good.

Bad document extraction
        ↓
Bad chunks
        ↓
Bad embeddings
        ↓
Bad retrieval
        ↓
Wrong context
        ↓
Wrong answer
Enter fullscreen mode Exit fullscreen mode

Evaluate retrieval independently from generation.

Improve retrieval

Useful techniques include:

  • metadata filtering
  • hybrid search
  • reranking
  • query rewriting
  • better chunking
  • multiple retrieval queries
  • domain-specific embeddings

Production architecture

Flutter
  |
Authentication
  |
FastAPI
  |
Conversation Service
  |
Retriever
  |
Vector DB
  |
Reranker
  |
Prompt Builder
  |
LLM
  |
Response + Sources
Enter fullscreen mode Exit fullscreen mode

Security

The backend should enforce:

  • authentication
  • authorization
  • request limits
  • input validation
  • document access controls
  • logging
  • tenant isolation when required

If documents belong to different users or organizations, retrieval must enforce permissions before returning context to the LLM.

Conclusion

RAG is more than connecting a vector database to an LLM. Quality depends on the entire pipeline: document extraction, chunking, embeddings, retrieval, prompt construction, generation, and source presentation.

Flutter provides the user experience, while FastAPI provides a clean backend layer for retrieval and AI orchestration.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)