mcpField
G
google-gemini-file-search

google-gemini-file-search

github.com/jezweb数据与数据库1.1k328 次安装3 周前更新
代码仓库

关于 google-gemini-file-search

为开发者提供 Google Gemini File Search 的设置与集成指南,使用托管式 RAG 处理文档检索。内容包括文档上传、索引、分块、查询和常见错误防范。

主要功能

  1. 01创建与管理 File Search 存储
  2. 02上传并索引文档
  3. 03配置分块与元数据
  4. 04使用自然语言查询文档
  5. 05检索结果引用处理
  6. 06常见错误预防

适用场景

  1. 01基于文档构建问答功能
  2. 02为应用接入托管式 RAG
  3. 03按元数据筛选文档检索

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jezweb/claude-skills --skill google-gemini-file-search

安装后在 Claude Code 中输入/google-gemini-file-search确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
PROJECT_STATUS.md
README.md
.claude-plugin/
plugin.json
references/
README.md
scripts/
README.md
create-store.ts
templates/
README.md

详细介绍

Google Gemini File Search 设置

概述

Google Gemini File Search 是一个全托管的 RAG 系统。上传文档(支持 100 多种格式:PDF、Word、Excel、代码),并使用自然语言进行查询——自动分块、生成嵌入向量、语义搜索和提供引用。

此技能提供:

  • 完整的 @google/genai File Search API 设置
  • 8 个错误的详细说明及预防策略
  • 优化检索效果的分块最佳实践
  • 成本优化(索引每 100 万个 Token 收费 $0.15,存储乘数为 3 倍)
  • Cloudflare Workers + Next.js 集成模板

前提条件

1. Google AI API 密钥

在 https://aistudio.google.com/apikey 创建 API 密钥

免费套餐限制:

  • 1 GB 存储空间(所有 file search store 的总容量)
  • 每天 1,500 次请求
  • 每分钟 100 万个 Token

付费套餐价格:

  • 索引:每 100 万个输入 Token 收费 $0.15(一次性)
  • 存储:免费(Tier 1:10 GB,Tier 2:100 GB,Tier 3:1 TB)
  • 查询时的嵌入向量:免费(检索到的上下文计为输入 Token)
2. Node.js 环境

最低版本: Node.js 18+(推荐 v20+)

node --version  # Should be >=18.0.0
3. 安装 @google/genai SDK
npm install @google/genai
# or
pnpm add @google/genai
# or
yarn add @google/genai

当前稳定版本: 1.30.0+(使用 npm view @google/genai version 验证)

⚠️ 重要: File Search API 要求使用 @google/genai v1.29.0 或更高版本。早期版本不支持 File Search。该 API 于 v1.29.0 中添加(2025 年 11 月 5 日)。

4. TypeScript 配置(可选,但推荐)
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  }
}

可预防的常见错误

此技能可预防实现 File Search 时常见的 12 个错误:

错误 1:文档不可变

症状:

Error: Documents cannot be modified after indexing

原因: 文档一经索引便不可更改。没有 PATCH 或 UPDATE 操作。

预防措施: 更新时使用先删除再重新上传的模式:

// ❌ WRONG: Trying to update document (no such API)
await ai.fileSearchStores.documents.update({
  name: documentName,
  customMetadata: { version: '2.0' }
})

// ✅ CORRECT: Delete then re-upload
const docs = await ai.fileSearchStores.documents.list({
  parent: fileStore.name
})

const oldDoc = docs.documents.find(d => d.displayName === 'manual.pdf')
if (oldDoc) {
  await ai.fileSearchStores.documents.delete({
    name: oldDoc.name,
    force: true
  })
}

await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('manual-v2.pdf'),
  config: { displayName: 'manual.pdf' }
})

来源: https://ai.google.dev/api/file-search/documents

错误 2:超出存储配额

症状:

Error: Quota exceeded. Expected 1GB limit, but 3.2GB used.

原因: 存储空间计算包括输入文件、嵌入向量和元数据。总存储空间约为输入文件大小的 3 倍。

预防措施: 上传前计算存储空间:

