ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零构建AI工程:告别调包侠,掌握RAG系统核心能力

从零构建AI工程:告别调包侠,掌握RAG系统核心能力 1. 从零搭建AI工程能力为什么我劝你别再当“调包侠”“ai-engineering-from-scratch”这个标题第一次看到的时候我愣了一下。不是因为它有多花哨恰恰相反它朴素得有点不像这个时代的项目名。现在满大街都是“大模型实战”“Agent开发从入门到精通”“RAG系统落地指南”突然冒出来一个“从零开始的AI工程”反而让我觉得有点意思。先把这个标题拆开看。AI工程不是算法研究不是发论文是把AI能力变成可运行、可维护、可交付的产品的那套手艺。From scratch从零开始意味着不依赖现成的高级封装不满足于调个API就完事而是要把底层的逻辑、数据流、部署链路都摸一遍。这两层意思叠在一起指向的其实是一类人那些已经会用几个框架、跑过几个demo但总觉得心里不踏实想搞清楚“下面到底发生了什么”的开发者。我自己在这个行当里摸爬滚打了十来年从早期做传统机器学习 pipeline到后来搞深度学习平台再到现在折腾大模型应用踩过的坑比写过的代码还多。我见过太多人包括早期的我自己拿着 LangChain 或者某个开源框架三行代码跑通一个问答机器人然后就觉得自己“会AI工程”了。结果一上生产环境延迟飙到十几秒检索结果驴唇不对马嘴成本失控日志里全是看不懂的报错。这时候才发现那些被封装掉的细节恰恰是决定项目生死的关键。这个项目标题吸引我的地方就在于它试图回答一个很根本的问题如果抛开所有现成的轮子让你从一张白纸开始构建一个AI应用你需要掌握哪些核心能力这不是要你重新发明 PyTorch而是要你理解一个AI系统从数据到模型到服务这条链路上每一个环节的“最小必要知识”。它适合那些不满足于当“调包侠”的开发者适合那些想从业务开发转向AI工程的人也适合那些被各种框架的抽象层搞得云里雾里、想回到地面重新理解一遍的从业者。我打算结合自己这些年的实操经验把这个“从零开始”的路径拆解一遍。不是给你一份标准答案而是把我认为最关键的几个环节、最容易踩的坑、以及那些“早知道就好了”的经验原原本本地分享出来。文章会比较长因为这件事本身就不是三言两语能说清的。你可以把它当成一个老工程师的复盘笔记也可以当成一份避坑指南。咱们直接进入正题。2. 整体设计思路为什么“从零”反而更快2.1 先想清楚你要的到底是“能跑”还是“能扛”很多人对“从零构建AI工程”有个误解觉得这是要你从矩阵乘法开始手写神经网络。不是的。From scratch 的核心不是重复造轮子而是理解轮子为什么是圆的。我在带团队的时候经常跟新人说一句话你可以用框架但你必须知道框架帮你做了什么以及它没帮你做什么。这个项目的整体设计思路我理解下来是分层的。最底层是数据层包括数据的采集、清洗、切分、向量化。往上是模型层涉及模型的选择、微调、推理优化。再往上是服务层也就是怎么把模型能力包装成API怎么处理并发、怎么做缓存、怎么监控。最上面是应用层也就是具体的业务逻辑比如问答、摘要、生成。为什么这么分因为每一层的技术选型和优化手段完全不同而且故障排查的时候你必须能快速定位问题出在哪一层。我见过一个典型的线上事故用户反馈问答机器人“变笨了”回答质量断崖式下降。团队第一反应是模型出了问题折腾了半天模型版本回滚结果发现是数据层的一个ETL任务挂了导致检索库里的文档索引没更新。你看如果你脑子里没有这个分层模型排查方向就会跑偏。提示在动手写任何代码之前先画一张你自己系统的分层架构图。不用很漂亮能标清楚数据从哪来、经过哪些处理、最终怎么到达用户就行。这张图会在你日后排查问题时救你一命。2.2 技术选型的“最小依赖原则”“从零开始”还有一个隐含的好处你会被迫做减法。现成的框架往往给你一堆功能但你真正需要的可能只有20%。剩下的80%不仅增加了系统的复杂度还引入了你无法控制的依赖风险。我在做技术选型的时候有一个很朴素的原则能用标准库解决的不用第三方库能用轻量库解决的不用重型框架。举个例子做文本向量化很多人上来就装 sentence-transformers这个库本身没问题但它会连带装一堆东西包括特定版本的 PyTorch、Transformers 等等。如果你的场景只是把几百条文本转成向量做个简单的相似度匹配用 scikit-learn 的 TfidfVectorizer 加上余弦相似度可能几十行代码就搞定了而且部署包小、启动快、没有GPU也能跑。当然我不是说不能用重型框架。当你的数据量到了百万级、需要GPU加速、需要复杂的检索策略时该用还得用。但关键是你要知道你为什么用以及不用会怎样。这个判断力恰恰是“从零”训练出来的。你如果一上来就用最高级的框架你永远不知道下限在哪里也就无法在资源受限或者需要极致优化的时候做出正确的取舍。2.3 一个可落地的项目骨架基于上面的思路我建议“ai-engineering-from-scratch”这个项目可以按照一个最小可用的RAG检索增强生成系统来设计。为什么选RAG因为它几乎涵盖了AI工程的所有核心环节数据处理、向量化、检索、模型调用、服务封装。而且它的业务价值很直观就是让模型能够回答基于特定知识库的问题。整个系统的数据流是这样的原始文档经过解析和清洗切成合适大小的片段每个片段通过嵌入模型转成向量存入向量数据库。用户提问时问题也转成向量在数据库里找到最相似的几个片段把这些片段和问题一起拼成提示词发给大语言模型模型生成答案返回给用户。这个流程听起来简单但每一个环节都有大量的细节可以深挖。比如文档怎么切切多大重叠多少嵌入模型选哪个向量数据库用哪个检索的时候取Top几相似度阈值怎么定提示词怎么设计这些问题的答案直接决定了最终系统的效果。而“从零开始”的意义就是让你亲手去调这些参数亲眼看到它们对结果的影响。3. 核心细节解析数据、模型、服务三层的实操要点3.1 数据层切分策略决定了系统的上限数据层是整个系统的地基。我敢说一个RAG系统效果不好80%的原因出在数据层而不是模型层。很多人花大量时间调模型参数却不愿意花时间研究文档怎么切分这是本末倒置。文档切分Chunking的核心矛盾在于切得太碎单个片段信息不完整检索出来也没用切得太大噪声太多而且会占用宝贵的上下文窗口。我试过很多种策略最后发现一个比较稳妥的起点是按语义边界切分目标长度300到500个token相邻片段之间保留10%到20%的重叠。为什么是300到500这是经验值。太短了比如100个token一个完整的论述可能被切成好几段检索的时候只能命中其中一段模型拿到的信息是残缺的。太长了比如1000个token一个片段里可能包含多个主题向量表示会被“平均”掉导致检索精度下降。重叠是为了防止关键信息刚好落在切分边界上被切断。具体操作上如果是Markdown或者HTML文档优先按标题层级切分因为标题天然就是语义边界。如果是PDF先用解析工具提取文本然后按段落切分段落太长再按句子切分。这里有个坑PDF解析出来的文本经常带有页眉页脚、乱码、断行问题一定要做清洗。我一般会用正则表达式去掉连续的空格、换行符把连字符连接的断词合并回去。注意切分参数没有绝对的最优解必须根据你的文档类型和查询特点来调。我的建议是先准备20到30个典型问题然后手动标注每个问题的答案应该来自哪些片段。用这个标注集来评估不同切分策略的检索命中率用数据说话别凭感觉。3.2 向量化嵌入模型的选择与本地化部署嵌入模型Embedding Model负责把文本转成向量。这个环节的选择直接影响到检索的准确性和系统的运行成本。市面上的嵌入模型大致分两类闭源API和开源本地模型。闭源API的好处是开箱即用效果通常不错但缺点也很明显按调用量收费数据要传到第三方而且网络延迟不可控。开源本地模型的好处是数据不出本地、没有调用成本、延迟稳定缺点是需要自己部署效果参差不齐。我的建议是在项目初期用开源模型快速验证等业务跑通了再根据成本和效果决定是否切换到API。开源模型里BGE系列和GTE系列都是不错的选择模型大小从几十MB到几个GB不等。如果你的场景是中文为主优先选在中文语料上训练过的模型。部署方面如果只是做原型验证直接用 sentence-transformers 加载模型写个简单的推理脚本就行。如果要上生产建议用 ONNX Runtime 或者专门的推理服务框架来加速。我实测下来同样的模型用ONNX Runtime做推理CPU上的速度能比原生PyTorch快2到3倍而且内存占用更小。这里有个细节嵌入模型输出的向量通常需要做归一化Normalization。归一化之后向量之间的余弦相似度就等价于点积计算更快。很多向量数据库默认要求输入向量是归一化的如果你忘了这一步检索结果可能会很奇怪。3.3 检索层向量数据库的选型与检索策略向量数据库是RAG系统的核心组件。选型的时候我主要看几个维度部署复杂度、查询性能、过滤能力、生态成熟度。如果是单机部署、数据量在百万级以内FAISS 或者 Chroma 就够用了。FAISS 是 Facebook 开源的库性能很好但它是库不是服务需要你自己封装成API。Chroma 更偏向应用层自带持久化和简单的查询接口上手快但大规模场景下性能一般。如果数据量到了千万级或者需要分布式部署可以考虑 Milvus 或者 Qdrant。检索策略上最基础的是向量相似度检索也就是把问题向量和所有文档向量做比较取最相似的Top K个。但纯向量检索有个问题它擅长捕捉语义相似但对关键词匹配不敏感。比如用户问“2024年第三季度的营收是多少”向量检索可能会返回一堆关于“营收”的文档但未必能精确匹配到“2024年第三季度”这个时间限定。所以混合检索Hybrid Search是更稳妥的方案。它同时做向量检索和关键词检索比如BM25然后把两边的结果融合排序。融合的算法可以用 Reciprocal Rank FusionRRF简单来说就是把两个排序列表里的文档按排名给分排名越靠前分越高最后按总分重新排序。这个方法不需要调什么参数效果却很稳定。还有一个容易被忽视的点元数据过滤。如果你的文档有明确的分类、时间、来源等元数据检索的时候一定要用上。比如用户问的是技术文档相关的问题你就应该把检索范围限制在技术文档类别里而不是全库检索。这能大幅提升检索精度也能减少无关片段对模型的干扰。3.4 模型层提示词设计与上下文管理到了模型层核心工作就两件事怎么把检索到的内容组织成提示词以及怎么管理上下文窗口。提示词设计上我见过太多人把提示词写成一坨所有指令、上下文、问题混在一起。这样做的后果是模型经常“迷失”在长文本里要么忽略了指令要么把上下文里的内容当成指令来执行。我的做法是用清晰的分隔符把不同部分隔开比如用三个井号或者三个等号。结构大概是系统指令区、上下文区、用户问题区。每个区域之间用明显的标记分隔。系统指令要简洁明确告诉模型它的角色是什么、要做什么、不要做什么。比如“你是一个基于给定文档回答问题的助手。如果文档中没有相关信息请直接说不知道不要编造。” 这句话看起来简单但能有效减少模型的幻觉。上下文管理是另一个关键点。大语言模型的上下文窗口是有限的虽然现在动辄128K甚至更长但塞得越多模型注意力越分散而且成本越高。我的经验是检索回来的片段不要全部塞进去而是根据相关度排序取前3到5个最相关的。如果片段之间有重叠还要做去重。另外可以在每个片段前面加上来源标记比如“来源1xxx文档”这样模型在回答时如果能引用来源可信度会更高。提示提示词里的指令最好用肯定句少用否定句。比如“请基于文档回答”比“不要编造答案”更有效。因为模型对否定指令的处理能力相对较弱你告诉它“不要做什么”它反而可能更容易想到那个东西。3.5 服务层API封装与性能优化服务层是把整个系统串起来对外提供服务的地方。最直接的做法是用 FastAPI 写一个接口接收用户问题走完检索和生成流程返回答案。但要做到“能扛”还需要考虑很多细节。并发处理是第一个要解决的问题。大模型推理是计算密集型的如果每个请求都同步等待系统的吞吐量会很低。我的做法是用异步接口把模型调用放到线程池或者单独的推理服务里主线程只负责编排流程。FastAPI 原生支持 async/await配合 httpx 做异步HTTP调用能显著提升并发能力。缓存是第二个关键点。很多用户的问题其实是重复的或者高度相似的。如果每个问题都走一遍完整的检索和生成流程既浪费算力又增加延迟。我一般会在两个层面做缓存一是问题级别的缓存把用户问题和最终答案的映射存起来下次同样的问题直接返回二是检索结果的缓存把问题向量和检索到的片段存起来如果问题相似度超过阈值直接复用检索结果只重新生成答案。监控和日志是第三个要点。AI系统的行为不像传统软件那么确定同样的输入可能得到不同的输出。所以你必须记录每一次请求的完整链路用户问了什么、检索到了哪些片段、每个片段的相似度是多少、最终生成的答案是什么、耗时多少。这些数据不仅能帮你排查问题还能用来分析系统的薄弱环节比如是不是某些类型的问题检索效果特别差。4. 实操过程从零搭建一个最小可用系统4.1 环境准备与依赖安装我假设你用的是 Linux 或者 macOSPython 版本 3.10 以上。先创建一个干净的虚拟环境这是好习惯能避免依赖冲突。python -m venv venv source venv/bin/activate然后安装核心依赖。我刻意保持依赖列表精简只装真正需要的东西。pip install fastapi uvicorn pip install sentence-transformers pip install chromadb pip install httpx pip install numpy这里解释一下每个包的作用。fastapi 和 uvicorn 用来做Web服务。sentence-transformers 用来加载嵌入模型。chromadb 是向量数据库轻量、自带持久化适合快速起步。httpx 用来异步调用大模型API。numpy 做向量运算。如果你要用本地大模型还需要装 transformers 和 torch但那个安装包很大而且对硬件有要求。为了快速验证我建议先用一个在线的模型API把流程跑通再说。4.2 文档处理与向量入库假设你有一批 Markdown 格式的技术文档放在docs/目录下。第一步是把它们读进来切分然后转成向量存入 Chroma。import os import re from sentence_transformers import SentenceTransformer import chromadb # 加载嵌入模型这里用一个小型的中文模型做演示 model SentenceTransformer(BAAI/bge-small-zh-v1.5) # 初始化Chroma客户端数据持久化到本地目录 client chromadb.PersistentClient(path./chroma_db) collection client.get_or_create_collection(namedocs) def split_text(text, chunk_size400, overlap50): 按字符数切分文本保留重叠部分 chunks [] start 0 while start len(text): end start chunk_size chunk text[start:end] chunks.append(chunk) start end - overlap return chunks def process_documents(doc_dir): doc_id 0 for filename in os.listdir(doc_dir): if not filename.endswith(.md): continue filepath os.path.join(doc_dir, filename) with open(filepath, r, encodingutf-8) as f: content f.read() # 简单清洗去掉多余空行 content re.sub(r\n{3,}, \n\n, content) chunks split_text(content) for chunk in chunks: # 生成向量 embedding model.encode(chunk).tolist() # 存入Chroma collection.add( ids[fdoc_{doc_id}], embeddings[embedding], documents[chunk], metadatas[{source: filename}] ) doc_id 1 print(f共处理 {doc_id} 个片段) process_documents(./docs)这段代码有几个细节值得说。第一切分函数是按字符数切的实际生产中应该按token数切但为了演示简单用字符数也能跑。第二每个片段都存了来源文件名作为元数据方便后续追溯。第三Chroma 的add方法会自动建立索引不需要额外操作。注意bge-small-zh-v1.5这个模型输出的向量维度是512。如果你换用其他模型维度可能不同但Chroma会自动适配不需要手动指定。4.3 检索与生成流程的实现数据入库之后就可以实现查询流程了。用户输入一个问题系统先检索相关片段然后拼成提示词调用大模型生成答案。import httpx async def retrieve(query, top_k3): 检索最相关的文档片段 query_embedding model.encode(query).tolist() results collection.query( query_embeddings[query_embedding], n_resultstop_k ) # 返回片段文本和来源 docs results[documents][0] sources [m[source] for m in results[metadatas][0]] return list(zip(docs, sources)) async def generate_answer(query, context_docs): 调用大模型生成答案 context_text \n\n.join([f来源{src}\n内容{doc} for doc, src in context_docs]) prompt f你是一个技术文档助手。请基于以下文档内容回答用户问题。 如果文档中没有相关信息请直接说“根据现有文档无法回答该问题”。 文档内容 {context_text} 用户问题{query} 请给出简洁准确的回答 # 这里用一个假设的API端点做演示实际使用时替换成你自己的模型服务 async with httpx.AsyncClient(timeout30.0) as client: response await client.post( https://your-model-api.com/v1/chat/completions, json{ model: your-model-name, messages: [{role: user, content: prompt}], temperature: 0.1 }, headers{Authorization: Bearer YOUR_API_KEY} ) result response.json() return result[choices][0][message][content]这段代码里temperature设成了0.1这是为了让模型的输出更稳定、更确定。在问答场景下我们不需要模型发挥创造力只需要它忠实地基于文档回答。检索的top_k设成3这是一个平衡点既能提供足够的上下文又不会让提示词太长。4.4 用FastAPI把流程串起来最后用 FastAPI 暴露一个接口把检索和生成串起来。from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class QueryRequest(BaseModel): question: str class QueryResponse(BaseModel): answer: str sources: list[str] app.post(/ask, response_modelQueryResponse) async def ask(request: QueryRequest): # 检索 context_docs await retrieve(request.question) # 生成 answer await generate_answer(request.question, context_docs) # 提取来源 sources list(set([src for _, src in context_docs])) return QueryResponse(answeranswer, sourcessources)启动服务uvicorn main:app --host 0.0.0.0 --port 8000然后就可以用 curl 或者 Postman 测试了curl -X POST http://localhost:8000/ask \ -H Content-Type: application/json \ -d {question: 如何配置系统的超时参数}整个流程跑通之后你会得到一个能回答文档相关问题的服务。虽然简陋但每一个环节都是透明的你可以清楚地知道数据是怎么流动的问题可能出在哪里。5. 常见问题与排查技巧实录5.1 检索结果不相关怎么办这是最常见的问题。用户问了一个问题检索出来的片段却完全不沾边。排查思路要按顺序来。第一步检查嵌入模型是否适合你的语言和领域。如果你用的是英文模型处理中文文档效果肯定差。如果你处理的是法律、医疗等专业领域文档通用模型可能也不够好。这时候可以考虑用领域数据微调嵌入模型或者换一个在相关领域表现更好的模型。第二步检查切分粒度是否合理。如果片段太短可能丢失关键信息如果太长向量表示会被稀释。我一般会打印出几个典型问题的检索结果人工看看片段内容是否完整、是否包含答案。第三步考虑引入混合检索。纯向量检索对关键词不敏感加入BM25之后精确匹配的能力会提升。Chroma 本身不直接支持BM25但你可以用 rank_bm25 这个库单独做关键词检索然后把结果融合。第四步检查是否需要重排序Rerank。检索回来的Top K个片段可以用一个重排序模型比如 BGE-Reranker做精排把最相关的排到最前面。这一步能显著提升最终答案的质量代价是增加一点延迟。5.2 模型回答“我不知道”或者胡编乱造模型说“不知道”通常是因为检索到的片段里确实没有答案或者提示词没有把上下文有效地传递给模型。先检查检索结果如果检索结果里有答案但模型还是说不知道那就是提示词的问题。试着把上下文放在更靠前的位置或者用更明确的指令比如“请仔细阅读以下文档然后回答问题”。模型胡编乱造也就是幻觉通常是因为提示词约束不够强或者temperature设得太高。把temperature降到0.1以下在提示词里明确要求“只基于文档回答”并且给出“如果文档中没有相关信息请说不知道”的指令。如果还是不行可以考虑在生成之后加一个校验步骤用另一个模型或者规则来判断答案是否忠实于原文。5.3 响应速度太慢怎么优化延迟主要来自三个环节嵌入模型推理、向量检索、大模型生成。嵌入模型推理通常很快几十毫秒级别除非你用的是很大的模型。向量检索在数据量不大的时候也很快。大头往往在大模型生成上。优化生成速度有几个方向。一是换用更小的模型或者用推理速度更快的模型服务。二是减少生成的token数量在提示词里要求模型“简洁回答”。三是做流式输出让用户先看到部分结果体验上会感觉快很多。FastAPI 支持 StreamingResponse配合模型的流式接口可以实现打字机效果。另外缓存也能大幅降低平均延迟。对于重复的问题直接返回缓存结果延迟可以降到毫秒级。5.4 常见问题速查表问题现象可能原因排查方向解决思路检索结果不相关嵌入模型不适配检查模型语言和领域换模型或微调检索结果不相关切分粒度不合理查看片段长度和完整性调整chunk_size和overlap模型说不知道上下文未有效传递检查提示词结构调整上下文位置和指令模型胡编乱造提示词约束不足检查temperature和指令降低temperature加强约束响应速度慢生成环节耗时检查模型调用耗时换小模型、流式输出、缓存服务崩溃内存或并发问题查看日志和资源占用限制并发、增加资源、异步化提示排查问题的时候一定要把中间结果打印出来。检索到了哪些片段、相似度是多少、提示词长什么样、模型返回了什么这些信息是定位问题的关键。不要凭猜测去改代码用数据说话。6. 我踩过的坑与实操心得6.1 别在数据清洗上偷懒我早期做的一个项目文档是从各种来源收集来的有PDF、有网页、有Word。我当时图省事直接用了一个开源的PDF解析库把文本提取出来就入库了。结果检索效果一直不好查了半天才发现PDF解析出来的文本里混入了大量的页眉页脚、页码、甚至水印文字。这些噪声严重干扰了向量表示。后来我花了两天时间专门写了一套清洗规则去掉重复出现的页眉页脚、合并断行、过滤掉非正文的短行。清洗之后检索准确率提升了将近30%。这个教训让我明白数据清洗的投入产出比远高于调模型参数。6.2 提示词要像写代码一样认真对待我见过很多团队把提示词随便写在代码里改的时候直接改字符串没有版本管理没有测试。这是很危险的。提示词是AI系统逻辑的一部分它应该像代码一样被管理。我的做法是把提示词单独放在一个文件里用模板引擎比如 Jinja2来渲染变量。每次修改提示词都要跑一遍回归测试确保没有破坏已有的功能。我还建了一个“提示词测试集”包含各种边界情况比如问题超出文档范围、问题包含多个子问题、问题有歧义等等。每次改提示词先跑测试集通过了再上线。6.3 监控要覆盖全链路AI系统的故障往往不是“崩了”这种明显的错误而是“效果变差了”这种隐性问题。如果没有全链路的监控你很难发现问题的根源。我在生产环境里会记录这些指标检索阶段的平均相似度分布、生成阶段的平均响应长度、用户反馈的点赞点踩比例、以及端到端的延迟分位数。这些指标能帮我快速判断系统是否健康。比如如果检索相似度突然下降可能是数据层出了问题如果生成长度突然变短可能是模型服务出了问题。6.4 从小处着手快速迭代“从零构建”听起来很宏大但实际操作的时候一定要从小处着手。不要一上来就想做一个支持多模态、多语言、高并发的全能系统。先做一个能回答单一领域问题的最小系统跑通全流程然后根据实际反馈逐步优化。我自己的习惯是第一个版本只求“能跑”代码可以丑架构可以简单但流程必须完整。第二个版本再考虑性能优化和代码重构。第三个版本才考虑扩展性和高可用。这个节奏能让你快速拿到反馈避免在错误的方向上投入太多。6.5 关于成本控制的几点经验如果你用的是按量付费的模型API成本控制是个绕不开的话题。我的经验是大部分成本花在了不必要的长上下文上。很多人为了“保险”把检索到的所有片段都塞进提示词导致输入token数暴涨。实际上经过重排序之后取前2到3个最相关的片段就足够了。另外缓存能省下大量重复调用的费用。我做过统计在一个内部知识库问答场景里大约30%的问题是重复的或者高度相似的。加上缓存之后模型调用量直接降了三成。还有一个技巧是对于简单的意图识别或者分类任务用小的本地模型就够了不需要调用大模型。把大模型用在真正需要它的地方比如复杂的推理和生成。7. 后续可以怎么扩展这套最小系统跑通之后你可以根据自己的需求往几个方向扩展。方向一多路召回。除了向量检索和关键词检索还可以加入基于知识图谱的检索、基于规则的检索等等。多路召回的结果融合能进一步提升检索的覆盖率和准确率。方向二查询改写。用户的提问往往很口语化或者包含指代和省略。可以在检索之前先用模型对查询进行改写补全信息生成多个查询变体然后分别检索再合并结果。这个技巧对提升召回率很有效。方向三答案校验。在生成答案之后加一个校验环节用另一个模型或者规则来判断答案是否忠实于检索到的文档。如果发现幻觉可以重新生成或者直接返回“无法回答”。方向四用户反馈闭环。在界面上加点赞点踩按钮收集用户的反馈数据。这些数据可以用来评估系统效果也可以用来微调模型或者优化检索策略。长期来看这是持续提升系统质量的最有效手段。方向五多模态扩展。如果你的文档里有图片、表格可以考虑引入多模态模型把图片内容也纳入检索范围。这个方向技术难度较高但业务价值也很大。我个人觉得对于大多数团队来说先把文本场景做深做透比急着上多模态要务实得多。毕竟大部分企业知识库的核心内容还是文本。这个项目最吸引我的地方是它强迫你回到地面重新审视每一个环节。当你亲手处理过脏数据、亲手调过检索参数、亲手写过提示词、亲手排查过线上问题之后你对AI工程的理解会完全不一样。那些框架和工具对你来说不再是黑盒而是你可以驾驭的杠杆。这种踏实感是调包永远给不了的。
返回列表