聊之前先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。
摘要
我之前在大厂做离线数仓,Hive SQL 写得飞起,后来转去中小厂带团队做知识图谱 RAG 项目。上线那天,PM 在群里问"为什么用户查不到自己部门的文档",我排查了两个小时,才发现是权限逻辑写在了业务层而不是检索层,日志又没有记录权限过滤结果,整个链路像是黑盒。
那个项目最终用了三个月才稳定下来,期间踩的坑里,80% 跟模型本身没关系——权限、日志、可观测性才是真正拖慢进度的东西。今天这篇复盘,我会讲清楚数据工程师转大模型到底要补什么课,以及小团队怎么在有限资源下把 RAG 跑通、跑稳。
目录
- 大数据和大模型的交叉点在哪
- 数据治理:RAG 的命门
- 向量数据库:选对比用好重要
- RAG 数据管道:从小团队视角看
- 代码解释
- 落地项目:权限、日志和可观测性
- 排查过程
- 失败原因:常见坑点拆解
- 适用边界:什么时候别照搬
- 总结
大数据和大模型的交叉点在哪

很多人以为转型就是学 LangChain、跑个 Demo 就行。错了。
数据工程师的核心能力是"把乱数据变成可信数据",这个能力在 RAG 项目里直接复用。你要做的就是把 Hive / Flink / Spark 那套数据处理能力迁移到向量检索链路里。
具体来说,有几个交叉点:
ETL 思维可以直接平移。RAG 的第一关是文档处理——PDF 拆分、表格提取、多语言清洗,这些跟你们平时做数据清洗没什么区别。区别只是产出物从"宽表"变成了"带元数据的 chunks"。
质量评估框架可以复用。之前你们做数据质量监控可能用的是 DQC 规则,现在换成"检索命中率"和"回答相关性"两个指标就够了。
权限体系设计思路相通。大数据项目里你们做过数据权限(比如某个部门只能看自己的明细数据),这个思路直接搬到 RAG 就行,区别只是从"查表过滤"变成"检索后过滤"或"检索前过滤"。
真正卡住人的是向量检索这套新范式——你们之前处理的行式数据,现在要变成向量化表示,中间涉及的 embedding 模型选择、分块策略、相似度计算,都是新东西。
数据治理:RAG 的命门