// ❌ WRONG: Assuming storage = file size
const fileSize = fs.statSync('data.pdf').size // 500 MB
// Expect 500 MB usage → WRONG

// ✅ CORRECT: Account for 3x multiplier
const fileSize = fs.statSync('data.pdf').size // 500 MB
const estimatedStorage = fileSize * 3 // 1.5 GB (embeddings + metadata)
console.log(`Estimated storage: ${estimatedStorage / 1e9} GB`)

// Check if within quota before upload
if (estimatedStorage > 1e9) {
  console.warn('⚠️ File may exceed free tier 1 GB limit')
}

来源: https://blog.google/technology/developers/file-search-gemini-api/

错误 3:分块配置不正确

症状: 检索质量差、结果不相关,或上下文在句子中间被截断。

原因: 默认分块设置可能不适合你的内容类型。

预防措施: 使用推荐的分块策略:

// ❌ WRONG: Using defaults without testing
await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('docs.pdf')
  // Default chunking may be too large or too small
})

// ✅ CORRECT: Configure chunking for precision
await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('docs.pdf'),
  config: {
    chunkingConfig: {
      whiteSpaceConfig: {
        maxTokensPerChunk: 500,  // Smaller chunks = more precise retrieval
        maxOverlapTokens: 50     // 10% overlap prevents context loss
      }
    }
  }
})

分块指南:

  • 技术文档/代码: 每块 500 个 Token,重叠 50 个 Token
  • 散文/文章: 每块 800 个 Token,重叠 80 个 Token
  • 法律文件/合同: 每块 300 个 Token,重叠 30 个 Token(高精度)

来源: https://www.philschmid.de/gemini-file-search-javascript

错误 4:超出元数据限制

症状:

Error: Maximum 20 custom metadata key-value pairs allowed

原因: 每份文档最多可包含 20 个元数据字段。

预防措施: 设计精简的元数据架构:

// ❌ WRONG: Too many metadata fields
await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('doc.pdf'),
  config: {
    customMetadata: {
      doc_type: 'manual',
      version: '1.0',
      author: 'John Doe',
      department: 'Engineering',
      created_date: '2025-01-01',
      // ... 18 more fields → Error!
    }
  }
})

// ✅ CORRECT: Use hierarchical keys or JSON strings
await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('doc.pdf'),
  config: {
    customMetadata: {
      doc_type: 'manual',
      version: '1.0',
      author_dept: 'John Doe|Engineering',  // Combine related fields
      dates: JSON.stringify({                // Or use JSON for complex data
        created: '2025-01-01',
        updated: '2025-01-15'
      })
    }
  }
})

来源: https://ai.google.dev/api/file-search/documents

错误 5:索引成本超出预期

症状: 上传 10 GB 文档后,收到意外的 $375 账单。

原因: 索引费用为一次性收费,但按输入 Token 计费(每 100 万个 Token 收费 $0.15)。

预防措施: 索引前估算成本:

// ❌ WRONG: No cost estimation
await uploadAllDocuments(fileStore.name, './data') // 10 GB uploaded → $375 surprise

// ✅ CORRECT: Calculate costs upfront
const totalSize = getTotalDirectorySize('./data') // 10 GB
const estimatedTokens = (totalSize / 4) // Rough estimate: 1 token ≈ 4 bytes
const indexingCost = (estimatedTokens / 1e6) * 0.15

console.log(`Estimated indexing cost: $${indexingCost.toFixed(2)}`)
console.log(`Estimated storage: ${(totalSize * 3) / 1e9} GB`)

// Confirm before proceeding
const proceed = await confirm(`Proceed with indexing? Cost: $${indexingCost.toFixed(2)}`)
if (proceed) {
  await uploadAllDocuments(fileStore.name, './data')
}

费用示例:

  • 1 GB 文本 ≈ 2.5 亿个 Token = $37.50 索引费
  • 100 MB PDF ≈ 2,500 万个 Token = $3.75 索引费
  • 10 MB 代码 ≈ 250 万个 Token = $0.38 索引费

来源: https://ai.google.dev/pricing

