Công cụ Gemini API File Search là một hệ thống RAG (Retrieval-Augmented Generation - Tạo phản hồi tăng cường tra cứu) được quản lý hoàn toàn (fully managed), tích hợp trực tiếp vào Gemini API. Nó tự động quản lý việc lưu trữ tệp, chia nhỏ dữ liệu (chunking), tạo vector nhúng (embeddings) và chèn các ngữ cảnh liên quan nhất vào prompt của bạn một cách mượt mà.
Chi phí có đắt không? Không, việc lưu trữ tệp và tạo embedding tại thời điểm truy vấn là hoàn toàn miễn phí. Bạn chỉ trả phí cho lần lập chỉ mục (indexing) ban đầu của tệp với mức giá cố định là 0.15$ trên 1 triệu token (dựa trên embedding).
Hướng dẫn này sẽ đi qua toàn bộ vòng đời của việc sử dụng File Search với JavaScript/TypeScript, bạn sẽ học cách:
  • Tạo một Kho tìm kiếm tệp (File Search Store)
  • Tìm một Kho bằng Tên hiển thị (Display Name)
  • Tải lên nhiều tệp cùng lúc (Concurrent Upload)
  • Tải lên nâng cao: Chia nhỏ dữ liệu & Siêu dữ liệu (Metadata)
  • Chạy truy vấn tạo văn bản tiêu chuẩn (RAG)
  • Tìm kiếm, Xóa và Cập nhật tài liệu cụ thể
  • Dọn dẹp: Xóa Kho tìm kiếm tệp
Trước khi bắt đầu, hãy đảm bảo bạn đã có API key từ Google AI Studio và cài đặt SDK mới nhất:
npm install @google/genai
Khởi tạo client trong môi trường JavaScript của bạn:
import { GoogleGenAI } from '@google/genai';
import fs from 'fs';
import path from 'path';
 
const ai = new GoogleGenAI({});

1) Tạo một File Search Store

Một File Search Store là một vùng chứa lâu dài cho các đoạn tài liệu và vector nhúng của bạn. Nó tách biệt với kho lưu trữ tệp thô và có thể chứa hàng gigabyte dữ liệu.
const fileStoreName = 'my-example-store';
 
const createStoreOp = await ai.fileSearchStores.create({
  config: { displayName: fileStoreName }
});
 
console.log(`Đã tạo Kho với tên: `);

2) Tìm một Kho bằng Tên hiển thị (Display Name)

Thông thường, việc tạo kho và sử dụng kho diễn ra ở các phiên làm việc khác nhau. Vì API gán một ID duy nhất (fileSearchStores/xyz...), bạn cần tìm kiếm nó thông qua displayName dễ đọc.
let fileStore = null;
// Liệt kê các kho với giới hạn 10 kết quả mỗi trang
const pager = await ai.fileSearchStores.list({ config: { pageSize: 10 } });
let page = pager.page;
 
// Lặp qua các trang cho đến khi tìm thấy kết quả khớp
searchLoop: while (true) {
  for (const store of page) {
    if (store.displayName === fileStoreName) {
      fileStore = store;
      break searchLoop;
    }
  }
  if (!pager.hasNextPage()) break;
  page = await pager.nextPage();
}
 
if (!fileStore) {
  throw new Error(`Không tìm thấy kho có tên hiển thị ''.`);
}
console.log(`Đã tìm thấy kho: `);

3) Tải lên nhiều tệp cùng lúc

Tốc độ là yếu tố quan trọng. Khi nạp một thư mục tài liệu, đừng xử lý chúng tuần tự. API hỗ trợ các thao tác đồng thời, vì vậy chúng ta có thể sử dụng Promise.all để tải lên và xử lý nhiều tệp cùng một lúc.
Chúng ta sẽ sử dụng phương thức hỗ trợ uploadToFileSearchStore, phương thức này xử lý việc tải tệp thô và bắt đầu quá trình lập chỉ mục trong một bước duy nhất. Sau đó, chúng ta theo dõi operation.done để đảm bảo quá trình xử lý hoàn tất trước khi tiếp tục.
const docsDir = "docs"; // Đảm bảo bạn có thư mục 'docs' chứa các tệp văn bản
const files = fs.readdirSync(docsDir).map(file => path.join(docsDir, file));
 
await Promise.all(files.map(async (filePath) => {
  // 1. Khởi tạo tải lên và lập chỉ mục
  let operation = await ai.fileSearchStores.uploadToFileSearchStore({
    file: filePath,
    fileSearchStoreName: fileStore.name,
    config: {
      displayName: path.basename(filePath),
    }
  });
 
  // 2. Kiểm tra trạng thái cho đến khi tài liệu được xử lý xong
  while (!operation.done) {
    await new Promise(resolve => setTimeout(resolve, 1000)); // đợi 1 giây
    operation = await ai.operations.get({ operation });
  }
  console.log(`Xử lý hoàn tất cho: `);
  return operation;
}));

4) Tải lên với Chiến lược Chia nhỏ dữ liệu (Chunking) tùy chỉnh

Mặc dù Gemini xử lý việc chia nhỏ dữ liệu rất thông minh theo mặc định, nhưng trong một số trường hợp, bạn có thể muốn kiểm soát chặt chẽ hơn cách dữ liệu được chia tách.
Bạn có thể xác định chunkingConfig khi tải lên để chỉ định các tham số như maxTokensPerChunk, maxOverlapTokens và customMetadata để gắn các cặp khóa-giá trị vào tài liệu.
const specialDocPath = 'special-docs/technical-manual.txt';
 