我见过太多人上来就搞向量库,文档直接丢进去,然后抱怨检索效果差。
问题出在哪里?出在数据治理上。
你们的文档是什么状态?PDF 扫描件、Word 文档、Excel 表格、网页抓取的内容混在一起,每种格式的处理方式都不一样。不做预处理直接向量化,出来的 chunk 质量很差。
我做的那个知识图谱 RAG 项目,最初的文档来源包括:
- 1200 份技术文档(PDF + Markdown)
- 300 个网页 FAQ
- 500 张产品截图
- 内部系统的 JSON 配置
第一次处理,我直接用 Unstructured 库批量抽取文本,embedding 之后入库,检索结果惨不忍睹——用户问"如何在测试环境部署",返回的答案全是生产环境的配置。
原因就是文档没有区分环境标签,chunk 里也没有保留来源元数据。
我们后来做了四层治理:
第一层,格式分类。根据文件后缀和 MIME 类型分桶,PDF 走 OCR,Word 直接解析,HTML 做 DOM 提取,图片走视觉模型。这一步决定了 chunk 的文本质量。
第二层,内容分层。技术文档、FAQ、配置项、截图,不同内容的处理方式不同。FAQ 适合整条保留,技术文档要按章节拆分,配置项要保持完整结构不能切碎。
第三层,元数据打标。每段 chunk 必须带这几个字段:文档来源、所属部门、适用环境、最后更新时间、敏感级别。这些字段后面会直接用于权限过滤和结果排序。
第四层,质量校验。用规则 + 模型双重校验——规则检查 chunk 长度(太短没意义,太长信息密度低),模型检查 chunk 是否包含有效信息(有些文档拆出来的 chunk 全是页眉页脚)。
代码层面,我们定义了一个标准的 chunk 数据结构:
@dataclass
class Chunk:
"""RAG 检索的基本单元"""
chunk_id: str # 唯一标识,推荐用文档路径+位置哈希
content: str # 清洗后的文本内容
embedding: List[float] # 向量表示
metadata: Dict[str, Any] # 元数据,包含来源、权限等
# 来源信息
source_doc_id: str # 原始文档ID
source_path: str # 文档路径,用于溯源
chunk_index: int # 在同文档中的序号
# 权限相关
accessible_roles: List[str] # 可访问的角色列表
accessible_orgs: List[str] # 可访问的组织列表
# 质量指标
quality_score: float # 0-1,由质量校验模块打分
last_updated: datetime # 最后更新时间
这个结构看着简单,但它是整个 RAG 系统的基石。后面权限过滤、结果排序、质量追溯都依赖这些元数据。我在简历里也会专门写"设计并实现了标准 Chunk 数据模型,支撑多源文档的统一处理流程"——这句话足够具体,面试官能追问,也能体现你的工程能力。
向量数据库:选对比用好重要
市面上向量数据库很多,Pinecone、Milvus、Weaviate、FAISS,还有 Elasticsearch 的 KNN 功能。
小团队怎么选?我的建议是:不要追求最新最炫的,选你能跑起来、能维护的。
我当时选的是 Milvus,原因很简单:团队里有人维护过,开源社区活跃,而且支持标量过滤——这个功能对权限控制至关重要。
很多新人会犯的错误是:先把数据全部入库,然后再想办法加权限。实际上,向量数据库的标量过滤能力直接决定了你能不能在检索阶段就完成权限控制。
Milvus 的查询语法大概是这样的:
from pymilvus import connections, Collection
connections.connect("default", host="localhost", port="19530")
collection = Collection("knowledge_chunks")
# 查询前先过滤权限
role_filter = "data_engineer"
org_filter = "tech_dept"
search_params = {
"metric_type": "IP", # 内积相似度
"params": {"nprobe": 10}
}
# 带权限过滤的向量检索
results = collection.search(
data=[query_embedding], # 查询向量
anns_field="embedding", # 向量字段
expr=f"accessible_roles contains '{role_filter}' and accessible_orgs contains '{org_filter}'", # 标量过滤
limit=5,
search_params=search_params,
output_fields=["content", "source_path", "quality_score"]
)
这段代码的关键在于 expr 参数。它让你在向量检索的同时做标量过滤,相当于在数据库层面就实现了权限控制,不需要把结果拿回来再手动过滤。
如果你用的是不支持标量过滤的向量库,那就只能在检索之后手动过滤,性能会差很多,而且容易出错。
选型建议:小团队优先选支持标量过滤、社区活跃的开源方案。等规模上去了再考虑商业化的。别一上来就上 Pinecone,账单出来会吓你一跳。
RAG 数据管道:从小团队视角看
我见过太多 RAG 教程,从 embedding 开始讲,一路讲到检索增强。但真实项目里,数据管道的建设和维护往往占了 60% 以上的工作量。
我们当时的管道分成三个阶段:
离线阶段,每天早上跑一次增量同步。监控文档目录的变化,新文档自动入库,修改过的文档更新 chunk,删除的文档清理向量。这个阶段用 Airflow 调度,和你们平时跑 Hive 任务没区别。
在线阶段,查询时实时处理。用户提问 → 文本预处理 → embedding → 向量检索 → 结果组装 → 返回答案。这个阶段延迟要求比较高,一般要在 2 秒内完成。
监控阶段,记录所有关键指标。包括检索命中率、回答相关性、权限过滤命中率、平均延迟等。
我重点说一个容易踩的坑:增量同步的逻辑。
很多团队第一次实现的时候,直接删掉旧向量、重新入库。这种方式简单,但有两个问题:一是全量入库代价大,二是无法追踪"哪个 chunk 对应哪次更新"。
我们后来的做法是:给每个 chunk 加版本号,文档更新时只替换发生变化的 chunk,未变化的 chunk 保持不变。同时记录变更日志,方便回溯。
def sync_document_changes(
source_dir: str,
collection: Collection,
change_log_path: str
):
"""
增量同步文档变更
Args:
source_dir: 文档源目录
collection: 向量库集合
change_log_path: 变更记录文件路径
"""
# 获取当前目录下所有文件的哈希值
current_hashes = compute_file_hashes(source_dir)
# 读取上次同步时的哈希值
last_hashes = load_last_hashes(change_log_path)
# 计算变更
added = set(current_hashes.keys()) - set(last_hashes.keys())
deleted = set(last_hashes.keys()) - set(current_hashes.keys())
modified = set()
for path in set(current_hashes.keys()) & set(last_hashes.keys()):
if current_hashes[path] != last_hashes[path]:
modified.add(path)
# 处理新增文档
for path in added:
chunks = process_document(path)
upsert_chunks(collection, chunks)
# 处理删除文档
for path in deleted:
delete_by_source(collection, path)
# 处理修改文档
for path in modified:
delete_by_source(collection, path)
chunks = process_document(path)
upsert_chunks(collection, chunks)
# 更新哈希记录
save_hashes(current_hashes, change_log_path)

