
如果你正在构建一个基于大语言模型LLM的智能应用比如一个能回答公司内部文档问题的客服机器人或者一个能理解专业领域知识的智能助手那么你大概率会遇到一个核心矛盾如何让模型“知道”它原本不知道的信息直接调用通用大模型如 GPT-4、通义千问会得到流畅但可能不准确或过时的回答因为它缺乏你的私有数据。为了解决这个问题业界形成了两大主流技术路径RAG检索增强生成和SFT监督微调。然而面对这两个选项很多开发者陷入了选择困境RAG听起来简单不就是“检索生成”吗但为什么我的知识库回答总是跑偏、不准确或者干脆“幻觉”频出SFT听起来很强大直接“教会”模型新知识。但动辄需要几十GB显存、复杂的训练流程和昂贵的数据标注让个人和小团队望而却步。更常见的情况是大家看了无数篇概念文章却依然不知道第一步该从哪里迈出如何搭建一个可运行、可评估的原型系统。本文将彻底解决这个问题。我们不空谈概念而是通过一个完整的实战项目带你走通从RAG 到 SFT的全链路。你会清晰地看到如何快速部署一个轻量级 Embedding 模型为你的文档建立高效的语义检索能力。如何用 LangChain 搭建一个健壮的 RAG 系统并理解其中每一个环节加载、切分、向量化、检索、生成的“坑”与最佳实践。当 RAG 效果遇到瓶颈时如何转向成本可控的 SFT以 LoRA 为例用有限的资源对模型进行私有化微调实现质的提升。本文的目标是让你在阅读后不仅能理解 RAG 和 SFT 的技术原理与适用场景更能亲手搭建、运行并优化一个属于自己的私有知识问答系统。我们将提供完整的代码、配置和排错指南。1. RAG 与 SFT不是选择题而是组合拳在深入实战前我们必须先厘清一个关键认知RAG 和 SFT 并非互斥的替代方案而是适用于不同场景、可以协同工作的技术手段。RAG检索增强生成的核心思想是“即用即查”。它不改变大模型本身而是通过一个外部知识库通常是向量数据库来提供实时、准确的信息。流程可以简化为用户提问 → 将问题转换为向量 → 从向量库中检索最相关的文档片段 → 将片段和问题一起交给大模型生成答案。优点知识更新成本低只需更新向量库、可解释性强可以追溯答案来源、不存在灾难性遗忘。缺点答案质量严重依赖检索精度“垃圾进垃圾出”对于复杂、多步推理问题支持较弱存在上下文长度限制。适合场景知识频繁变更、需要精确溯源、数据量大且多样的场景如客服问答、企业知识库、法律条文查询。SFT监督微调的核心思想是“内化知识”。它通过在有监督的数据集上继续训练让大模型将新知识“融合”到其参数中。优点模型响应速度快无需外部检索能更好地学习数据中的复杂模式和风格在特定任务上可能达到更高精度。缺点训练成本高数据、算力知识更新需要重新训练存在灾难性遗忘风险可解释性差。适合场景任务模式固定、风格化输出要求高、对响应延迟敏感的场景如代码生成、特定格式报告撰写、模仿特定对话风格。我们的实战策略对于大多数私有化应用优先搭建 RAG 系统。因为它启动快、成本低、易验证。当 RAG 在特定任务上因模型本身能力不足如不理解专业术语关系、无法遵循特定格式而效果不佳时再考虑针对性地采集数据对基础模型进行 SFT尤其是 LoRA 等参数高效微调方法形成“RAG 提供精准知识 SFT 优化模型任务能力”的组合方案。2. 环境准备构建可复现的 Python 环境工欲善其事必先利其器。一个独立、版本清晰的环境是后续所有步骤的基础。我们推荐使用 Conda 或 venv。# 1. 创建并激活一个全新的 Python 3.10 环境3.8-3.11均可 conda create -n rag-sft-tutorial python3.10 -y conda activate rag-sft-tutorial # 2. 安装核心依赖 # 深度学习框架和基础工具 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 请根据你的CUDA版本调整 pip install transformers datasets accelerate peft # RAG 相关框架与组件 pip install langchain langchain-community langchain-text-splitters langchain-chroma # 向量数据库我们选用轻量级的Chroma pip install chromadb # 文档加载器支持txt, pdf, md, html等 pip install pypdf pymupdf markdown beautifulsoup4 # 实用工具 pip install sentence-transformers tiktoken # tiktoken用于文本切分计数关键版本说明与选择PyTorch请务必访问 PyTorch 官网 获取与你的 CUDA 版本匹配的安装命令。无 GPU 用户可使用 CPU 版本。LangChain这是一个快速迭代的框架。本文基于langchain0.1.0的较新版本编写其模块化程度更高如langchain-community。如果你遇到导入错误请检查版本并参考官方文档。Chroma一个开源、轻量、易用的向量数据库非常适合原型开发和中小规模应用。3. 第一步部署与测试 Embedding 模型Embedding 模型是将文本转换为数值向量嵌入的模型它是 RAG 的“检索之眼”其质量直接决定了检索的相关性。我们将部署一个开源、效果优秀且对中文友好的模型BAAI/bge-small-zh-v1.5。3.1 为什么选择这个模型专为中文优化相比通用多语言模型在中文语义相似度任务上表现更佳。轻量高效参数量小推理速度快适合本地部署。社区活跃由智源研究院维护在中文社区被广泛验证。3.2 本地部署与封装我们不直接调用 Hugging Face 的 pipeline而是将其封装成一个 LangChain 兼容的 Embeddings 类便于后续集成。# file: local_embedding.py from langchain.embeddings.base import Embeddings from typing import List import torch from transformers import AutoModel, AutoTokenizer class LocalBgeEmbeddings(Embeddings): 本地部署的BGE Embedding模型封装 def __init__(self, model_name: str BAAI/bge-small-zh-v1.5): self.device torch.device(cuda if torch.cuda.is_available() else cpu) print(fLoading embedding model: {model_name} on {self.device}) self.tokenizer AutoTokenizer.from_pretrained(model_name) self.model AutoModel.from_pretrained(model_name).to(self.device) self.model.eval() # 设置为评估模式 def _embed(self, texts: List[str]) - List[List[float]]: 核心嵌入方法 inputs self.tokenizer(texts, paddingTrue, truncationTrue, max_length512, # BGE模型建议的最大长度 return_tensorspt).to(self.device) with torch.no_grad(): outputs self.model(**inputs) # 使用 [CLS] token 的表示作为句子向量并做归一化 embeddings outputs.last_hidden_state[:, 0] embeddings torch.nn.functional.normalize(embeddings, p2, dim1) return embeddings.cpu().tolist() def embed_documents(self, texts: List[str]) - List[List[float]]: 为文档列表生成嵌入 return self._embed(texts) def embed_query(self, text: str) - List[float]: 为单个查询生成嵌入注意BGE的查询需要添加指令前缀 # 对于检索任务BGE建议为查询添加特定指令 instruction_for_retrieval 为这个句子生成表示以用于检索相关文章 encoded_query instruction_for_retrieval text return self._embed([encoded_query])[0] # 测试代码 if __name__ __main__: embeddings LocalBgeEmbeddings() docs [机器学习是人工智能的一个分支。, 深度学习是基于神经网络的机器学习方法。] doc_vectors embeddings.embed_documents(docs) print(f文档向量维度: {len(doc_vectors[0])}) query 什么是AI query_vector embeddings.embed_query(query) print(f查询向量维度: {len(query_vector)}) # 简单计算相似度余弦相似度 import numpy as np sim np.dot(query_vector, doc_vectors[0]) / (np.linalg.norm(query_vector) * np.linalg.norm(doc_vectors[0])) print(f查询与第一个文档的相似度: {sim:.4f})运行此脚本如果看到成功加载模型并输出向量维度说明 Embedding 模型部署成功。4. 第二步构建完整的 RAG 流水线LangChain现在我们将利用 LangChain 的模块化设计搭建一个包含文档加载、切分、向量化存储、检索和生成的完整流水线。4.1 文档加载与智能切分文本切分是 RAG 的“暗坑”之一。切得太碎丢失上下文切得太大检索精度下降且消耗大量上下文窗口。# file: build_rag_pipeline.py import os from langchain_community.document_loaders import TextLoader, PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from local_embedding import LocalBgeEmbeddings from langchain_chroma import Chroma class RAGPipelineBuilder: def __init__(self, persist_directory: str ./chroma_db): self.persist_directory persist_directory self.embeddings LocalBgeEmbeddings() # 初始化文本分割器针对中文优化参数 self.text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块的最大字符数 chunk_overlap100, # 块之间的重叠字符数保持上下文连贯 separators[\n\n, \n, 。, , , , , , ], # 中文优先分割符 length_functionlen, ) def load_and_split_documents(self, file_path: str): 根据文件类型加载并分割文档 _, ext os.path.splitext(file_path) if ext.lower() .pdf: loader PyPDFLoader(file_path) elif ext.lower() in [.txt, .md, .html]: loader TextLoader(file_path, encodingutf-8) else: raise ValueError(fUnsupported file type: {ext}) documents loader.load() print(fLoaded {len(documents)} raw document(s) from {file_path}) # 执行分割 split_docs self.text_splitter.split_documents(documents) print(fSplit into {len(split_docs)} chunks.) # 打印前两个块的内容预览 for i, doc in enumerate(split_docs[:2]): print(f\n--- Chunk {i} (length: {len(doc.page_content)}) ---) print(doc.page_content[:200] ...) return split_docs def create_vectorstore(self, documents): 创建或加载向量数据库 # 如果数据库已存在则加载否则创建 if os.path.exists(self.persist_directory): print(fLoading existing vectorstore from {self.persist_directory}) vectorstore Chroma(persist_directoryself.persist_directory, embedding_functionself.embeddings) else: print(fCreating new vectorstore at {self.persist_directory}) vectorstore Chroma.from_documents(documentsdocuments, embeddingself.embeddings, persist_directoryself.persist_directory) vectorstore.persist() # 持久化到磁盘 return vectorstore # 使用示例 if __name__ __main__: builder RAGPipelineBuilder() # 假设你有一个名为 knowledge.pdf 的文档 # split_docs builder.load_and_split_documents(./knowledge.pdf) # 为了演示我们使用模拟文本 from langchain_core.documents import Document mock_docs [Document(page_content机器学习是人工智能的核心研究领域之一其目的是让计算机系统通过经验自动改进性能。深度学习是机器学习的一个子领域它使用多层神经网络来学习数据的层次化表示。), Document(page_contentTransformer 是一种基于自注意力机制的神经网络架构广泛应用于自然语言处理任务如机器翻译和文本生成。BERT 和 GPT 都是基于 Transformer 的著名模型。)] split_docs builder.text_splitter.split_documents(mock_docs) vectorstore builder.create_vectorstore(split_docs) print(Vectorstore created/loaded successfully.)4.2 检索与生成链我们将检索器与大语言模型LLM连接起来。这里我们使用 OpenAI API 作为示例需自行准备 API Key但你完全可以替换为本地部署的 ChatGLM、Qwen 等开源模型。# 续上文件 build_rag_pipeline.py from langchain.chains import RetrievalQA from langchain_openai import ChatOpenAI from langchain.prompts import PromptTemplate class RAGQASystem: def __init__(self, vectorstore): self.vectorstore vectorstore # 初始化 LLM - 使用 OpenAI GPT需设置环境变量 OPENAI_API_KEY # 对于本地模型可替换为from langchain_community.llms import LlamaCpp 等 self.llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.1) # 定义一个更有效的提示模板指导模型基于上下文回答 self.prompt_template 请根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题请直接说“根据已知信息无法回答该问题”不要编造信息。 上下文 {context} 问题{question} 请基于上下文给出答案 self.PROMPT PromptTemplate( templateself.prompt_template, input_variables[context, question] ) # 构建检索器使用相似度搜索返回前3个最相关的文档块 self.retriever self.vectorstore.as_retriever(search_kwargs{k: 3}) # 构建检索增强生成链 self.qa_chain RetrievalQA.from_chain_type( llmself.llm, chain_typestuff, # 将检索到的文档“塞”进上下文 retrieverself.retriever, return_source_documentsTrue, # 返回源文档便于溯源 chain_type_kwargs{prompt: self.PROMPT} ) def ask(self, question: str): 提问并获取答案 result self.qa_chain.invoke({query: question}) answer result[result] source_docs result[source_documents] print(f\n问题{question}) print(f答案{answer}) print(\n--- 参考来源 ---) for i, doc in enumerate(source_docs): print(f[来源{i1}] {doc.page_content[:150]}...) return answer, source_docs # 整合并运行 if __name__ __main__: # 假设 vectorstore 已在上一步创建 builder RAGPipelineBuilder() # 这里需要先有已构建的向量库我们直接连接一个如果是第一次运行请先注释掉下面几行运行上一步创建数据库 vectorstore Chroma(persist_directory./chroma_db, embedding_functionbuilder.embeddings) qa_system RAGQASystem(vectorstore) # 测试提问 test_questions [ 什么是机器学习, Transformer 是用来做什么的, ] for q in test_questions: qa_system.ask(q)关键点解析search_kwargs{“k”: 3}控制检索返回的文档块数量。k值需要权衡太小可能信息不全太大会引入噪声并消耗更多 LLM 上下文。chain_type“stuff”最简单的处理方式将所有检索到的文档拼接后送入 LLM。适用于文档块较小、总长度不超过模型上下文窗口的情况。对于更长的上下文可考虑map_reduce、refine等复杂链类型。提示模板Prompt Template这是提升 RAG 回答质量的关键。明确的指令可以显著减少模型“幻觉”并强制其基于上下文回答。5. 第三步当 RAG 不够用时引入 SFTLoRA 微调假设你的 RAG 系统在回答特定领域问题时尽管检索到了正确文档但模型仍然无法理解复杂的领域逻辑或者无法按照你期望的格式如特定的 JSON 结构输出。这时可以考虑对 LLM 进行微调。我们将使用LoRALow-Rank Adaptation技术它是一种参数高效微调方法只需训练极少量参数通常小于原模型的1%就能达到接近全参数微调的效果极大降低了显存和计算需求。5.1 准备微调数据微调数据需要是高质量的问答对。可以从 RAG 系统的日志中收集用户真实问题并结合人工标注或利用大模型如 GPT-4辅助生成答案来构建。# file: sft_train_data.jsonl # 每一行是一个 JSON 对象 {instruction: 根据公司规定年假如何计算, input: , output: 根据《员工手册》第三章第五条员工年假自入职之日起按自然年度计算累计工作满1年不满10年的年休假5天满10年不满20年的年休假10天满20年的年休假15天。} {instruction: 请将以下需求转化为产品功能描述文档。, input: 用户希望在手机APP首页增加一个天气widget。, output: **功能名称**首页天气Widget\n**功能描述**在APP首页顶部区域新增一个可折叠的天气信息卡片默认显示当前城市、实时温度、天气状况图标及未来6小时简要预报。用户可点击卡片进入详细天气页面。\n**交互逻辑**1. 支持手动刷新2. 长按可拖动调整位置3. 点击右上角关闭按钮可隐藏隐藏后可在‘设置-首页管理’中重新开启。} {instruction: 解释一下什么是‘神经网络中的Dropout技术’。, input: , output: Dropout是一种在神经网络训练过程中用于防止过拟合的正则化技术。其核心思想是在每次训练迭代中随机‘丢弃’即暂时移除网络中一部分神经元及其连接迫使网络不依赖于任何单个神经元从而学习到更加鲁棒的特征。在推理预测阶段所有神经元都参与工作但它们的输出值会乘以一个与训练时丢弃率相关的缩放因子以保持期望输出的一致性。}5.2 LoRA 微调脚本基于 Qwen1.5-1.8B 模型我们选择通义千问的轻量级模型Qwen/Qwen1.5-1.8B作为基座模型进行演示。# file: lora_finetune.py from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq, ) from peft import LoraConfig, get_peft_model, TaskType import torch import os def prepare_model_and_tokenizer(model_nameQwen/Qwen1.5-1.8B): 加载预训练模型和分词器 tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) # 设置填充token某些模型可能需要 if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.bfloat16 if torch.cuda.is_available() else torch.float32, device_mapauto, trust_remote_codeTrue ) return model, tokenizer def format_instruction(example): 将数据格式化为模型输入的文本 # 格式|im_start|system\nYou are a helpful assistant.|im_end|\n|im_start|user\n{instruction}\n{input}|im_end|\n|im_start|assistant\n{output}|im_end| # 这是 Qwen1.5 的 ChatML 格式 system_msg You are a helpful assistant. user_msg example[instruction] if example[input]: user_msg \n example[input] assistant_msg example[output] formatted_text f|im_start|system\n{system_msg}|im_end|\n \ f|im_start|user\n{user_msg}|im_end|\n \ f|im_start|assistant\n{assistant_msg}|im_end| return {text: formatted_text} def tokenize_function(examples, tokenizer, max_length512): 分词函数 tokenized tokenizer( examples[text], truncationTrue, paddingmax_length, max_lengthmax_length, return_tensorspt, ) # 将标签设置为与输入相同训练时只计算 assistant 部分的损失 tokenized[labels] tokenized[input_ids].clone() return tokenized def main(): # 1. 加载并格式化数据 dataset load_dataset(json, data_filessft_train_data.jsonl, splittrain) dataset dataset.map(format_instruction) # 2. 加载模型和分词器 model, tokenizer prepare_model_and_tokenizer() # 3. 应用 LoRA 配置 lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, # 因果语言模型任务 r8, # LoRA 秩rank影响参数量和能力通常 4, 8, 16 lora_alpha32, # 缩放参数 lora_dropout0.1, # Dropout 概率 target_modules[q_proj, k_proj, v_proj, o_proj], # 针对 Qwen 的注意力模块 biasnone, ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量应该非常少 # 4. 分词处理 tokenized_dataset dataset.map( lambda x: tokenize_function(x, tokenizer), batchedTrue, remove_columnsdataset.column_names, ) # 5. 设置训练参数 training_args TrainingArguments( output_dir./qwen1.5-1.8b-lora-finetuned, per_device_train_batch_size2, # 根据 GPU 显存调整 gradient_accumulation_steps4, # 模拟更大批次 num_train_epochs3, logging_steps10, save_steps100, learning_rate2e-4, fp16torch.cuda.is_available(), # 混合精度训练节省显存 report_tonone, # 可改为 tensorboard 等 save_total_limit2, push_to_hubFalse, # 如果希望上传到 Hugging Face Hub 可设为 True ) # 6. 初始化 Trainer 并开始训练 trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset, data_collatorDataCollatorForSeq2Seq(tokenizertokenizer, paddingTrue), ) print(Starting training...) trainer.train() # 7. 保存 LoRA 适配器权重 model.save_pretrained(./my_lora_adapter) tokenizer.save_pretrained(./my_lora_adapter) print(LoRA adapter saved to ./my_lora_adapter) if __name__ __main__: main()5.3 加载并使用微调后的模型训练完成后你可以轻松地将 LoRA 适配器加载到原始模型上并与 LangChain 集成。# file: load_lora_model.py from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel from langchain.llms import HuggingFacePipeline from transformers import pipeline import torch # 加载基础模型和分词器 base_model_name Qwen/Qwen1.5-1.8B model AutoModelForCausalLM.from_pretrained( base_model_name, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue ) tokenizer AutoTokenizer.from_pretrained(base_model_name, trust_remote_codeTrue) # 加载 LoRA 适配器权重 model PeftModel.from_pretrained(model, ./my_lora_adapter) # 创建文本生成 pipeline text_generation_pipeline pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens256, temperature0.7, do_sampleTrue, ) # 包装成 LangChain 的 LLM local_llm HuggingFacePipeline(pipelinetext_generation_pipeline) # 现在你可以用 local_llm 替换之前 RAGQASystem 中的 self.llm (ChatOpenAI)构建一个完全本地化的、经过私有知识微调的 RAG 系统6. 运行、验证与效果对比6.1 运行你的 RAG 系统准备知识文档将你的 PDF、TXT 等文档放入项目目录。构建向量库运行build_rag_pipeline.py中的文档加载和向量化部分。启动问答运行RAGQASystem的测试部分输入问题查看检索到的文档和生成的答案。6.2 验证效果的关键指标检索相关性系统返回的文档片段是否与问题真正相关可以人工评估。答案准确性答案是否基于提供的上下文是否存在“幻觉”答案完整性是否充分回答了问题响应速度从提问到获得答案的总耗时。6.3 RAG 与 SFT 效果对比在同一个问题上你可以对比纯基础模型直接问Qwen1.5-1.8B。RAG 基础模型使用我们搭建的 RAG 系统。RAG SFT 微调模型使用集成了 LoRA 微调模型的 RAG 系统。通常你会观察到纯基础模型可能回答泛泛或错误。RAG 基础模型答案更准确但格式、语气可能不符合领域要求。RAG SFT 微调模型在保持准确性的基础上答案的格式、专业术语使用、逻辑组织更贴近你的私有数据风格。7. 常见问题与排查思路问题现象可能原因排查方式解决方案导入 LangChain 模块失败版本不兼容或模块路径变化。检查pip list | grep langchain确认版本。查看错误信息中缺失的模块名。安装langchain-community,langchain-chroma等子包。或参考 LangChain 官方文档调整导入语句如from langchain_community.document_loaders import ...。Embedding 模型加载慢或失败网络问题首次下载或模型文件损坏。观察下载进度条检查~/.cache/huggingface/目录。使用国内镜像源或手动下载模型文件到本地通过LocalBgeEmbeddings(model_name“/本地路径”)加载。Chroma 数据库报错数据库版本不兼容或持久化目录损坏。查看完整的错误堆栈信息。尝试删除旧的./chroma_db目录重新生成向量库。确保 Chroma 客户端版本与服务器端嵌入式兼容。RAG 回答出现“幻觉”1. 检索到的文档不相关。2. 提示模板指令不够强。3. LLM 自身倾向。1. 检查source_documents内容。2. 审查提示模板。3. 降低 LLM 的temperature。1. 优化文本切分策略调整chunk_size,chunk_overlap或尝试不同 Embedding 模型。2. 强化提示模板明确要求“仅基于上下文”。3. 使用temperature0.1或尝试更强大的 LLM。LoRA 训练不收敛或损失为 NaN学习率过高数据格式错误或梯度爆炸。1. 检查训练日志的损失曲线。2. 检查一两份tokenized_dataset样本。3. 使用torch.autograd.detect_anomaly()检查。1. 大幅降低学习率如5e-5。2. 确保数据格式化和分词正确特别是labels的掩码。3. 启用梯度裁剪 (gradient_clipping)。微调后模型输出乱码或退化过拟合或训练数据量太少、质量差。在保留的验证集上测试模型表现。1. 增加训练数据量并确保多样性。2. 减少训练轮次 (num_train_epochs)。3. 增加 LoRA 的dropout。GPU 显存不足OOM批次大小太大或模型本身太大。监控nvidia-smi的显存使用情况。1. 减小per_device_train_batch_size。2. 增大gradient_accumulation_steps以补偿。3. 启用fp16或bf16混合精度训练。4. 使用gradient_checkpointing。5. 考虑更小的基座模型或使用量化如 bitsandbytes。8. 最佳实践与进阶建议RAG 优化是第一步在考虑 SFT 之前务必先优化你的 RAG 流水线。包括文档预处理清洗无关字符、页眉页脚。智能切分尝试按章节、段落或语义切分如SemanticTextSplitter。检索策略除了相似度搜索可结合关键词搜索Hybrid Search或元数据过滤。重排序Re-ranking使用更精细的模型对检索结果进行二次排序提升 Top1 精度。提示工程精心设计提示模板明确角色、任务和格式要求。SFT 数据质量至上微调的效果 80% 取决于数据质量。多样性覆盖尽可能多的任务类型和问题表述。准确性答案必须正确无误。格式一致性输出格式应符合你的应用需求。规模对于 LoRA几百到几千条高质量数据通常就能看到明显效果。混合策略采用RAG SFT的混合模式。让 RAG 负责提供最新、最准确的事实知识让 SFT 后的模型负责理解领域逻辑、遵循特定格式和风格。这往往是性价比最高的方案。评估与迭代建立评估体系。可以人工评估一批测试问题也可以设计自动化的评估指标如答案与标准答案的 ROUGE-L 分数、检索命中率等。基于评估结果持续迭代你的数据、模型和流程。走向生产向量数据库考虑使用PGVector与 PostgreSQL 集成、Milvus或Weaviate等更适用于生产环境的数据库。服务化将 Embedding 模型、LLM 和 RAG 链封装为 API 服务如使用 FastAPI。监控记录用户提问、检索结果、模型回答和反馈用于持续优化。从快速验证概念的 RAG 原型到针对特定任务进行深度优化的 SFT 模型这条路径为你提供了构建强大私有化 AI 应用的完整工具箱。记住没有银弹。理解你数据的特性、明确你的需求边界并在 RAG 的灵活性与 SFT 的专一性之间找到平衡点是项目成功的关键。建议从本文的完整示例代码开始替换上你自己的数据和需求逐步迭代你将会构建出一个真正理解你业务的知识助手。