错误 6:未轮询操作状态

症状: 上传后立即查询却没有结果,或索引不完整。

原因: 文件上传采用异步处理。必须轮询操作,直到 done: true。

预防措施: 始终轮询操作状态,并设置超时和回退方案:

// ❌ WRONG: Assuming upload is instant
const operation = await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('large.pdf')
})
// Immediately query → No results!

// ✅ CORRECT: Poll until indexing complete with timeout
const operation = await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('large.pdf')
})

// Poll with timeout and fallback
const MAX_POLL_TIME = 60000 // 60 seconds
const POLL_INTERVAL = 1000
let elapsed = 0

while (!operation.done && elapsed < MAX_POLL_TIME) {
  await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL))
  elapsed += POLL_INTERVAL

  try {
    operation = await ai.operations.get({ name: operation.name })
    console.log(`Indexing progress: ${operation.metadata?.progress || 'processing...'}`)
  } catch (error) {
    console.warn('Polling failed, assuming complete:', error)
    break
  }
}

if (operation.error) {
  throw new Error(`Indexing failed: ${operation.error.message}`)
}

// ⚠️ Warning: operations.get() can be unreliable for large files
// If timeout reached, verify document exists manually
if (elapsed >= MAX_POLL_TIME) {
  console.warn('Polling timeout - verifying document manually')
  const docs = await ai.fileSearchStores.documents.list({ parent: fileStore.name })
  const uploaded = docs.documents?.find(d => d.displayName === 'large.pdf')
  if (uploaded) {
    console.log('✅ Document found despite polling timeout')
  } else {
    throw new Error('Upload failed - document not found')
  }
}

console.log('✅ Indexing complete:', operation.response?.displayName)

来源: https://ai.google.dev/api/file-search/file-search-stores#uploadtofilesearchstore、[GitHub Issue #1211](https://github.com/googleapis/js-genai/issues/1211)

错误 7:忘记强制删除

症状:

Error: Cannot delete store with documents. Set force=true.

原因: 包含文档的存储区需要设置 force: true 才能删除(以防止意外删除)。

预防措施: 删除非空存储区时,始终使用 force: true:

// ❌ WRONG: Trying to delete store with documents
await ai.fileSearchStores.delete({
  name: fileStore.name
})
// Error: Cannot delete store with documents

// ✅ CORRECT: Use force delete
await ai.fileSearchStores.delete({
  name: fileStore.name,
  force: true  // Deletes store AND all documents
})

// Alternative: Delete documents first
const docs = await ai.fileSearchStores.documents.list({ parent: fileStore.name })
for (const doc of docs.documents || []) {
  await ai.fileSearchStores.documents.delete({
    name: doc.name,
    force: true
  })
}
await ai.fileSearchStores.delete({ name: fileStore.name })

来源: https://ai.google.dev/api/file-search/file-search-stores#delete

错误 8:使用不受支持的模型

症状:

Error: File Search is only supported for Gemini 3 Pro and Flash models

原因: 文件搜索需要 Gemini 3 Pro 或 Gemini 3 Flash。不支持 Gemini 2.x 和 1.5 模型。

预防措施: 始终使用 Gemini 3 模型:

// ❌ WRONG: Using Gemini 1.5 model
const response = await ai.models.generateContent({
  model: 'gemini-1.5-pro',  // Not supported!
  contents: 'What is the installation procedure?',
  config: {
    tools: [{
      fileSearch: { fileSearchStoreNames: [fileStore.name] }
    }]
  }
})

// ✅ CORRECT: Use Gemini 3 models
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',  // ✅ Supported (fast, cost-effective)
  // OR
  // model: 'gemini-3-pro',   // ✅ Supported (higher quality)
  contents: 'What is the installation procedure?',
  config: {
    tools: [{
      fileSearch: { fileSearchStoreNames: [fileStore.name] }
    }]
  }
})

来源: https://ai.google.dev/gemini-api/docs/file-search

错误 9:Blob 来源的 displayName 未保留(已在 v1.34.0+ 修复)

症状:

