AI Engineer Roadmap: Triển Khai RAG Với LangChain Hoặc LlamaIndex

Chào mừng các bạn trở lại với loạt bài viết “AI Engineer Roadmap”! Trong những bài trước, chúng ta đã cùng nhau khám phá nhiều khía cạnh quan trọng của thế giới AI hiện đại, từ vai trò của Kỹ sư AI, sự khác biệt giữa Kỹ sư AI và Kỹ sư ML, đến việc làm quen với các mô hình AI được huấn luyện trướccách sử dụng các API phổ biến như OpenAI. Chúng ta cũng đã tìm hiểu sâu về Embeddings, Vector Database, và cách triển khai tìm kiếm ngữ nghĩa.

Gần đây, chúng ta đã dành một bài viết chi tiết để giải thích RAG (Retrieval Augmented Generation) là gìxây dựng pipeline RAG cơ bản. RAG là một kỹ thuật cực kỳ mạnh mẽ giúp các mô hình ngôn ngữ lớn (LLM) trả lời câu hỏi dựa trên dữ liệu tùy chỉnh, không có trong tập huấn luyện ban đầu của chúng. Điều này mở ra cánh cửa cho việc xây dựng các ứng dụng AI có khả năng tương tác với kiến thức chuyên sâu của doanh nghiệp hoặc cá nhân bạn.

Tuy nhiên, việc xây dựng pipeline RAG từ đầu, quản lý các bước từ tải dữ liệu, phân đoạn, tạo embeddings, lưu trữ, truy xuất, đến kết hợp với LLM có thể khá phức tạp. Đây là lúc các framework như LangChain và LlamaIndex phát huy vai trò của mình. Chúng cung cấp các abstraction và công cụ giúp đơn giản hóa đáng kể quá trình phát triển ứng dụng dựa trên LLM và đặc biệt là pipeline RAG.

Trong bài viết này, chúng ta sẽ đi sâu vào cách triển khai pipeline RAG bằng cách sử dụng cả hai framework phổ biến này: LangChain và LlamaIndex. Bạn sẽ thấy cách chúng giúp bạn “đóng gói” các bước phức tạp, cho phép bạn tập trung vào logic nghiệp vụ của ứng dụng. Hãy cùng bắt đầu hành trình biến lý thuyết RAG thành thực tế với sự trợ giúp của hai công cụ mạnh mẽ này!

LangChain và LlamaIndex: Tại Sao Lại Là Chúng?

Trước khi đi vào chi tiết triển khai, hãy hiểu rõ tại sao LangChain và LlamaIndex lại trở thành những công cụ không thể thiếu đối với các AI Engineer khi làm việc với LLM và RAG:

  • Trừu tượng hóa phức tạp: Cả hai framework đều cung cấp các component (thành phần) được trừu tượng hóa cao cho các tác vụ phổ biến như tải dữ liệu (Document Loading), phân đoạn văn bản (Text Splitting), tạo embeddings (Embeddings), tương tác với vector stores (Vector Stores), và gọi LLM (LLMs). Điều này giúp bạn không cần phải viết lại code boilerplate cho mỗi bước.
  • Kết nối linh hoạt: Chúng có khả năng kết nối với rất nhiều nguồn dữ liệu khác nhau (PDFs, trang web, cơ sở dữ liệu…), các mô hình embedding (OpenAI, Hugging Face, Cohere…), các vector stores (Chroma, Pinecone, Weaviate, FAISS…), và các nhà cung cấp LLM (OpenAI, Anthropic, Google, Hugging Face…).
  • Xây dựng chuỗi (Chains/Pipelines): Cả hai đều tập trung vào việc xây dựng các chuỗi xử lý (chains hoặc pipelines) logic các thành phần lại với nhau để thực hiện một tác vụ phức tạp, ví dụ như pipeline RAG.
  • Cộng đồng và hệ sinh thái: Cả hai đều có cộng đồng lớn mạnh và được cập nhật liên tục, cung cấp nhiều tài nguyên và tích hợp sẵn sàng.