代码解释
前面贴了三段关键代码,下面逐段拆解它们的实现原理,这样你写进简历或者面试的时候能讲清楚。
1. Chunk 数据类
输入:无构造函数参数,字段全部通过实例化时赋值。
核心逻辑:用 @dataclass 装饰器自动生成 __init__、__repr__ 等方法,减少样板代码。chunk_id 的设计建议用"文档路径 + 位置哈希"拼接,这样既能保证唯一性,又能通过 chunk_id 反推出原始文档位置,方便后续溯源。embedding 字段存的是 float 列表,维度取决于你选的 embedding 模型(常见的有 768、1536、3072 等),这个维度必须和向量库的 schema 定义一致,否则入库会报错。accessible_roles 和 accessible_orgs 是两个可选的权限过滤维度,用列表结构是因为一个文档可能同时属于多个组织。
输出:一个结构化对象,可以被序列化存入向量库或数据库。
异常处理:代码中没有显式处理异常,但实际使用中要注意 last_updated 字段如果缺失会导致比较逻辑出错,建议在调用方加默认值兜底。另外 metadata 是 Dict 类型,如果存入非 JSON 序列化的对象(比如 datetime),入库前需要转换。
2. Milvus 检索代码
输入:
query_embedding:用户问题经过 embedding 模型后得到的向量,维度必须和 collection schema 一致role_filter和org_filter:字符串,对应 Chunk 中记录的权限字段值search_params:搜索参数,nprobe控制搜索精度,越大越准确但越慢,一般 10-100 之间调
核心逻辑:先用 Collection 加载已创建的集合,然后通过 expr 参数在向量检索的同时做标量过滤。这步很关键——很多新人会把权限过滤放在检索之后手动过滤,那样要先召回全部 top-N 再二次过滤,如果有大量文档被权限排除,实际返回的结果数量可能不足,需要回退补充检索,逻辑复杂很多。Milvus 的 expr 支持 AND、OR、contains 等操作符,语法接近 SQL where 子句。
输出:一个 SearchResult 对象,包含每条结果的得分(distance)和命中的字段值(output_fields)。
异常处理:connections.connect 如果连接失败会抛异常,需要 try-except 捕获并做重试或降级。collection.search 如果 embedding 维度不匹配会直接报错,建议入库前做一次维度校验。expr 中的变量如果来自用户输入,需要做注入防护——这里用 f-string 拼接是有风险的,生产环境应该用参数化方式。
3. 增量同步函数
输入:
source_dir:文档源目录路径collection:Milvus Collection 对象change_log_path:本地存储哈希记录的文件路径(可以是 JSON 或 SQLite)
核心逻辑:这是一个典型的"基于文件哈希的增量同步"模式,和大数据领域常用的一致性哈希、CDN 缓存失效逻辑一脉相承。核心思想是不比较文件内容,而是比较文件的 MD5 哈希值——哈希相同则文件未变,哈希不同则文件已修改。通过集合运算(差集和交集)把文件分为 added、deleted、modified 三类,分别走不同的处理路径。注意 modified 的处理是先删后增,这是因为 chunk 的划分可能因文档内容变化而完全不同,简单 update 不够可靠。
输出:无返回值,副作用是更新向量库和本地哈希记录文件。
异常处理:函数本身没有 try-except,因为任何一步失败都应该让任务整体失败并回滚,由外层调度器(如 Airflow)负责重试。但有几个点需要注意:compute_file_hashes 如果遇到权限不足的目录应该跳过而不是崩溃;process_document 如果解析失败需要记录失败列表,避免单个坏文件阻断整个同步;save_hashes 应该在所有变更处理完之后再写,否则中途失败会导致哈希记录与实际状态不一致。
这三段代码串联起来就是一个最小可行 RAG 管道的骨架:定义数据结构 → 入库 → 带权限检索 → 增量更新。面试的时候照着这个顺序讲,比泛泛而谈"做过 RAG"要清楚得多。
落地项目:权限、日志和可观测性
回到我开头说的那个故事。
上线第一天崩了,PM 问我为什么用户查不到自己部门的文档。我查了日志,发现根本没有权限相关的记录——系统只知道"搜到了什么",不知道"为什么搜到这些、没搜到那些"。
这个项目的权限模型是这样的:每个用户属于一个组织,每个文档也有所属组织,检索时只能看到自己组织的文档。
实现方式有两种:检索前过滤和检索后过滤。
检索前过滤,在查询时就加上权限条件,向量库里只返回符合条件的结果。这种方式效率高,但对向量库的标量过滤能力有要求。
检索后过滤,先做向量检索拿到 top N,然后根据权限过滤。这种方式兼容性好,但可能有权限外的文档混入结果。
我们后来选了混合方案:检索前过滤主要组织权限,检索后过滤补充部门级权限。
日志方面,我们设计了三层:
执行日志,记录每个请求的输入输出、耗时、token 消耗。这部分用于日常监控和问题排查。
决策日志,记录每次检索的过滤条件、召回数量、排序结果。这部分用于分析检索效果和优化策略。
审计日志,记录所有权限相关的操作,包括谁在什么时间访问了什么文档。这部分用于合规和安全审计。
最初我们只写了执行日志,上线后发现问题排查困难,后来又补了决策日志和审计日志。小团队资源有限,不可能一次性做全套,但至少要把执行日志做好——这是排查问题的底线。
可观测性方面,我推荐用一个简单的指标面板,包含以下几个指标:
- 请求成功率
- 平均响应时间
- 检索命中率(查询有结果的比例)
- 权限过滤命中率(过滤后有结果的比例)
- 用户满意度(通过点赞/点踩收集)
这些指标不需要复杂的基础设施,Prometheus + Grafana 就能跑起来。
排查过程
上线那天出问题后的完整排查链路如下,这是真实案例,可以复现类似的定位思路。
现象:用户反馈查不到自己部门的文档,前端返回空结果,没有报错信息。
第一步:确认问题范围。我先查了请求日志,发现同一个查询,A 用户有结果,B 用户无结果,两人属于不同部门。说明问题不是全局的,而是和权限有关。
第二步:定位权限链路。我翻代码找到检索逻辑,发现权限过滤是在业务层做的——先查用户所属部门,然后在代码里手动拼 filter 条件。但问题是,filter 条件传的是部门名称,而文档的权限字段存的是部门 ID,两边对不上。
第三步:验证数据一致性。我去向量库里直接查了几条文档的 metadata,发现 accessibleorgs 里存的是 orgid(如 "org123"),而代码里传的是 orgname(如 "技术部")。这个 mismatch 是根本原因。
第四步:修复和验证。我把代码改成统一用 org_id,同时在日志里加了权限过滤前后的结果数量对比。修复后重新上线,问题消失。
这个排查过程的关键在于:第一步确认不是全局问题,缩小了范围;第二步找到权限链路的具体实现;第三步发现了数据格式不一致这个隐藏问题。如果一开始就直接改代码,很可能修了这里出那里,因为日志里没有记录过滤前后的数量,根本不知道哪一步出了问题。
后来我给团队立了个规矩:所有涉及权限过滤的查询,日志里必须记录"过滤前数量"和"过滤后数量"。这个改动花不了半小时,但排查效率提升了至少一个数量级。
失败原因:常见坑点拆解
我做这个项目过程中,遇到过几种典型失败:
业务错误:权限逻辑写错了。用户属于多个组织,但代码里只取了第一个。排查方法是打印权限过滤前后的结果数量,对比是否符合预期。这类错误的特征是日志里有正常返回但结果不对,没有异常堆栈。
配置错误:embedding 模型和向量库维度不匹配。有的模型输出 768 维,有的输出 1536 维,入库前没检查导致检索失败。排查方法是查向量库 schema,确认 embedding 字段维度和模型输出维度一致。这类错误通常在部署后第一次运行时暴露。
环境错误:向量库服务不可用。开发环境用 FAISS(文件存储),生产环境用 Milvus(服务),代码里硬编码了 FAISS 的文件路径,部署后找不到文件。排查方法是检查环境变量和配置文件,确认生产环境的路径正确。这类错误的特征是请求直接报错,连检索都没走到。
区分这三种错误有一个简单的方法:先看日志里有没有异常堆栈。有堆栈的通常是环境问题(服务启动失败、连接超时),无堆栈但结果不对的可能是配置或业务问题,连请求都发不出去的通常是环境问题。
另一个实用的区分技巧:把同一份代码在三个环境(本地、测试、生产)各跑一遍,看哪个环境出问题。如果只有生产环境出问题,大概率是配置或环境差异;如果三个环境都有问题,那就是业务逻辑或代码本身的 bug。
适用边界:什么时候别照搬
这个方案适合中小团队快速构建 RAG 系统,文档量在万级以内,并发不高,对延迟要求不严格。
如果你的场景是:
- 文档量百万级,需要高性能检索——要考虑分布式向量库和索引优化
- 并发很高,有严格的 SLA 要求——要考虑缓存层和限流
- 权限模型很复杂(层级关系、动态权限)——要考虑专门的权限服务
这些情况下,上面的方案需要做调整,不能直接照搬。
另外,如果你只是做一个个人项目或者 Demo,没必要搞这么重的权限和日志体系。先用最简方案跑通流程,等真正上线了再补。过度设计是小团队的杀手。
还有一个取舍要考虑:标量过滤虽然方便,但会限制向量库的检索性能。Milvus 官方建议标量过滤的字段基数不要太低(比如只有 2-3 个值的字段),否则索引效果不好。如果你的权限字段基数很低(比如只有"公开/内部/机密"三级),建议换一种方式,比如在业务层做过滤而不是在向量库层。
总结
数据工程师转大模型,最大的优势不是算法能力,而是工程能力和数据处理经验。RAG 系统的核心挑战从来不是模型本身,而是数据质量、权限控制和可观测性。
小团队做这件事,建议的学习顺序是:
1. 先把 ETL 思维迁移过来,学会处理非结构化数据
2. 选一个支持标量过滤的向量库,跑通最基本的检索流程
3. 在检索阶段加入权限过滤,不要等结果出来再过滤
4. 补上日志和监控,这是线上稳定运行的底线
5. 最后才考虑优化检索效果和提升响应速度
简历上写相关经验的时候,不要只说"做过 RAG 项目",要具体到"设计并实现了带权限过滤的 RAG 数据管道,支持 XX 万级文档检索,平均响应时间 XX 毫秒"。这种描述既真实又有分量。
转型不是换赛道,是换工具解决类似问题。你们过去几年积累的数据治理能力,在大模型时代依然值钱。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。




需要这份AI大模型资料清单的话,在评论区回复「清单」即可;我会根据大家的问题继续补充对应的实战内容。

671



被折叠的 条评论
为什么被折叠?