groundingChunks[0].title === null  // No document source shown

原因: 在 @google/genai v1.34.0 之前的版本中,以 Blob 对象(而非文件路径)上传文件时,SDK 会丢弃 displayName 和 customMetadata 配置字段。

预防措施:

// ✅ CORRECT: Upgrade to v1.34.0+ for automatic fix
npm install @google/genai@latest  // v1.34.0+

await ai.fileSearchStores.uploadToFileSearchStore({
  name: storeName,
  file: new Blob([arrayBuffer], { type: 'application/pdf' }),
  config: {
    displayName: 'Safety Manual.pdf',  // ✅ Now preserved
    customMetadata: { version: '1.0' }  // ✅ Now preserved
  }
})

// ⚠️ WORKAROUND for v1.33.0 and earlier: Use resumable upload
const uploadUrl = `https://generativelanguage.googleapis.com/upload/v1beta/${storeName}:uploadToFileSearchStore?key=${API_KEY}`

// Step 1: Initiate with displayName in body
const initResponse = await fetch(uploadUrl, {
  method: 'POST',
  headers: {
    'X-Goog-Upload-Protocol': 'resumable',
    'X-Goog-Upload-Command': 'start',
    'X-Goog-Upload-Header-Content-Length': numBytes.toString(),
    'X-Goog-Upload-Header-Content-Type': 'application/pdf',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    displayName: 'Safety Manual.pdf'  // ✅ Works with resumable upload
  })
})

// Step 2: Upload file bytes
const uploadUrl2 = initResponse.headers.get('X-Goog-Upload-URL')
await fetch(uploadUrl2, {
  method: 'PUT',
  headers: {
    'Content-Length': numBytes.toString(),
    'X-Goog-Upload-Offset': '0',
    'X-Goog-Upload-Command': 'upload, finalize',
    'Content-Type': 'application/pdf'
  },
  body: fileBytes
})

来源: GitHub 问题 #1078

错误 10:JSON 响应模式下忽略接地元数据

症状:

response.candidates[0].groundingMetadata === undefined
// Even though fileSearch tool is configured

原因: 使用 responseMimeType: 'application/json' 进行结构化输出时,API 会忽略 fileSearch 工具,且不会返回接地元数据,即使使用 Gemini 3 模型也是如此。

预防措施:

// ❌ WRONG: Structured output overrides grounding
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'Summarize guidelines',
  config: {
    responseMimeType: 'application/json',  // Loses grounding
    tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
  }
})

// ✅ CORRECT: Two-step approach
// Step 1: Get grounded text response
const textResponse = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'Summarize guidelines',
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
  }
})

const grounding = textResponse.candidates[0].groundingMetadata

// Step 2: Convert to structured format in prompt
const jsonResponse = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: `Convert to JSON: ${textResponse.text}

Format:
{
  "summary": "...",
  "key_points": ["..."]
}`,
  config: {
    responseMimeType: 'application/json',
    responseSchema: {
      type: 'object',
      properties: {
        summary: { type: 'string' },
        key_points: { type: 'array', items: { type: 'string' } }
      }
    }
  }
})

// Combine results
const result = {
  data: JSON.parse(jsonResponse.text),
  sources: grounding.groundingChunks
}

来源: GitHub 问题 #829

错误 11:Google Search 和 File Search 工具互斥

症状:

Error: "Search as a tool and file search tool are not supported together"
Status: INVALID_ARGUMENT

原因: Gemini API 不允许在同一请求中同时使用 googleSearch 和 fileSearch 工具。

预防措施:

// ❌ WRONG: Combining search tools
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'What are the latest industry guidelines?',
  config: {
    tools: [
      { googleSearch: {} },
      { fileSearch: { fileSearchStoreNames: [storeName] } }
    ]
  }
})

// ✅ CORRECT: Use separate specialist agents
async function searchWeb(query: string) {
  return ai.models.generateContent({
    model: 'gemini-3-flash',
    contents: query,
    config: { tools: [{ googleSearch: {} }] }
  })
}