Mặc dù có mục tiêu chung, LangChain và LlamaIndex có triết lý thiết kế và cách tiếp cận hơi khác nhau. Chúng ta sẽ khám phá sự khác biệt này kỹ hơn sau khi xem xét cách triển khai RAG với từng framework.

Triển Khai RAG Với LangChain: Từng Bước Một

LangChain được thiết kế với triết lý hướng component, cho phép bạn xây dựng các ứng dụng phức tạp bằng cách ghép nối các “chuỗi” (chains) các thành phần khác nhau. Đối với RAG, LangChain cung cấp các modules riêng biệt cho từng bước của pipeline.

Hãy xem cách chúng ta xây dựng một pipeline RAG đơn giản với LangChain:

  1. Cài đặt thư viện cần thiết:
  2. pip install langchain-community langchain-openai chromadb tiktoken

    langchain-community chứa các tích hợp với bên thứ ba (document loaders, vector stores, v.v.). langchain-openai chứa tích hợp với các mô hình của OpenAI. chromadb là một vector store phổ biến chúng ta sẽ sử dụng làm ví dụ. tiktoken là một thư viện tính toán token thường dùng với OpenAI.

  3. Tải dữ liệu (Document Loading):
  4. LangChain hỗ trợ tải dữ liệu từ nhiều nguồn. Giả sử chúng ta có một file văn bản đơn giản data.txt.

    from langchain_community.document_loaders import TextLoader
    
    # Tạo một file data.txt mẫu để thử nghiệm
    with open("data.txt", "w") as f:
        f.write("Bài viết này nói về LangChain và LlamaIndex.\n")
        f.write("LangChain là một framework để phát triển ứng dụng LLM.\n")
        f.write("LlamaIndex tập trung vào việc kết nối LLM với dữ liệu bên ngoài.\n")
        f.write("Cả hai đều rất hữu ích cho việc triển khai RAG.\n")
        f.write("RAG giúp LLM trả lời câu hỏi dựa trên kiến thức mới.\n")
    
    # Tải dữ liệu từ file
    loader = TextLoader("data.txt")
    documents = loader.load()
    print(f"Đã tải {len(documents)} tài liệu.")
    print(f"Nội dung tài liệu đầu tiên:\n{documents[0].page_content}")
        
  5. Phân đoạn văn bản (Text Splitting):
  6. LLM và Embedding models có giới hạn về độ dài ngữ cảnh (context window) và cũng tốt hơn khi xử lý các đoạn văn bản nhỏ hơn, tập trung vào một chủ đề. Chúng ta cần chia tài liệu lớn thành các đoạn nhỏ (chunks).

    from langchain.text_splitter import RecursiveCharacterTextSplitter
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=100, # Kích thước mỗi đoạn
        chunk_overlap=20 # Phần chồng lấp giữa các đoạn (giúp giữ ngữ cảnh)
    )
    splits = text_splitter.split_documents(documents)
    print(f"Đã chia thành {len(splits)} đoạn.")
    print(f"Đoạn đầu tiên:\n{splits[0].page_content}")
        
  7. Tạo Embeddings (Embeddings):
  8. Chúng ta cần chuyển các đoạn văn bản thành vector số để có thể lưu trữ và tìm kiếm ngữ nghĩa trong vector database. Chúng ta có thể sử dụng các embedding models như OpenAI Embeddings hoặc Sentence Transformers.

    from langchain_openai import OpenAIEmbeddings
    import os
    
    # Thay thế YOUR_OPENAI_API_KEY bằng API key của bạn
    # Hoặc đặt biến môi trường OPENAI_API_KEY
    # os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
    
    embeddings = OpenAIEmbeddings()
    
    # Nếu bạn muốn sử dụng embedding model mã nguồn mở
    # from langchain_community.embeddings import SentenceTransformerEmbeddings
    # embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
        
  9. Lưu trữ vào Vector Store (Vector Stores):
  10. Các vector embeddings cùng với nội dung văn bản gốc sẽ được lưu trữ trong một vector database. Cơ sở dữ liệu này cho phép tìm kiếm hiệu quả các vector “gần giống” nhau về mặt ngữ nghĩa.

    from langchain_community.vectorstores import Chroma
    
    # Tạo một vector store từ các đoạn văn bản và embeddings
    # persist_directory='db' sẽ lưu trữ vector store vào thư mục 'db'
    vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings, persist_directory="chroma_db")
    
    print("Đã tạo và lưu trữ vector store thành công.")
        

    Sau khi tạo lần đầu, bạn có thể tải lại vector store từ đĩa:

    # Tải lại vector store đã lưu
    # vectorstore = Chroma(persist_directory="chroma_db", embedding_function=embeddings)
        
  11. Thiết lập Retriever:
  12. Retriever là thành phần chịu trách nhiệm truy vấn vector store để tìm các đoạn văn bản liên quan nhất đến câu hỏi của người dùng.

    retriever = vectorstore.as_retriever()
    print("Đã tạo retriever.")
        
  13. Tạo Chain (Combining Retriever and LLM):
  14. Bước cuối cùng là kết hợp Retriever với LLM. LangChain cung cấp các “chains” sẵn có cho mục đích này, phổ biến nhất là RetrievalQA.

    from langchain.chains import RetrievalQA
    from langchain_openai import ChatOpenAI
    
    # Khởi tạo LLM (ví dụ: GPT-4)
    llm = ChatOpenAI(model="gpt-4o-mini") # Hoặc model="gpt-3.5-turbo"
    
    # Tạo RetrievalQA chain
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff", # "stuff" đơn giản là nhét tất cả các đoạn retrieved vào prompt
        retriever=retriever,
        return_source_documents=True # Trả về cả các đoạn văn bản nguồn được sử dụng
    )
    
    # Đặt câu hỏi
    query = "LangChain và LlamaIndex dùng để làm gì?"
    
    # Chạy chain
    result = qa_chain.invoke({"query": query})
    
    print("\n--- Kết Quả RAG LangChain ---")
    print(f"Câu hỏi: {query}")
    print(f"Trả lời: {result['result']}")
    print("\nCác đoạn văn bản nguồn được sử dụng:")
    for doc in result['source_documents']:
        print(f"- {doc.page_content}")
        print(f"  (Source: {doc.metadata.get('source', 'N/A')})")
        

    Trong ví dụ trên, chain_type="stuff" là cách đơn giản nhất, nó nhồi tất cả các đoạn văn bản được truy xuất vào ngữ cảnh của LLM. Các chiến lược khác như map_reduce, refine, hay map_rerank có thể phù hợp hơn cho các tài liệu dài hoặc số lượng đoạn retrieved lớn.

    Như bạn thấy, LangChain cung cấp các lớp rõ ràng cho từng bước của pipeline RAG, cho phép bạn tùy chỉnh và thay thế các component dễ dàng.

    Triển Khai RAG Với LlamaIndex: Tiếp Cận Hướng Dữ Liệu

    LlamaIndex có một triết lý hơi khác so với LangChain. Nó tập trung mạnh mẽ vào việc “index” (lập chỉ mục) dữ liệu của bạn và cung cấp các “query engines” (công cụ truy vấn) để tương tác với chỉ mục đó. LlamaIndex thường tích hợp nhiều bước của pipeline RAG (tải, phân đoạn, nhúng, lưu trữ) vào trong quá trình tạo chỉ mục.

    Hãy xây dựng pipeline RAG tương tự với LlamaIndex:

    1. Cài đặt thư viện cần thiết:
    2. pip install llama-index-core llama-index-embeddings-openai llama-index-vector-stores-chroma llama-index-llms-openai

      LlamaIndex đã tách các tích hợp thành các gói con riêng biệt để giữ gói core gọn nhẹ.

    3. Tải dữ liệu và Tạo Index:
    4. LlamaIndex thường kết hợp các bước tải, phân đoạn, nhúng và lưu trữ vào quá trình tạo chỉ mục. VectorStoreIndex là loại index phổ biến nhất cho RAG.

      from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
      from llama_index.embeddings.openai import OpenAIEmbedding
      from llama_index.vector_stores.chroma import ChromaVectorStore
      from llama_index.core import StorageContext
      from llama_index.llms.openai import OpenAI
      import chromadb
      import os
      
      # Thay thế YOUR_OPENAI_API_KEY bằng API key của bạn
      # Hoặc đặt biến môi trường OPENAI_API_KEY
      # os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
      
      # Chuẩn bị embedding model và LLM
      embed_model = OpenAIEmbedding(model="text-embedding-3-small") # Hoặc model="text-embedding-ada-002"
      llm = OpenAI(model="gpt-4o-mini") # Hoặc model="gpt-3.5-turbo"
      
      # Cấu hình LlamaIndex để sử dụng các models này theo mặc định (tùy chọn)
      # from llama_index.core import Settings
      # Settings.embed_model = embed_model
      # Settings.llm = llm
      
      # Tải dữ liệu từ thư mục hiện tại (nơi có file data.txt)
      # SimpleDirectoryReader sẽ tự động tìm các file trong thư mục
      documents = SimpleDirectoryReader("./").load_data()
      print(f"Đã tải {len(documents)} tài liệu.")
      
      # Thiết lập ChromaDB (tạo client và collection)
      # Persist database vào thư mục 'chroma_db_llama'
      db = chromadb.PersistentClient(path="./chroma_db_llama")
      collection = db.get_or_create_collection("my_llama_collection")
      vector_store = ChromaVectorStore(chroma_collection=collection)
      storage_context = StorageContext.from_defaults(vector_store=vector_store)
      
      # Tạo index từ tài liệu
      # LlamaIndex sẽ tự động phân đoạn, nhúng và lưu trữ vào Chroma
      # Bạn có thể tùy chỉnh splitter bằng ServiceContext
      index = VectorStoreIndex.from_documents(
          documents,
          storage_context=storage_context,
          embed_model=embed_model,
          # Nếu muốn tùy chỉnh text splitter, dùng ServiceContext
          # service_context=ServiceContext.from_defaults(text_splitter=text_splitter)
      )
      
      print("Đã tạo và lưu trữ index LlamaIndex thành công.")
          
    5. Truy vấn Index (Querying):
    6. Sau khi index được tạo, bạn có thể tạo ra một “query engine” để đặt câu hỏi. Query engine sẽ tự động thực hiện các bước truy xuất (retrieval) từ index và truyền kết quả cho LLM để tạo câu trả lời.

      # Tạo query engine từ index
      query_engine = index.as_query_engine(llm=llm)
      
      # Đặt câu hỏi
      query = "LangChain và LlamaIndex dùng để làm gì?"
      
      # Chạy query engine
      response = query_engine.query(query)
      
      print("\n--- Kết Quả RAG LlamaIndex ---")
      print(f"Câu hỏi: {query}")
      print(f"Trả lời: {response.response}")
      print("\nCác đoạn văn bản nguồn được sử dụng:")
      for source_node in response.source_nodes:
          print(f"- {source_node.get_content()}")
          print(f"  (Score: {source_node.score:.4f})")
          print(f"  (Source: {source_node.metadata.get('file_path', 'N/A')})")
          

      LlamaIndex cung cấp các loại query engine khác nhau và cho phép tùy chỉnh retriever (số lượng node trả về, bộ lọc metadata, v.v.).

      So với LangChain, LlamaIndex có vẻ tích hợp chặt chẽ các bước đầu của pipeline RAG (tải, phân đoạn, nhúng, lưu) vào quá trình tạo index, làm cho workflow ban đầu có vẻ đơn giản hơn nếu bạn chấp nhận các thiết lập mặc định.

      LangChain vs. LlamaIndex: Lựa Chọn Nào Phù Hợp Cho Bạn?

      Cả LangChain và LlamaIndex đều là những công cụ tuyệt vời để xây dựng ứng dụng LLM và triển khai RAG. Lựa chọn giữa chúng thường phụ thuộc vào sở thích cá nhân, triết lý phát triển, và nhu cầu cụ thể của dự án. Dưới đây là bảng so sánh một số điểm chính:

      Đặc điểm LangChain LlamaIndex
      Triết lý cốt lõi Hướng component/modular. Xây dựng “chains” bằng cách ghép nối các khối chức năng. Hướng dữ liệu. Tập trung vào việc “index” dữ liệu và “query” index đó.
      Cấu trúc Pipeline RAG Các bước tải, phân đoạn, nhúng, lưu trữ, truy xuất, tạo sinh là các component riêng biệt được kết nối thủ công hoặc thông qua các chain/LCEL. Các bước tải, phân đoạn, nhúng, lưu trữ thường được tích hợp vào quá trình tạo “index”. Truy xuất và tạo sinh là chức năng của “query engine”.
      Độ linh hoạt Rất linh hoạt, cho phép thay thế hoặc tùy chỉnh từng component một cách chi tiết. Tuyệt vời cho việc xây dựng các workflow phức tạp hơn ngoài RAG. Linh hoạt trong việc tùy chỉnh các bước trong quá trình tạo index và cấu hình query engine. Mạnh mẽ cho các ứng dụng tập trung vào truy vấn dữ liệu.
      Tính dễ sử dụng (cho RAG cơ bản) Từng bước rõ ràng, dễ hiểu cấu trúc bên trong. Cần ghép nối nhiều component ban đầu. Tích hợp các bước vào index tạo cảm giác “plug-and-play” hơn cho pipeline RAG đơn giản.
      Hệ sinh thái và tích hợp Hệ sinh thái rất rộng, tích hợp với vô số tools, models, services. Có module Agents mạnh mẽ. Tích hợp tốt với các nguồn dữ liệu và vector stores. Mạnh về các chiến lược index và query.
      Trường hợp sử dụng điển hình Xây dựng các ứng dụng LLM phức tạp bao gồm nhiều bước xử lý (RAG, Agents, tương tác APIs…). Các workflow tùy chỉnh cao. Xây dựng các ứng dụng hỏi đáp, phân tích dữ liệu dựa trên tập dữ liệu riêng (document Q&A, chat với data).

      Đối với AI Engineers mới bắt đầu, cả hai đều đáng học. Bạn có thể thấy LangChain cung cấp cái nhìn chi tiết hơn về từng bước riêng lẻ, trong khi LlamaIndex có thể giúp bạn xây dựng một ứng dụng RAG cơ bản nhanh chóng hơn. Nhiều nhà phát triển thậm chí còn kết hợp cả hai trong cùng một dự án, sử dụng LlamaIndex để indexing dữ liệu và LangChain để xây dựng các workflow phức tạp hơn sử dụng dữ liệu từ index đó.

      Những Điều Cần Lưu Ý Khi Triển Khai RAG Thực Tế

      Việc triển khai RAG trong môi trường thực tế đòi hỏi nhiều cân nhắc hơn ví dụ đơn giản trên:

      • Kích thước đoạn (Chunk Size) và Chồng lấp (Overlap): Việc chọn kích thước đoạn và mức độ chồng lấp phù hợp là rất quan trọng và thường phụ thuộc vào loại dữ liệu của bạn. Kích thước nhỏ hơn giúp embedding tập trung hơn, nhưng có thể làm mất ngữ cảnh. Kích thước lớn hơn giữ ngữ cảnh tốt hơn nhưng có thể vượt quá giới hạn của embedding model hoặc LLM.
      • Lựa chọn Embedding Model: Chất lượng của embedding model ảnh hưởng trực tiếp đến khả năng truy xuất các đoạn văn bản liên quan. Hãy so sánh và chọn mô hình phù hợp với nhu cầu và ngân sách của bạn (các mô hình OpenAI, các mô hình mã nguồn mở trên Hugging Face…).
      • Lựa chọn Vector Database: Chroma là lựa chọn tuyệt vời cho phát triển cục bộ và thử nghiệm. Tuy nhiên, cho các ứng dụng production với lượng dữ liệu lớn hoặc cần khả năng mở rộng, các Vector Database chuyên dụng như Pinecone, Weaviate, Qdrant, Milvus… sẽ phù hợp hơn.
      • Tối ưu hóa Truy xuất (Retrieval): Kỹ thuật truy xuất đơn giản (ví dụ: top-k) có thể chưa đủ tốt. Các kỹ thuật nâng cao như Retrievial đánh giá lại (re-ranking), Multi-query retrieval, hay sử dụng các chiến lược truy xuất phân cấp (hierarchical retrieval) có thể cải thiện đáng kể chất lượng trả lời. LangChain và LlamaIndex đều hỗ trợ các kỹ thuật này.
      • Quản lý Metadata: Sử dụng metadata (thông tin bổ sung về tài liệu, như nguồn, ngày tháng, tiêu đề…) có thể giúp bạn lọc hoặc tăng cường kết quả truy xuất.
      • Đánh giá (Evaluation): Làm sao để biết pipeline RAG của bạn hoạt động tốt? Việc đánh giá là rất quan trọng. Có các metrics và framework (ví dụ: RAGAS) để đo lường chất lượng của các đoạn truy xuất và câu trả lời được tạo ra.
      • Chi phí: Sử dụng các API thương mại (OpenAI, Anthropic…) cho cả embedding và LLM có thể tốn kém, đặc biệt với lượng dữ liệu lớn hoặc tần suất truy vấn cao. Hãy lưu ý đến chi phí token. Việc sử dụng các mô hình mã nguồn mở và chạy LLM cục bộ với Ollama có thể là các lựa chọn thay thế tiết kiệm chi phí.
      • An toàn và Đạo đức: Giống như mọi ứng dụng LLM, hãy cẩn trọng với prompt injection, thiên vị, và vấn đề đạo đức trong AI. Các framework này có thể hỗ trợ một phần trong việc này (ví dụ: tích hợp với moderation API), nhưng AI Engineer cần chủ động thiết kế hệ thống an toàn.

      Việc làm chủ LangChain hoặc LlamaIndex (hoặc cả hai) là một kỹ năng quan trọng trên lộ trình trở thành AI Engineer. Chúng giúp bạn tăng tốc đáng kể quá trình phát triển và thử nghiệm các ý tưởng ứng dụng LLM mới mẻ.

      Lời Kết

      Qua bài viết này, chúng ta đã cùng nhau tìm hiểu cách triển khai một pipeline RAG cơ bản sử dụng hai framework hàng đầu hiện nay là LangChain và LlamaIndex. Bạn đã thấy cách chúng trừu tượng hóa các bước phức tạp, từ tải và xử lý dữ liệu đến tương tác với vector database và LLM, giúp việc xây dựng các ứng dụng hỏi đáp dựa trên dữ liệu riêng trở nên dễ dàng hơn bao giờ hết.

      Việc lựa chọn giữa LangChain và LlamaIndex phụ thuộc vào phong cách làm việc và yêu cầu cụ thể của dự án, nhưng cả hai đều là những công cụ mạnh mẽ mà mọi AI Engineer đều nên khám phá.

      Hãy thử nghiệm với các đoạn code ví dụ, thay đổi dữ liệu nguồn, thử các mô hình embedding và LLM khác nhau, và khám phá các tính năng nâng cao mà các framework này cung cấp. Thực hành là chìa khóa để làm chủ các kỹ năng này.

      Trong các bài viết tiếp theo của series “AI Engineer Roadmap”, chúng ta sẽ tiếp tục khám phá những chủ đề hấp dẫn khác trong thế giới AI, có thể là đi sâu hơn vào các kỹ thuật RAG nâng cao, giới thiệu về Agents, hoặc các chủ đề liên quan đến triển khai và tối ưu hóa.

      Nếu bạn có bất kỳ câu hỏi nào hoặc muốn chia sẻ kinh nghiệm của mình với LangChain hoặc LlamaIndex, đừng ngần ngại để lại bình luận bên dưới nhé! Hẹn gặp lại các bạn trong bài viết tiếp theo!

Chỉ mục