这是我们 AI 与算法平台团队 14 个人耗时 87 天,把一套用了六年的"关键词匹配 + if-else 规则引擎 + 单机 sklearn 浅层模型 + 纯人工特征工程 + 模型 pickle 一存了事 + 无版本管理 + 上线后从不监控漂移 + 效果好坏靠人肉抽查"的远古智能问答与推荐体系,整体重构到 2026 年"大语言模型 LLM + RAG 检索增强生成 + 向量数据库语义检索 + vLLM 高吞吐推理服务 + LoRA 高效微调 + 结构化输出 + LLMOps 评测与可观测"现代 AI 体系的真实战役复盘。重构前,我们的"智能"是典型的"用户换个说法就匹配不到、规则堆到几千条无人敢动、模型躺在某台机器的 pickle 文件里没人知道怎么训出来的、上线后效果悄悄衰退也没人察觉"的危局;答非所问、答不出来是家常便饭,业务方对 AI 的信任降到冰点。重构后,我们建立起一套以向量检索做语义召回、以 RAG 让 LLM 基于真实知识作答、以 vLLM 扛高并发推理、以 LoRA 低成本微调对齐业务、以离线评测 + 在线可观测保障质量的现代 AI 体系。这 87 天里我们沉淀了 47 套工程修法、7 个 P0 事故复盘和 6 条工程哲学,本文毫无保留地分享出来。
需要先说明:AI 现代化不只是"接个大模型 API",而是一次从"关键词规则 + 黑盒模型 + 无评测"到"语义检索 + RAG 接地 + 评测驱动"的体系跃迁。下面这张表,概括了我们重构前后在十个核心维度上的对比,每一行背后都是数周攻坚。
| 维度 | 重构前(规则 + 浅层模型) | 重构后(2026 现代 AI 体系) |
|---|---|---|
| 语义理解 | 关键词精确匹配 | 向量语义检索 |
| 知识来源 | 规则里硬编码 | RAG 检索真实知识库 |
| 核心模型 | 单机 sklearn 浅层 | LLM + LoRA 微调 |
| 检索 | SQL LIKE 模糊匹配 | 向量数据库 ANN 检索 |
| 推理服务 | 单机同步,易超时 | vLLM 高吞吐批处理 |
| 幻觉控制 | 无,模型瞎编 | RAG 接地 + 引用溯源 |
| 输出格式 | 自由文本难解析 | 结构化 JSON 强约束 |
| 效果评测 | 人肉抽查 | 离线评测集自动跑分 |
| 线上监控 | 无,漂移无感知 | LLMOps 全链路可观测 |
| 迭代周期 | 改规则数周 | 调 Prompt/微调天级 |
一、RAG 检索增强:让 LLM 基于真实知识作答
重构的第一仗,也是整个 AI 体系的地基,是 RAG(检索增强生成)。直接问 LLM 业务问题,它要么不知道我们的私有知识(产品文档、订单政策、内部规章),要么一本正经地胡编(幻觉)。RAG 的思路是:先把私有知识切成块、用嵌入模型(embedding)转成向量存进向量库;用户提问时,先把问题也转成向量、检索出最相关的几段知识,再把"问题 + 检索到的知识"一起塞给 LLM,让它基于真实材料作答,并能给出引用出处。这样 LLM 不再凭空瞎编,而是"开卷考试"。下面是我们的知识入库(ingestion)管道:
# RAG 入库管道:文档切块 → 嵌入向量化 → 存入向量库(带元数据便于溯源)
from openai import OpenAI
import psycopg
client = OpenAI()
def chunk_text(text: str, size=500, overlap=80):
# 重叠切块:避免把一句话从中间切断,保留上下文连续性
chunks, i = [], 0
while i < len(text):
chunks.append(text[i:i + size])
i += size - overlap
return chunks
def ingest(doc_id: str, text: str, source: str):
chunks = chunk_text(text)
# 批量嵌入,比逐条调用快一个数量级
resp = client.embeddings.create(model="text-embedding-3-large", input=chunks)
with psycopg.connect("dbname=kb") as conn:
for chunk, item in zip(chunks, resp.data):
conn.execute(
"INSERT INTO kb_chunks (doc_id, content, source, embedding) "
"VALUES (%s, %s, %s, %s)",
(doc_id, chunk, source, item.embedding), # source 用于回答时溯源引用
)
RAG 让我们的 LLM 从"凭训练记忆瞎编"进化到了"基于真实知识库开卷作答":私有知识被切块、向量化、存入向量库后,每次回答都先检索出最相关的真实材料再让模型组织语言,幻觉被大幅压制;更关键的是每段知识都带着来源元数据,回答时能给出"依据是哪篇文档的哪一段"的引用溯源,业务方第一次敢信 AI 的回答了。知识更新也变得轻量——过去改规则要改代码、重新上线,现在只需把新文档重新入库,模型立刻就能用上最新知识,无需重新训练。我们在切块上踩过坑:块切太大检索不精准、太小又丢上下文,最后用了带重叠的切块策略在二者间取得平衡。RAG 是 LLM 落地企业场景的第一性原理:模型的通用能力 + 你的私有知识,二者结合才是真正可用的智能,而把私有知识"喂"给模型最划算的方式不是微调,而是检索。
二、向量数据库:从 LIKE 模糊匹配到语义近邻检索
第二仗是检索方式的革命。过去我们的"搜索"是 SQL 的 LIKE 模糊匹配或关键词全文索引——用户搜"怎么退钱",知识库里写的是"退款流程",关键词对不上就一无所获;同义词、近义表达、换个说法,统统抓瞎。向量检索彻底改变了这一点:文本被嵌入模型转成高维向量后,语义相近的文本在向量空间里距离也近,"退钱"和"退款"的向量自然相邻。向量数据库用近似最近邻(ANN)算法,能在千万级向量里毫秒级找出语义最相似的若干条。我们用 PostgreSQL 的 pgvector 扩展落地(复用了已有的 PG 基础设施):
-- pgvector:语义向量检索,"退钱"也能召回"退款流程",告别关键词死板匹配
CREATE EXTENSION IF NOT EXISTS vector;
ALTER TABLE kb_chunks ADD COLUMN embedding vector(3072);
-- HNSW 索引:千万级向量也能毫秒级近似最近邻检索
CREATE INDEX ON kb_chunks USING hnsw (embedding vector_cosine_ops);
-- 查询:把用户问题的向量传进来,按余弦距离取最相似的 5 段知识
-- <=> 是 pgvector 的余弦距离操作符,越小越相似
SELECT content, source, 1 - (embedding <=> %(q_vec)s) AS similarity
FROM kb_chunks
ORDER BY embedding <=> %(q_vec)s -- 走 HNSW 索引,不全表扫
LIMIT 5;
向量检索让我们的召回能力从"关键词必须精确对上才有结果"跃迁到了"理解语义、同义近义都能召回":用户用任何说法提问,系统都能找到语义相关的知识,"换个说法就搜不到"这个困扰了我们六年的老问题被根治;基于 HNSW 索引,千万级知识块的检索也是毫秒级,完全扛得住在线实时问答的延迟要求;复用 pgvector 还让我们省掉了引入独立向量数据库的运维成本,语义检索和业务数据在同一个 PG 里、还能在一条 SQL 里做"向量相似 + 结构化条件过滤"的混合检索。我们也实践了"向量召回 + 重排(rerank)"两阶段策略:先用向量快速召回一批候选,再用更精细的重排模型排序,精度进一步提升。从 LIKE 到向量检索,本质是把检索从"字面匹配"升级为"语义匹配",这是让 AI 真正"听懂人话"的关键基础设施。
三、vLLM:高吞吐推理服务扛住在线并发
第三仗,是推理服务化。早期我们用最朴素的方式跑模型——一个 Flask 接口里同步加载模型、逐个请求处理,并发一上来显存爆、延迟飙、动不动超时,根本扛不住在线流量。LLM 推理的瓶颈在显存和批处理效率,我们引入 vLLM 作为推理引擎:它用 PagedAttention 像操作系统管内存一样精细管理 KV Cache,用 continuous batching 把不同请求的 token 动态拼批,显存利用率和吞吐都比朴素方式高出数倍,还提供 OpenAI 兼容的 API,应用层几乎零改造就能接入。下面是我们的 vLLM 服务部署与调用:
# vLLM 推理服务:PagedAttention + continuous batching,高吞吐扛在线并发
# 启动 OpenAI 兼容服务(命令行):
# python -m vllm.entrypoints.openai.api_server \
# --model /models/qwen2.5-7b-lora-merged \
# --max-model-len 8192 --gpu-memory-utilization 0.9 \
# --tensor-parallel-size 2 # 双卡张量并行
# 应用侧:OpenAI 兼容,换个 base_url 即可,业务代码零改造
from openai import OpenAI
llm = OpenAI(base_url="http://vllm-svc:8000/v1", api_key="EMPTY")
def answer(question: str, contexts: list[str]) -> str:
ctx = "\n\n".join(contexts) # 来自向量检索的 Top-K 知识
resp = llm.chat.completions.create(
model="qwen2.5-7b-lora-merged",
messages=[
{"role": "system", "content": "只依据提供的资料作答,无依据就说不知道,严禁编造。"},
{"role": "user", "content": f"资料:\n{ctx}\n\n问题:{question}"},
],
temperature=0.2, # 问答场景调低温度,减少发散和幻觉
)
return resp.choices[0].message.content
vLLM 让我们的推理服务从"朴素 Flask 同步加载、并发即崩"进化到了"高吞吐、高显存利用率的生产级服务":PagedAttention 把过去大量浪费的 KV Cache 显存碎片回收利用,同样的显卡能跑更长上下文、更多并发;continuous batching 让不同长度的请求动态拼批、GPU 不空转,整体吞吐相比朴素部署提升数倍;OpenAI 兼容 API 更是让我们的应用层几乎零改造就接入了,还能在自托管模型和云端 API 之间灵活切换。我们用张量并行把大模型拆到多卡上跑,突破了单卡显存的限制。推理服务化的本质,是把"能跑出结果"和"能在生产高并发下稳定低延迟跑出结果"这两件事分开——前者是 demo,后者才是真正的工程,而 vLLM 这类专用推理引擎,就是跨越这道鸿沟的关键。
四、LoRA 微调:低成本让通用模型对齐业务
第四仗是微调。通用 LLM 虽强,但在我们的垂直业务上(特定术语、特定回答风格、特定格式)总差一口气;而全参数微调一个大模型动辄要海量显卡、成本高得吓人,还容易把模型原有的通用能力训坏(灾难性遗忘)。LoRA(低秩适配)给了我们一条低成本的路:冻结原模型的全部参数,只在每层旁边插入一小撮可训练的低秩矩阵,训练时只更新这极少量参数(往往不到原模型的 1%),既大幅降低了显存和算力成本,又因为不动原参数而保住了通用能力。下面是我们的 LoRA 微调配置:
# LoRA 微调:只训不到 1% 的参数,低成本对齐业务、还不破坏通用能力
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, TrainingArguments
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B", torch_dtype="bfloat16")
lora = LoraConfig(
r=16, # 低秩矩阵的秩,越大容量越强但参数越多
lora_alpha=32,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 只给注意力投影加 LoRA
lora_dropout=0.05,
task_type="CAUSAL_LM",
)
model = get_peft_model(base, lora)
model.print_trainable_parameters() # 可训练参数占比 < 1%
args = TrainingArguments(
output_dir="out/order-lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=8, # 梯度累积,小显存也能等效大 batch
learning_rate=2e-4,
num_train_epochs=3,
bf16=True,
)
# 训练数据是我们人工标注的业务问答对,几千条就有明显效果
LoRA 让我们用一张消费级显卡、几千条标注数据、几个小时,就把通用模型对齐到了业务场景:特定术语理解更准、回答风格更贴合我们的业务调性、结构化输出更稳定,而成本相比全参数微调降了一两个数量级;因为只动那一小撮低秩矩阵、不碰原模型参数,模型的通用能力被完整保留,没有出现"学会了业务、忘了常识"的灾难性遗忘;LoRA 权重只有几十兆,我们能为不同业务线训练不同的 LoRA、按需挂载,一个基座模型 + 多个轻量 LoRA 服务多个场景。我们的实践经验是:先用 RAG 解决"知识"问题,确实还差的"风格/格式/术语对齐"再用 LoRA 补——能用 RAG 解决的别上微调,能用 LoRA 解决的别上全参微调,永远选成本最低、最不破坏既有能力的那条路。
五、LLMOps:离线评测 + 在线可观测,给质量上保险
第五仗,也是过去最缺的一环,是质量保障。规则时代我们判断效果好坏靠人肉抽查几条,既不全面也不客观,上线后模型效果悄悄衰退(数据漂移、知识过期)更是毫无察觉。我们建立了 LLMOps 体系:离线维护一个覆盖各类业务场景的评测集,每次改 Prompt、换模型、更新 LoRA 都自动跑分(准确率、引用正确率、格式合规率等),不达标不许上线;在线则全链路记录每次问答的检索结果、Prompt、输出、用户反馈,持续监控质量指标和分布漂移。下面是我们的离线评测骨架:
# LLMOps 离线评测:每次变更自动跑分,不达标不许上线,告别人肉抽查
import json
def evaluate(answer_fn, eval_set: list[dict]) -> dict:
hit, grounded, fmt_ok = 0, 0, 0
for case in eval_set:
out = answer_fn(case["question"], case["contexts"])
# 1) 答案是否命中标准答案的关键事实(可用 LLM-as-judge 打分)
if judge_correct(out, case["expected"]):
hit += 1
# 2) 是否"接地":答案内容能在检索到的资料里找到依据,防幻觉
if is_grounded(out, case["contexts"]):
grounded += 1
# 3) 结构化输出是否合规(能否被 json.loads 解析)
if try_parse_json(out):
fmt_ok += 1
n = len(eval_set)
return {"accuracy": hit / n, "grounded_rate": grounded / n, "format_ok": fmt_ok / n}
# CI 里跑:任一指标低于基线则发布失败,把质量回退挡在上线前
scores = evaluate(answer, json.load(open("eval_set.json")))
assert scores["accuracy"] >= 0.85 and scores["grounded_rate"] >= 0.95
LLMOps 让我们的 AI 质量保障从"人肉抽查、漂移无感"进化到了"评测集自动跑分 + 在线全链路可观测":任何对 Prompt、模型、知识库的改动,都先在离线评测集上自动跑出准确率、接地率、格式合规率,达不到基线就直接发布失败,把质量回退死死挡在上线之前;在线则记录每一次问答的检索命中、Prompt、输出和用户反馈,质量指标一旦下滑或分布漂移立刻告警,再也不会出现"效果悄悄衰退几个月没人发现"的尴尬。我们尤其重视"接地率(grounded rate)"这个指标——它衡量答案有多少比例是真正基于检索资料而非模型瞎编,是 RAG 系统对抗幻觉的核心护栏。LLM 应用和传统软件最大的不同,是它的输出是概率性的、会随数据和模型变化而漂移,因此"评测驱动 + 持续可观测"不是可选项而是必需品——没有量化评测的 LLM 应用,本质上是在生产环境裸奔。
六、结构化输出:从自由文本到可解析的强约束
规则时代我们的模型输出是一段自由文本,下游想拿到其中的字段(意图、实体、置信度)只能用正则去抠,模型措辞稍变正则就失效,脆弱得不得了。现代 LLM 支持结构化输出——通过 JSON Schema 约束、function calling 或约束解码,强制模型按预定义的 JSON 结构输出,字段、类型都锁死。我们把所有需要被程序消费的 LLM 输出全部改成了结构化 JSON:意图分类、实体抽取、工单分派这些场景,模型直接吐出可被 json.loads 稳定解析的结构化结果,下游再也不用写脆弱的正则去抠文本,集成可靠性发生了质变;配合 JSON Schema 约束,模型连"少个字段、类型写错"都不会发生,输出即合约。这一步看似简单,却是 LLM 从"给人看的聊天玩具"升级为"可被系统可靠集成的组件"的关键——人能容忍措辞变化,但程序需要确定的结构。我们的原则是:凡是 LLM 输出要被代码消费的,一律上结构化输出 + Schema 校验,把"模型可能不按格式来"这个不确定性,用工程手段消灭在源头。
七、Prompt 工程与版本管理:把提示词当代码管
早期我们的 Prompt 是散落在代码里的硬编码字符串,谁改了、为什么改、改完效果是好是坏,全无记录;线上出了问题想回退到上一版 Prompt 都找不到。我们把 Prompt 当成一等公民的代码资产来管理:Prompt 模板独立存放、版本化、走评审,每次改动都在评测集上跑分对比、效果可量化,线上 Prompt 可灰度、可一键回退。把 Prompt 纳入版本管理和评测后,我们告别了"改 Prompt 像玄学、改完不知道好坏"的混沌:每一版 Prompt 都有评测分数背书,A/B 对比哪版更好一目了然;线上 Prompt 和模型版本、LoRA 版本一起被记录,任意一次回答都能追溯到"是哪版 Prompt + 哪版模型 + 检索了哪些知识"生成的,排查问题有据可依。我们还沉淀了一套 Prompt 设计范式:明确角色与边界、给少量示例(few-shot)、强调"无依据就说不知道"以抑制幻觉、要求结构化输出。Prompt 工程不是"碰运气凑咒语",而是一门可以系统化、可评测、可迭代的工程实践——把它当代码管,才能让它的优化可积累、可复利。
八、迁移策略:影子模式并行,效果达标再切流
把一个跑了六年、业务方已经习惯(哪怕不满意)的规则系统换成 LLM 体系,直接切换风险极大——万一新系统在某些场景还不如老规则,就是业务事故。我们用了"影子模式(shadow mode)"过渡:新 LLM 系统先和老规则系统并行跑,所有线上请求两边都处理,但只把老系统的结果返给用户,新系统的结果只记录、不生效;我们持续比对两边结果、用评测集打分,直到新系统在各场景上稳定优于老系统,才开始灰度切流——先切 5% 真实流量给新系统、观察真实效果和用户反馈,再逐步放大。影子模式让我们在零业务风险的前提下,用真实线上流量充分验证了新系统的效果,积累了大量"新老对比"的数据来定位新系统的短板并针对性优化;切流则全程灰度、可回退,核心业务零事故。最稳的一点是绝不凭"我觉得新系统更好"就切换,而是用真实流量的量化对比数据说话,让数据决定何时切、切多少。AI 系统的迁移尤其需要这种克制——因为 LLM 的效果是概率性的、场景相关的,你在 demo 里看到的惊艳不代表它在长尾真实流量上同样可靠,只有影子模式的真实流量验证才能给你切换的底气。
九、7 个 P0 事故复盘
7 事故:(1) RAG 切块太大导致检索召回不精准、答非所问,改带重叠的小块 + rerank 重排;(2) 没限制"无依据就说不知道",模型对检索不到的问题瞎编,加强 system prompt 接地约束;(3) vLLM 显存利用率配太高(0.95)偶发 OOM,回调到 0.9 留余量;(4) LoRA 学习率过大把模型训过拟合、通用能力下降,调小学习率 + 早停;(5) 结构化输出偶发非法 JSON 导致下游崩溃,加 Schema 校验 + 失败重试;(6) 嵌入模型升级后新老向量不兼容、检索全乱,全量重新入库 + 版本隔离;(7) 知识库文档更新了但忘了重新入库,答案用了过期知识,加入库自动化 + 时效监控。每个 P0 都做 5-Why 复盘,固化成评测用例或上线检查清单,确保同类问题不再复发。
十、AI 工程师的 6 条工程哲学
6 哲学:(1) 知识用检索、能力用模型——能 RAG 解决的别微调,先接地再谈风格;(2) 检索要语义不要字面,向量检索是让 AI 听懂人话的地基;(3) 输出要结构化,凡被代码消费的一律 JSON + Schema;(4) 微调选最省的路,LoRA 优于全参,不破坏通用能力;(5) 质量靠评测不靠感觉,没有量化评测的 LLM 应用是在裸奔;(6) AI 迁移用影子模式,让真实流量的数据决定何时切换。这 6 条哲学,是我们用 7 个 P0 事故和 87 天深夜攻坚换来的集体共识。它们共同指向一个认知:现代 AI 工程的核心,不是追最新最大的模型,而是建立起"RAG 接地、语义检索、结构化、评测驱动、可观测"的体系化工程能力——模型会迭代,但这套工程方法论长青。
十一、重构收益的量化:7 个关键数字
7 数字:(1) 问答准确率:规则时代约 47% → RAG + 微调后 90%+;(2) "换说法搜不到"的召回失败:频发 → 基本归零(语义检索);(3) 幻觉率:无控制瞎编 → RAG 接地后大幅压制;(4) 推理吞吐:朴素部署 → vLLM 提升数倍;(5) 微调成本:全参微调 → LoRA 降一两个数量级;(6) 迭代周期:改规则数周 → 调 Prompt/微调天级;(7) 效果衰退发现:几个月无感 → 在线可观测实时告警。这些数字背后,是 87 天里 14 个人无数次的攻坚,但每一个都实打实地转化成了用户体验和业务方信任的提升。当我们把这份数据汇报给管理层时,最有说服力的不是任何模型名词,而是"问答准确率从五成到九成、业务方第一次敢信 AI"这两条。
十二、留给后来者的最后一句话
87 天的 AI 现代化战役,我们走过的不只是一条从关键词规则到 LLM、从 LIKE 匹配到向量检索、从瞎编到 RAG 接地的技术升级路,更是一次从"伪智能堆规则 + 黑盒裸奔"到"语义理解 + 知识接地 + 评测驱动"的体系跃迁。当用户随便换个说法系统都能听懂、当每个回答都能给出"依据是哪篇文档"的引用溯源、当我们第一次能用评测集量化地说出"这版比上版准确率高了几个点"而不再靠拍脑袋的那一刻,真正点燃我们的,不是某个具体的大模型,而是"AI 这件事终于从业务方将信将疑的玄学,变成了可量化、可迭代、可信赖的工程"的踏实与笃定。AI 落地没有银弹,关键是理解 RAG、向量检索、微调、评测各自解决什么问题、代价是什么,然后结合业务场景做体系化取舍——尤其要克制"无脑追最大模型"的冲动,把工程基本功(检索质量、评测体系、可观测)做扎实,往往比换个更大的模型收益更大。愿每一位还困在关键词规则、黑盒模型、效果靠人肉抽查的同行,都能早日建立起属于自己的现代 AI 体系。共勉,后会有期。
—— 别看了 · 2026