async function searchDocuments(query: string) {
  return ai.models.generateContent({
    model: 'gemini-3-flash',
    contents: query,
    config: { tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }] }
  })
}

// Orchestrate based on query type
const needsWeb = query.includes('latest') || query.includes('current')
const response = needsWeb
  ? await searchWeb(query)
  : await searchDocuments(query)

来源: GitHub 问题 #435、Google Codelabs

错误 12:Batch API 缺少响应元数据(社区提供)

症状: 使用 metadata 字段时,无法将批次响应与请求对应起来。

原因: 使用包含 metadata 字段的 InlinedRequest 调用 Batch API 时,相应的 InlinedResponse 不会返回该 metadata。

预防措施:

// ❌ WRONG: Expecting metadata in response
const batchRequest = {
  metadata: { key: 'my-request-id' },
  contents: [{ parts: [{ text: 'Question?' }], role: 'user' }],
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
  }
}

const batchResponse = await ai.batch.create({ requests: [batchRequest] })
console.log(batchResponse.responses[0].metadata)  // ❌ undefined

// ✅ CORRECT: Use array index to correlate
const requests = [
  { metadata: { id: 'req-1' }, contents: [...] },
  { metadata: { id: 'req-2' }, contents: [...] }
]

const responses = await ai.batch.create({ requests })

// Map by index (not ideal but works)
responses.responses.forEach((response, i) => {
  const requestMetadata = requests[i].metadata
  console.log(`Response for ${requestMetadata.id}:`, response)
})

社区验证: 维护者已确认,并提交了内部缺陷报告。

来源: GitHub 问题 #1191

设置说明

第 1 步:初始化客户端
import { GoogleGenAI } from '@google/genai'
import fs from 'fs'

// Initialize client with API key
const ai = new GoogleGenAI({
  apiKey: process.env.GOOGLE_API_KEY
})

// Verify API key is set
if (!process.env.GOOGLE_API_KEY) {
  throw new Error('GOOGLE_API_KEY environment variable is required')
}
第 2 步:创建文件搜索存储区
// Create a store (container for documents)
const fileStore = await ai.fileSearchStores.create({
  config: {
    displayName: 'my-knowledge-base',  // Human-readable name
    // Optional: Add store-level metadata
    customMetadata: {
      project: 'customer-support',
      environment: 'production'
    }
  }
})

console.log('Created store:', fileStore.name)
// Output: fileSearchStores/abc123xyz...

查找现有存储区:

// List all stores (paginated)
const stores = await ai.fileSearchStores.list({
  pageSize: 20  // Max 20 per page
})

// Find by display name
let targetStore = null
let pageToken = null

do {
  const page = await ai.fileSearchStores.list({ pageToken })
  targetStore = page.fileSearchStores.find(
    s => s.displayName === 'my-knowledge-base'
  )
  pageToken = page.nextPageToken
} while (!targetStore && pageToken)

if (targetStore) {
  console.log('Found existing store:', targetStore.name)
} else {
  console.log('Store not found, creating new one...')
}
第 3 步:上传文档

上传单个文件:

const operation = await ai.fileSearchStores.uploadToFileSearchStore({
  name: fileStore.name,
  file: fs.createReadStream('./docs/manual.pdf'),
  config: {
    displayName: 'Installation Manual',
    customMetadata: {
      doc_type: 'manual',
      version: '1.0',
      language: 'en'
    },
    chunkingConfig: {
      whiteSpaceConfig: {
        maxTokensPerChunk: 500,
        maxOverlapTokens: 50
      }
    }
  }
})

// Poll until indexing complete
while (!operation.done) {
  await new Promise(resolve => setTimeout(resolve, 1000))
  operation = await ai.operations.get({ name: operation.name })
}

console.log('✅ Indexed:', operation.response.displayName)

批量上传(并发):

const filePaths = [
  './docs/manual.pdf',
  './docs/faq.md',
  './docs/troubleshooting.docx'
]