let advancedUploadOp = await ai.fileSearchStores.uploadToFileSearchStore({
  file: specialDocPath,
  fileSearchStoreName: fileStore.name,
  config: {
    displayName: 'technical-manual.txt',
    customMetadata: [
      { key: "doc_type", stringValue: "manual" },
    ],
    chunkingConfig: {
      whiteSpaceConfig: {
        maxTokensPerChunk: 500, // Các đoạn nhỏ hơn để tra cứu chính xác hơn
        maxOverlapTokens: 50    // Đảm bảo ngữ cảnh không bị mất giữa các đoạn
      }
    }
  }
});
 
// Đợi tệp xử lý xong
while (!advancedUploadOp.done) {
  await new Promise(resolve => setTimeout(resolve, 1000));
  advancedUploadOp = await ai.operations.get({ operation: advancedUploadOp });
}
console.log("Đã xử lý xong tệp nâng cao.");

5) Chạy truy vấn sử dụng File Search (RAG)

Chúng ta không cần phải tự mình lấy các đoạn dữ liệu một cách thủ công. Chỉ cần yêu cầu mô hình Gemini sử dụng công cụ fileSearch và trỏ nó tới tên kho lưu trữ của chúng ta. Gemini sẽ hiểu rằng nó cần thêm thông tin, tự động tìm kiếm trong kho và đưa ra phản hồi dựa trên dữ liệu thực tế (grounding).
const response = await ai.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "Gemini là gì và File API là gì?",
  config: {
    tools: [{
      fileSearch: {
        fileSearchStoreNames: [fileStore.name]
      }
    }]
  }
});
 
console.log("Phản hồi từ mô hình:", response.text);
// Bạn có thể kiểm tra response.candidates[0].groundingMetadata để xem các nguồn trích dẫn!
Vì chúng ta đã gắn thẻ (tag) cho hướng dẫn kỹ thuật ở Bước 4, chúng ta có thể ép buộc Gemini chỉ xem các tài liệu khớp với thẻ đó bằng cách sử dụng metadataFilter.
const responseFiltered = await ai.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "Làm thế nào để khởi động lại thiết bị theo hướng dẫn sử dụng?",
  config: {
    tools: [{
      fileSearch: {
        fileSearchStoreNames: [fileStore.name],
        metadataFilter: 'doc_type="manual"'
      }
    }]
  }
});
 
console.log("Phản hồi đã lọc:", responseFiltered.text);

6) Tìm một Tài liệu cụ thể trong Kho

Bạn sẽ thường xuyên cần quản lý các tài liệu riêng lẻ bên trong kho của mình. Bạn có thể tìm một tài liệu cụ thể dựa trên tên hiển thị của nó.
const docToFind = 'doc1.txt';
let targetDoc = null;
 
let documentPager = await ai.fileSearchStores.documents.list({
  parent: fileStore.name,
});
 
// Lặp qua danh sách tài liệu trong kho
searchDocsLoop: while (true) {
  for (const document of documentPager.page) {
    if (document.displayName === docToFind) {
      targetDoc = document;
      break searchDocsLoop;
    }
  }
  if (!documentPager.hasNextPage()) break;
  documentPager = await documentPager.nextPage();
}
 
if (!targetDoc) throw new Error(`Không tìm thấy tài liệu ''.`);

7) Xóa một Tài liệu

Hiện tại, quy trình tiêu chuẩn để cập nhật một tài liệu trong File Search là xóa phiên bản cũ và tải lên phiên bản mới.
await ai.fileSearchStores.documents.delete({
    name: targetDoc.name,
    config: { force: true } // Bắt buộc để xóa vĩnh viễn khỏi kho
});

8) Cập nhật một Tài liệu

Các tài liệu trong File Search là bất biến (immutable) sau khi đã được lập chỉ mục. Để "cập nhật" một tài liệu, bạn phải tìm nó, xóa nó và tải lên phiên bản mới. Trong bước này, chúng ta sẽ tự động hóa toàn bộ vòng lặp để cập nhật doc1.txt với thông tin mới.
const docToUpdate = 'doc1.txt'; // giả sử tệp này đã có nội dung mới
const localDocPath = path.join(docsDir, docToUpdate);
 
// 1. Tìm ID của tài liệu hiện có trong kho dựa trên tên hiển thị
let documentPager = await ai.fileSearchStores.documents.list({ parent: fileStore.name });
let foundDoc = null;
findLoop: while (true) {
  for (const doc of documentPager.page) {
    if (doc.displayName === docToUpdate) {
      foundDoc = doc;
      break findLoop;
    }
  }
  if (!documentPager.hasNextPage()) break;
  documentPager = await documentPager.nextPage();
}
 
// 2. Nếu tìm thấy, tiến hành xóa nó
if (foundDoc) {
   await ai.fileSearchStores.documents.delete({
     name: foundDoc.name,
     config: { force: true } // 'force' là bắt buộc để xóa các tài liệu đã lập chỉ mục
   });
}
 
// 3. Tải lên phiên bản mới
let updateOp = await ai.fileSearchStores.uploadToFileSearchStore({
  file: localDocPath,
  fileSearchStoreName: fileStore.name,
  config: { displayName: docToUpdate }
});

while (!updateOp.done) {
   await new Promise(resolve => setTimeout(resolve, 1000));
   updateOp = await ai.operations.get({ operation: updateOp });
}
 
console.log("Bản hiệu chỉnh đã được tải lên và lập chỉ mục thành công.");

9) Dọn dẹp: Xóa File Search Store

Hiện tại, bạn bị giới hạn tối đa 10 File Search Store cho mỗi dự án, vì vậy việc dọn dẹp tài nguyên sau khi kết thúc quá trình phát triển là rất quan trọng.
await ai.fileSearchStores.delete({
    name: fileStore.name,
    config: { force: true } // Xóa toàn bộ kho và các tài liệu bên trong
});
Nguồn bài viết từ Tác giả Phil Schmid