RAG系统前端实现
检索增强生成(RAG)系统是现代AI应用的重要组成部分,它通过结合外部知识库和大语言模型,提供更准确、更相关的回答。本文将详细介绍如何在前端实现一个完整的RAG系统。
RAG系统架构
整体架构设计
核心RAG管理器
// RAG系统核心管理器
class RAGSystemManager {
constructor(config = {}) {
this.config = {
vectorDimension: config.vectorDimension || 1536,
chunkSize: config.chunkSize || 1000,
chunkOverlap: config.chunkOverlap || 200,
maxRetrievalResults: config.maxRetrievalResults || 5,
similarityThreshold: config.similarityThreshold || 0.7,
...config
};
this.components = {
documentProcessor: null,
vectorStore: null,
retriever: null,
generator: null,
cacheManager: null
};
this.isInitialized = false;
}
// 初始化RAG系统
async initialize() {
try {
console.log('正在初始化RAG系统...');
// 初始化文档处理器
this.components.documentProcessor = new DocumentProcessor({
chunkSize: this.config.chunkSize,
chunkOverlap: this.config.chunkOverlap
});
// 初始化向量存储
this.components.vectorStore = new VectorStore({
dimension: this.config.vectorDimension,
storageType: this.config.storageType || 'indexeddb'
});
// 初始化检索器
this.components.retriever = new DocumentRetriever({
vectorStore: this.components.vectorStore,
maxResults: this.config.maxRetrievalResults,
threshold: this.config.similarityThreshold
});
// 初始化生成器
this.components.generator = new ResponseGenerator({
apiKey: this.config.apiKey,
model: this.config.model || 'gpt-3.5-turbo',
temperature: this.config.temperature || 0.7
});
// 初始化缓存管理器
this.components.cacheManager = new CacheManager({
maxSize: this.config.cacheSize || 1000,
ttl: this.config.cacheTTL || 3600000 // 1小时
});
// 初始化各组件
await Promise.all([
this.components.vectorStore.initialize(),
this.components.cacheManager.initialize()
]);
this.isInitialized = true;
console.log('RAG系统初始化完成');
} catch (error) {
console.error('RAG系统初始化失败:', error);
throw error;
}
}
// 添加文档到知识库
async addDocument(document) {
if (!this.isInitialized) {
throw new Error('RAG系统未初始化');
}
try {
// 处理文档
const processedDoc = await this.components.documentProcessor.processDocument(document);
// 分块
const chunks = await this.components.documentProcessor.chunkDocument(processedDoc);
// 向量化并存储
const results = [];
for (const chunk of chunks) {
const vector = await this.generateEmbedding(chunk.content);
const result = await this.components.vectorStore.addVector({
id: chunk.id,
vector,
metadata: {
...chunk.metadata,
documentId: document.id,
content: chunk.content
}
});
results.push(result);
}
console.log(`文档 ${document.id} 添加成功,共 ${chunks.length} 个分块`);
return results;
} catch (error) {
console.error('添加文档失败:', error);
throw error;
}
}
// 查询RAG系统
async query(question, options = {}) {
if (!this.isInitialized) {
throw new Error('RAG系统未初始化');
}
try {
// 检查缓存
const cacheKey = this.generateCacheKey(question, options);
const cachedResult = await this.components.cacheManager.get(cacheKey);
if (cachedResult) {
console.log('从缓存返回结果');
return cachedResult;
}
// 检索相关文档
const retrievalResults = await this.retrieveDocuments(question, options);
// 生成回答
const response = await this.generateResponse(question, retrievalResults, options);
// 构建完整结果
const result = {
question,
answer: response.content,
sources: retrievalResults.map(r => ({
id: r.id,
content: r.metadata.content,
score: r.score,
metadata: r.metadata
})),
metadata: {
retrievalCount: retrievalResults.length,
tokens: response.tokens,
timestamp: new Date().toISOString()
}
};
// 缓存结果
await this.components.cacheManager.set(cacheKey, result);
return result;
} catch (error) {
console.error('RAG查询失败:', error);
throw error;
}
}
// 检索相关文档
async retrieveDocuments(question, options = {}) {
// 生成查询向量
const queryVector = await this.generateEmbedding(question);
// 执行向量搜索
const searchResults = await this.components.retriever.search(queryVector, {
maxResults: options.maxResults || this.config.maxRetrievalResults,
threshold: options.threshold || this.config.similarityThreshold,
filters: options.filters
});
return searchResults;
}
// 生成回答
async generateResponse(question, retrievalResults, options = {}) {
// 构建上下文
const context = this.buildContext(retrievalResults);
// 构建提示词
const prompt = this.buildPrompt(question, context, options);
// 调用LLM生成回答
const response = await this.components.generator.generate(prompt, {
temperature: options.temperature || this.config.temperature,
maxTokens: options.maxTokens || 2000
});
return response;
}
// 生成文本嵌入向量
async generateEmbedding(text) {
// 这里可以调用OpenAI Embeddings API或使用本地模型
try {
const response = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
input: text,
model: 'text-embedding-ada-002'
})
});
const data = await response.json();
return data.data[0].embedding;
} catch (error) {
console.error('生成嵌入向量失败:', error);
throw error;
}
}
// 构建上下文
buildContext(retrievalResults) {
return retrievalResults
.map((result, index) => `[${index + 1}] ${result.metadata.content}`)
.join('\n\n');
}
// 构建提示词
buildPrompt(question, context, options = {}) {
const systemPrompt = options.systemPrompt || `你是一个有用的AI助手。请基于提供的上下文信息回答用户的问题。
规则:
1. 只基于提供的上下文信息回答问题
2. 如果上下文中没有相关信息,请明确说明
3. 回答要准确、简洁、有帮助
4. 可以引用具体的上下文片段来支持你的回答`;
return `${systemPrompt}
上下文信息:
${context}
用户问题:${question}
请基于上述上下文信息回答用户的问题:`;
}
// 生成缓存键
generateCacheKey(question, options) {
const key = {
question,
maxResults: options.maxResults || this.config.maxRetrievalResults,
threshold: options.threshold || this.config.similarityThreshold,
filters: options.filters || {}
};
return btoa(JSON.stringify(key)).replace(/[^a-zA-Z0-9]/g, '').substring(0, 32);
}
// 删除文档
async removeDocument(documentId) {
try {
await this.components.vectorStore.removeByMetadata({ documentId });
console.log(`文档 ${documentId} 删除成功`);
} catch (error) {
console.error('删除文档失败:', error);
throw error;
}
}
// 获取系统统计信息
async getStats() {
const vectorStats = await this.components.vectorStore.getStats();
const cacheStats = await this.components.cacheManager.getStats();
return {
vectors: vectorStats,
cache: cacheStats,
config: this.config,
initialized: this.isInitialized
};
}
// 清理系统
async cleanup() {
if (this.components.cacheManager) {
await this.components.cacheManager.cleanup();
}
if (this.components.vectorStore) {
await this.components.vectorStore.cleanup();
}
console.log('RAG系统清理完成');
}
}
文档处理系统
文档处理流程
文档处理器
// 文档处理器
class DocumentProcessor {
constructor(config = {}) {
this.config = {
chunkSize: config.chunkSize || 1000,
chunkOverlap: config.chunkOverlap || 200,
supportedTypes: config.supportedTypes || ['text', 'pdf', 'docx', 'html', 'markdown'],
...config
};
this.parsers = new Map();
this.initializeParsers();
}
// 初始化解析器
initializeParsers() {
// 文本解析器
this.parsers.set('text', new TextParser());
this.parsers.set('txt', new TextParser());
// Markdown解析器
this.parsers.set('markdown', new MarkdownParser());
this.parsers.set('md', new MarkdownParser());
// HTML解析器
this.parsers.set('html', new HTMLParser());
this.parsers.set('htm', new HTMLParser());
// PDF解析器(需要PDF.js)
this.parsers.set('pdf', new PDFParser());
// JSON解析器
this.parsers.set('json', new JSONParser());
}
// 处理文档
async processDocument(document) {
try {
// 检测文档类型
const type = this.detectDocumentType(document);
if (!this.parsers.has(type)) {
throw new Error(`不支持的文档类型: ${type}`);
}
// 获取对应的解析器
const parser = this.parsers.get(type);
// 解析文档内容
const content = await parser.parse(document);
// 清理和预处理文本
const cleanedContent = this.cleanText(content);
return {
id: document.id || this.generateDocumentId(),
type,
content: cleanedContent,
metadata: {
...document.metadata,
originalSize: document.size || content.length,
processedSize: cleanedContent.length,
processedAt: new Date().toISOString()
}
};
} catch (error) {
console.error('文档处理失败:', error);
throw error;
}
}
// 文档分块
async chunkDocument(document) {
const chunks = [];
const content = document.content;
const chunkSize = this.config.chunkSize;
const overlap = this.config.chunkOverlap;
// 按段落分割
const paragraphs = content.split(/\n\s*\n/);
let currentChunk = '';
let chunkIndex = 0;
for (let i = 0; i < paragraphs.length; i++) {
const paragraph = paragraphs[i].trim();
if (!paragraph) continue;
// 检查添加当前段落是否会超过块大小
if (currentChunk.length + paragraph.length > chunkSize && currentChunk.length > 0) {
// 创建当前块
chunks.push(this.createChunk(currentChunk, chunkIndex, document, chunks.length > 0));
chunkIndex++;
// 开始新块,包含重叠内容
if (overlap > 0 && currentChunk.length > overlap) {
currentChunk = currentChunk.slice(-overlap) + '\n\n' + paragraph;
} else {
currentChunk = paragraph;
}
} else {
// 添加到当前块
if (currentChunk.length > 0) {
currentChunk += '\n\n' + paragraph;
} else {
currentChunk = paragraph;
}
}
}
// 添加最后一个块
if (currentChunk.trim().length > 0) {
chunks.push(this.createChunk(currentChunk, chunkIndex, document, chunks.length > 0));
}
return chunks;
}
// 创建文档块
createChunk(content, index, document, hasOverlap) {
return {
id: `${document.id}_chunk_${index}`,
content: content.trim(),
metadata: {
documentId: document.id,
chunkIndex: index,
hasOverlap,
contentLength: content.length,
...document.metadata
}
};
}
// 检测文档类型
detectDocumentType(document) {
if (document.type) {
return document.type.toLowerCase();
}
if (document.name) {
const extension = document.name.split('.').pop()?.toLowerCase();
if (extension && this.parsers.has(extension)) {
return extension;
}
}
// 默认为文本类型
return 'text';
}
// 清理文本
cleanText(text) {
return text
// 移除多余的空白字符
.replace(/\s+/g, ' ')
// 移除多余的换行符
.replace(/\n\s*\n\s*\n/g, '\n\n')
// 移除首尾空白
.trim();
}
// 生成文档ID
generateDocumentId() {
return 'doc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
}
// 文本解析器
class TextParser {
async parse(document) {
if (typeof document.content === 'string') {
return document.content;
}
if (document.file instanceof File) {
return await this.readFileAsText(document.file);
}
throw new Error('无法解析文档内容');
}
async readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e);
reader.readAsText(file);
});
}
}
// Markdown解析器
class MarkdownParser extends TextParser {
async parse(document) {
const content = await super.parse(document);
// 简单的Markdown到文本转换
return content
// 移除标题标记
.replace(/^#{1,6}\s+/gm, '')
// 移除粗体和斜体标记
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
// 移除链接标记,保留文本
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
// 移除代码块标记
.replace(/```[\s\S]*?```/g, '')
.replace(/`([^`]+)`/g, '$1');
}
}
// HTML解析器
class HTMLParser {
async parse(document) {
let content;
if (typeof document.content === 'string') {
content = document.content;
} else if (document.file instanceof File) {
const reader = new FileReader();
content = await new Promise((resolve, reject) => {
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsText(document.file);
});
} else {
throw new Error('无法解析HTML文档');
}
// 创建临时DOM元素来解析HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
// 移除脚本和样式标签
const scripts = tempDiv.querySelectorAll('script, style');
scripts.forEach(el => el.remove());
// 提取文本内容
return tempDiv.textContent || tempDiv.innerText || '';
}
}
// PDF解析器(需要PDF.js库)
class PDFParser {
async parse(document) {
if (!window.pdfjsLib) {
throw new Error('PDF.js库未加载,无法解析PDF文档');
}
try {
const arrayBuffer = await this.fileToArrayBuffer(document.file);
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items.map(item => item.str).join(' ');
fullText += pageText + '\n\n';
}
return fullText;
} catch (error) {
console.error('PDF解析失败:', error);
throw error;
}
}
async fileToArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
}
// JSON解析器
class JSONParser {
async parse(document) {
let content;
if (typeof document.content === 'string') {
content = document.content;
} else if (document.file instanceof File) {
const reader = new FileReader();
content = await new Promise((resolve, reject) => {
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsText(document.file);
});
} else {
throw new Error('无法解析JSON文档');
}
try {
const jsonData = JSON.parse(content);
return this.jsonToText(jsonData);
} catch (error) {
throw new Error('无效的JSON格式');
}
}
jsonToText(obj, prefix = '') {
let text = '';
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
text += `${fullKey}: ${value.join(', ')}\n`;
} else {
text += this.jsonToText(value, fullKey);
}
} else {
text += `${fullKey}: ${value}\n`;
}
}
return text;
}
}
向量存储系统
向量存储管理器
// 向量存储管理器
class VectorStore {
constructor(config = {}) {
this.config = {
dimension: config.dimension || 1536,
storageType: config.storageType || 'indexeddb',
dbName: config.dbName || 'rag_vectors',
version: config.version || 1,
...config
};
this.db = null;
this.vectors = new Map();
this.isInitialized = false;
}
// 初始化向量存储
async initialize() {
try {
if (this.config.storageType === 'indexeddb') {
await this.initializeIndexedDB();
} else {
// 使用内存存储
this.isInitialized = true;
}
console.log('向量存储初始化完成');
} catch (error) {
console.error('向量存储初始化失败:', error);
throw error;
}
}
// 初始化IndexedDB
async initializeIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.config.dbName, this.config.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
this.isInitialized = true;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建向量存储表
if (!db.objectStoreNames.contains('vectors')) {
const store = db.createObjectStore('vectors', { keyPath: 'id' });
store.createIndex('documentId', 'metadata.documentId', { unique: false });
store.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
// 添加向量
async addVector(vectorData) {
if (!this.isInitialized) {
throw new Error('向量存储未初始化');
}
const vector = {
id: vectorData.id,
vector: vectorData.vector,
metadata: vectorData.metadata || {},
timestamp: new Date().toISOString()
};
if (this.config.storageType === 'indexeddb') {
return await this.addVectorToIndexedDB(vector);
} else {
this.vectors.set(vector.id, vector);
return vector;
}
}
// 添加向量到IndexedDB
async addVectorToIndexedDB(vector) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['vectors'], 'readwrite');
const store = transaction.objectStore('vectors');
const request = store.put(vector);
request.onsuccess = () => resolve(vector);
request.onerror = () => reject(request.error);
});
}
// 搜索相似向量
async searchSimilar(queryVector, options = {}) {
const maxResults = options.maxResults || 5;
const threshold = options.threshold || 0.7;
const filters = options.filters || {};
let vectors;
if (this.config.storageType === 'indexeddb') {
vectors = await this.getAllVectorsFromIndexedDB();
} else {
vectors = Array.from(this.vectors.values());
}
// 应用过滤器
if (Object.keys(filters).length > 0) {
vectors = vectors.filter(v => this.matchesFilters(v.metadata, filters));
}
// 计算相似度
const similarities = vectors.map(vector => ({
...vector,
score: this.cosineSimilarity(queryVector, vector.vector)
}));
// 过滤和排序
return similarities
.filter(item => item.score >= threshold)
.sort((a, b) => b.score - a.score)
.slice(0, maxResults);
}
// 从IndexedDB获取所有向量
async getAllVectorsFromIndexedDB() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['vectors'], 'readonly');
const store = transaction.objectStore('vectors');
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 计算余弦相似度
cosineSimilarity(vecA, vecB) {
if (vecA.length !== vecB.length) {
throw new Error('向量维度不匹配');
}
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
normA = Math.sqrt(normA);
normB = Math.sqrt(normB);
if (normA === 0 || normB === 0) {
return 0;
}
return dotProduct / (normA * normB);
}
// 检查是否匹配过滤器
matchesFilters(metadata, filters) {
for (const [key, value] of Object.entries(filters)) {
if (metadata[key] !== value) {
return false;
}
}
return true;
}
// 根据元数据删除向量
async removeByMetadata(filters) {
if (this.config.storageType === 'indexeddb') {
return await this.removeFromIndexedDBByMetadata(filters);
} else {
const toRemove = [];
for (const [id, vector] of this.vectors) {
if (this.matchesFilters(vector.metadata, filters)) {
toRemove.push(id);
}
}
toRemove.forEach(id => this.vectors.delete(id));
return toRemove.length;
}
}
// 从IndexedDB根据元数据删除
async removeFromIndexedDBByMetadata(filters) {
const vectors = await this.getAllVectorsFromIndexedDB();
const toRemove = vectors.filter(v => this.matchesFilters(v.metadata, filters));
const transaction = this.db.transaction(['vectors'], 'readwrite');
const store = transaction.objectStore('vectors');
for (const vector of toRemove) {
store.delete(vector.id);
}
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve(toRemove.length);
transaction.onerror = () => reject(transaction.error);
});
}
// 获取统计信息
async getStats() {
let count = 0;
let totalSize = 0;
if (this.config.storageType === 'indexeddb') {
const vectors = await this.getAllVectorsFromIndexedDB();
count = vectors.length;
totalSize = vectors.reduce((sum, v) => sum + JSON.stringify(v).length, 0);
} else {
count = this.vectors.size;
totalSize = Array.from(this.vectors.values())
.reduce((sum, v) => sum + JSON.stringify(v).length, 0);
}
return {
count,
totalSize,
averageSize: count > 0 ? Math.round(totalSize / count) : 0,
storageType: this.config.storageType
};
}
// 清理存储
async cleanup() {
if (this.config.storageType === 'indexeddb' && this.db) {
this.db.close();
} else {
this.vectors.clear();
}
this.isInitialized = false;
}
}
检索和生成系统
RAG 检索生成流程
文档检索器
// 文档检索器
class DocumentRetriever {
constructor(config = {}) {
this.vectorStore = config.vectorStore;
this.config = {
maxResults: config.maxResults || 5,
threshold: config.threshold || 0.7,
rerankingEnabled: config.rerankingEnabled || false,
...config
};
}
// 搜索相关文档
async search(queryVector, options = {}) {
try {
// 执行向量搜索
const searchResults = await this.vectorStore.searchSimilar(queryVector, {
maxResults: options.maxResults || this.config.maxResults,
threshold: options.threshold || this.config.threshold,
filters: options.filters
});
// 重排序(如果启用)
if (this.config.rerankingEnabled && searchResults.length > 1) {
return await this.rerankResults(searchResults, options.query);
}
return searchResults;
} catch (error) {
console.error('文档检索失败:', error);
throw error;
}
}
// 重排序结果
async rerankResults(results, query) {
// 简单的重排序策略:基于文本匹配度
if (!query) return results;
const queryTerms = query.toLowerCase().split(/\s+/);
return results.map(result => {
const content = result.metadata.content.toLowerCase();
let textScore = 0;
// 计算查询词在内容中的匹配度
for (const term of queryTerms) {
const matches = (content.match(new RegExp(term, 'g')) || []).length;
textScore += matches / content.length;
}
// 结合向量相似度和文本匹配度
const combinedScore = (result.score * 0.7) + (textScore * 0.3);
return {
...result,
score: combinedScore,
textScore
};
}).sort((a, b) => b.score - a.score);
}
// 多查询检索
async multiQuery(queries, options = {}) {
const allResults = [];
for (const query of queries) {
const queryVector = await this.generateEmbedding(query);
const results = await this.search(queryVector, { ...options, query });
allResults.push(...results);
}
// 去重和合并结果
const uniqueResults = this.deduplicateResults(allResults);
// 按分数排序
return uniqueResults
.sort((a, b) => b.score - a.score)
.slice(0, options.maxResults || this.config.maxResults);
}
// 去重结果
deduplicateResults(results) {
const seen = new Set();
return results.filter(result => {
if (seen.has(result.id)) {
return false;
}
seen.add(result.id);
return true;
});
}
// 生成嵌入向量(这里应该与RAG系统的方法保持一致)
async generateEmbedding(text) {
// 这里应该调用与RAG系统相同的嵌入生成方法
throw new Error('需要实现嵌入向量生成方法');
}
}
// 响应生成器
class ResponseGenerator {
constructor(config = {}) {
this.config = {
apiKey: config.apiKey,
model: config.model || 'gpt-3.5-turbo',
temperature: config.temperature || 0.7,
maxTokens: config.maxTokens || 2000,
...config
};
}
// 生成响应
async generate(prompt, options = {}) {
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: options.model || this.config.model,
messages: [{ role: 'user', content: prompt }],
temperature: options.temperature || this.config.temperature,
max_tokens: options.maxTokens || this.config.maxTokens
})
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = await response.json();
return {
content: data.choices[0].message.content,
tokens: data.usage?.total_tokens || 0,
model: data.model
};
} catch (error) {
console.error('响应生成失败:', error);
throw error;
}
}
// 流式生成响应
async generateStream(prompt, options = {}, onChunk) {
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: options.model || this.config.model,
messages: [{ role: 'user', content: prompt }],
temperature: options.temperature || this.config.temperature,
max_tokens: options.maxTokens || this.config.maxTokens,
stream: true
})
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
break;
}
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content || '';
if (content) {
fullContent += content;
if (onChunk) {
onChunk(content, fullContent);
}
}
} catch (e) {
// 忽略解析错误
}
}
}
}
return {
content: fullContent,
tokens: 0, // 流式响应中无法获取准确的token数
model: options.model || this.config.model
};
} catch (error) {
console.error('流式响应生成失败:', error);
throw error;
}
}
}
缓存管理系统
缓存管理器
// 缓存管理器
class CacheManager {
constructor(config = {}) {
this.config = {
maxSize: config.maxSize || 1000,
ttl: config.ttl || 3600000, // 1小时
storageType: config.storageType || 'memory',
...config
};
this.cache = new Map();
this.accessTimes = new Map();
this.isInitialized = false;
}
// 初始化缓存
async initialize() {
if (this.config.storageType === 'localStorage') {
await this.loadFromLocalStorage();
}
// 启动清理定时器
this.startCleanupTimer();
this.isInitialized = true;
console.log('缓存管理器初始化完成');
}
// 设置缓存
async set(key, value, ttl) {
const expiry = Date.now() + (ttl || this.config.ttl);
const item = {
value,
expiry,
accessCount: 0,
createdAt: Date.now()
};
this.cache.set(key, item);
this.accessTimes.set(key, Date.now());
// 检查缓存大小限制
if (this.cache.size > this.config.maxSize) {
await this.evictLRU();
}
// 持久化到localStorage(如果配置了)
if (this.config.storageType === 'localStorage') {
await this.saveToLocalStorage(key, item);
}
}
// 获取缓存
async get(key) {
const item = this.cache.get(key);
if (!item) {
return null;
}
// 检查是否过期
if (Date.now() > item.expiry) {
this.cache.delete(key);
this.accessTimes.delete(key);
if (this.config.storageType === 'localStorage') {
localStorage.removeItem(`rag_cache_${key}`);
}
return null;
}
// 更新访问信息
item.accessCount++;
this.accessTimes.set(key, Date.now());
return item.value;
}
// LRU淘汰
async evictLRU() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, time] of this.accessTimes) {
if (time < oldestTime) {
oldestTime = time;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
this.accessTimes.delete(oldestKey);
if (this.config.storageType === 'localStorage') {
localStorage.removeItem(`rag_cache_${oldestKey}`);
}
}
}
// 从localStorage加载
async loadFromLocalStorage() {
const keys = Object.keys(localStorage).filter(key => key.startsWith('rag_cache_'));
for (const storageKey of keys) {
try {
const item = JSON.parse(localStorage.getItem(storageKey));
const key = storageKey.replace('rag_cache_', '');
// 检查是否过期
if (Date.now() <= item.expiry) {
this.cache.set(key, item);
this.accessTimes.set(key, item.createdAt);
} else {
localStorage.removeItem(storageKey);
}
} catch (error) {
console.warn('加载缓存项失败:', storageKey, error);
localStorage.removeItem(storageKey);
}
}
}
// 保存到localStorage
async saveToLocalStorage(key, item) {
try {
localStorage.setItem(`rag_cache_${key}`, JSON.stringify(item));
} catch (error) {
console.warn('保存缓存项失败:', key, error);
}
}
// 启动清理定时器
startCleanupTimer() {
setInterval(() => {
this.cleanup();
}, 300000); // 每5分钟清理一次
}
// 清理过期缓存
cleanup() {
const now = Date.now();
const toRemove = [];
for (const [key, item] of this.cache) {
if (now > item.expiry) {
toRemove.push(key);
}
}
for (const key of toRemove) {
this.cache.delete(key);
this.accessTimes.delete(key);
if (this.config.storageType === 'localStorage') {
localStorage.removeItem(`rag_cache_${key}`);
}
}
if (toRemove.length > 0) {
console.log(`清理了 ${toRemove.length} 个过期缓存项`);
}
}
// 获取统计信息
async getStats() {
const items = Array.from(this.cache.values());
const totalSize = JSON.stringify(Array.from(this.cache.entries())).length;
return {
size: this.cache.size,
maxSize: this.config.maxSize,
totalSize,
averageSize: items.length > 0 ? Math.round(totalSize / items.length) : 0,
hitRate: this.calculateHitRate(),
oldestItem: this.getOldestItem(),
storageType: this.config.storageType
};
}
// 计算命中率
calculateHitRate() {
const items = Array.from(this.cache.values());
const totalAccess = items.reduce((sum, item) => sum + item.accessCount, 0);
return items.length > 0 ? totalAccess / items.length : 0;
}
// 获取最旧的缓存项
getOldestItem() {
let oldest = null;
let oldestTime = Date.now();
for (const [key, time] of this.accessTimes) {
if (time < oldestTime) {
oldestTime = time;
oldest = key;
}
}
return oldest;
}
// 清空缓存
async clear() {
this.cache.clear();
this.accessTimes.clear();
if (this.config.storageType === 'localStorage') {
const keys = Object.keys(localStorage).filter(key => key.startsWith('rag_cache_'));
keys.forEach(key => localStorage.removeItem(key));
}
}
}
完整RAG应用示例
RAG聊天应用
// 完整的RAG聊天应用
class RAGChatApplication {
constructor(config = {}) {
this.config = {
apiKey: config.apiKey,
containerId: config.containerId || 'rag-chat-app',
theme: config.theme || 'light',
...config
};
this.ragSystem = null;
this.ui = null;
this.isInitialized = false;
this.currentConversation = [];
}
// 初始化应用
async initialize() {
try {
console.log('正在初始化RAG聊天应用...');
// 初始化RAG系统
this.ragSystem = new RAGSystemManager({
apiKey: this.config.apiKey,
chunkSize: 800,
chunkOverlap: 150,
maxRetrievalResults: 3,
similarityThreshold: 0.75
});
await this.ragSystem.initialize();
// 初始化UI
this.ui = new RAGChatUI({
containerId: this.config.containerId,
theme: this.config.theme,
onMessage: this.handleUserMessage.bind(this),
onFileUpload: this.handleFileUpload.bind(this),
onClearKnowledge: this.handleClearKnowledge.bind(this)
});
this.ui.initialize();
this.isInitialized = true;
console.log('RAG聊天应用初始化完成');
// 显示欢迎消息
this.ui.addMessage({
type: 'system',
content: '欢迎使用RAG智能问答系统!请上传文档来构建知识库,然后开始提问。',
timestamp: new Date()
});
} catch (error) {
console.error('RAG聊天应用初始化失败:', error);
throw error;
}
}
// 处理用户消息
async handleUserMessage(message) {
if (!this.isInitialized) {
throw new Error('应用未初始化');
}
try {
// 添加用户消息到UI
this.ui.addMessage({
type: 'user',
content: message,
timestamp: new Date()
});
// 显示加载状态
const loadingId = this.ui.showLoading('正在思考中...');
// 查询RAG系统
const result = await this.ragSystem.query(message, {
maxResults: 3,
threshold: 0.7
});
// 隐藏加载状态
this.ui.hideLoading(loadingId);
// 添加AI回复到UI
this.ui.addMessage({
type: 'assistant',
content: result.answer,
sources: result.sources,
metadata: result.metadata,
timestamp: new Date()
});
// 更新对话历史
this.currentConversation.push(
{ role: 'user', content: message },
{ role: 'assistant', content: result.answer }
);
} catch (error) {
console.error('处理用户消息失败:', error);
this.ui.addMessage({
type: 'error',
content: '抱歉,处理您的问题时出现了错误。请稍后重试。',
timestamp: new Date()
});
}
}
// 处理文件上传
async handleFileUpload(files) {
if (!this.isInitialized) {
throw new Error('应用未初始化');
}
try {
const loadingId = this.ui.showLoading('正在处理文档...');
let successCount = 0;
let totalChunks = 0;
for (const file of files) {
try {
// 创建文档对象
const document = {
id: `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: file.name,
file: file,
metadata: {
fileName: file.name,
fileSize: file.size,
fileType: file.type,
uploadedAt: new Date().toISOString()
}
};
// 添加到RAG系统
const results = await this.ragSystem.addDocument(document);
successCount++;
totalChunks += results.length;
} catch (error) {
console.error(`处理文件 ${file.name} 失败:`, error);
}
}
this.ui.hideLoading(loadingId);
// 显示处理结果
this.ui.addMessage({
type: 'system',
content: `文档处理完成!成功处理 ${successCount}/${files.length} 个文件,共生成 ${totalChunks} 个知识片段。`,
timestamp: new Date()
});
// 更新知识库统计
await this.updateKnowledgeStats();
} catch (error) {
console.error('文件上传处理失败:', error);
this.ui.addMessage({
type: 'error',
content: '文档处理失败,请检查文件格式并重试。',
timestamp: new Date()
});
}
}
// 处理清空知识库
async handleClearKnowledge() {
if (!this.isInitialized) {
return;
}
try {
const confirmed = confirm('确定要清空所有知识库内容吗?此操作不可撤销。');
if (confirmed) {
await this.ragSystem.components.vectorStore.cleanup();
await this.ragSystem.components.vectorStore.initialize();
this.ui.addMessage({
type: 'system',
content: '知识库已清空。',
timestamp: new Date()
});
await this.updateKnowledgeStats();
}
} catch (error) {
console.error('清空知识库失败:', error);
this.ui.addMessage({
type: 'error',
content: '清空知识库失败,请重试。',
timestamp: new Date()
});
}
}
// 更新知识库统计
async updateKnowledgeStats() {
try {
const stats = await this.ragSystem.getStats();
this.ui.updateKnowledgeStats(stats);
} catch (error) {
console.error('更新知识库统计失败:', error);
}
}
// 导出对话历史
exportConversation() {
const data = {
conversation: this.currentConversation,
timestamp: new Date().toISOString(),
appVersion: '1.0.0'
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rag-conversation-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
// 清理应用
async cleanup() {
if (this.ragSystem) {
await this.ragSystem.cleanup();
}
if (this.ui) {
this.ui.cleanup();
}
this.isInitialized = false;
}
}
// RAG聊天UI管理器
class RAGChatUI {
constructor(config = {}) {
this.config = {
containerId: config.containerId || 'rag-chat-app',
theme: config.theme || 'light',
onMessage: config.onMessage,
onFileUpload: config.onFileUpload,
onClearKnowledge: config.onClearKnowledge,
...config
};
this.container = null;
this.messagesContainer = null;
this.inputContainer = null;
this.knowledgePanel = null;
this.loadingElements = new Map();
}
// 初始化UI
initialize() {
this.container = document.getElementById(this.config.containerId);
if (!this.container) {
throw new Error(`容器元素 #${this.config.containerId} 不存在`);
}
this.createLayout();
this.attachEventListeners();
this.applyTheme();
}
// 创建布局
createLayout() {
this.container.innerHTML = `
<div class="rag-chat-layout">
<div class="rag-chat-header">
<h2>RAG智能问答系统</h2>
<div class="rag-chat-controls">
<button id="theme-toggle" class="control-btn">🌓</button>
<button id="export-btn" class="control-btn">📥</button>
<button id="clear-knowledge-btn" class="control-btn">🗑️</button>
</div>
</div>
<div class="rag-chat-main">
<div class="rag-chat-content">
<div id="messages-container" class="messages-container"></div>
<div class="input-container">
<div class="file-upload-area">
<input type="file" id="file-input" multiple accept=".txt,.md,.pdf,.docx,.html,.json" style="display: none;">
<button id="upload-btn" class="upload-btn">📎 上传文档</button>
</div>
<div class="message-input-area">
<textarea id="message-input" placeholder="请输入您的问题..." rows="3"></textarea>
<button id="send-btn" class="send-btn">发送</button>
</div>
</div>
</div>
<div class="rag-knowledge-panel">
<h3>知识库状态</h3>
<div id="knowledge-stats" class="knowledge-stats">
<div class="stat-item">
<span class="stat-label">文档数量:</span>
<span class="stat-value" id="doc-count">0</span>
</div>
<div class="stat-item">
<span class="stat-label">知识片段:</span>
<span class="stat-value" id="chunk-count">0</span>
</div>
<div class="stat-item">
<span class="stat-label">缓存命中率:</span>
<span class="stat-value" id="cache-hit-rate">0%</span>
</div>
</div>
</div>
</div>
</div>
`;
this.messagesContainer = document.getElementById('messages-container');
this.inputContainer = document.querySelector('.input-container');
this.knowledgePanel = document.querySelector('.rag-knowledge-panel');
}
// 附加事件监听器
attachEventListeners() {
// 发送消息
const sendBtn = document.getElementById('send-btn');
const messageInput = document.getElementById('message-input');
sendBtn.addEventListener('click', () => this.sendMessage());
messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// 文件上传
const uploadBtn = document.getElementById('upload-btn');
const fileInput = document.getElementById('file-input');
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
this.handleFileUpload(Array.from(e.target.files));
e.target.value = ''; // 清空文件输入
}
});
// 主题切换
const themeToggle = document.getElementById('theme-toggle');
themeToggle.addEventListener('click', () => this.toggleTheme());
// 导出对话
const exportBtn = document.getElementById('export-btn');
exportBtn.addEventListener('click', () => this.exportConversation());
// 清空知识库
const clearBtn = document.getElementById('clear-knowledge-btn');
clearBtn.addEventListener('click', () => this.clearKnowledge());
}
// 发送消息
sendMessage() {
const messageInput = document.getElementById('message-input');
const message = messageInput.value.trim();
if (message && this.config.onMessage) {
this.config.onMessage(message);
messageInput.value = '';
messageInput.style.height = 'auto';
}
}
// 处理文件上传
handleFileUpload(files) {
if (this.config.onFileUpload) {
this.config.onFileUpload(files);
}
}
// 清空知识库
clearKnowledge() {
if (this.config.onClearKnowledge) {
this.config.onClearKnowledge();
}
}
// 添加消息
addMessage(message) {
const messageElement = document.createElement('div');
messageElement.className = `message message-${message.type}`;
let content = `
<div class="message-content">
<div class="message-text">${this.formatMessageContent(message.content)}</div>
<div class="message-time">${this.formatTime(message.timestamp)}</div>
</div>
`;
// 添加来源信息(如果有)
if (message.sources && message.sources.length > 0) {
content += `
<div class="message-sources">
<h4>参考来源:</h4>
<ul>
${message.sources.map((source, index) => `
<li>
<strong>来源 ${index + 1}</strong> (相似度: ${(source.score * 100).toFixed(1)}%)
<div class="source-content">${this.truncateText(source.content, 200)}</div>
</li>
`).join('')}
</ul>
</div>
`;
}
messageElement.innerHTML = content;
this.messagesContainer.appendChild(messageElement);
this.scrollToBottom();
}
// 显示加载状态
showLoading(text = '加载中...') {
const loadingId = 'loading_' + Date.now();
const loadingElement = document.createElement('div');
loadingElement.className = 'message message-loading';
loadingElement.id = loadingId;
loadingElement.innerHTML = `
<div class="message-content">
<div class="loading-spinner"></div>
<div class="message-text">${text}</div>
</div>
`;
this.messagesContainer.appendChild(loadingElement);
this.loadingElements.set(loadingId, loadingElement);
this.scrollToBottom();
return loadingId;
}
// 隐藏加载状态
hideLoading(loadingId) {
const loadingElement = this.loadingElements.get(loadingId);
if (loadingElement) {
loadingElement.remove();
this.loadingElements.delete(loadingId);
}
}
// 更新知识库统计
updateKnowledgeStats(stats) {
document.getElementById('doc-count').textContent = stats.vectors?.count || 0;
document.getElementById('chunk-count').textContent = stats.vectors?.count || 0;
document.getElementById('cache-hit-rate').textContent =
((stats.cache?.hitRate || 0) * 100).toFixed(1) + '%';
}
// 格式化消息内容
formatMessageContent(content) {
return content
.replace(/\n/g, '<br>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>');
}
// 格式化时间
formatTime(timestamp) {
return new Date(timestamp).toLocaleTimeString();
}
// 截断文本
truncateText(text, maxLength) {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + '...';
}
// 滚动到底部
scrollToBottom() {
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
}
// 切换主题
toggleTheme() {
const currentTheme = this.config.theme;
this.config.theme = currentTheme === 'light' ? 'dark' : 'light';
this.applyTheme();
}
// 应用主题
applyTheme() {
this.container.setAttribute('data-theme', this.config.theme);
}
// 导出对话
exportConversation() {
// 这里应该调用应用的导出方法
console.log('导出对话功能需要在应用层实现');
}
// 清理UI
cleanup() {
if (this.container) {
this.container.innerHTML = '';
}
this.loadingElements.clear();
}
}
CSS样式
/* RAG聊天应用样式 */
.rag-chat-layout {
display: flex;
flex-direction: column;
height: 100vh;
max-height: 800px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.rag-chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
}
.rag-chat-header h2 {
margin: 0;
font-size: 1.2rem;
color: #333;
}
.rag-chat-controls {
display: flex;
gap: 0.5rem;
}
.control-btn {
padding: 0.5rem;
border: none;
background: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
.control-btn:hover {
background: #e9ecef;
}
.rag-chat-main {
display: flex;
flex: 1;
overflow: hidden;
}
.rag-chat-content {
flex: 1;
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: #fff;
}
.message {
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: 8px;
max-width: 80%;
}
.message-user {
background: #007bff;
color: white;
margin-left: auto;
}
.message-assistant {
background: #f8f9fa;
border: 1px solid #e0e0e0;
}
.message-system {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
text-align: center;
margin: 0 auto;
}
.message-error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.message-loading {
background: #fff3cd;
border: 1px solid #ffeaa7;
display: flex;
align-items: center;
gap: 0.5rem;
}
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.message-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.message-time {
font-size: 0.8rem;
opacity: 0.7;
align-self: flex-end;
}
.message-sources {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid #e0e0e0;
}
.message-sources h4 {
margin: 0 0 0.5rem 0;
font-size: 0.9rem;
color: #666;
}
.message-sources ul {
margin: 0;
padding-left: 1rem;
}
.message-sources li {
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.source-content {
margin-top: 0.25rem;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 4px;
font-style: italic;
color: #666;
}
.input-container {
padding: 1rem;
border-top: 1px solid #e0e0e0;
background: #f8f9fa;
}
.file-upload-area {
margin-bottom: 0.5rem;
}
.upload-btn {
padding: 0.5rem 1rem;
border: 1px solid #007bff;
background: #fff;
color: #007bff;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s;
}
.upload-btn:hover {
background: #007bff;
color: white;
}
.message-input-area {
display: flex;
gap: 0.5rem;
align-items: flex-end;
}
#message-input {
flex: 1;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
min-height: 60px;
font-family: inherit;
}
.send-btn {
padding: 0.75rem 1.5rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
}
.send-btn:hover {
background: #0056b3;
}
.rag-knowledge-panel {
width: 250px;
padding: 1rem;
background: #f8f9fa;
border-left: 1px solid #e0e0e0;
overflow-y: auto;
}
.rag-knowledge-panel h3 {
margin: 0 0 1rem 0;
font-size: 1rem;
color: #333;
}
.knowledge-stats {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background: #fff;
border-radius: 4px;
border: 1px solid #e0e0e0;
}
.stat-label {
font-size: 0.85rem;
color: #666;
}
.stat-value {
font-weight: 600;
color: #007bff;
}
/* 暗色主题 */
.rag-chat-layout[data-theme="dark"] {
background: #1a1a1a;
border-color: #333;
}
.rag-chat-layout[data-theme="dark"] .rag-chat-header {
background: #2d2d2d;
border-color: #333;
color: #fff;
}
.rag-chat-layout[data-theme="dark"] .rag-chat-header h2 {
color: #fff;
}
.rag-chat-layout[data-theme="dark"] .control-btn {
background: #333;
color: #fff;
}
.rag-chat-layout[data-theme="dark"] .control-btn:hover {
background: #444;
}
.rag-chat-layout[data-theme="dark"] .messages-container {
background: #1a1a1a;
}
.rag-chat-layout[data-theme="dark"] .message-assistant {
background: #2d2d2d;
border-color: #333;
color: #fff;
}
.rag-chat-layout[data-theme="dark"] .input-container,
.rag-chat-layout[data-theme="dark"] .rag-knowledge-panel {
background: #2d2d2d;
border-color: #333;
}
.rag-chat-layout[data-theme="dark"] #message-input {
background: #1a1a1a;
border-color: #333;
color: #fff;
}
.rag-chat-layout[data-theme="dark"] .stat-item {
background: #1a1a1a;
border-color: #333;
}
.rag-chat-layout[data-theme="dark"] .stat-label {
color: #ccc;
}
/* 响应式设计 */
@media (max-width: 768px) {
.rag-chat-main {
flex-direction: column;
}
.rag-knowledge-panel {
width: 100%;
max-height: 200px;
border-left: none;
border-top: 1px solid #e0e0e0;
}
.message {
max-width: 95%;
}
}
最佳实践
性能优化
// RAG系统性能优化器
class RAGPerformanceOptimizer {
constructor(ragSystem) {
this.ragSystem = ragSystem;
this.queryCache = new Map();
this.embeddingCache = new Map();
this.batchProcessor = new BatchProcessor();
}
// 优化查询性能
async optimizeQuery(query, options = {}) {
// 查询预处理
const processedQuery = this.preprocessQuery(query);
// 检查嵌入缓存
let queryVector = this.embeddingCache.get(processedQuery);
if (!queryVector) {
queryVector = await this.ragSystem.generateEmbedding(processedQuery);
this.embeddingCache.set(processedQuery, queryVector);
}
// 执行优化的检索
return await this.optimizedRetrieval(queryVector, options);
}
// 查询预处理
preprocessQuery(query) {
return query
.toLowerCase()
.trim()
.replace(/\s+/g, ' ')
.replace(/[^\w\s\u4e00-\u9fff]/g, '');
}
// 优化的检索
async optimizedRetrieval(queryVector, options) {
// 使用分层检索策略
const initialResults = await this.ragSystem.components.retriever.search(
queryVector,
{ ...options, maxResults: options.maxResults * 2 }
);
// 重排序和过滤
return this.rerankAndFilter(initialResults, options);
}
// 重排序和过滤
rerankAndFilter(results, options) {
// 基于多个因素重排序
const scoredResults = results.map(result => ({
...result,
finalScore: this.calculateFinalScore(result)
}));
return scoredResults
.sort((a, b) => b.finalScore - a.finalScore)
.slice(0, options.maxResults || 5);
}
// 计算最终分数
calculateFinalScore(result) {
const vectorScore = result.score * 0.6;
const lengthScore = Math.min(result.metadata.content.length / 1000, 1) * 0.2;
const freshnessScore = this.calculateFreshnessScore(result.metadata) * 0.2;
return vectorScore + lengthScore + freshnessScore;
}
// 计算新鲜度分数
calculateFreshnessScore(metadata) {
if (!metadata.processedAt) return 0;
const age = Date.now() - new Date(metadata.processedAt).getTime();
const dayAge = age / (1000 * 60 * 60 * 24);
return Math.max(0, 1 - dayAge / 30); // 30天内的文档有新鲜度加分
}
// 批量处理文档
async batchProcessDocuments(documents) {
return await this.batchProcessor.process(documents, async (doc) => {
return await this.ragSystem.addDocument(doc);
});
}
}
// 批处理器
class BatchProcessor {
constructor(batchSize = 5, delay = 100) {
this.batchSize = batchSize;
this.delay = delay;
}
async process(items, processor) {
const results = [];
for (let i = 0; i < items.length; i += this.batchSize) {
const batch = items.slice(i, i + this.batchSize);
const batchResults = await Promise.all(
batch.map(item => processor(item).catch(error => ({ error, item })))
);
results.push(...batchResults);
// 添加延迟以避免API限制
if (i + this.batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, this.delay));
}
}
return results;
}
}
错误处理和监控
// RAG系统错误处理器
class RAGErrorHandler {
constructor() {
this.errorCounts = new Map();
this.errorCallbacks = new Map();
}
// 处理错误
async handleError(error, context = {}) {
const errorType = this.classifyError(error);
const errorKey = `${errorType}_${context.operation || 'unknown'}`;
// 记录错误
this.recordError(errorKey, error, context);
// 执行错误处理策略
return await this.executeErrorStrategy(errorType, error, context);
}
// 错误分类
classifyError(error) {
if (error.message.includes('API')) return 'api_error';
if (error.message.includes('网络')) return 'network_error';
if (error.message.includes('存储')) return 'storage_error';
if (error.message.includes('解析')) return 'parsing_error';
return 'unknown_error';
}
// 记录错误
recordError(errorKey, error, context) {
if (!this.errorCounts.has(errorKey)) {
this.errorCounts.set(errorKey, []);
}
this.errorCounts.get(errorKey).push({
timestamp: new Date(),
error: error.message,
stack: error.stack,
context
});
}
// 执行错误处理策略
async executeErrorStrategy(errorType, error, context) {
switch (errorType) {
case 'api_error':
return await this.handleAPIError(error, context);
case 'network_error':
return await this.handleNetworkError(error, context);
case 'storage_error':
return await this.handleStorageError(error, context);
case 'parsing_error':
return await this.handleParsingError(error, context);
default:
return await this.handleUnknownError(error, context);
}
}
// 处理API错误
async handleAPIError(error, context) {
if (error.message.includes('429')) {
// 速率限制,等待后重试
await new Promise(resolve => setTimeout(resolve, 5000));
return { retry: true, delay: 5000 };
}
if (error.message.includes('401')) {
// 认证错误
return {
retry: false,
userMessage: 'API密钥无效,请检查配置'
};
}
return {
retry: false,
userMessage: 'API服务暂时不可用,请稍后重试'
};
}
// 处理网络错误
async handleNetworkError(error, context) {
return {
retry: true,
delay: 2000,
maxRetries: 3,
userMessage: '网络连接不稳定,正在重试...'
};
}
// 处理存储错误
async handleStorageError(error, context) {
if (error.message.includes('QuotaExceededError')) {
return {
retry: false,
userMessage: '存储空间不足,请清理部分数据后重试'
};
}
return {
retry: false,
userMessage: '数据存储出现问题,请刷新页面重试'
};
}
// 处理解析错误
async handleParsingError(error, context) {
return {
retry: false,
userMessage: '文档格式不支持或文件损坏,请检查文件格式'
};
}
// 处理未知错误
async handleUnknownError(error, context) {
return {
retry: false,
userMessage: '系统出现未知错误,请联系技术支持'
};
}
// 获取错误统计
getErrorStats() {
const stats = {};
for (const [key, errors] of this.errorCounts) {
stats[key] = {
count: errors.length,
lastOccurred: errors[errors.length - 1]?.timestamp,
recentErrors: errors.slice(-5)
};
}
return stats;
}
}
// RAG系统监控器
class RAGSystemMonitor {
constructor(ragSystem) {
this.ragSystem = ragSystem;
this.metrics = {
queries: 0,
successfulQueries: 0,
failedQueries: 0,
averageResponseTime: 0,
documentsProcessed: 0,
cacheHitRate: 0
};
this.responseTimes = [];
this.startTime = Date.now();
}
// 记录查询
recordQuery(success, responseTime) {
this.metrics.queries++;
if (success) {
this.metrics.successfulQueries++;
} else {
this.metrics.failedQueries++;
}
this.responseTimes.push(responseTime);
// 保持最近100次查询的响应时间
if (this.responseTimes.length > 100) {
this.responseTimes.shift();
}
this.updateAverageResponseTime();
}
// 更新平均响应时间
updateAverageResponseTime() {
if (this.responseTimes.length > 0) {
const sum = this.responseTimes.reduce((a, b) => a + b, 0);
this.metrics.averageResponseTime = sum / this.responseTimes.length;
}
}
// 记录文档处理
recordDocumentProcessed() {
this.metrics.documentsProcessed++;
}
// 更新缓存命中率
async updateCacheHitRate() {
try {
const cacheStats = await this.ragSystem.components.cacheManager.getStats();
this.metrics.cacheHitRate = cacheStats.hitRate || 0;
} catch (error) {
console.warn('更新缓存命中率失败:', error);
}
}
// 获取系统健康状态
getHealthStatus() {
const uptime = Date.now() - this.startTime;
const successRate = this.metrics.queries > 0
? this.metrics.successfulQueries / this.metrics.queries
: 1;
let status = 'healthy';
if (successRate < 0.9) {
status = 'degraded';
}
if (successRate < 0.5) {
status = 'unhealthy';
}
return {
status,
uptime,
successRate,
metrics: this.metrics
};
}
// 生成监控报告
generateReport() {
const health = this.getHealthStatus();
return {
timestamp: new Date().toISOString(),
health,
performance: {
averageResponseTime: this.metrics.averageResponseTime,
cacheHitRate: this.metrics.cacheHitRate,
throughput: this.metrics.queries / (health.uptime / 1000 / 60) // 每分钟查询数
},
usage: {
totalQueries: this.metrics.queries,
documentsProcessed: this.metrics.documentsProcessed
}
};
}
}
学习检验
理论问题
-
RAG系统架构
- RAG系统的核心组件有哪些?它们之间是如何协作的?
- 向量数据库在RAG系统中的作用是什么?
- 如何选择合适的文档分块策略?
-
检索优化
- 什么是语义检索?它与传统关键词检索有什么区别?
- 如何提高检索的准确性和相关性?
- 重排序(Reranking)的作用和实现方法是什么?
-
性能优化
- RAG系统的主要性能瓶颈在哪里?
- 如何设计有效的缓存策略?
- 批处理在RAG系统中的应用场景有哪些?
-
质量控制
- 如何评估RAG系统的回答质量?
- 什么情况下RAG系统可能产生幻觉?如何避免?
- 如何处理多语言文档的检索和生成?
实践练习
初级练习
-
基础RAG实现
- 实现一个简单的文档上传和问答功能
- 支持文本文件的解析和分块
- 实现基本的向量搜索和回答生成
-
文档处理增强
- 添加对PDF、Word文档的支持
- 实现智能分块策略
- 添加文档预处理和清理功能
-
UI界面开发
- 创建用户友好的聊天界面
- 实现文件拖拽上传功能
- 添加知识库管理面板
中级练习
-
检索优化
- 实现混合检索(向量+关键词)
- 添加查询扩展和重写功能
- 实现结果重排序算法
-
缓存系统
- 设计多层缓存架构
- 实现智能缓存失效策略
- 添加缓存性能监控
-
错误处理
- 实现完整的错误分类和处理
- 添加自动重试机制
- 创建错误监控和报警系统
高级练习
-
多模态RAG
- 支持图片和表格的处理
- 实现跨模态检索
- 添加多模态内容生成
-
分布式RAG
- 设计分布式向量存储
- 实现负载均衡和故障转移
- 添加分布式缓存同步
-
智能优化
- 实现自适应检索策略
- 添加用户反馈学习机制
- 创建A/B测试框架
项目建议
初级项目
-
个人知识库助手
- 功能:文档上传、智能问答、知识搜索
- 技术栈:Vanilla JS + IndexedDB + OpenAI API
- 特色:离线存储、隐私保护
-
学习资料问答系统
- 功能:课程资料上传、学习问答、知识点总结
- 技术栈:React + LocalStorage + AI API
- 特色:学习进度跟踪、知识图谱
中级项目
-
企业文档智能助手
- 功能:多格式文档支持、团队协作、权限管理
- 技术栈:Vue.js + Node.js + Vector Database
- 特色:企业级安全、API集成
-
多语言知识问答平台
- 功能:多语言支持、跨语言检索、翻译集成
- 技术栈:React + TypeScript + Multiple AI APIs
- 特色:语言检测、自动翻译
高级项目
-
智能客服系统
- 功能:实时对话、知识库管理、客服分析
- 技术栈:Full Stack + WebSocket + AI + Analytics
- 特色:情感分析、智能路由
-
AI驱动的研究助手
- 功能:论文分析、研究问答、引用管理
- 技术栈:Advanced AI + Graph Database + NLP
- 特色:学术搜索、引用网络
延伸阅读
技术文档
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
- Dense Passage Retrieval for Open-Domain Question Answering
- OpenAI Embeddings API Documentation
- Vector Database Comparison Guide
学习资源
- LangChain RAG Tutorial
- Building RAG Applications with JavaScript
- Vector Search Best Practices
- RAG Evaluation Methods
工具和库
- 向量数据库: Pinecone, Weaviate, Qdrant, Chroma
- 文档处理: PDF.js, Mammoth.js, Cheerio
- AI服务: OpenAI, Anthropic, Cohere, Hugging Face
- 前端框架: React, Vue.js, Svelte, Angular
社区和论坛
通过本文的学习,你应该能够理解RAG系统的核心概念,掌握前端RAG应用的实现方法,并能够根据具体需求设计和优化RAG系统。记住,RAG技术正在快速发展,保持学习和实践是掌握这项技术的关键。