// Upload all files concurrently
const uploadPromises = filePaths.map(filePath =>
  ai.fileSearchStores.uploadToFileSearchStore({
    name: fileStore.name,
    file: fs.createReadStream(filePath),
    config: {
      displayName: filePath.split('/').pop(),
      customMetadata: {
        doc_type: 'support',
        source_path: filePath
      },
      chunkingConfig: {
        whiteSpaceConfig: {
          maxTokensPerChunk: 500,
          maxOverlapTokens: 50
        }
      }
    }
  })
)

const operations = await Promise.all(uploadPromises)

// Poll all operations
for (const operation of operations) {
  let op = operation
  while (!op.done) {
    await new Promise(resolve => setTimeout(resolve, 1000))
    op = await ai.operations.get({ name: op.name })
  }
  console.log('✅ Indexed:', op.response.displayName)
}
第 4 步:使用文件搜索进行查询

基本查询:

const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'What are the safety precautions for installation?',
  config: {
    tools: [{
      fileSearch: {
        fileSearchStoreNames: [fileStore.name]
      }
    }]
  }
})

console.log('Answer:', response.text)

// Access citations
const grounding = response.candidates[0].groundingMetadata
if (grounding?.groundingChunks) {
  console.log('\nSources:')
  grounding.groundingChunks.forEach((chunk, i) => {
    console.log(`${i + 1}. ${chunk.retrievedContext?.title || 'Unknown'}`)
    console.log(`   URI: ${chunk.retrievedContext?.uri || 'N/A'}`)
  })
}

使用元数据过滤进行查询:

const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'How do I reset the device?',
  config: {
    tools: [{
      fileSearch: {
        fileSearchStoreNames: [fileStore.name],
        // Filter to only search troubleshooting docs in English, version 1.0
        metadataFilter: 'doc_type="troubleshooting" AND language="en" AND version="1.0"'
      }
    }]
  }
})

console.log('Answer:', response.text)

元数据过滤语法:

  • AND:key1="value1" AND key2="value2"
  • OR:key1="value1" OR key1="value2"
  • 括号:(key1="a" OR key1="b") AND key2="c"
第 5 步:列出和管理文档
// List all documents in store
const docs = await ai.fileSearchStores.documents.list({
  parent: fileStore.name,
  pageSize: 20
})

console.log(`Total documents: ${docs.documents?.length || 0}`)

docs.documents?.forEach(doc => {
  console.log(`- ${doc.displayName} (${doc.name})`)
  console.log(`  Metadata:`, doc.customMetadata)
})

// Get specific document details
const docDetails = await ai.fileSearchStores.documents.get({
  name: docs.documents[0].name
})

console.log('Document details:', docDetails)

// Delete document
await ai.fileSearchStores.documents.delete({
  name: docs.documents[0].name,
  force: true
})
第 6 步:清理
// Delete entire store (force deletes all documents)
await ai.fileSearchStores.delete({
  name: fileStore.name,
  force: true
})

console.log('✅ Store deleted')

推荐的分块策略

分块配置会显著影响检索质量。请根据内容类型进行调整:

技术文档
chunkingConfig: {
  whiteSpaceConfig: {
    maxTokensPerChunk: 500,   // Smaller chunks for precise code/API lookup
    maxOverlapTokens: 50      // 10% overlap
  }
}

适用于: API 文档、SDK 参考资料、代码示例、配置指南

散文和文章
chunkingConfig: {
  whiteSpaceConfig: {
    maxTokensPerChunk: 800,   // Larger chunks preserve narrative flow
    maxOverlapTokens: 80      // 10% overlap
  }
}

适用于: 博客文章、新闻报道、产品描述、营销材料

法律文件和合同
chunkingConfig: {
  whiteSpaceConfig: {
    maxTokensPerChunk: 300,   // Very small chunks for high precision
    maxOverlapTokens: 30      // 10% overlap
  }
}

适用于: 法律文件、合同、法规、合规文档

常见问题和支持
chunkingConfig: {
  whiteSpaceConfig: {
    maxTokensPerChunk: 400,   // Medium chunks (1-2 Q&A pairs)
    maxOverlapTokens: 40      // 10% overlap
  }
}

适用于: 常见问题、故障排除指南、操作指南文章

