ARTICLE DETAIL

资讯详情

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

从零构建AI工程:知识库问答系统的完整实践路径

从零构建AI工程:知识库问答系统的完整实践路径 1. 从零开始先搞清楚 ai-engineering 到底在解决什么问题1.1 为什么说“from scratch”是个硬核选择最近有朋友问我想学 AI 方向看到网上铺天盖地的教程、课程、开源项目反而不知道该从哪里下手。我给他推荐了一个思路就是项目标题写的这个ai-engineering-from-scratch。这个标题本身挺诚实——它不教你三天上手写个聊天机器人而是把“AI 工程”这四个字拆开从底层一步步搭起来。听起来慢但恰恰是这种“慢”能避免后面大量的返工。先说实话市面上介绍 AI 的内容并不少但大部分是碎片化的要么是某个模型的调用示例要么是某个框架的 README 翻译要么是铺了一堆概念名词却连一个完整的工程闭环都没有。真正缺的是一条把所有环节串起来的路径怎么从一行代码都不写到能独立搭建一个可以评估、可以上线、可以迭代的 AI 应用。这条路就是 ai-engineering 的核心内容。我把“from scratch”理解为两层意思。第一层是知识体系的从零开始不预设你已经懂深度学习、懂分布式、懂 Kubernetes而是从一个合格的软件工程师视角把 AI 应用落地所需要的核心环节逐个打通。第二层是能力的从零开始不是把别人的 demo 跑起来就算会了而是亲手把数据、模型、检索、评估、部署这些环节都写过一遍踩过坑、对比过方案才知道系统为什么这么设计。1.2 能力栈拆解不是只会调 API 就叫 AI 工程很多初学者对 ai-engineering 有误解以为就是“调大模型接口”。真做下去才会发现这只是整个链路里很小的一段。一个真正能落地的 AI 工程至少需要下面这几块能力数据工程数据采集、清洗、切分、格式转换这些是 RAG 和微调的基础。数据质量直接影响最终效果很多项目死在数据上。模型工程了解主流模型的 API 协议、参数含义、上下文长度、定价差异有能力做选型评估而不是永远只会用同一个模型。检索与编排向量数据库、嵌入模型、检索策略、重排序以及多步任务的逻辑编排。评测体系离线评估集、线上指标、回归测试。没有评测的 AI 项目优化起来就是无头苍蝇。工程化底座并发控制、缓存、限流、可观测性、错误处理、成本控制。这几块听起来多但它们在真实项目里是互相耦合的。比如你做了一个知识库问答系统数据切分方式会影响检索效果检索效果又直接影响生成质量生成质量需要评测集来量化评测结果反过来又指导你调整切分策略。这个循环转起来才叫 AI 工程只停留在“调用-返回”的单向链路那不叫工程叫脚本。1.3 不同基础的人怎么定位自己的起点在开始搭建学习路径之前先做一次自我定位。我的经验是ai-engineering 的学习人群大致分三类准备方式完全不同第一类是完全零编程基础的建议先补 Python 基础、HTTP 请求、命令行操作、Git 基本用法。这不是浪费时间的门槛因为 AI 工程里的很多问题本质上是工程问题只是披着 AI 的外衣。第二类是传统后端或全栈工程师有代码能力和系统设计意识但对 Prompt、向量检索、评测这些偏 AI 的概念比较陌生。这类人学起来最快重点是补齐 AI 领域特有的知识同时把原有的工程经验迁移过来上手效果往往很扎实。第三类是算法工程师转向应用方向模型理解深但对工程化场景不熟比如如何处理高并发、如何做降级、如何设计可观测体系。这类人要主动“下沉”到具体业务场景因为 AI 工程的目标是解决问题不是发论文。不管你现在属于哪一类主线任务都是一样的亲手完成一个从数据到上线的小项目把每个环节的错误都犯一遍。接下来我用自己当时搭的一个知识库问答项目作为例子把这套路径每一步都拆开讲。2. 真正动手前先把地基夯实2.1 环境与项目结构设计我记得自己刚开始时犯过一个典型错误项目刚开始先在代码里堆了一堆依赖结果第二天打开电脑连自己写的代码都跑不起来了。后来我把环境管理这件事看成项目的第一优先级宁可先花半天时间把底子铺好也不要边写边装。推荐用 venv 或 poetry 管理 Python 环境。venv 是 Python 自带的轻量直接poetry 更适合长期维护能锁定顶层依赖避免“我这机器上能跑你那就不行”的经典问题。选哪个不关键关键是锁定版本。大模型相关的库更新极快这个月 API 还能用下个月就 deprecated 的情况一点不新鲜。用一个 requirements.txt 或者 poetry.lock 把版本固定下来是后续所有工作不出幺蛾子的前提。项目结构上我分享一个现在用着很顺手的模板. ├── app/ # 服务入口、路由 │ ├── main.py │ └── api.py ├── core/ # 核心业务逻辑 │ ├── llm.py # 模型调用封装 │ ├── retriever.py # 检索逻辑 │ └── pipeline.py # 编排逻辑 ├── data/ # 数据与预处理脚本 ├── eval/ # 评测集与评测脚本 ├── tests/ # 单元测试与回归测试 └── configs/ # 配置项如模型选择、参数权重这种分层的核心思想是隔离变化。模型换一家、检索换个库、数据换一批都只影响对应的模块不会牵一发动全身。比如你把 LLM 调用单独封装成一个模块对外只暴露 generate(prompt) 这个方法后面从 GPT 切到国产模型或者从云端切到本地部署业务逻辑一行都不用改。2.2 最小可行链路一次完整的大模型调用能吃透多少东西万事开头难。我的建议是先别碰 Agent、别碰 RAG先把最朴素的事做扎实通过 API 完成一次完整的对话把请求、响应、异常处理、超时重试全走一遍。就这么一个看似简单的动作包含的信息量比我预想的大得多。我自己写过一个几十行的脚本核心就是在超时、重试和错误处理上做文章。这里我把代码片段简化一下放上来import time from openai import OpenAI client OpenAI(timeout30, max_retries0) # 关闭 SDK 默认重试自己控制 def generate_with_retry(prompt: str, max_retries: int 3) - str: for attempt in range(max_retries): try: resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.3, ) return resp.choices[0].message.content except Exception as e: wait_time 2 ** attempt # 指数退避1s, 2s, 4s print(fattempt {attempt 1} failed: {e}, retry in {wait_time}s) time.sleep(wait_time) raise RuntimeError(all retries failed)这段代码不复杂但每行都有讲究。关闭 SDK 默认重试是因为默认重试策略太“死板”一旦遇到限流或网络抖动它不会告诉你真实情况你连问题在哪都不知道。把重试逻辑自己控制用指数退避避免在服务端故障时继续制造压力。此外temperature 这个参数值得多说一句它控制随机性业务场景做分类、抽取、检索生成这类任务我习惯设 0.2 到 0.4做创意写作、头脑风暴再调高到 0.7 以上。很多人不提这个参数默认值往往偏高结果产出的东西飘。2.3 模型选型不是越强越好等你能熟练控制单次调用了下一个问题就是选型。很多人听到“模型选型”就以为越大越贵越好但在工程里选型的核心是匹配任务复杂度和成本约束。我自己总结了一个简单的选择逻辑如果任务只需要抽取关键词、做格式转换、生成短文案一个小参数模型就够如果任务需要复杂推理、多步工具调用、长文本理解才需要旗舰模型。同一个系统里完全可以混合使用多个模型比如用小型模型做意图识别和路由把复杂的生成任务只交给大模型处理这样成本能下降一大截。我整理了一个对比表格方便你建立初步感知任务类型推荐选型理由分类、抽取、格式化小型模型如 4o-mini 级别成本低、延迟低、效果足够多轮对话、长文总结中大型模型需要长上下文和语义理解复杂推理、工具调用旗舰模型指令遵循和逻辑推理更强私有化部署场景开源模型如 Qwen 系列数据不出内网可微调定制这里还要提醒一件事不要只看模型在排行榜上的分数一定要拿自己的真实数据去测。公开榜单测的是通用能力你的业务场景有自己独特的语言习惯、格式要求、知识边界只有用真实输入才能看出差距。3. 一个完整的从零项目知识库问答助手3.1 数据准备与切分我建议第一个从零项目做一个知识库问答助手因为这个场景覆盖了 AI 工程的大部分核心环节数据、检索、生成、评估而且效果容易感知后期扩展空间也大。我当时选了公司内部的几十篇产品文档作为数据源目的很明确让系统回答产品相关问题而不是复读网上已有的公开内容。数据准备这一步看着不起眼其实是决定项目成败的关键。原始文档通常是 Markdown、PDF、Word 混合体第一步先全部转成统一纯文本去掉图片、表格、代码块等等干扰信息。第二步是切分切分这个事学问很多切大了 embedding 会稀释语义细节记不住切小了上下文信息不完整生成时缺背景。我试过用固定长度切片也试过按标题结构切最后发现比较稳的方案是“结构优先 长度兜底”。比如文档有明确的一级、二级标题就按标题把内容切块每块控制在 600 到 1200 字左右没有结构的地方用滑动窗口切让相邻窗口有 20% 到 30% 的重叠避免语义在边界处被硬生生截断。具体到实现我用过 LangChain 的 RecursiveCharacterTextSplitter它是一级一级尝试分隔符尽量保留语义块比无脑按字符数切强太多了。3.2 向量化与检索数据切好之后要做两件事把每块文本转成向量存进向量数据库然后在用户提问时把问题也转成向量从库里找出最相似的内容作为参考。这一套机制就是 RAG检索增强生成的核心逻辑。我画个简单流程帮助理解用户输入 → 查询向量化 → 相似度检索 → 拼接上下文 → 模型生成答案。向量化用什么模型直接影响检索质量。现在的主流选择是 OpenAI 的 text-embedding-3-small 或 3-large国内也有大量的开源 embedding 模型可以用。需要注意两点第一查询和文档最好用同一个 embedding 模型否则向量空间不一致检索效果无从谈起第二embedding 维度不是越高越好维度高意味着存储和计算开销大而很多场景下 500 到 1500 维已经够用。检索这里有个新手特别容易忽略的细节只做向量相似度检索效果往往不够。比如用户问“怎么退款”文档里写的是“退费流程”字面完全不重合但语义很近向量检索能解决一部分。更稳妥的做法是在向量检索之后加一个重排序rerank环节先用向量召回 top 20再用一个更强的 rerank 模型把最相关的 top 5 挑出来。这一步对最终生成质量的提升非常明显尤其当文档库很大的时候。我把代码片段放上来方便你理解这个流程results vector_store.similarity_search(query, k20) # 粗召回 top 20 reranked reranker.rerank(query, results) # 重排序 top_docs reranked[:5] # 取 top 5 进上下文重排序为什么有用向量检索看重语义相似但“语义相似”不等于“能回答用户问题”。举个例子用户问“产品支持哪些平台”检索回来的片段可能包含产品历史和多平台列表最相关的内容反而在第三、第四位。大模型上下文有限你不可能把所有文档都塞进去rerank 的作用就是在有限的窗口内把最有用的信息顶上去。3.3 生成策略与提示词设计检索做完接下来就是把检索到的内容拼成一个 Prompt交给模型生成答案。这一步的差异直接体现在用户体验上。刚开始我犯过一个很低级的错把检索到的原文一股脑全塞给模型结果模型把文档里一段介绍性的废话原封不动复述出来完全没有提炼和总结。后来我重新设计了提示词核心思想是给模型一个明确的角色和任务、划定信息边界、规定回答格式。一个比较稳的模板类似这样你是产品文档的答疑助手。请基于以下参考资料回答用户问题。 参考资料 {context} 要求 1. 如果没有资料能回答问题直接说“根据现有资料无法回答”不要编造。 2. 回答使用简洁的中文段落可以列出关键步骤。 3. 优先参考资料中的具体流程和数据不要泛泛而谈。 用户问题{question}里面那句“没有资料就直说”特别关键这是对抗幻觉的第一道防线。模型在生成时如果资料里没有答案又没被明确禁止编造它大概率会一本正经地胡扯。加上这句就算效果不好至少它知道“不知道”是一个合法回答。生成端还有一个参数需要反复调max_tokens 要设置合理。设太短回答会被截断得莫名其妙设太长浪费延迟和成本而且内容容易注水。我的建议是先按你预期答案的 1.5 倍去设测试一轮再收紧。3.4 评估闭环没有评测就没有优化这是整个项目里我最想强调的一步评估。很多人做一个 RAG 项目跑通 demo 就觉得完事了然后进入一个尴尬的循环感觉效果不对但说不清哪里不对改几版 Prompt 也只能靠感觉猜。我的做法是在动手优化之前先手写 30 到 50 条评测问题覆盖高频常见问题和几个刁钻的边界问题。每一条都给一个“标准答案”或者至少标准答案的评分要点。然后用这组固定的评测集去测系统每改一次 Prompt、换一次切分策略、调一次检索参数都用同一组问题跑一遍看答案质量的趋势变化。用代码实现一个简易的评估脚本不难。我给的示例是调用模型对每个问题生成答案把它和期望答案一起交给一个裁判模型打分。这个方法比较粗糙但足以量化你的改进方向eval_prompt f 请对以下答案进行评分0-10分 用户问题{question} 参考答案{reference} 待评答案{answer} 请从答案是否准确、是否完整、是否偏离问题三个维度打分。 只输出一个数字。 score int(gpt_eval(eval_prompt))这里有一个工程习惯非常重要评测集和评测脚本要作为固定资产随项目一起维护并纳入版本控制。一旦后续修改了任何代码测试集能立刻发现回归问题。我看到过太多项目改了一行检索逻辑拍脑袋觉得“应该更好”结果上线后用户反馈变差。评测闭环的存在就是为了把“应该”变成“证据”。4. 从“能跑”到“能上线”的工程化改造4.1 缓存与成本控制项目跑通之后要问自己一个问题如果现在有 100 个用户同时使用系统还能撑住吗如果不能那还不是真正的 AI 工程。成本和延迟是 AI 应用上线前的两只拦路虎而缓存是化解这两只拦路虎最直接的手段。我第一版上线时发现用户经常在同一时间段问相似的问题尤其活动期间的“怎么参加”“怎么领券”这类高频问题。如果每次都调用大模型既慢又贵。后来加了一层缓存把“问题 检索结果”作为缓存 key对应的生成回答存进 Redis命中缓存直接返回连模型调用都省了。这里要注意缓存不能直接把“问题”作为 key因为同一个问题在不同文档更新之后正确答案可能变了。所以更稳妥的是给缓存加失效时间比如高频活动期间设 5 到 10 分钟就够了。另外对非常相似的问法比如“怎么注册”和“注册流程是什么”字面上不同但语义一样可以考虑先做一层归一化处理再查缓存这样命中率更高。成本控制还有两个被低估的手段。第一个是 Prompt 瘦身把模板里不必要的例子和冗余描述去掉能直接减少输入 token。第二个是模型分级简单任务用便宜小模型复杂任务才升级到贵模型。这些看起来都是小改动但当你每天有几十万次调用时差异就是一个月多几千块还是少几千块的区别。4.2 可观测性与错误处理AI 应用的一大特点就是不确定性。同样的输入这次和上次的回答可能有微妙差异检索召回的内容也可能因为文档更新而变化。如果没有可观测性出了问题只能“瞪眼猜”。我在项目里接入了三种日志调用日志、质量日志和性能日志。调用日志记录每次请求的时间戳、模型、token 用量、耗时、返回状态质量日志记录用户有没有点“有帮助”或“无帮助”性能日志记录 p50、p95 延迟还有错误类型分布。这套数据积累一段时间后你能非常清晰地看到系统的瓶颈在哪。比如发现 p95 延迟特别高多半是有几步串行调用导致可以改成并行或走缓存。错误处理也分优先级。第一优先级是“可重试错误”比如网络超时、限流用指数退避重试第二优先级是“不可重试错误”比如参数校验失败直接返回错误码第三优先级是“降级方案”比如大模型服务挂了就返回一个基于规则匹配的最接近文档片段虽然效果差点但至少不抛异常给用户。工程和脚本的差异很多时候就是从这些细节上体现的。4.3 评测工程与回归保护前面提到的人工评测集只是一个开始。一个相对成熟的项目评测应该逐步升级为自动化流水线在每次修改代码后自动跑一遍评测集把关键指标的变化直接展示在监控面板上。这一步看着繁琐但能极大降低“改坏了自己不知道”的风险。我维护过一个 200 条左右的评测集里面除了常规问答还有几条专门“狙击”幻觉的比如“文档里没有提到的产品功能”期望答案是拒绝回答还有几条“狙击”检索召回的比如需要跨多个文档才能拼出答案的复杂问题。每次改动我先跑一遍这 200 条如果分数下降超过预设阈值就要么回退改动要么必须找到合理的理由。这个评测集本身也会过时半年左右我会重新审视一遍把过时的文档相关题目更新掉把新增功能的问题加进来。毕竟 AI 工程是快速演进的评测体系和代码一样需要维护。有一句话我印象很深“没有评测等于没有系统优化。”现在看确实如此。5. 常见问题与排查技巧实录5.1 高频翻车场景梳理先把常见问题做成一张速查表方便你对照排查症状可能原因排查方向回答完全跑偏检索没召回相关内容检查切分粒度、embedding 模型是否一致回答看起来合理但细节错误幻觉提示词加约束引入引用来源长问题只回答前半段max_tokens 过小调大生成长度上限检索结果与问题无关查询缺少关键词扩展用 query rewriting 做归一化或换更强 embedding响应很慢串行调用过多把独立调用并行化加缓存频繁报限流错误并发与速率策略不匹配限流降级设置合理重试退避这几种问题我都踩过。印象最深的一个是“回答跑偏”排查了整整一个下午最后发现是 embedding 模型的问题我给文档用的是 text-embedding-3-small但查询时用了 text-embedding-3-large两个模型的向量空间不一致检索结果自然是乱的。这个错误低级但极容易犯强烈建议你在代码里把 embedding 模型的名称集中配置用同一个变量引用避免类似事故。5.2 几个我很建议建立的工程习惯第一给所有外部服务调用加超时。我曾经遇到过某个第三方模型服务卡住整个请求链悬挂几分钟用户直接放弃。现在我的代码里所有外部调用必然有超时控制宁可快速失败也不能无限等待。第二尽早做端到端测试。很多人喜欢先写一堆抽象类、接口最后才把真实数据接进来结果一接通全是问题。我的习惯是第一条数据、第一个检索、第一次生成就串起来走通再拆开优化。端到端的链路能让你时刻清楚系统到底处于什么状态。第三Prompt 也是一种代码要纳入版本管理。我一直用 Git 管理 Prompt 模板文件改之前先记录当前版本改完跑评测集对比。这样如果新版本效果反而差了可以秒级回退不用凭记忆复原。第四把配置与代码分离。模型名、温度、切分大小、检索 top K 这些参数一律放到配置文件里不要硬编码在代码中。AI 项目的调参频率远超传统项目这个习惯能让你在调参时少改很多代码。6. 我在实操中最想分享的一句话ai-engineering-from-scratch 这条路径走到最后我发现真正留下的不是某个具体模型的知识也不是某个框架的用法而是一套“从数据到上线的完整视角”。网上随时都有新的模型、新的框架出现今天熟悉的东西可能半年后就被替代但你掌握的评估方法论、系统设计思路、问题排查意识会在每一次技术更换中持续复用。最后分享一个自己踩过坑之后养成的习惯项目里永远保留一个跑通的最小复现脚本无论代码怎么演进这个小脚本都能在一分钟内让整个系统运转起来。它既是新人的入门向导也是你自己排查问题的安全网。祝你在 from scratch 的路上少走弯路。
返回列表