资讯中心

从零开始搭建AI工程:RAG智能问答系统的完整落地指南

📅 2026/10/2 9:19:46
从零开始搭建AI工程:RAG智能问答系统的完整落地指南
先说自己在大模型和机器学习项目里摸爬滚打这几年最深的感受从零开始做AI工程难点从来不是模型而是整个系统的落地能力。模型选型、训练调优这些事情网上教程一抓一大把真正容易把人卡住的是——数据怎么管、实验怎么复现、模型怎么部署、上线之后怎么盯。这篇文章想把“ai-engineering-from-scratch”这条主线拆开从项目定义、工具链、数据、模型开发、部署、监控运维到踩坑记录完整过一遍我是怎么从一个只有想法、没有工程体系的人慢慢搭出一套能跑、能维护、能迭代的AI系统。这篇文章适合三类人看刚入门想系统规划AI项目的开发者已经在做模型但总觉得“训练完就结束”的算法工程师以及带小团队做AI落地、想给项目搭一套最小可用工程骨架的技术Leader。内容围绕一个贯穿全文的示例项目展开——一个基于私有知识库的智能问答系统这个例子覆盖面足够广数据、模型、部署、监控都能落在实处你看完可以直接把方案迁移到自己的场景里。1. 项目整体设计与思路拆解1.1 从零开始的“零”到底指什么很多人一提从零开始做AI工程第一反应是“是不是要从写神经网络开始”。我的回答是工程意义上的从零指的是从“只有一个业务问题、一堆散乱数据、一个模糊目标”开始把它推进到“系统稳定运行、效果可衡量、迭代有节奏”的状态。以智能文档问答系统为例如果一上来就直奔“我要微调一个ChatGPT”那大概率走偏。更合理的拆法是业务目标员工上传PDF、Word等资料后系统能自动回答相关问题技术形态检索增强生成RAG为主模型既可以直接用向量数据库召回相关片段再交给大模型生成答案约束条件预算有限、开发人手不足、涉及私有化部署这个拆法决定了后续所有技术选型。你会发现工程的关键动作不是“训练模型”而是“定义清楚边界”。比如回答必须基于上传文档内容不能胡编这就是一个约束它会推导出下面要讲的评估方案和提示词设计。从零开始还意味着两件事第一你不需要一开始就把所有环节做完整但要有一个最小闭环——从数据到模型到接口再到评价环环打通第二你需要在早期就把“可持续维护”的成本算进去而不是等到模型上线之后再来补工程债。1.2 为什么先搭工程骨架再填模型血肉我见过太多AI项目死在同一个模式上先花三个月精调模型效果在离线测试集上很漂亮结果一到部署就懵了——原来代码没有规范、模型文件没有版本、复用全靠手动拷贝、QA只能拿命令行交互测试。先搭工程骨架本质上是把不确定性拦在门外。骨架包括五个核心部件数据管线数据采集、清洗、切分、版本管理有固定流程更新数据不会污染已有实验实验管理系统每一次训练迭代代码、参数、数据、模型产物、指标全部有记录能回放、能对比评估体系离线指标加在线指标避免“指标好看但业务不认可”的情况部署与接口模型不是脚本而是可以被其他系统调用的API服务有明确输入输出和异常处理监控回环线上数据分布变化、服务稳定性、用户反馈形成一个持续改进的闭环这套骨架方案的选型逻辑很朴素任何一步能被自动化、能被记录、能被回滚未来的时间成本就会指数级下降。你可以把AI项目理解成写长篇小说骨架就是章节大纲和人物设定表先花力气把小说的人物关系理清楚写起来不仅快而且不会崩。1.3 智能问答系统的最小闭环长什么样我要在全文推进的这个示例系统最小闭环包括八步上传文档解析内容对文档做清洗、分块chunking形成结构化文本调用Embedding模型生成向量向量写入向量数据库如Milvus、Chroma、Qdrant用户提问系统把问题向量化做相似度检索取回Top-K相关片段和用户问题一起组装成Prompt大模型基于Prompt生成答案并标注引用来源系统记录日志将用户反馈和检索质量数据沉淀下来这八步里模型反而只是第3步和第7步的两个组件。整个链条的复杂度和风险分布在数据、检索、组装、服务、评估各个位置。这也是我特别想强调的一点AI工程化大多数工作围绕模型以外的事情展开。2. 核心细节解析与实操要点2.1 数据工程的细节决定下限文档问答系统的数据管线里最容易被忽视的是“文档解析”和“分块策略”。很多资料扫描件或PDF导出的文本其实是有结构的标题、段落、表格、页眉页脚混杂。如果不做解析直接全文切块检索效果会很难看。我在实践中通常分成几层纯文本提取用pypdf或pdfplumber先提取原始内容结构识别对文档标题层级做检测让每个分块尽可能对应一个语义完整的段落或章节清洗与去重去掉噪音、修复乱码、合并断行分块参数调整块大小和重叠率两个核心参数需要针对文档长度和问答粒度做实验以我常用的配置为例对于一般的企业制度文档chunk_size512按字符数比较合理overlap50可以让块与块之间不丢上下文。但技术手册类的文档代码片段多我会把块大小调大一点同时按代码块边界做强制切分。这里有一个基础判断不要追求一次性找到“完美分块参数”而是把它当作一个需要实验验证的工程参数。把分块策略暴露出配置项批量跑几组对比用检索命中率来选比靠感觉拍脑袋靠谱得多。另外一个关键点是数据版本管理。你训练了一个模型三个月后数据改了想复现当时的实验结果却发现数据只剩新版本了——这个场景我踩过。现在我的标准做法是数据目录带日期和用途标签例如data/20250410_v1_clean/同时用类似DVC的工具对数据目录做版本记录。哪怕只是手动备份一个数据快照也比不备份强。2.2 向量检索与Embedding模型选型不留死角向量检索是RAG链路里的“命门”之一。Embedding模型选什么向量数据库选什么相似度计算用哪种这组问题可以展开讲一套完整内容。Embedding模型方面我一般看三个指标语义表达能力、向量维度、服务化成本。语义表达能力越强模型往往越大、推理越慢。小知识库场景下中文场景可以优先考虑BGE-M3这类均衡型模型英文和技术类内容则尝试text-embedding-3-small。实际选型不建议跟风拿一批自己的真实问题去测top-5命中率数据说话。向量数据库方面数据量小于10万条时压根不用上重负载的分布式方案本地文件型数据库足够。数据量超过百万级、并发上来之后再考虑Milvus这类专门服务。选型的关键不是“哪个最强大”而是“哪个最适合当前规模和团队维护能力”。检索参数上top_k是召回数量score_threshold是相似度阈值这两个组合会影响答案质量的直接体验。top_k太小容易漏内容太大则会让大模型负担加重。我通常在初始阶段设top_k5同时记录相似度分数后续根据人工评估结果再做调整。2.3 提示词工程与上下文组装很多人把RAG项目效果不好归因于“模型不够聪明”其实大部分问题出在上下文组装上。组装Prompt的时候要考虑三件事系统指令、检索证据、生成约束。以问答系统为例我的系统提示词包含以下要素角色设定你是企业内部文档助手只能基于提供的资料回答回答规则如果资料中没有相关信息明确回答“资料库中未找到相关内容”不要编造引用要求回答末尾列出参考来源方便用户核验结构化输出需要让答案便于后续流程解析时可以要求输出固定字段提示词写得好不好直接影响“幻觉”发生率。我还发现一个细节检索结果放在提示词中时建议用编号列表组织同时在提问中明确“资料1-5请基于以上资料回答”模型对有编号的上下文会更敏感。这个小技巧在很多开源方案里都有使用实测能减少信息遗漏。2.4 评估体系不能缺否则一切优化都是盲人摸象你可能看到群友说效果“很好”但这个“好”到底怎么衡量在AI工程里没有评估体系优化就是碰运气。离线评估要分两层。第一层检索质量评估可以用命中率Hit Rate和MRRMean Reciprocal Rank来量化。构造一批“问题-正确文档片段”的测试集跑检索逻辑看正确答案出现在前几位的比例。第二层生成质量评估回答是否有误、是否忠于资料、是否完整。纯靠人看效率太低小型项目可以做一个带评分标准的打分表人工抽样打分条件成熟的可以考虑用LLM当裁判来辅助评估。在线评估方面用户反馈是最真实的信号。给生成的答案提供“有帮助/无帮助”的反馈按钮把反馈数据回流到评估集。这套闭环一旦跑起来你对系统的掌控力就上来了。3. 工具链选型与开发环境搭建3.1 从零开始的环境配置清单开发环境的搭建看似琐碎实际上决定效率。我的推荐清单不完全追求最新版优先稳定和生态兼容用途工具选型理由编程语言Python 3.10以上AI生态兼容性最好包管理uv或poetry比pip更快、依赖锁定更可靠代码管理Git无可替代配合GitHub/GitLab使用开发环境VS Code Jupyter脚本探索和工程开发兼顾实验追踪MLflow实验记录、模型注册、部署一体化容器化Docker Docker Compose本地和服务器环境保持一致大模型接口OpenAI兼容API或本地部署方案统一接口规格方便切换后端向量存储Chroma小规模/Milvus大规模按数据规模灵活选择依赖管理这个细节我要多说一句。用pip freeze锁定不了稳定的环境我推荐用uv或者poetry这类工具把依赖锁定到精确版本尤其是numpy、torch、transformers这种底层库版本漂移会让训练结果不可复现。3.2 一个可复制的项目目录结构项目结构是工程习惯的直观体现。以下是我压箱底用的目录模板新项目直接套用project/ ├── app/ # API服务 │ ├── __init__.py │ ├── main.py # FastAPI入口 │ ├── routers/ │ ├── schemas.py │ └── dependencies.py ├── core/ # 核心业务逻辑 │ ├── config.py # 配置管理 │ ├── embeddings.py │ ├── retriever.py │ ├── llm.py │ └── prompt.py ├── data/ # 数据文件不提交到Git │ ├── raw/ │ ├── processed/ │ └── testset/ ├── services/ # 交叉服务向量库读写等 ├── scripts/ # 数据管线和运维脚本 │ ├── download_data.py │ ├── ingest_docs.py │ └── evaluate.py ├── tests/ ├── mlruns/ # MLflow实验记录 ├── docker-compose.yml ├── pyproject.toml └── README.md这个结构的核心思想是“领域分层”API层只做参数校验和响应组装核心层做业务逻辑脚本层做离线任务。模型训练和推理服务分开数据管线和API服务分开互相不干扰调试时能快速定位问题。3.3 本地开发与服务器部署的统一AI开发最大的“灵异事件”之一就是本地跑得好好的服务器上就是报错。根因基本都是环境不一致。Docker Compose是解决这个问题的最轻量方案。我一般会先写好一套用于本地开发的服务编排包含API服务、向量数据库、可选的后端存储。开发时在容器里跑API修改代码用--reload热更新部署到服务器时同一套编排文件稍作环境变量调整就能用。注意配置管理的细节所有环境相关的变量比如OPENAI_API_KEY、向量库连接地址一律通过环境变量注入不写死在代码里。项目里准备一份.env.example模板开发者拉下来复制成.env填写自己的值即可。这套习惯能让你从个人开发顺利过渡到团队协作。4. 实操过程与核心环节实现4.1 数据摄取管线的完整实现我直接给出一段可运行的核心代码框架你要做的是理解思路再改写成自己项目里的形式。完整的重点在于步骤和异常处理而不只是功能能跑。第一步定义文档加载Processor。以PDF和Word纯文本为例可以用unstructured库做统一解析它对常见文档格式兼容性不错from typing import List from pathlib import Path from unstructured.partition.auto import partition def load_documents(file_path: Path) - List[str]: 加载并初步提取文档内容 elements partition(filenamestr(file_path)) texts [e.text for e in elements if e.text and len(e.text.strip()) 10] return texts第二步做分块处理。这里要考虑到标题、列表、代码块等结构我先用最简单的固定大小加重叠方式做可运行版本def chunk_texts(texts: List[str], chunk_size: int 512, overlap: int 50) - List[str]: chunks [] buffer for text in texts: buffer text \n while len(buffer) chunk_size: chunks.append(buffer[:chunk_size]) buffer buffer[chunk_size - overlap:] # 保留重叠部分 if buffer.strip(): chunks.append(buffer) return chunks这里补充一个容易忽略的点分块时尽量按照句子边界切分而不是硬切在词中间。比如我在项目中会用句号、换行符作为“切分优先级”的标记硬编码实现虽然粗糙但比完全无脑切片好很多。第三步生成向量并写入库。示例用Chroma作为向量存储import chromadb from chromadb.utils import embedding_functions # 连接本地Chroma client chromadb.PersistentClient(path./chroma_data) collection client.get_or_create_collection( nameknowledge_base, embedding_functionembedding_functions.DefaultEmbeddingFunction() ) # 写入向量 def ingest_documents(doc_paths: List[Path], chunk_size: int 512): for doc_path in doc_paths: texts load_documents(doc_path) chunks chunk_texts(texts, chunk_sizechunk_size) ids [f{doc_path.stem}_{i} for i in range(len(chunks))] metadatas [{source: str(doc_path.name)} for _ in range(len(chunks))] collection.add(idsids, documentschunks, metadatasmetadatas)这一步看起来简单实际生产里会有很多问题重复摄取会产生重复向量源文件更新了需要重新摄取删除文档需要级联删除。我的经验是给每个文档生成一个唯一MD5哈希写入时做去重更新时先删后加。这个细节网上教程很少提但能省大量时间。4.2 检索问答链路实现检索链路是整个系统的“心脏”查询时先做问题向量化再做相似度检索最后组装Prompt给大模型def retrieve_and_answer(question: str, top_k: int 5) - dict: # 1. 检索候选 results collection.query( query_texts[question], n_resultstop_k, include[documents, metadatas, distances] ) contexts [ f[{i1}] {doc} for i, doc in enumerate(results[documents][0]) ] sources [ meta[source] for meta in results[metadatas][0] ] context_text \n\n.join(contexts) # 2. 组装提示词 prompt PROMPT_TEMPLATE.format(contextcontext_text, questionquestion) # 3. 调用大模型 response llm_client.chat.completions.create( modelmodel_name, messages[{role: user, content: prompt}], temperature0.1 ) answer response.choices[0].message.content return { answer: answer, sources: sources, top_k_contexts: contexts }这里需要解释为什么temperature0.1。问答类任务希望答案稳定、忠实于资料过高的temperature会让同样问题在不同时间点返回不一致的措辞用户体验很差。如果你想让回答更有创造性可以调到0.4但工程落地阶段稳定性优先。4.3 API服务封装与性能瓶颈把检索问答链路暴露成API我首选FastAPI理由很实际类型校验完善、异步支持好、自带Swagger文档团队协作时接口文档都是自动生成的。接口设计这个点容易踩坑。很多新手直接把内部函数暴露出去不做输入输出约束。正规做法是定义Pydantic模型from pydantic import BaseModel, Field class QueryRequest(BaseModel): question: str Field(..., min_length1, max_length500) top_k: int Field(5, ge1, le10) class SourceItem(BaseModel): source: str index: int class QueryResponse(BaseModel): answer: str sources: List[SourceItem] elapsed_ms: int性能瓶颈方面RAG链路慢常见就三个地方Embedding推理、向量检索、大模型生成。如果你用本地大模型生成速度可能是最大瓶颈如果调用在线API网络延迟和Token数量是瓶颈。优化策略对大模型回答做流式输出首Token时间会明显改善用户观感对高频且结果确定的问题可以考虑缓存答案Embedding模型持久化加载避免每次请求重新载入向量检索的集合数据量较大时为集合建立合适的索引类型4.4 模型部署与运行环境配置模型部署有两类常见路径一类是通过Web框架直接加载模型提供服务另一类是借助专门的推理服务框架。从零起步时前者更快规模化后后者更稳。以本地Embedding模型为例我会在app中维护一个全局模型实例让它常驻内存from transformers import AutoTokenizer, AutoModel import numpy as np model_name BAAI/bge-small-zh-v1.5 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModel.from_pretrained(model_name) model.eval()调用时对文本做规范化处理并做均值池化得出向量。注意推理服务一定要设置并发锁或使用独立的模型进程否则多线程同时推理可能报错。大模型部分如果你有GPU资源可以本地部署开源模型比如Qwen2系列没有GPU资源直接接入在线大模型API是性价比最高的起步方案。我个人习惯是在core/config.py里抽象一层统一的LLM接口运行时通过配置切换后端这样后续从API切换到私有化模型代码改动很小。Docker镜像的编写有一个专属经验镜像体积不是越小越好而是要在构建时间和运行性能之间找平衡。基础镜像选用python:3.10-slim额外的系统依赖按需安装安装PyTorch之类的重量级库建议用官方预编译版本不要从源码编译否则一次构建可能要几小时。5. 监控运维与持续迭代方法5.1 在线监控的三个核心维度系统上线只是开始。监控要覆盖三个维度服务可用性、性能指标、效果指标。服务可用性最简单记录接口请求量、错误率、平均延迟、P99延迟。可以用Prometheus加Grafana搭一套也可以先用更轻量的方式日志采集加定时检查。从零开始我建议先在日志里输出结构化JSON比如{request_id: ..., question: ..., elapsed_ms: 123, status: success}后续接入监控系统时解析方便。性能指标除了常规延迟还要重点看两个RAG特有指标检索延迟、生成首Token延迟。这两个指标能帮你快速定位用户的“卡顿感”来自检索还是生成。效果指标最直接的是用户反馈答案点赞和点踩数据。也可以定期做人工盲测从线上随机采样问答记录评估回答质量。线上问法和测试集差异大这个指标体系能告诉你真实的短板。5.2 数据漂移与反馈回流的闭环设计模型上线后新文档不断进来用户提问方式不断变化知识的分布会慢慢漂移。如果不做反馈闭环系统效果会逐渐退化而不自知。我的标准闭环设计包含三部分日志记录每次问答都记录问题、检索TopK的相似度、答案、用户点击反馈反馈队列把用户点踩的问题定期整理成“困难问题集”再评估与再检索每隔一段时间用困难问题集跑一遍离线评估看看哪些问题回答失败率高针对性地调整分块策略或补充知识库实操中扼要提及一个常见误区很多人只在模型层面加“hard examples”做微调而没有先检查知识库是否缺失、检索是否错误。RAG项目里90%的bad case先把检索修好答案质量就会显著上升。5.3 持续迭代节奏小而频繁优于大而稀少AI项目的迭代节奏我建议学习DevOps的思路但要做“数据版本化的DevOps”。以两周为一个迭代周期比较合理。每周期只做一到两个明确优化点比如“本周提升技术文档的检索命中率”或“降低回答错误率”。其他保持不变避免多变量混杂导致不知道哪个改动起作用了。迭代时一定注意回滚方案。模型改动、数据改动都要先备份旧的模型权重或数据版本。我见过有人改了分块参数之后发现效果下降想回滚却找不到原始代码和参数配置的惨状。现在所有的实验记录都在MLflow里包括参数、数据版本、代码commit号每轮实验都是可以复现和回滚的。6. 常见问题与排查技巧实录6.1 检索结果明明包含答案回答却说不存在这个问题遇到最多。排查顺序别乱先看检索命中的片段是否真是有效答案——往往问题出在分块把答案拦腰斩断上下文信息丢了。对策是把chunk_size调大或者按标题层级重切再看Prompt里的组装方式——答案被埋在长文本证据中间模型没注意到。对策是把最相关片段放最前面并用编号显式指出最后看生成参数——temperature太高模型可能偏离资料自由发挥6.2 首轮问答正常并发一上来就开始报错这基本上是资源占用问题。Embedding模型加载占了大量显存或者向量库连接数已经打满。解法有几个层面单进程服务先用信号量控制并发避免显存OOM多进程模式下把模型放在独立进程里用IPC通信向量数据库采用连接池不要每个请求重新建连接上层的Web服务器比如uvicorn开合适数量的workers不是越开越多越好CPU核数和内存要匹配我把最常遇到的线上问题整理成一个速查表方便你对照排查问题现象可能原因排查方案回答内容与资料无关检索错误、分块过大或过小检查TopK召回文本先单独验证检索结果回答总说“未找到答案”阈值设置过高、知识库覆盖面不足调低score_threshold检查知识库导入范围延迟高但检索快大模型生成Token过多限制max_tokens考虑流式输出同样问题答案不一致temperature偏高、上下文顺序波动降低temperature到0附近知识库更新后查询结果未变化向量未重新写入或使用了缓存确认摄取脚本执行成功清理缓存内存持续上涨连接未释放、模型重复加载排查数据库连接池确认模型单例加载6.3 向量数据库选型错误引发的性能事故这是我在中期踩过的一个大坑。最初图方便用内存型向量库做了全量加载数据量到20万条时问答延迟飙到两三秒。后来迁移到服务化的向量数据库把数据导入导出、建立索引、重新验证检索质量整个过程花了整整一个周末。这个教训让我养成了一个习惯数据量预估和选型测试一定要在项目初期做。我的经验参考值5万条以内随便用轻量方案5万到50万条要关注索引类型和内存占用超过百万级别建议直接上独立向量数据库。起步时可以简单但要给迁移留出抽象层服务层不要跟某个具体向量库写死。6.4 让LLM给你当“裁判”但别全信为了节省评估的人工成本我尝试用大模型给系统回答评分。做法是构造一个打分Prompt把“资料片段、系统回答、参考标准”喂给大模型要求按相关性、完整性、忠实度打分并说明理由。这个方法能筛选出明显的高分低分极大提升评估效率。但也踩过坑大模型打分有偏差有时会被系统生成的花哨措辞带偏。所以我的原则是“LLM初筛人工定案”让LLM把分数过高或过低的bad case标记出来再由人工抽检中分段这样两人一机的小团队也可以维持比较可靠的评估闭环。6.5 工程护栏安全合规的底线设计在AI系统对外提供问答时有一个底线不能碰不能输出违法违规、偏激不合适的内容。具体到工程实现我会加两道护栏第一道输入输出侧的内容过滤。用户问题经过敏感词和分类器检查后转发给模型模型回答同样经过检查命中风险规则时就直接返回预设的安全兜底话术。第二道正视“幻觉”问题。RAG方案虽然比纯参数化生成可靠得多但也不能完全杜绝编造。系统提示词里反复强化“资料库没有相关信息就明确说没有”不是为了彻底解决幻觉而是为了降低误导风险。同时回答附上引用出处让用户能手动核验这是工程上实用且负责任的做法。关于个人信息和隐私数据这套资料入库时要先做脱敏评估。系统日志里不要把完整问题文本写到普通日志必要时脱敏或加密存储。这些不是刻意上纲上线是为了让项目能走得更远、交付后不用在后期补安全隐患。7. 我的真实项目体会与下一步扩展建议写到这里回头看这个从零到一的完整工程链条我想再强调一遍开头那句话AI工程的复杂度不在单个算法点而在于全局的协调与落地。你可能会发现好像我没讲什么特别高深的模型理论但这恰恰是工程落地的真相——把每个环节做扎实把数据、实验、部署、监控串成闭环系统自然稳。根据我踩过多次坑之后的经验如果你是第一次实践这个链路可以先砍掉所有“锦上添花”的功能只保留最小闭环文档摄取、向量检索、大模型生成、基础日志。跑通这个闭环你就有资格自称“从零开始完成了一个AI工程”。最后分享一个后续可以扩展的方向把这个系统升级成多知识库的形态不同团队维护自己的知识域系统按权限隔离。权限隔离不只是“用户不能看其他库的内容”还涉及“检索结果必须过滤权限域”这会让工程复杂度上一个台阶但也是企业级应用真正需要的能力。把这个做好了你的AI工程能力会再高一个段位。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案