一般规则: 保持 10% 的重叠(重叠量 = 分块大小 / 10),以避免在分块边界处丢失上下文。

元数据最佳实践

设计元数据架构,以便进行筛选和整理:

示例:客户支持知识库
customMetadata: {
  doc_type: 'faq' | 'manual' | 'troubleshooting' | 'guide',
  product: 'widget-pro' | 'widget-lite',
  version: '1.0' | '2.0',
  language: 'en' | 'es' | 'fr',
  category: 'installation' | 'configuration' | 'maintenance',
  priority: 'critical' | 'normal' | 'low',
  last_updated: '2025-01-15',
  author: 'support-team'
}

查询示例:

metadataFilter: 'product="widget-pro" AND (doc_type="troubleshooting" OR doc_type="faq") AND language="en"'
示例:法律文档存储库
customMetadata: {
  doc_type: 'contract' | 'regulation' | 'case-law' | 'policy',
  jurisdiction: 'US' | 'EU' | 'UK',
  practice_area: 'employment' | 'corporate' | 'ip' | 'tax',
  effective_date: '2025-01-01',
  status: 'active' | 'archived',
  confidentiality: 'public' | 'internal' | 'privileged'
}
示例:代码文档
customMetadata: {
  doc_type: 'api-reference' | 'tutorial' | 'example' | 'changelog',
  language: 'javascript' | 'python' | 'java' | 'go',
  framework: 'react' | 'nextjs' | 'express' | 'fastapi',
  version: '1.2.0',
  difficulty: 'beginner' | 'intermediate' | 'advanced'
}

提示:

  • 使用一致的键命名方式(snake_case 或 camelCase)
  • 仅保留最重要、可筛选的字段(最多 20 个)
  • 使用枚举/常量作为值(便于筛选)
  • 添加版本和日期字段,以便按时间筛选

成本优化

1. 上传前去重
// Track uploaded file hashes to avoid duplicates
const uploadedHashes = new Set<string>()

async function uploadWithDeduplication(filePath: string) {
  const fileHash = await getFileHash(filePath)

  if (uploadedHashes.has(fileHash)) {
    console.log(`Skipping duplicate: ${filePath}`)
    return
  }

  await ai.fileSearchStores.uploadToFileSearchStore({
    name: fileStore.name,
    file: fs.createReadStream(filePath)
  })

  uploadedHashes.add(fileHash)
}
2. 压缩大文件
// Convert images to text before indexing (OCR)
// Compress PDFs (remove images, use text-only)
// Use markdown instead of Word docs (smaller size)
3. 使用元数据筛选缩小查询范围
// ❌ EXPENSIVE: Search all 10GB of documents
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'Reset procedure?',
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [fileStore.name] } }]
  }
})

// ✅ CHEAPER: Filter to only troubleshooting docs (subset)
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'Reset procedure?',
  config: {
    tools: [{
      fileSearch: {
        fileSearchStoreNames: [fileStore.name],
        metadataFilter: 'doc_type="troubleshooting"'  // Reduces search scope
      }
    }]
  }
})
4. 选择 Flash 而非 Pro 以节省成本
// Gemini 3 Flash is 10x cheaper than Pro for queries
// Use Flash unless you need Pro's advanced reasoning

// Development/testing: Use Flash
model: 'gemini-3-flash'

// Production (high-stakes answers): Use Pro
model: 'gemini-3-pro'
5. 监控存储用量
// List stores and estimate storage
const stores = await ai.fileSearchStores.list()

for (const store of stores.fileSearchStores || []) {
  const docs = await ai.fileSearchStores.documents.list({
    parent: store.name
  })

  console.log(`Store: ${store.displayName}`)
  console.log(`Documents: ${docs.documents?.length || 0}`)
  // Estimate storage (3x input size)
  console.log(`Estimated storage: ~${(docs.documents?.length || 0) * 10} MB`)
}

测试与验证

验证存储区创建
const store = await ai.fileSearchStores.get({
  name: fileStore.name
})

