ARTICLE DETAIL

资讯详情

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

从零开始AI工程实践:知识库问答与Agent编排完整指南

从零开始AI工程实践:知识库问答与Agent编排完整指南 前阵子有个朋友跑过来问我说他很想认真做几个AI应用但每次一打开编辑器脑子里全是空白。他说自己看了不少提示词教程也在网页上玩过各种聊天机器可一旦要面对真正的业务需求就完全不知道从哪里下手。这个问题我太熟悉了。AI工程化不是“会问问题”就行更不是“把脏活全丢给大模型”就算完事。它是一套从需求拆解、技术选型、提示词设计、Agent编排到评估、上线、兜底迭代的完整工程链路。这篇文章就是我基于“从零开始做AI工程”这个主题的一次完整复盘既讲思路也给你一套可以直接照做的落地方案适合刚入门的开发者、想转AI方向的产品经理也包括那些已经能写点代码但还没系统梳理过AI工程流程的团队。我会拿一个真实的“个人知识库问答机器人”作为贯穿全文的案例。它不是最炫的但足够小、足够完整能让你在几个小时内跑通“数据进、问题进、答案出”的闭环。更重要的是这个过程中你会碰到几乎所有AI工程实践里都会遇到的坑输出不稳定、上下文爆炸、检索不到、Agent死循环、成本失控。把这些坑提前踩一遍后面再做复杂系统心里就有底了。1. AI工程的整体设计与思路拆解1.1 AI工程和传统软件工程到底差在哪很多人第一次做AI应用潜意识里还是在用写普通后端服务的方式思考定义接口、做参数校验、处理异常、返回结果。这套思路没错但漏掉了最核心的变量大模型的输出是概率性的。同样的Prompt温度调到0输出也可能因为版本更新而变化昨天能稳定输出的格式今天可能突然多了一句废话导致JSON解析失败。我在刚开始做AI项目时也天真地以为“只要模型够强结果就够稳”。后来被现实教育了。传统开发像是在铺铁轨轨距固定火车跑得快不快完全取决于铺得好不好AI工程更像是在驯牧羊犬你能训练它、引导它但永远没法保证它每一步都严格执行命令。所以工程上要做的不只是“把模型接进来”而是设计一套约束、校验、降级和重试机制让概率性的输出在可控范围内完成业务目标。这一点决定了整个项目的架构风格。比如我们不能假设模型一定会返回合规JSON就要在调用后加一层解析校验不能假设模型一定有知识就要引入检索或者兜底回复不能假设Agent一定会按计划走完流程就要设置最大轮次和终止条件。所有传统软件里看起来“没必要”的防御性代码在AI工程里都变成了必需品。1.2 “From Scratch”到底是什么意思标题里的“from-scratch”我的理解不是让你从反向传播、Transformer源码开始造轮子——对绝大多数业务场景来说那是没有必要的。它的核心含义是不依赖别人已经封装好的“黑盒脚手架”从最底层的能力模块开始自己搭建一套可理解、可控制、可演进的AI系统。也就是说你要能回答这几个问题你的业务场景需要调用什么能力是大模型生成、知识检索、工具调用还是多步规划这些能力如何串成一个工作流每个环节失败了你怎么办你如何判断系统整体表现变好了还是变坏了如果你只会在LangChain里chain.invoke(...)却说不清内部发生了什么那遇到问题就会非常被动。我见过不少团队最开始用了一套很重的框架代码写得很“高级”结果一改需求就崩。反而是那些从“原生模型调用 几十行胶水代码”起步的小项目因为每一步都看得懂、改得动后来成长得很结实。所以这篇文章里我的建议是不要急着上重型框架先用原生SDK和最小代码把闭环跑通等真正理解了每个环节的痛点再决定要不要引入编排工具。1.3 为什么选择“个人知识库问答”作为起点选这个案例不是没有原因的。首先它的业务目标非常清晰用户提问题系统基于给定的文档内容回答并且要能追溯答案来源。这里面天然包含了AI工程最重要的几个模块文档加载与切分、向量检索、上下文组装、Prompt设计、输出格式约束、兜底逻辑。任何一个模块都是可以单独深挖的但组合在一起又不复杂适合从零开始。其次这类需求几乎存在于所有行业。企业内部的制度问答、技术团队的文档助手、客服知识库、个人笔记检索……只要你把“知识库问答”做明白了后面迁移到任何垂直场景都会很快。更关键的是它逼着你去处理“模型不知道答案”这个问题。如果你只用大模型本身聊天不涉及私有知识很多工程问题永远不会暴露出来知识过期、幻觉、无法溯源。一旦接了文档你就会开始认真想检索质量、chunk大小、相关性阈值、引用展示这些事而这些才是AI应用能否落地的分水岭。我对案例的设定是你手里有一批Markdown或TXT文档可能包含产品说明、团队笔记或技术手册你想让一个AI助手基于这些文档回答问题。它不联网、不靠模型知识随口编而是先去文档里找证据再根据证据生成回答。如果找不到证据它会明确说“资料里没有相关信息”。就这一个行为已经比很多直接调模型的机器人靠谱太多了。2. 核心细节解析与实操要点2.1 Prompt Engineering把提示词当接口来设计很多新手写Prompt是“口头禅式”的想到什么写什么模型答得不好就继续加话。我也这么干过但后来发现这样效率极低。成熟的Prompt Engineering不是聊天调参而是接口设计。你需要像定义函数签名一样定义输入输出格式、约束条件和边界情况。举个实际例子。最开始我让模型总结文档写的是“请总结下面的内容”结果有时输出一段话有时给个列表格式完全随机。后来我改成结构化模板系统提示词里固定行为规则用户消息里才放真正要处理的内容。我还会在系统提示词里加上输出格式的JSON Schema要求模型严格填充字段。这样一来模型输出的稳定性和可解析性都大幅提升下游代码不需要再做一堆正则去猜。实操中我建议把每个Prompt当作代码来管理放在单独的文件或配置里同时写上版本号。我会记录每次修改前后的效果对比。比如把“如果文档里没有答案不允许编造”改成“如果检索结果中不包含明确答案请回复资料库中暂无相关信息”这一步看似简单却能明显减少幻觉。测试的时候也不用每次都开网页聊天写个批量脚本把测试集跑一遍量化一下准确率变化比“感觉好多了”要靠谱得多。注意不要在Prompt里塞太多互相矛盾的规则。系统提示词只放稳定不变的行为约束具体任务参数放用户消息多次对话的历史要主动控制长度不要无限堆叠。规则越多模型越容易顾此失彼。2.2 AI Agent的搭建逻辑拆任务而不是拆代码很多人对Agent的第一印象是“它能自己干活”但真正上手后会发现难点不在“让模型干活”而在“让模型知道自己该用哪些工具、什么时候停”。我的理解是Agent模型 工具列表 循环控制。模型负责理解需求、判断下一步动作工具负责执行具体的原子操作循环控制决定它能不能及时结束。举一个特别简单的例子做一个“会议纪要与待办提取”的Agent。它有两个工具一个读取会议文字稿一个把待办事项追加到本地清单文件。模型先读取内容然后自动提取待办最后调用写入工具。这里的关键是工具描述要足够清晰模型才知道“这个工具是干什么的、参数长什么样、什么情况下该用”。我踩过的坑是工具描述写得含糊模型反复调用无效参数浪费了大量token。更实际的一点是别让模型拥有无约束的“代码执行”权限。如果你给Agent一个“运行任何Python代码”的工具又没做沙箱和超时限制那它可能为了完成一个简单任务写出危险操作。我在早期设计Agent时会把所有工具都做成白名单模式能查数据库就只给只读SQL工具能操作文件就只开放指定目录并且强制最长执行时间和返回结果大小。这套边界看起来保守但放在生产环境里能救你很多次。2.3 多AI协作与工作流别让一个Agent什么都干单Agent有个很烦人的问题上下文窗口是有限的任务一多前面的关键信息很容易被冲掉。我试过让一个Agent从读文档、写大纲、生成文章、校对排版从头干到尾结果写到后面它已经忘了自己的预设风格甚至开始编造前面没出现过的事实。这个问题在真实业务里非常常见。后来我改用多Agent协作其实核心思想就是“专人专事”。分为规划者、执行者、审查者三个角色。规划者拆解需求执行者负责生成或检索审查者做质量校验。每个Agent只处理自己擅长的那个环节上下文更干净Prompt也更容易优化。你可以把它理解成一条流水线而不是一个全能工人。我最近在做的一个内容工作流就是“选题Agent → 大纲Agent → 撰写Agent → 校对Agent”。选题Agent产出十个候选方向大纲Agent把它扩成带小标题的结构撰写Agent只根据大纲和素材输出初稿校对Agent检查事实错误、语气偏差和格式问题。每个环节的输出都会保留下来哪一步出问题直接看那一步的日志就能定位。这种模式还有一个额外好处每个环节都可以单独升级或替换模型比如大纲阶段用便宜的小模型终稿阶段才用最贵的旗舰模型成本控制起来非常灵活。3. 实操过程与核心环节实现3.1 环境准备只需要Python和一把API钥匙先说环境。我建议直接用Python 3.10以上的虚拟环境避免把系统Python搞乱。核心依赖其实很少一个openai SDK用来调用支持OpenAI协议的大模型接口一个向量库本地可以用chromadb或sqlite-vec再加一个文档解析库读Markdown和TXT其实标准库就够了。没装复杂框架对先不装。等你跑通了再考虑是不是需要LangChain或者别的编排工具。配置环境变量把API密钥放到.env文件里不要硬编码进代码。下面是最小化调用封装包含重试逻辑和超时控制我每次开新项目都会先存一个这样的底座import os import json from openai import OpenAI import time client OpenAI( api_keyos.environ.get(LLM_API_KEY), base_urlos.environ.get(LLM_BASE_URL), ) def llm_call(system_prompt: str, user_content: str, temperature: float 0.2, max_tokens: int 1024) - str: for attempt in range(3): try: resp client.chat.completions.create( modelos.environ.get(LLM_MODEL, gpt-4o-mini), messages[ {role: system, content: system_prompt}, {role: user, content: user_content}, ], temperaturetemperature, max_tokensmax_tokens, ) return resp.choices[0].message.content.strip() except Exception as e: print(fLLM调用失败: {e}重试 {attempt 1}/3) time.sleep(2 * (attempt 1)) raise RuntimeError(LLM调用连续失败)这段代码看着简单但解决了一个很实际的问题网络波动或限流时不会直接让整个程序崩溃而是自动重试。在AI工程里API调用失败不是异常而是常态提前做好重试和超时后面会省很多事。注意把base_url设置成可配置的能让你灵活切换不同厂商的模型接口只要它们兼容OpenAI协议。而且“模型名”也走环境变量这样从测试到上线换模型时不用改代码。3.2 核心闭环从文档到答案的完整实现现在进入正题我们来实现“问文档得答案”的闭环。第一步是切分文档。切分这件事听起来简单但做不好直接影响检索效果。我的经验是按标题、段落等结构切而不是无脑按固定字数硬切。Markdown文件可以按“##”划分章节再把每个章节下的小段落合并成500到800字左右的分块。分块太短会让检索失去上下文分块太长又会塞入太多无关信息干扰模型判断。切完之后转成向量。如果文档量不大比如几百个片段直接用一个轻量级向量库就能搞定。我用过chromadb它自带持久化本地起步很方便。核心代码如下from chromadb import Client from chromadb.config import Settings client Client(Settings(persist_directory./kb_vec)) collection client.get_or_create_collection(nameknowledge_base) # documents: list[str], ids: list[str] collection.add(documentsdocuments, idsids, metadatasmetadatas)第二步是搭建检索。用户提问后把问题做同样的向量化然后查相似度最高的若干个分块。这里我会额外做一层“相关性过滤”只保留相似度大于某个阈值的分块。阈值没有固定值要靠实测调一般0.3到0.5之间取决于所用embedding模型。如果全部低于阈值我会走“无答案”分支避免模型拿着低质量资料硬编答案。第三步是把检索到的文本组装成上下文交给模型。这里的关键是把问题和证据分开同时明确告诉模型只能使用给定资料回答不得依赖预训练知识。下面是一个可直接使用的用户端模板def build_rag_prompt(question: str, context_chunks: list[str]) - str: context_text \n\n---\n\n.join( [f[片段{i1}]\n{chunk} for i, chunk in enumerate(context_chunks)] ) return f请根据以下资料回答用户问题。 资料内容 {context_text} 用户问题{question} 要求 1. 只基于资料回答不要编造资料里没有的信息。 2. 如果资料中没有明确答案请回复资料库中暂无相关信息。 3. 回答结尾引用相关片段编号例如[引用片段1]调用时我会用系统提示词“你是一个严谨的知识库助手输出内容必须与检索证据一致”。温度设成0或者0.1因为这类任务不需要创造性。跑了基线测试之后再逐步调阈值和Prompt。整个闭环流程是查向量库→筛阈值→组装Prompt→生成→返回。每一行代码都能解释为什么这就是“from scratch”的价值。3.3 加两道保险格式校验与日志留痕初级版本能跑通之后一定要补上格式校验和日志。很多回答看起来正常但下游系统拿不到字段。比如我希望模型返回{answer: ..., sources: [片段1, 片段2]}。那就不能只靠肉眼判断要在代码里做JSON解析校验解析失败就重试一次重试后还失败就直接返回兜底文案。日志这件事我一开始完全没重视。后来有一次模型输出质量突然下降我翻看前面的对话和上下文发现是某次更新后文档切分逻辑变了导致检索到的片段全是噪音。如果没有日志这种问题会排查到崩溃。所以我的习惯是至少记录三层信息每轮用户输入、最终提交给模型的Prompt含上下文片段、模型原始输出。同一轮请求产生一个request_id方便全链路追踪。补充一个很实用的兜底策略如果检索得分特别低与其硬回答不如让模型把问题拆成几个子问题去换种方式检索。这其实已经有一点Agent的味道了。但一开始别做复杂先把“拒绝回答”的路径跑通保证系统不会一本正经地胡编这比什么都重要。4. 常见问题与排查技巧实录4.1 问题速查表先对着症状找病根我在做AI工程的过程中整理了一个问题速查表。实际排障时我会先看症状再按表格里的方向查效率高很多症状可能原因排查方向模型回答天马行空不基于文档检索阈值过低相关片段没被召回或Prompt里没有强调“只基于资料”检查阈值与TopK打印检索结果人工判断相关性输出JSON经常解析失败模型未遵循输出格式温度过高Prompt中示例不足固定temperature为0加入JSON Schema和few-shot示例大段文档被截断上下文窗口超限分块过多或单个分块过长降低TopK压缩片段使用摘要中间件必要时切分任务Agent反复执行同一动作缺少终止条件工具反馈不清晰设置最大迭代轮数增加人工审批节点让工具返回结构化结果检索结果看似相关但错得离谱分块切得不伦不类语义被打散embedding模型领域不匹配改为标题结构切块测试不同embedding模型成本快速失控连续调用大模型做简单任务Token浪费在长上下文中用小模型做分类和抽取大模型只做最终生成增加Prompt缓存换模型后质量波动极大不同模型对Prompt风格和格式要求不同建立模型评估集先小范围A/B对比再全量切流量这张表不能覆盖所有问题但能覆盖我遇到过的80%情况。记住一个原则不要一上来怀疑“模型太笨”先查输入给得对不对。大量AI应用的问题不在模型智商而在上游数据质量和Prompt上下文混乱。4.2 三个从失败中提炼出来的实战教训第一个教训是别一开始就追求全自动。我做第一个Agent时非要让它自己规划、自己执行、自己生成结果。结果它在中间某一环开始放飞把一个只需要查数据库的任务变成了“我帮你联系一个专家”的空想。后来我把流程改成半自动先生成计划我确认后再执行执行完的结果还要再过一道校验。虽然多了两次人工点击但稳定性和可信度完全不一样。对于刚起步的AI工程半自动永远优于全自动。第二个教训是评估比Prompt优化更重要。有段时间我陷入“多写几版Prompt看效果”的循环但其实根本没有量化的评估标准。后来我花了半天时间从真实用户问题里整理出30条测试集每条都标注了标准答案来源。之后每次改Prompt、改切分方式、改阈值都拿这30条去跑统计有效答案率和检索命中率。从那时起系统才真正进入可迭代状态。没有评估集的AI工程等于在黑暗中开车。第三个教训是可观测性一定要早做。很多人觉得记录日志是上线后的事但等到上线再补你根本不知道历史输出里发生了什么。我现在做任何一个AI模块第一天就会加上最基础的日志输入、输出、耗时、token数、模型版本。后面所有优化判断都依赖这些数据。特别是你想知道“为什么这个回答错了”时一份包含最终Prompt的完整日志能让你少走两小时弯路。4.3 给零基础起步者的一份“避坑行动清单”如果你现在正要动手我这里有一套行动清单可以照着走。先不要管复杂概念按顺序做完你会比90%只聊概念的人更接近真正的AI工程。第一步选一个极其具体的痛点。可以是“帮我查公司内部制度里的年假规则”不要做“帮我解决所有行政问题”。第二步用手边的文档跑通一个最简单问答脚本不接向量库也行先把几百条文档塞进一个小库里试。第三步给自己做一份20条测试问题记录每一条的对错。第四步把输出格式固定成JSON加校验重试。第五步加上检索和阈值过滤。第六步加上日志和“无答案”兜底。第七步再去研究Agent、多Agent协作、评估平台这些进阶能力。这个清单最大的好处是每一步都有明确产出每步都不会因为引入过多新技术而失控。我建议你至少把一个版本完整跑完再考虑搭框架。真正让你对AI工程有感觉的不是架构图的宏伟程度而是调试到深夜后对某个错误原因恍然大悟的那一刻。我在实际开发里还有一个很小但很值得养成的习惯每次模型返回结果我都会顺手把当时的Prompt、模型名、版本号和评分记到一个Markdown表格里。几个月积累下来这份记录就是最有价值的团队知识库比任何教程都适合你们自己的业务。AI工程从零到成熟从来不是靠一次神操作而是靠一次次训练环境、记录反馈、修正偏差慢慢逼近稳定。希望这篇复盘里的思路和代码能帮你把第一脚踩实。
返回列表