console.assert(store.displayName === 'my-knowledge-base', 'Store name mismatch')
console.log('✅ Store created successfully')
验证文档索引
const docs = await ai.fileSearchStores.documents.list({
  parent: fileStore.name
})

console.assert(docs.documents?.length > 0, 'No documents indexed')
console.log(`✅ ${docs.documents?.length} documents indexed`)
验证查询功能
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'What is this knowledge base about?',
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [fileStore.name] } }]
  }
})

console.assert(response.text.length > 0, 'Empty response')
console.log('✅ Query successful:', response.text.substring(0, 100) + '...')
验证引用
const response = await ai.models.generateContent({
  model: 'gemini-3-flash',
  contents: 'Provide a specific answer with citations.',
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [fileStore.name] } }]
  }
})

const grounding = response.candidates[0].groundingMetadata
console.assert(
  grounding?.groundingChunks?.length > 0,
  'No grounding/citations returned'
)
console.log(`✅ ${grounding?.groundingChunks?.length} citations returned`)

集成示例

流式支持

File Search 支持使用 generateContentStream() 流式返回响应:

// ✅ Streaming works with File Search (v1.34.0+)
const stream = await ai.models.generateContentStream({
  model: 'gemini-3-flash',
  contents: 'Summarize the document',
  config: {
    tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
  }
})

for await (const chunk of stream) {
  process.stdout.write(chunk.text)
}

// Access grounding after stream completes
const grounding = stream.candidates[0].groundingMetadata

**注意:**较早版本的 SDK(v1.34.0 之前)可能存在流式处理问题。请使用 v1.34.0+ 以确保流式支持稳定可靠。

来源:GitHub Issue #1221

可运行模板

此技能在 templates/ 目录中包含 3 个可运行模板:

模板 1:basic-node-rag

简洁的 Node.js/TypeScript 示例,演示如何:

  • 创建文件搜索存储区
  • 上传多个文档
  • 使用自然语言查询
  • 显示引用

**适用场景:**学习 File Search、快速原型开发、简单的 CLI 工具

运行:

cd templates/basic-node-rag
npm install
npm run dev
模板 2:cloudflare-worker-rag

Cloudflare Workers 集成示例,展示:

  • 用于文档上传的边缘 API
  • 用于语义搜索的边缘 API
  • 与 R2 集成以存储文档
  • 混合架构(Gemini File Search + Cloudflare 边缘)

**适用场景:**构建全球边缘应用、与 Cloudflare 技术栈集成

部署:

cd templates/cloudflare-worker-rag
npm install
npx wrangler deploy
模板 3:nextjs-docs-search

全栈 Next.js 应用,包含:

  • 支持拖放的文档上传界面
  • 实时搜索界面
  • 带来源链接的引用呈现
  • 元数据筛选界面

**适用场景:**构建生产级文档网站、知识库

运行:

cd templates/nextjs-docs-search
npm install
npm run dev

参考资料

官方文档:

教程:

此技能中随附的资源:

  • references/api-reference.md - 完整的 API 文档
  • references/chunking-best-practices.md - 详细的分块策略
  • references/pricing-calculator.md - 成本估算指南
  • references/migration-from-openai.md - 从 OpenAI Files API 迁移的指南
  • scripts/create-store.ts - 用于创建存储区的 CLI 工具
  • scripts/upload-batch.ts - 批量上传脚本
  • scripts/query-store.ts - 交互式查询工具
  • scripts/cleanup.ts - 清理脚本

可运行模板:

  • templates/basic-node-rag/ - 简洁的 Node.js 示例
  • templates/cloudflare-worker-rag/ - 边缘部署示例
  • templates/nextjs-docs-search/ - 全栈 Next.js 应用

**技能版本:**1.1.0 **上次验证:**2026-01-21 软件包版本:@google/genai ^1.38.0(最低要求为 1.29.0) **节省 Token:**约 67% **避免的错误:**12 **更改:**根据社区研究新增 4 个错误(displayName Blob 问题、JSON 模式下的 grounding、工具冲突、批处理 API 元数据),增强带回退验证的轮询超时模式,添加流式支持说明

评论

支持 Markdown